File indexing completed on 2024-05-05 05:01:20

0001 // SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
0002 // SPDX-License-Identifier: LGPL-2.0-or-later
0003 
0004 #include "actionsmodel.h"
0005 
0006 #include "chatbarcache.h"
0007 #include "neochatroom.h"
0008 #include "roommanager.h"
0009 #include <Quotient/events/roommemberevent.h>
0010 #include <Quotient/events/roompowerlevelsevent.h>
0011 
0012 #include <KLocalizedString>
0013 
0014 using Action = ActionsModel::Action;
0015 using namespace Quotient;
0016 
0017 QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls, "#ffd500"_ls, "#ffff00"_ls, "#d4ff00"_ls, "#aaff00"_ls, "#80ff00"_ls,
0018                           "#55ff00"_ls, "#2bff00"_ls, "#00ff00"_ls, "#00ff2b"_ls, "#00ff55"_ls, "#00ff80"_ls, "#00ffaa"_ls, "#00ffd5"_ls, "#00ffff"_ls,
0019                           "#00d4ff"_ls, "#00aaff"_ls, "#007fff"_ls, "#0055ff"_ls, "#002bff"_ls, "#0000ff"_ls, "#2a00ff"_ls, "#5500ff"_ls, "#7f00ff"_ls,
0020                           "#aa00ff"_ls, "#d400ff"_ls, "#ff00ff"_ls, "#ff00d4"_ls, "#ff00aa"_ls, "#ff0080"_ls, "#ff0055"_ls, "#ff002b"_ls, "#ff0000"_ls};
0021 
0022 auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0023     if (text.isEmpty()) {
0024         Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
0025         room->connection()->leaveRoom(room);
0026     } else {
0027         QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
0028         auto regexMatch = roomRegex.match(text);
0029         if (!regexMatch.hasMatch()) {
0030             Q_EMIT room->showMessage(NeoChatRoom::Error,
0031                                      i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
0032             return QString();
0033         }
0034         auto leaving = room->connection()->room(text);
0035         if (!leaving) {
0036             leaving = room->connection()->roomByAlias(text);
0037         }
0038         if (leaving) {
0039             Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
0040             room->connection()->leaveRoom(leaving);
0041         } else {
0042             Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
0043         }
0044     }
0045     return QString();
0046 };
0047 
0048 auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0049     if (text.isEmpty()) {
0050         Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
0051     } else {
0052         room->connection()->user()->rename(text, room);
0053     }
0054     return QString();
0055 };
0056 
0057 QList<ActionsModel::Action> actions{
0058     Action{
0059         QStringLiteral("shrug"),
0060         [](const QString &message, NeoChatRoom *, ChatBarCache *) {
0061             return QStringLiteral("¯\\\\_(ツ)_/¯ %1").arg(message);
0062         },
0063         true,
0064         std::nullopt,
0065         kli18n("<message>"),
0066         kli18n("Prepends ¯\\_(ツ)_/¯ to a plain-text message"),
0067     },
0068     Action{
0069         QStringLiteral("lenny"),
0070         [](const QString &message, NeoChatRoom *, ChatBarCache *) {
0071             return QStringLiteral("( ͡° ͜ʖ ͡°) %1").arg(message);
0072         },
0073         true,
0074         std::nullopt,
0075         kli18n("<message>"),
0076         kli18n("Prepends ( ͡° ͜ʖ ͡°) to a plain-text message"),
0077     },
0078     Action{
0079         QStringLiteral("tableflip"),
0080         [](const QString &message, NeoChatRoom *, ChatBarCache *) {
0081             return QStringLiteral("(╯°□°)╯︵ ┻━┻ %1").arg(message);
0082         },
0083         true,
0084         std::nullopt,
0085         kli18n("<message>"),
0086         kli18n("Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message"),
0087     },
0088     Action{
0089         QStringLiteral("unflip"),
0090         [](const QString &message, NeoChatRoom *, ChatBarCache *) {
0091             return QStringLiteral("┬──┬ ノ( ゜-゜ノ) %1").arg(message);
0092         },
0093         true,
0094         std::nullopt,
0095         kli18n("<message>"),
0096         kli18n("Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message"),
0097     },
0098     Action{
0099         QStringLiteral("rainbow"),
0100         [](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
0101             QString rainbowText;
0102             for (int i = 0; i < text.length(); i++) {
0103                 rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
0104             }
0105             // Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
0106             room->postMessage(QStringLiteral("/rainbow %1").arg(text),
0107                               rainbowText,
0108                               RoomMessageEvent::MsgType::Text,
0109                               chatBarCache->replyId(),
0110                               chatBarCache->editId());
0111             return QString();
0112         },
0113         false,
0114         std::nullopt,
0115         kli18n("<message>"),
0116         kli18n("Sends the given message colored as a rainbow"),
0117     },
0118     Action{
0119         QStringLiteral("rainbowme"),
0120         [](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
0121             QString rainbowText;
0122             for (int i = 0; i < text.length(); i++) {
0123                 rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
0124             }
0125             // Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
0126             room->postMessage(QStringLiteral("/rainbow %1").arg(text),
0127                               rainbowText,
0128                               RoomMessageEvent::MsgType::Emote,
0129                               chatBarCache->replyId(),
0130                               chatBarCache->editId());
0131             return QString();
0132         },
0133         false,
0134         std::nullopt,
0135         kli18n("<message>"),
0136         kli18n("Sends the given emote colored as a rainbow"),
0137     },
0138     Action{
0139         QStringLiteral("plain"),
0140         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0141             room->postMessage(text, text.toHtmlEscaped(), RoomMessageEvent::MsgType::Text, {}, {});
0142             return QString();
0143         },
0144         false,
0145         std::nullopt,
0146         kli18n("<message>"),
0147         kli18n("Sends the given message as plain text"),
0148     },
0149     Action{
0150         QStringLiteral("spoiler"),
0151         [](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
0152             // Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
0153             room->postMessage(QStringLiteral("/spoiler %1").arg(text),
0154                               QStringLiteral("<span data-mx-spoiler>%1</span>").arg(text),
0155                               RoomMessageEvent::MsgType::Text,
0156                               chatBarCache->replyId(),
0157                               chatBarCache->editId());
0158             return QString();
0159         },
0160         false,
0161         std::nullopt,
0162         kli18n("<message>"),
0163         kli18n("Sends the given message as a spoiler"),
0164     },
0165     Action{
0166         QStringLiteral("me"),
0167         [](const QString &text, NeoChatRoom *, ChatBarCache *) {
0168             return text;
0169         },
0170         true,
0171         RoomMessageEvent::MsgType::Emote,
0172         kli18n("<message>"),
0173         kli18n("Sends the given emote"),
0174     },
0175     Action{
0176         QStringLiteral("notice"),
0177         [](const QString &text, NeoChatRoom *, ChatBarCache *) {
0178             return text;
0179         },
0180         true,
0181         RoomMessageEvent::MsgType::Notice,
0182         kli18n("<message>"),
0183         kli18n("Sends the given message as a notice"),
0184     },
0185     Action{
0186         QStringLiteral("invite"),
0187         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0188             static const QRegularExpression mxidRegex(
0189                 QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
0190             auto regexMatch = mxidRegex.match(text);
0191             if (!regexMatch.hasMatch()) {
0192                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
0193                 return QString();
0194             }
0195             const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
0196             if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
0197                 Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
0198                 return QString();
0199             }
0200             if (roomMemberEvent && roomMemberEvent->membership() == Membership::Ban) {
0201                 Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
0202                 return QString();
0203             }
0204             if (room->localUser()->id() == text) {
0205                 Q_EMIT room->showMessage(NeoChatRoom::Positive, i18n("You are already in this room."));
0206                 return QString();
0207             }
0208             if (room->users().contains(room->user(text))) {
0209                 Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
0210                 return QString();
0211             }
0212             room->inviteToRoom(text);
0213             Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was invited into this room", "%1 was invited into this room", text));
0214             return QString();
0215         },
0216         false,
0217         std::nullopt,
0218         kli18n("<user id>"),
0219         kli18n("Invites the user to this room"),
0220     },
0221     Action{
0222         QStringLiteral("join"),
0223         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0224             QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
0225             auto regexMatch = roomRegex.match(text);
0226             if (!regexMatch.hasMatch()) {
0227                 Q_EMIT room->showMessage(NeoChatRoom::Error,
0228                                          i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
0229                 return QString();
0230             }
0231             auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
0232             if (targetRoom) {
0233                 RoomManager::instance().resolveResource(targetRoom->id());
0234                 return QString();
0235             }
0236             Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
0237             RoomManager::instance().resolveResource(text, "join"_ls);
0238             return QString();
0239         },
0240         false,
0241         std::nullopt,
0242         kli18n("<room alias or id>"),
0243         kli18n("Joins the given room"),
0244     },
0245     Action{
0246         QStringLiteral("knock"),
0247         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0248             auto parts = text.split(QLatin1String(" "));
0249             QString roomName = parts[0];
0250             QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
0251             auto regexMatch = roomRegex.match(roomName);
0252             if (!regexMatch.hasMatch()) {
0253                 Q_EMIT room->showMessage(NeoChatRoom::Error,
0254                                          i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
0255                 return QString();
0256             }
0257             auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
0258             if (targetRoom) {
0259                 RoomManager::instance().resolveResource(targetRoom->id());
0260                 return QString();
0261             }
0262             Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
0263             auto connection = room->connection();
0264             const auto knownServer = roomName.mid(roomName.indexOf(":"_ls) + 1);
0265             if (parts.length() >= 2) {
0266                 RoomManager::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});
0267             } else {
0268                 RoomManager::instance().knockRoom(connection, roomName, QString(), QStringList{knownServer});
0269             }
0270             return QString();
0271         },
0272         false,
0273         std::nullopt,
0274         kli18n("<room alias or id> [<reason>]"),
0275         kli18n("Requests to join the given room"),
0276     },
0277     Action{
0278         QStringLiteral("j"),
0279         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0280             QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
0281             auto regexMatch = roomRegex.match(text);
0282             if (!regexMatch.hasMatch()) {
0283                 Q_EMIT room->showMessage(NeoChatRoom::Error,
0284                                          i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
0285                 return QString();
0286             }
0287             if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
0288                 Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
0289                 return QString();
0290             }
0291             Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
0292             RoomManager::instance().resolveResource(text, "join"_ls);
0293             return QString();
0294         },
0295         false,
0296         std::nullopt,
0297         kli18n("<room alias or id>"),
0298         kli18n("Joins the given room"),
0299     },
0300     Action{
0301         QStringLiteral("part"),
0302         leaveRoomLambda,
0303         false,
0304         std::nullopt,
0305         kli18n("[<room alias or id>]"),
0306         kli18n("Leaves the given room or this room, if there is none given"),
0307     },
0308     Action{
0309         QStringLiteral("leave"),
0310         leaveRoomLambda,
0311         false,
0312         std::nullopt,
0313         kli18n("[<room alias or id>]"),
0314         kli18n("Leaves the given room or this room, if there is none given"),
0315     },
0316     Action{
0317         QStringLiteral("nick"),
0318         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0319             if (text.isEmpty()) {
0320                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
0321             } else {
0322                 room->connection()->user()->rename(text);
0323             }
0324             return QString();
0325         },
0326         false,
0327         std::nullopt,
0328         kli18n("<display name>"),
0329         kli18n("Changes your global display name"),
0330     },
0331     Action{
0332         QStringLiteral("roomnick"),
0333         roomNickLambda,
0334         false,
0335         std::nullopt,
0336         kli18n("<display name>"),
0337         kli18n("Changes your display name in this room"),
0338     },
0339     Action{
0340         QStringLiteral("myroomnick"),
0341         roomNickLambda,
0342         false,
0343         std::nullopt,
0344         kli18n("<display name>"),
0345         kli18n("Changes your display name in this room"),
0346     },
0347     Action{
0348         QStringLiteral("ignore"),
0349         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0350             static const QRegularExpression mxidRegex(
0351                 QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
0352             auto regexMatch = mxidRegex.match(text);
0353             if (!regexMatch.hasMatch()) {
0354                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
0355                 return QString();
0356             }
0357             auto user = room->connection()->users()[text];
0358             if (room->connection()->ignoredUsers().contains(user->id())) {
0359                 Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
0360                 return QString();
0361             }
0362             if (user) {
0363                 room->connection()->addToIgnoredUsers(user);
0364                 Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
0365             } else {
0366                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not a known user", "%1 is not a known user.", text));
0367             }
0368             return QString();
0369         },
0370         false,
0371         std::nullopt,
0372         kli18n("<user id>"),
0373         kli18n("Ignores the given user"),
0374     },
0375     Action{
0376         QStringLiteral("unignore"),
0377         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0378             static const QRegularExpression mxidRegex(
0379                 QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
0380             auto regexMatch = mxidRegex.match(text);
0381             if (!regexMatch.hasMatch()) {
0382                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
0383                 return QString();
0384             }
0385             auto user = room->connection()->users()[text];
0386             if (user) {
0387                 if (!room->connection()->ignoredUsers().contains(user->id())) {
0388                     Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
0389                     return QString();
0390                 }
0391                 room->connection()->removeFromIgnoredUsers(user);
0392                 Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
0393             } else {
0394                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not a known user", "%1 is not a known user.", text));
0395             }
0396             return QString();
0397         },
0398         false,
0399         std::nullopt,
0400         kli18n("<user id>"),
0401         kli18n("Unignores the given user"),
0402     },
0403     Action{
0404         QStringLiteral("react"),
0405         [](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
0406             if (chatBarCache->replyId().isEmpty()) {
0407                 for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
0408                     const auto &evt = **it;
0409                     if (const auto event = eventCast<const RoomMessageEvent>(&evt)) {
0410                         room->toggleReaction(event->id(), text);
0411                         return QString();
0412                     }
0413                 }
0414             }
0415             room->toggleReaction(chatBarCache->replyId(), text);
0416             return QString();
0417         },
0418         false,
0419         std::nullopt,
0420         kli18n("<reaction text>"),
0421         kli18n("React to the message with the given text"),
0422     },
0423     Action{
0424         QStringLiteral("ban"),
0425         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0426             auto parts = text.split(QLatin1String(" "));
0427             static const QRegularExpression mxidRegex(
0428                 QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
0429             auto regexMatch = mxidRegex.match(parts[0]);
0430             if (!regexMatch.hasMatch()) {
0431                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
0432                 return QString();
0433             }
0434             auto state = room->currentState().get<RoomMemberEvent>(parts[0]);
0435             if (state && state->membership() == Membership::Ban) {
0436                 Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
0437                 return QString();
0438             }
0439             auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
0440             if (!plEvent) {
0441                 return QString();
0442             }
0443             if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
0444                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to ban users from this room."));
0445                 return QString();
0446             }
0447             if (plEvent->powerLevelForUser(room->localUser()->id()) <= plEvent->powerLevelForUser(parts[0])) {
0448                 Q_EMIT room->showMessage(
0449                     NeoChatRoom::Error,
0450                     i18nc("You are not allowed to ban <username> from this room.", "You are not allowed to ban %1 from this room.", parts[0]));
0451                 return QString();
0452             }
0453             room->ban(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
0454             Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was banned from this room.", "%1 was banned from this room.", parts[0]));
0455             return QString();
0456         },
0457         false,
0458         std::nullopt,
0459         kli18n("<user id> [<reason>]"),
0460         kli18n("Bans the given user"),
0461     },
0462     Action{
0463         QStringLiteral("unban"),
0464         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0465             static const QRegularExpression mxidRegex(
0466                 QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
0467             auto regexMatch = mxidRegex.match(text);
0468             if (!regexMatch.hasMatch()) {
0469                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
0470                 return QString();
0471             }
0472             auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
0473             if (!plEvent) {
0474                 return QString();
0475             }
0476             if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
0477                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to unban users from this room."));
0478                 return QString();
0479             }
0480             auto state = room->currentState().get<RoomMemberEvent>(text);
0481             if (state && state->membership() != Membership::Ban) {
0482                 Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
0483                 return QString();
0484             }
0485             room->unban(text);
0486             Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
0487 
0488             return QString();
0489         },
0490         false,
0491         std::nullopt,
0492         kli18n("<user id>"),
0493         kli18n("Removes the ban of the given user"),
0494     },
0495     Action{
0496         QStringLiteral("kick"),
0497         [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
0498             auto parts = text.split(QLatin1String(" "));
0499             static const QRegularExpression mxidRegex(
0500                 QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
0501             auto regexMatch = mxidRegex.match(parts[0]);
0502             if (!regexMatch.hasMatch()) {
0503                 Q_EMIT room->showMessage(NeoChatRoom::Error,
0504                                          i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0]));
0505                 return QString();
0506             }
0507             if (parts[0] == room->localUser()->id()) {
0508                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You cannot kick yourself from the room."));
0509                 return QString();
0510             }
0511             if (!room->isMember(parts[0])) {
0512                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
0513                 return QString();
0514             }
0515             auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
0516             if (!plEvent) {
0517                 return QString();
0518             }
0519             auto kick = plEvent->kick();
0520             if (plEvent->powerLevelForUser(room->localUser()->id()) < kick) {
0521                 Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to kick users from this room."));
0522                 return QString();
0523             }
0524             if (plEvent->powerLevelForUser(room->localUser()->id()) <= plEvent->powerLevelForUser(parts[0])) {
0525                 Q_EMIT room->showMessage(
0526                     NeoChatRoom::Error,
0527                     i18nc("You are not allowed to kick <username> from this room", "You are not allowed to kick %1 from this room.", parts[0]));
0528                 return QString();
0529             }
0530             room->kickMember(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
0531             Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was kicked from this room.", "%1 was kicked from this room.", parts[0]));
0532             return QString();
0533         },
0534         false,
0535         std::nullopt,
0536         kli18n("<user id> [<reason>]"),
0537         kli18n("Removes the user from the room"),
0538     },
0539 };
0540 
0541 int ActionsModel::rowCount(const QModelIndex &parent) const
0542 {
0543     Q_UNUSED(parent);
0544     return actions.size();
0545 }
0546 
0547 QVariant ActionsModel::data(const QModelIndex &index, int role) const
0548 {
0549     if (index.row() < 0 || index.row() >= actions.size()) {
0550         return {};
0551     }
0552     if (role == Prefix) {
0553         return actions[index.row()].prefix;
0554     }
0555     if (role == Description) {
0556         return actions[index.row()].description.toString();
0557     }
0558     if (role == CompletionType) {
0559         return QStringLiteral("action");
0560     }
0561     if (role == Parameters) {
0562         return actions[index.row()].parameters.toString();
0563     }
0564     return {};
0565 }
0566 
0567 QHash<int, QByteArray> ActionsModel::roleNames() const
0568 {
0569     return {
0570         {Prefix, "prefix"},
0571         {Description, "description"},
0572         {CompletionType, "completionType"},
0573     };
0574 }
0575 
0576 QList<Action> &ActionsModel::allActions() const
0577 {
0578     return actions;
0579 }