diff --git a/CHANGELOG.md b/CHANGELOG.md index 1458dd484..7fc6cf373 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Minor: Added `flags.action` filter variable, allowing you to filter on `/me` messages. (#5397) - Minor: The size of the emote popup is now saved. (#5415) - Minor: Added the ability to duplicate tabs. (#5277) +- Minor: Moderators can now see when users are warned. (#5441) - Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426) - Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378) - Bugfix: Fixed restricted users usernames not being clickable. (#5405) diff --git a/src/Application.cpp b/src/Application.cpp index f9250f781..77663923f 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -650,6 +650,24 @@ void Application::initPubSub() chan->addOrReplaceTimeout(msg.release()); }); }); + + std::ignore = this->twitchPubSub->moderation.userWarned.connect( + [&](const auto &action) { + auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); + + if (chan->isEmpty()) + { + return; + } + + // TODO: Resolve the moderator's user ID into a full user here, so message can look better + postToThread([chan, action] { + MessageBuilder msg(action); + msg->flags.set(MessageFlag::PubSub); + chan->addMessage(msg.release()); + }); + }); + std::ignore = this->twitchPubSub->moderation.messageDeleted.connect( [&](const auto &action) { auto chan = this->twitch->getChannelOrEmptyByID(action.roomID); diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp index cccd540d0..ef0cb8c1c 100644 --- a/src/messages/MessageBuilder.cpp +++ b/src/messages/MessageBuilder.cpp @@ -323,6 +323,31 @@ MessageBuilder::MessageBuilder(const UnbanAction &action) this->message().searchText = text; } +MessageBuilder::MessageBuilder(const WarnAction &action) + : MessageBuilder() +{ + this->emplace(); + this->message().flags.set(MessageFlag::System); + + QString text; + + // TODO: Use MentionElement here, once WarnAction includes username/displayname + this->emplaceSystemTextAndUpdate("A moderator", text) + ->setLink({Link::UserInfo, "id:" + action.source.id}); + this->emplaceSystemTextAndUpdate("warned", text); + this->emplaceSystemTextAndUpdate( + action.target.login + (action.reasons.isEmpty() ? "." : ":"), text) + ->setLink({Link::UserInfo, action.target.login}); + + if (!action.reasons.isEmpty()) + { + this->emplaceSystemTextAndUpdate(action.reasons.join(", "), text); + } + + this->message().messageText = text; + this->message().searchText = text; +} + MessageBuilder::MessageBuilder(const AutomodUserAction &action) : MessageBuilder() { diff --git a/src/messages/MessageBuilder.hpp b/src/messages/MessageBuilder.hpp index c7277997a..63b9fd8cb 100644 --- a/src/messages/MessageBuilder.hpp +++ b/src/messages/MessageBuilder.hpp @@ -12,6 +12,7 @@ namespace chatterino { struct BanAction; struct UnbanAction; +struct WarnAction; struct AutomodAction; struct AutomodUserAction; struct AutomodInfoAction; @@ -78,6 +79,7 @@ public: const QTime &time = QTime::currentTime()); MessageBuilder(const BanAction &action, uint32_t count = 1); MessageBuilder(const UnbanAction &action); + MessageBuilder(const WarnAction &action); MessageBuilder(const AutomodUserAction &action); MessageBuilder(LiveUpdatesAddEmoteMessageTag, const QString &platform, diff --git a/src/providers/twitch/PubSubActions.hpp b/src/providers/twitch/PubSubActions.hpp index 89abdb244..82c94da30 100644 --- a/src/providers/twitch/PubSubActions.hpp +++ b/src/providers/twitch/PubSubActions.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -171,4 +172,12 @@ struct AutomodInfoAction : PubSubAction { } type; }; +struct WarnAction : PubSubAction { + using PubSubAction::PubSubAction; + + ActionUser target; + + QStringList reasons; +}; + } // namespace chatterino diff --git a/src/providers/twitch/PubSubManager.cpp b/src/providers/twitch/PubSubManager.cpp index acb75cb59..cefde9169 100644 --- a/src/providers/twitch/PubSubManager.cpp +++ b/src/providers/twitch/PubSubManager.cpp @@ -286,6 +286,37 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval) this->moderation.userUnbanned.invoke(action); }; + this->moderationActionHandlers["warn"] = [this](const auto &data, + const auto &roomID) { + WarnAction action(data, roomID); + + action.source.id = data.value("created_by_user_id").toString(); + action.source.login = + data.value("created_by").toString(); // currently always empty + + action.target.id = data.value("target_user_id").toString(); + action.target.login = data.value("target_user_login").toString(); + + const auto reasons = data.value("args").toArray(); + bool firstArg = true; + for (const auto &reasonValue : reasons) + { + if (firstArg) + { + // Skip first arg in the reasons array since it's not a reason + firstArg = false; + continue; + } + const auto &reason = reasonValue.toString(); + if (!reason.isEmpty()) + { + action.reasons.append(reason); + } + } + + this->moderation.userWarned.invoke(action); + }; + /* // This handler is no longer required as we use the automod-queue topic now this->moderationActionHandlers["automod_rejected"] = @@ -1131,8 +1162,8 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message) case PubSubChatModeratorActionMessage::Type::INVALID: default: { - qCDebug(chatterinoPubSub) - << "Invalid whisper type:" << innerMessage.typeString; + qCDebug(chatterinoPubSub) << "Invalid moderator action type:" + << innerMessage.typeString; } break; } diff --git a/src/providers/twitch/PubSubManager.hpp b/src/providers/twitch/PubSubManager.hpp index 6ddd98369..eb27e32f8 100644 --- a/src/providers/twitch/PubSubManager.hpp +++ b/src/providers/twitch/PubSubManager.hpp @@ -34,6 +34,7 @@ struct PubSubAutoModQueueMessage; struct AutomodAction; struct AutomodUserAction; struct AutomodInfoAction; +struct WarnAction; struct PubSubLowTrustUsersMessage; struct PubSubWhisperMessage; @@ -97,6 +98,7 @@ public: Signal userBanned; Signal userUnbanned; + Signal userWarned; Signal suspiciousMessageReceived; Signal suspiciousTreatmentUpdated;