diff --git a/CHANGELOG.md b/CHANGELOG.md index 036b53e57..e087eeef6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - Minor: Added `--login ` CLI argument to specify which account to start logged in as. (#5626) - Minor: When blocking a channel, Chatterino will now warn you about that action. (#5615) - Minor: Indicate when subscriptions and resubscriptions are for multiple months. (#5642) +- Minor: Added a setting to control whether or not to show "Blocked Term" automod messages. (#5690) - Minor: Proxy URL information is now included in the `/debug-env` command. (#5648) - Minor: Make raid entry message usernames clickable. (#5651) - Minor: Tabs unhighlight when their content is read in other tabs. (#5649) diff --git a/src/common/ChatterinoSetting.hpp b/src/common/ChatterinoSetting.hpp index f2006e6d7..9db04def4 100644 --- a/src/common/ChatterinoSetting.hpp +++ b/src/common/ChatterinoSetting.hpp @@ -6,6 +6,10 @@ #include #include +#include +#include +#include + namespace chatterino { void _registerSetting(std::weak_ptr setting); @@ -148,6 +152,9 @@ struct IsChatterinoSettingT : std::false_type { template struct IsChatterinoSettingT> : std::true_type { }; +template +struct IsChatterinoSettingT> : std::true_type { +}; template concept IsChatterinoSetting = IsChatterinoSettingT::value; diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp index f88a7c499..00b5eedbf 100644 --- a/src/messages/MessageBuilder.cpp +++ b/src/messages/MessageBuilder.cpp @@ -26,6 +26,7 @@ #include "providers/twitch/api/Helix.hpp" #include "providers/twitch/ChannelPointReward.hpp" #include "providers/twitch/PubSubActions.hpp" +#include "providers/twitch/pubsubmessages/AutoMod.hpp" #include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchBadge.hpp" #include "providers/twitch/TwitchBadges.hpp" @@ -1637,6 +1638,12 @@ std::pair MessageBuilder::makeAutomodMessage( { MessageBuilder builder, builder2; + if (action.reasonCode == PubSubAutoModQueueMessage::Reason::BlockedTerm) + { + builder.message().flags.set(MessageFlag::AutoModBlockedTerm); + builder2.message().flags.set(MessageFlag::AutoModBlockedTerm); + } + // // Builder for AutoMod message with explanation builder.message().id = "automod_" + action.msgID; diff --git a/src/messages/MessageFlag.hpp b/src/messages/MessageFlag.hpp index 60c213f03..306587a09 100644 --- a/src/messages/MessageFlag.hpp +++ b/src/messages/MessageFlag.hpp @@ -52,6 +52,8 @@ enum class MessageFlag : std::int64_t { Action = (1LL << 36), /// The message is sent in a different source channel as part of a Shared Chat session SharedMessage = (1LL << 37), + /// AutoMod message that showed up due to containing a blocked term in the channel + AutoModBlockedTerm = (1LL << 38), }; using MessageFlags = FlagsEnum; diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index 81c75c6b4..dc51113f6 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -141,6 +141,9 @@ void MessageLayout::actuallyLayout(const MessageLayoutContext &ctx) bool hideModerated = getSettings()->hideModerated; bool hideModerationActions = getSettings()->hideModerationActions; + bool hideBlockedTermAutomodMessages = + getSettings()->showBlockedTermAutomodMessages.getEnum() == + ShowModerationState::Never; bool hideSimilar = getSettings()->hideSimilar; bool hideReplies = !ctx.flags.has(MessageElementFlag::RepliedMessage); @@ -154,9 +157,21 @@ void MessageLayout::actuallyLayout(const MessageLayoutContext &ctx) continue; } + if (hideBlockedTermAutomodMessages && + this->message_->flags.has(MessageFlag::AutoModBlockedTerm)) + { + // NOTE: This hides the message but it will make the message re-appear if moderation message hiding is no longer active, and the layout is re-laid-out. + // This is only the case for the moderation messages that don't get filtered during creation. + // We should decide which is the correct method & apply that everywhere + continue; + } + if (this->message_->flags.has(MessageFlag::Timeout) || this->message_->flags.has(MessageFlag::Untimeout)) { + // NOTE: This hides the message but it will make the message re-appear if moderation message hiding is no longer active, and the layout is re-laid-out. + // This is only the case for the moderation messages that don't get filtered during creation. + // We should decide which is the correct method & apply that everywhere if (hideModerationActions || (getSettings()->streamerModeHideModActions && getApp()->getStreamerMode()->isEnabled())) diff --git a/src/providers/twitch/PubSubActions.hpp b/src/providers/twitch/PubSubActions.hpp index 5f83b4051..58e1a35e3 100644 --- a/src/providers/twitch/PubSubActions.hpp +++ b/src/providers/twitch/PubSubActions.hpp @@ -1,5 +1,7 @@ #pragma once +#include "providers/twitch/pubsubmessages/AutoMod.hpp" + #include #include #include @@ -143,6 +145,7 @@ struct AutomodAction : PubSubAction { QString message; QString reason; + PubSubAutoModQueueMessage::Reason reasonCode; QString msgID; }; diff --git a/src/providers/twitch/PubSubManager.cpp b/src/providers/twitch/PubSubManager.cpp index 89a08327b..9a2f03848 100644 --- a/src/providers/twitch/PubSubManager.cpp +++ b/src/providers/twitch/PubSubManager.cpp @@ -349,40 +349,6 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval) this->moderation.raidCanceled.invoke(action); }; - /* - // This handler is no longer required as we use the automod-queue topic now - this->moderationActionHandlers["automod_rejected"] = - [this](const auto &data, const auto &roomID) { - AutomodAction action(data, roomID); - - action.source.id = data.value("created_by_user_id").toString(); - action.source.login = data.value("created_by").toString(); - - action.target.id = data.value("target_user_id").toString(); - - const auto args = data.value("args").toArray(); - - if (args.isEmpty()) - { - return; - } - - action.msgID = data.value("msg_id").toString(); - - if (action.msgID.isEmpty()) - { - // Missing required msg_id parameter - return; - } - - action.target.login = args[0].toString(); - action.message = args[1].toString(); // May be omitted - action.reason = args[2].toString(); // May be omitted - - this->moderation.autoModMessageBlocked.invoke(action); - }; - */ - this->moderationActionHandlers["automod_message_rejected"] = [this](const auto &data, const auto &roomID) { AutomodInfoAction action(data, roomID); diff --git a/src/providers/twitch/TwitchIrcServer.cpp b/src/providers/twitch/TwitchIrcServer.cpp index 9e2fe2fbf..901ac0525 100644 --- a/src/providers/twitch/TwitchIrcServer.cpp +++ b/src/providers/twitch/TwitchIrcServer.cpp @@ -494,6 +494,7 @@ void TwitchIrcServer::initialize() action.msgID = msg.messageID; action.message = msg.messageText; + action.reasonCode = msg.reason; // this message also contains per-word automod data, which could be implemented diff --git a/src/providers/twitch/pubsubmessages/AutoMod.cpp b/src/providers/twitch/pubsubmessages/AutoMod.cpp index 697db1e32..3dd3d77df 100644 --- a/src/providers/twitch/pubsubmessages/AutoMod.cpp +++ b/src/providers/twitch/pubsubmessages/AutoMod.cpp @@ -15,6 +15,10 @@ PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root) this->type = oType.value(); } + this->reason = + qmagicenum::enumCast(data.value("reason_code").toString()) + .value_or(Reason::INVALID); + auto contentClassification = data.value("content_classification").toObject(); diff --git a/src/providers/twitch/pubsubmessages/AutoMod.hpp b/src/providers/twitch/pubsubmessages/AutoMod.hpp index 9f40d39da..3179abae3 100644 --- a/src/providers/twitch/pubsubmessages/AutoMod.hpp +++ b/src/providers/twitch/pubsubmessages/AutoMod.hpp @@ -13,15 +13,24 @@ struct PubSubAutoModQueueMessage { INVALID, }; + + enum class Reason { + AutoMod, + BlockedTerm, + + INVALID, + }; + QString typeString; Type type = Type::INVALID; + Reason reason = Reason::INVALID; QJsonObject data; QString status; QString contentCategory; - int contentLevel; + int contentLevel{}; QString messageID; QString messageText; @@ -51,3 +60,20 @@ constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name< return default_tag; } } + +template <> +constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name< + chatterino::PubSubAutoModQueueMessage::Reason>( + chatterino::PubSubAutoModQueueMessage::Reason value) noexcept +{ + switch (value) + { + case chatterino::PubSubAutoModQueueMessage::Reason::AutoMod: + return "AutoModCaughtMessageReason"; + case chatterino::PubSubAutoModQueueMessage::Reason::BlockedTerm: + return "BlockedTermCaughtMessageReason"; + + default: + return default_tag; + } +} diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 94366dabe..fa039184e 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -69,6 +69,13 @@ enum class ChatSendProtocol : int { Helix = 2, }; +enum class ShowModerationState : int { + // Always show this moderation-related item + Always = 0, + // Never show this moderation-related item + Never = 1, +}; + enum StreamerModeSetting { Disabled = 0, Enabled = 1, @@ -345,6 +352,10 @@ public: IntSetting timeoutStackStyle = { "/moderation/timeoutStackStyle", static_cast(TimeoutStackStyle::Default)}; + EnumStringSetting showBlockedTermAutomodMessages = { + "/moderation/showBlockedTermAutomodMessages", + ShowModerationState::Always, + }; /// Highlighting // BoolSetting enableHighlights = {"/highlighting/enabled", true}; diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp index 88d5ec565..75d09bde7 100644 --- a/src/singletons/WindowManager.cpp +++ b/src/singletons/WindowManager.cpp @@ -140,6 +140,8 @@ WindowManager::WindowManager(const Paths &paths, Settings &settings, this->forceLayoutChannelViewsListener.add(settings.enableRedeemedHighlight); this->forceLayoutChannelViewsListener.add(settings.colorUsernames); this->forceLayoutChannelViewsListener.add(settings.boldUsernames); + this->forceLayoutChannelViewsListener.add( + settings.showBlockedTermAutomodMessages); this->layoutChannelViewsListener.add(settings.timestampFormat); this->layoutChannelViewsListener.add(fonts.fontChanged); diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index 84ebaaf1b..58cc37319 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -1171,6 +1171,14 @@ void GeneralPage::initLayout(GeneralPageView &layout) layout.addIntInput("Usercard scrollback limit (requires restart)", s.scrollbackUsercardLimit, 100, 100000, 100); + layout.addDropdownEnumClass( + "Show blocked term automod messages", + qmagicenum::enumNames(), + s.showBlockedTermAutomodMessages, + "Show messages that are blocked by AutoMod for containing a public " + "blocked term in the current channel.", + {}); + layout.addDropdown( "Stack timeouts", {"Stack", "Stack until timeout", "Don't stack"}, s.timeoutStackStyle,