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"