File indexing completed on 2024-10-06 12:53:58

0001 // SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
0002 // SPDX-License-Identifier: GPL-3.0-or-later
0003 
0004 #include "actionshandler.h"
0005 
0006 #include <Quotient/csapi/joining.h>
0007 #include <Quotient/events/roommemberevent.h>
0008 
0009 #include <cmark.h>
0010 
0011 #include <KLocalizedString>
0012 #include <QStringBuilder>
0013 
0014 #include "models/actionsmodel.h"
0015 #include "models/customemojimodel.h"
0016 #include "neochatconfig.h"
0017 #include "texthandler.h"
0018 
0019 using namespace Quotient;
0020 
0021 ActionsHandler::ActionsHandler(QObject *parent)
0022     : QObject(parent)
0023 {
0024 }
0025 
0026 NeoChatRoom *ActionsHandler::room() const
0027 {
0028     return m_room;
0029 }
0030 
0031 void ActionsHandler::setRoom(NeoChatRoom *room)
0032 {
0033     if (m_room == room) {
0034         return;
0035     }
0036 
0037     m_room = room;
0038     Q_EMIT roomChanged();
0039 }
0040 
0041 void ActionsHandler::handleNewMessage()
0042 {
0043     checkEffects(m_room->chatBoxText());
0044     if (!m_room->chatBoxAttachmentPath().isEmpty()) {
0045         QUrl url(m_room->chatBoxAttachmentPath());
0046         auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
0047         m_room->uploadFile(path, m_room->chatBoxText().isEmpty() ? path.mid(path.lastIndexOf('/') + 1) : m_room->chatBoxText());
0048         m_room->setChatBoxAttachmentPath({});
0049         m_room->setChatBoxText({});
0050         return;
0051     }
0052 
0053     QString handledText = m_room->chatBoxText();
0054     handledText = handleMentions(handledText);
0055     handleMessage(m_room->chatBoxText(), handledText);
0056 }
0057 
0058 void ActionsHandler::handleEdit()
0059 {
0060     checkEffects(m_room->editText());
0061 
0062     QString handledText = m_room->editText();
0063     handledText = handleMentions(handledText, true);
0064     handleMessage(m_room->editText(), handledText, true);
0065 }
0066 
0067 QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
0068 {
0069     if (!m_room) {
0070         return QString();
0071     }
0072 
0073     QVector<Mention> *mentions;
0074     if (isEdit) {
0075         mentions = m_room->editMentions();
0076     } else {
0077         mentions = m_room->mentions();
0078     }
0079 
0080     std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
0081         return a.cursor.anchor() > b.cursor.anchor();
0082     });
0083 
0084     for (const auto &mention : *mentions) {
0085         if (mention.text.isEmpty() || mention.id.isEmpty()) {
0086             continue;
0087         }
0088         handledText = handledText.replace(mention.cursor.anchor(),
0089                                           mention.cursor.position() - mention.cursor.anchor(),
0090                                           QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text, mention.id));
0091     }
0092     mentions->clear();
0093 
0094     return handledText;
0095 }
0096 
0097 void ActionsHandler::handleMessage(const QString &text, QString handledText, const bool &isEdit)
0098 {
0099     if (NeoChatConfig::allowQuickEdit()) {
0100         QRegularExpression sed("^s/([^/]*)/([^/]*)(/g)?$");
0101         auto match = sed.match(text);
0102         if (match.hasMatch()) {
0103             const QString regex = match.captured(1);
0104             const QString replacement = match.captured(2).toHtmlEscaped();
0105             const QString flags = match.captured(3);
0106 
0107             for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
0108                 if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
0109                     if (event->senderId() == m_room->localUser()->id() && event->hasTextContent()) {
0110                         QString originalString;
0111                         if (event->content()) {
0112                             originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
0113                         } else {
0114                             originalString = event->plainBody();
0115                         }
0116                         if (flags == "/g") {
0117                             m_room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), "", event->id());
0118                         } else {
0119                             m_room->postHtmlMessage(handledText,
0120                                                     originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
0121                                                     event->msgtype(),
0122                                                     "",
0123                                                     event->id());
0124                         }
0125                         return;
0126                     }
0127                 }
0128             }
0129         }
0130     }
0131     auto messageType = RoomMessageEvent::MsgType::Text;
0132 
0133     if (handledText.startsWith(QLatin1Char('/'))) {
0134         for (const auto &action : ActionsModel::instance().allActions()) {
0135             if (handledText.indexOf(action.prefix) == 1
0136                 && (handledText.indexOf(" ") == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
0137                 handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room);
0138                 if (action.messageType.has_value()) {
0139                     messageType = *action.messageType;
0140                 }
0141                 if (action.messageAction) {
0142                     break;
0143                 } else {
0144                     return;
0145                 }
0146             }
0147         }
0148     }
0149 
0150     handledText = CustomEmojiModel::instance().preprocessText(handledText);
0151     TextHandler textHandler;
0152     textHandler.setData(handledText);
0153     handledText = textHandler.handleSendText();
0154 
0155     if (handledText.count("<p>") == 1 && handledText.count("</p>") == 1) {
0156         handledText.remove("<p>");
0157         handledText.remove("</p>");
0158     }
0159 
0160     if (handledText.length() == 0) {
0161         return;
0162     }
0163 
0164     m_room->postMessage(text, handledText, messageType, m_room->chatBoxReplyId(), isEdit ? m_room->chatBoxEditId() : "");
0165 }
0166 
0167 void ActionsHandler::checkEffects(const QString &text)
0168 {
0169     std::optional<QString> effect = std::nullopt;
0170     if (text.contains("\u2744")) {
0171         effect = QLatin1String("snowflake");
0172     } else if (text.contains("\u1F386")) {
0173         effect = QLatin1String("fireworks");
0174     } else if (text.contains("\u2F387")) {
0175         effect = QLatin1String("fireworks");
0176     } else if (text.contains("\u1F389")) {
0177         effect = QLatin1String("confetti");
0178     } else if (text.contains("\u1F38A")) {
0179         effect = QLatin1String("confetti");
0180     }
0181     if (effect.has_value()) {
0182         Q_EMIT showEffect(*effect);
0183     }
0184 }
0185 
0186 #include "moc_actionshandler.cpp"