From 9640837957919a54cc987a57cf7c04dcf90e6411 Mon Sep 17 00:00:00 2001 From: Mm2PL Date: Sat, 19 Jun 2021 13:56:00 +0200 Subject: [PATCH] Allow moderators to see who deleted a message (#2874) Co-authored-by: pajlada --- CHANGELOG.md | 2 +- src/Application.cpp | 41 ++++++++++++++++++ src/providers/twitch/PubsubActions.hpp | 9 ++++ src/providers/twitch/PubsubClient.cpp | 40 ++++++++++++++++++ src/providers/twitch/PubsubClient.hpp | 1 + src/providers/twitch/TwitchMessageBuilder.cpp | 42 ++++++++++++++++++- src/providers/twitch/TwitchMessageBuilder.hpp | 3 ++ 7 files changed, 136 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f28d192e9..aca34963f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - Minor: Limit the number of recent chatters to improve memory usage and reduce freezes. (#2796, #2814) - Minor: Added `/popout` command. Usage: `/popout [channel]`. It opens browser chat for the provided channel. Can also be used without arguments to open current channels browser chat. (#2556, #2812) - Minor: Improved matching of game names when using `/setgame` command (#2636) -- Minor: Now shows deletions of messages like timeouts (#1155, #2841) +- Minor: Now shows deletions of messages like timeouts (#1155, #2841, #2867, #2874) - Minor: Added a link to accounts page in settings to "You need to be logged in to send messages" message. (#2862) - Minor: Switch to Twitch v2 emote API for animated emote support. (#2863) - Bugfix: Fixed FFZ emote links for global emotes (#2807, #2808) diff --git a/src/Application.cpp b/src/Application.cpp index 34f26a5ec..76015d91c 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -18,6 +18,7 @@ #include "providers/irc/Irc2.hpp" #include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/TwitchIrcServer.hpp" +#include "providers/twitch/TwitchMessageBuilder.hpp" #include "singletons/Emotes.hpp" #include "singletons/Fonts.hpp" #include "singletons/Logging.hpp" @@ -287,6 +288,46 @@ void Application::initPubsub() chan->addOrReplaceTimeout(msg); }); }); + this->twitch.pubsub->signals_.moderation.messageDeleted.connect( + [&](const auto &action) { + auto chan = + this->twitch.server->getChannelOrEmptyByID(action.roomID); + + if (chan->isEmpty()) + { + return; + } + + MessageBuilder msg; + TwitchMessageBuilder::deletionMessage(action, &msg); + msg->flags.set(MessageFlag::PubSub); + + postToThread([chan, msg = msg.release()] { + auto replaced = false; + LimitedQueueSnapshot snapshot = + chan->getMessageSnapshot(); + int snapshotLength = snapshot.size(); + + // without parens it doesn't build on windows + int end = (std::max)(0, snapshotLength - 200); + + for (int i = snapshotLength - 1; i >= end; --i) + { + auto &s = snapshot[i]; + if (!s->flags.has(MessageFlag::PubSub) && + s->timeoutUser == msg->timeoutUser) + { + chan->replaceMessage(s, msg); + replaced = true; + break; + } + } + if (!replaced) + { + chan->addMessage(msg); + } + }); + }); this->twitch.pubsub->signals_.moderation.userUnbanned.connect( [&](const auto &action) { diff --git a/src/providers/twitch/PubsubActions.hpp b/src/providers/twitch/PubsubActions.hpp index f6cfa2cf8..8e93e5e47 100644 --- a/src/providers/twitch/PubsubActions.hpp +++ b/src/providers/twitch/PubsubActions.hpp @@ -75,6 +75,15 @@ struct BanAction : PubSubAction { } }; +struct DeleteAction : PubSubAction { + using PubSubAction::PubSubAction; + + ActionUser target; + + QString messageId; + QString messageText; +}; + struct UnbanAction : PubSubAction { using PubSubAction::PubSubAction; diff --git a/src/providers/twitch/PubsubClient.cpp b/src/providers/twitch/PubsubClient.cpp index de91b70d3..d1100315c 100644 --- a/src/providers/twitch/PubsubClient.cpp +++ b/src/providers/twitch/PubsubClient.cpp @@ -428,6 +428,46 @@ PubSub::PubSub() } }; + this->moderationActionHandlers["delete"] = [this](const auto &data, + const auto &roomID) { + DeleteAction action(data, roomID); + + getCreatedByUser(data, action.source); + getTargetUser(data, action.target); + + try + { + const auto &args = getArgs(data); + + if (args.Size() < 3) + { + return; + } + + if (!rj::getSafe(args[0], action.target.name)) + { + return; + } + + if (!rj::getSafe(args[1], action.messageText)) + { + return; + } + + if (!rj::getSafe(args[2], action.messageId)) + { + return; + } + + this->signals_.moderation.messageDeleted.invoke(action); + } + catch (const std::runtime_error &ex) + { + qCDebug(chatterinoPubsub) + << "Error parsing moderation action:" << ex.what(); + } + }; + this->moderationActionHandlers["ban"] = [this](const auto &data, const auto &roomID) { BanAction action(data, roomID); diff --git a/src/providers/twitch/PubsubClient.hpp b/src/providers/twitch/PubsubClient.hpp index 260a40016..ab068b743 100644 --- a/src/providers/twitch/PubsubClient.hpp +++ b/src/providers/twitch/PubsubClient.hpp @@ -124,6 +124,7 @@ public: struct { struct { Signal chatCleared; + Signal messageDeleted; Signal modeChanged; Signal moderationStateChanged; diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index fb85d778f..b6f041c38 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -1352,6 +1352,8 @@ void TwitchMessageBuilder::hostingSystemMessage(const QString &channelName, MessageColor::System, FontStyle::ChatMediumBold) ->setLink({Link::UserInfo, channelName}); } + +// irc variant void TwitchMessageBuilder::deletionMessage(const MessagePtr originalMessage, MessageBuilder *builder) { @@ -1373,7 +1375,7 @@ void TwitchMessageBuilder::deletionMessage(const MessagePtr originalMessage, if (originalMessage->messageText.length() > 50) { builder->emplace( - originalMessage->messageText.left(50) + "...", + originalMessage->messageText.left(50) + "…", MessageElementFlag::Text, MessageColor::Text); } else @@ -1382,6 +1384,44 @@ void TwitchMessageBuilder::deletionMessage(const MessagePtr originalMessage, MessageElementFlag::Text, MessageColor::Text); } + builder->message().timeoutUser = "msg:" + originalMessage->id; +} + +// pubsub variant +void TwitchMessageBuilder::deletionMessage(const DeleteAction &action, + MessageBuilder *builder) +{ + builder->emplace(); + builder->message().flags.set(MessageFlag::System); + builder->message().flags.set(MessageFlag::DoNotTriggerNotification); + builder->message().flags.set(MessageFlag::Timeout); + + builder + ->emplace(action.source.name, MessageElementFlag::Username, + MessageColor::System, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, action.source.name}); + // TODO(mm2pl): If or when jumping to a single message gets implemented a link, + // add a link to the originalMessage + builder->emplace( + "deleted message from", MessageElementFlag::Text, MessageColor::System); + builder + ->emplace(action.target.name, MessageElementFlag::Username, + MessageColor::System, FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, action.target.name}); + builder->emplace("saying:", MessageElementFlag::Text, + MessageColor::System); + if (action.messageText.length() > 50) + { + builder->emplace(action.messageText.left(50) + "…", + MessageElementFlag::Text, + MessageColor::Text); + } + else + { + builder->emplace( + action.messageText, MessageElementFlag::Text, MessageColor::Text); + } + builder->message().timeoutUser = "msg:" + action.messageId; } } // namespace chatterino diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index 918c5bc7e..14d2430f9 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -4,6 +4,7 @@ #include "common/Outcome.hpp" #include "messages/SharedMessageBuilder.hpp" #include "providers/twitch/ChannelPointReward.hpp" +#include "providers/twitch/PubsubActions.hpp" #include "providers/twitch/TwitchBadge.hpp" #include @@ -60,6 +61,8 @@ public: MessageBuilder *builder); static void deletionMessage(const MessagePtr originalMessage, MessageBuilder *builder); + static void deletionMessage(const DeleteAction &action, + MessageBuilder *builder); private: void parseUsernameColor() override;