File indexing completed on 2024-12-01 07:40:33

0001 // SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
0002 // SPDX-License-Identifier: GPL-2.0-or-later
0003 
0004 #include "customemojimodel.h"
0005 
0006 #include <QImage>
0007 #include <QMimeDatabase>
0008 
0009 #include "emojimodel.h"
0010 
0011 #include <Quotient/csapi/account-data.h>
0012 #include <Quotient/csapi/content-repo.h>
0013 
0014 using namespace Quotient;
0015 
0016 void CustomEmojiModel::setConnection(NeoChatConnection *connection)
0017 {
0018     if (connection == m_connection) {
0019         return;
0020     }
0021     m_connection = connection;
0022     Q_EMIT connectionChanged();
0023     fetchEmojis();
0024 }
0025 
0026 NeoChatConnection *CustomEmojiModel::connection() const
0027 {
0028     return m_connection;
0029 }
0030 
0031 void CustomEmojiModel::fetchEmojis()
0032 {
0033     if (!m_connection) {
0034         return;
0035     }
0036 
0037     const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
0038     if (data == nullptr) {
0039         return;
0040     }
0041     QJsonObject emojis = data->contentJson()["images"_ls].toObject();
0042 
0043     // TODO: Remove with stable migration
0044     const auto legacyEmojis = data->contentJson()["emoticons"_ls].toObject();
0045     for (const auto &emoji : legacyEmojis.keys()) {
0046         if (!emojis.contains(emoji)) {
0047             emojis[emoji] = legacyEmojis[emoji];
0048         }
0049     }
0050 
0051     beginResetModel();
0052     m_emojis.clear();
0053 
0054     for (const auto &emoji : emojis.keys()) {
0055         const auto &data = emojis[emoji];
0056 
0057         const auto e = emoji.startsWith(":"_ls) ? emoji : (QStringLiteral(":") + emoji + QStringLiteral(":"));
0058 
0059         m_emojis << CustomEmoji{e, data.toObject()["url"_ls].toString(), QRegularExpression(e)};
0060     }
0061 
0062     endResetModel();
0063 }
0064 
0065 void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
0066 {
0067     using namespace Quotient;
0068 
0069     auto job = m_connection->uploadFile(location.toLocalFile());
0070 
0071     connect(job, &BaseJob::success, this, [name, location, job, this] {
0072         const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
0073         auto json = data != nullptr ? data->contentJson() : QJsonObject();
0074         auto emojiData = json["images"_ls].toObject();
0075 
0076         QString url;
0077         url = job->contentUri().toString();
0078 
0079         QImage image(location.toLocalFile());
0080         QJsonObject imageInfo;
0081         imageInfo["w"_ls] = image.width();
0082         imageInfo["h"_ls] = image.height();
0083         imageInfo["mimetype"_ls] = QMimeDatabase().mimeTypeForFile(location.toLocalFile()).name();
0084         imageInfo["size"_ls] = image.sizeInBytes();
0085 
0086         emojiData[QStringLiteral("%1").arg(name)] = QJsonObject({
0087             {QStringLiteral("url"), url},
0088             {QStringLiteral("info"), imageInfo},
0089             {QStringLiteral("body"), location.fileName()},
0090             {"usage"_ls, "emoticon"_ls},
0091         });
0092 
0093         json["images"_ls] = emojiData;
0094         m_connection->setAccountData("im.ponies.user_emotes"_ls, json);
0095     });
0096 }
0097 
0098 void CustomEmojiModel::removeEmoji(const QString &name)
0099 {
0100     using namespace Quotient;
0101 
0102     const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
0103     Q_ASSERT(data);
0104     auto json = data->contentJson();
0105     const QString _name = name.mid(1).chopped(1);
0106     auto emojiData = json["images"_ls].toObject();
0107 
0108     if (emojiData.contains(name)) {
0109         emojiData.remove(name);
0110         json["images"_ls] = emojiData;
0111     }
0112     if (emojiData.contains(_name)) {
0113         emojiData.remove(_name);
0114         json["images"_ls] = emojiData;
0115     }
0116     emojiData = json["emoticons"_ls].toObject();
0117     if (emojiData.contains(name)) {
0118         emojiData.remove(name);
0119         json["emoticons"_ls] = emojiData;
0120     }
0121     if (emojiData.contains(_name)) {
0122         emojiData.remove(_name);
0123         json["emoticons"_ls] = emojiData;
0124     }
0125     m_connection->setAccountData("im.ponies.user_emotes"_ls, json);
0126 }
0127 
0128 CustomEmojiModel::CustomEmojiModel(QObject *parent)
0129     : QAbstractListModel(parent)
0130 {
0131     connect(this, &CustomEmojiModel::connectionChanged, this, [this]() {
0132         if (!m_connection) {
0133             return;
0134         }
0135         CustomEmojiModel::fetchEmojis();
0136         connect(m_connection, &Connection::accountDataChanged, this, [this](const QString &id) {
0137             if (id != QStringLiteral("im.ponies.user_emotes")) {
0138                 return;
0139             }
0140             fetchEmojis();
0141         });
0142     });
0143     CustomEmojiModel::fetchEmojis();
0144 }
0145 
0146 QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
0147 {
0148     const auto row = idx.row();
0149     if (row >= m_emojis.length()) {
0150         return QVariant();
0151     }
0152     const auto &data = m_emojis[row];
0153 
0154     switch (Roles(role)) {
0155     case Roles::ModelData:
0156         return QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + data.url.mid(6), data.name, true));
0157     case Roles::Name:
0158     case Roles::DisplayRole:
0159     case Roles::ReplacedTextRole:
0160         return data.name;
0161     case Roles::ImageURL:
0162         return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
0163     case Roles::MxcUrl:
0164         return data.url.mid(6);
0165     default:
0166         return {};
0167     }
0168 
0169     return QVariant();
0170 }
0171 
0172 int CustomEmojiModel::rowCount(const QModelIndex &parent) const
0173 {
0174     Q_UNUSED(parent)
0175 
0176     return m_emojis.length();
0177 }
0178 
0179 QHash<int, QByteArray> CustomEmojiModel::roleNames() const
0180 {
0181     return {
0182         {Name, "name"},
0183         {ImageURL, "imageURL"},
0184         {ModelData, "modelData"},
0185         {MxcUrl, "mxcUrl"},
0186     };
0187 }
0188 
0189 QString CustomEmojiModel::preprocessText(QString text)
0190 {
0191     for (const auto &emoji : std::as_const(m_emojis)) {
0192         text.replace(
0193             emoji.regexp,
0194             QStringLiteral(R"(<img data-mx-emoticon="" src="%1" alt="%2" title="%2" height="32" vertical-align="middle" />)").arg(emoji.url, emoji.name));
0195     }
0196     return text;
0197 }
0198 
0199 QVariantList CustomEmojiModel::filterModel(const QString &filter)
0200 {
0201     QVariantList results;
0202     for (const auto &emoji : std::as_const(m_emojis)) {
0203         if (results.length() >= 10)
0204             break;
0205         if (!emoji.name.contains(filter, Qt::CaseInsensitive))
0206             continue;
0207 
0208         results << QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + emoji.url.mid(6), emoji.name, true));
0209     }
0210     return results;
0211 }
0212 
0213 #include "moc_customemojimodel.cpp"