diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d99e2fb3d8..aaaf842b4eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unversioned - Major: Added ability to toggle visibility of Channel Tabs - This can be done by right-clicking the tab area or pressing the keyboard shortcut (default: Ctrl+U). (#2600) +- Minor: Restore automod functionality for moderators (#2817, #2887) - Minor: Add setting for username style (#2889, #2891) - Minor: Searching for users in the viewer list now searches anywhere in the user's name. (#2861) - Minor: Added moderation buttons to search popup when searching in a split with moderation mode enabled. (#2148, #2803) diff --git a/src/providers/twitch/PubsubClient.cpp b/src/providers/twitch/PubsubClient.cpp index 518a96b24a0..de91b70d330 100644 --- a/src/providers/twitch/PubsubClient.cpp +++ b/src/providers/twitch/PubsubClient.cpp @@ -900,6 +900,28 @@ void PubSub::listenToChannelModerationActions( this->listenToTopic(topic, account); } +void PubSub::listenToAutomod(const QString &channelID, + std::shared_ptr account) +{ + static const QString topicFormat("automod-queue.%1.%2"); + assert(!channelID.isEmpty()); + assert(account != nullptr); + QString userID = account->getUserId(); + if (userID.isEmpty()) + return; + + auto topic = topicFormat.arg(userID).arg(channelID); + + if (this->isListeningToTopic(topic)) + { + return; + } + + qCDebug(chatterinoPubsub) << "Listen to topic" << topic; + + this->listenToTopic(topic, account); +} + void PubSub::listenToChannelPointRewards(const QString &channelID, std::shared_ptr account) { @@ -1287,6 +1309,108 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData) << "Invalid point event type:" << pointEventType.c_str(); } } + else if (topic.startsWith("automod-queue.")) + { + auto topicParts = topic.split("."); + assert(topicParts.length() == 3); + auto &data = msg["data"]; + + std::string automodEventType; + if (!rj::getSafe(msg, "type", automodEventType)) + { + qCDebug(chatterinoPubsub) << "Bad automod event data"; + return; + } + + if (automodEventType == "automod_caught_message") + { + QString status; + if (!rj::getSafe(data, "status", status)) + { + qDebug() << "Failed to get status"; + return; + } + if (status == "PENDING") + { + AutomodAction action(data, topicParts[2]); + rapidjson::Value classification; + if (!rj::getSafeObject(data, "content_classification", + classification)) + { + qDebug() << "Failed to get content_classification"; + return; + } + + QString contentCategory; + if (!rj::getSafe(classification, "category", contentCategory)) + { + qDebug() << "Failed to get content category"; + return; + } + int contentLevel; + if (!rj::getSafe(classification, "level", contentLevel)) + { + qDebug() << "Failed to get content level"; + return; + } + action.reason = QString("%1 level %2") + .arg(contentCategory) + .arg(contentLevel); + + rapidjson::Value messageData; + if (!rj::getSafeObject(data, "message", messageData)) + { + qDebug() << "Failed to get message data"; + return; + } + + rapidjson::Value messageContent; + if (!rj::getSafeObject(messageData, "content", messageContent)) + { + qDebug() << "Failed to get message content"; + return; + } + if (!rj::getSafe(messageData, "id", action.msgID)) + { + qDebug() << "Failed to get message id"; + return; + } + + if (!rj::getSafe(messageContent, "text", action.message)) + { + qDebug() << "Failed to get message text"; + return; + } + + // this message also contains per-word automod data, which could be implemented + + // extract sender data manually because twitch loves not being consistent + rapidjson::Value senderData; + if (!rj::getSafeObject(messageData, "sender", senderData)) + { + qDebug() << "Failed to get sender"; + return; + } + QString sender_id; + if (!rj::getSafe(senderData, "user_id", sender_id)) + { + qDebug() << "Failed to get sender user id"; + return; + } + QString sender_login; + if (!rj::getSafe(senderData, "login", sender_login)) + { + qDebug() << "Failed to get sender login"; + return; + } + action.target = ActionUser{sender_id, sender_login}; + qDebug() << action.msgID; + this->signals_.moderation.automodMessage.invoke(action); + } + // "ALLOWED" and "DENIED" statuses remain unimplemented + // They are versions of automod_message_(denied|approved) but for mods. + } + } else { qCDebug(chatterinoPubsub) << "Unknown topic:" << topic; diff --git a/src/providers/twitch/PubsubClient.hpp b/src/providers/twitch/PubsubClient.hpp index 4e0eccddcb3..260a40016b9 100644 --- a/src/providers/twitch/PubsubClient.hpp +++ b/src/providers/twitch/PubsubClient.hpp @@ -153,6 +153,8 @@ class PubSub void listenToChannelModerationActions( const QString &channelID, std::shared_ptr account); + void listenToAutomod(const QString &channelID, + std::shared_ptr account); void listenToChannelPointRewards(const QString &channelID, std::shared_ptr account); diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index fefd048bba1..1105e593d4b 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -810,6 +810,7 @@ void TwitchChannel::refreshPubsub() auto account = getApp()->accounts->twitch.getCurrent(); getApp()->twitch2->pubsub->listenToChannelModerationActions(roomId, account); + getApp()->twitch2->pubsub->listenToAutomod(roomId, account); getApp()->twitch2->pubsub->listenToChannelPointRewards(roomId, account); }