File indexing completed on 2024-05-12 16:25:03

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