From df184b65e70f7ca0182f9dc7a81a417a4cc760dd Mon Sep 17 00:00:00 2001 From: iProdigy Date: Sun, 31 Dec 2023 20:55:00 -0800 Subject: [PATCH 01/10] feat: show chat badges on suspicious user messages --- src/Application.cpp | 12 +++-- src/providers/twitch/TwitchMessageBuilder.cpp | 46 +++++++++++++------ src/providers/twitch/TwitchMessageBuilder.hpp | 10 +++- .../twitch/pubsubmessages/LowTrustUsers.cpp | 6 ++- .../twitch/pubsubmessages/LowTrustUsers.hpp | 15 ++---- 5 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/Application.cpp b/src/Application.cpp index 37ba100cd88..0ecefefee3c 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -507,10 +507,16 @@ void Application::initPubSub() return; } - auto chan = + auto channel = this->twitch->getChannelOrEmptyByID(action.channelID); - if (chan->isEmpty()) + if (channel->isEmpty()) + { + return; + } + + auto *chan = dynamic_cast(channel.get()); + if (!chan) { return; } @@ -518,7 +524,7 @@ void Application::initPubSub() postToThread([chan, action] { const auto p = TwitchMessageBuilder::makeLowTrustUserMessage( - action, chan->getName()); + action, chan->getName(), chan); chan->addMessage(p.first); chan->addMessage(p.second); }); diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 0035f52c337..e7ced2a6020 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -1114,10 +1114,9 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name) } std::optional TwitchMessageBuilder::getTwitchBadge( - const Badge &badge) const + const Badge &badge, const TwitchChannel *chan) { - if (auto channelBadge = - this->twitchChannel->twitchBadge(badge.key_, badge.value_)) + if (auto channelBadge = chan->twitchBadge(badge.key_, badge.value_)) { return channelBadge; } @@ -1190,10 +1189,23 @@ void TwitchMessageBuilder::appendTwitchBadges() auto badgeInfos = TwitchMessageBuilder::parseBadgeInfoTag(this->tags); auto badges = this->parseBadgeTag(this->tags); + TwitchMessageBuilder::appendTwitchBadges(this, badges, badgeInfos, + this->twitchChannel); +} + +void TwitchMessageBuilder::appendTwitchBadges( + MessageBuilder *builder, const std::vector &badges, + const std::unordered_map &badgeInfos, + const TwitchChannel *chan) +{ + if (chan == nullptr) + { + return; + } for (const auto &badge : badges) { - auto badgeEmote = this->getTwitchBadge(badge); + auto badgeEmote = getTwitchBadge(badge, chan); if (!badgeEmote) { continue; @@ -1208,9 +1220,10 @@ void TwitchMessageBuilder::appendTwitchBadges() else if (badge.key_ == "moderator" && getSettings()->useCustomFfzModeratorBadges) { - if (auto customModBadge = this->twitchChannel->ffzCustomModBadge()) + if (auto customModBadge = chan->ffzCustomModBadge()) { - this->emplace( + builder + ->emplace( *customModBadge, MessageElementFlag::BadgeChannelAuthority) ->setTooltip((*customModBadge)->tooltip.string); @@ -1220,9 +1233,10 @@ void TwitchMessageBuilder::appendTwitchBadges() } else if (badge.key_ == "vip" && getSettings()->useCustomFfzVipBadges) { - if (auto customVipBadge = this->twitchChannel->ffzCustomVipBadge()) + if (auto customVipBadge = chan->ffzCustomVipBadge()) { - this->emplace( + builder + ->emplace( *customVipBadge, MessageElementFlag::BadgeChannelAuthority) ->setTooltip((*customVipBadge)->tooltip.string); @@ -1253,8 +1267,9 @@ void TwitchMessageBuilder::appendTwitchBadges() auto badgeInfoIt = badgeInfos.find(badge.key_); if (badgeInfoIt != badgeInfos.end()) { + auto infoValue = badgeInfoIt->second; auto predictionText = - badgeInfoIt->second + infoValue .replace(R"(\s)", " ") // standard IRC escapes .replace(R"(\:)", ";") .replace(R"(\\)", R"(\)") @@ -1265,12 +1280,12 @@ void TwitchMessageBuilder::appendTwitchBadges() } } - this->emplace(*badgeEmote, badge.flag_) + builder->emplace(*badgeEmote, badge.flag_) ->setTooltip(tooltip); } - this->message().badges = badges; - this->message().badgeInfos = badgeInfos; + builder->message().badges = badges; + builder->message().badgeInfos = badgeInfos; } void TwitchMessageBuilder::appendChatterinoBadges() @@ -2006,7 +2021,8 @@ MessagePtr TwitchMessageBuilder::makeLowTrustUpdateMessage( } std::pair TwitchMessageBuilder::makeLowTrustUserMessage( - const PubSubLowTrustUsersMessage &action, const QString &channelName) + const PubSubLowTrustUsersMessage &action, const QString &channelName, + const TwitchChannel *chan) { MessageBuilder builder, builder2; @@ -2089,6 +2105,10 @@ std::pair TwitchMessageBuilder::makeLowTrustUserMessage( builder2.message().flags.set(MessageFlag::PubSub); builder2.message().flags.set(MessageFlag::LowTrustUsers); + // sender badges + TwitchMessageBuilder::appendTwitchBadges(&builder2, action.senderBadges, {}, + chan); + // sender username builder2 .emplace(action.suspiciousUserDisplayName + ":", diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index 9bffd8f7a8f..7c93f79a911 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -95,7 +95,8 @@ class TwitchMessageBuilder : public SharedMessageBuilder static MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action); static std::pair makeLowTrustUserMessage( - const PubSubLowTrustUsersMessage &action, const QString &channelName); + const PubSubLowTrustUsersMessage &action, const QString &channelName, + const TwitchChannel *chan); static MessagePtr makeLowTrustUpdateMessage( const PubSubLowTrustUsersMessage &action); @@ -119,7 +120,8 @@ class TwitchMessageBuilder : public SharedMessageBuilder void runIgnoreReplaces(std::vector &twitchEmotes); - std::optional getTwitchBadge(const Badge &badge) const; + static std::optional getTwitchBadge(const Badge &badge, + const TwitchChannel *chan); Outcome tryAppendEmote(const EmoteName &name) override; void addWords(const QStringList &words, @@ -128,6 +130,10 @@ class TwitchMessageBuilder : public SharedMessageBuilder void addTextOrEmoji(const QString &value) override; void appendTwitchBadges(); + static void appendTwitchBadges( + MessageBuilder *builder, const std::vector &badges, + const std::unordered_map &badgeInfos, + const TwitchChannel *chan); void appendChatterinoBadges(); void appendFfzBadges(); void appendSeventvBadges(); diff --git a/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp b/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp index 630558944b6..dc60b997a1d 100644 --- a/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp +++ b/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp @@ -35,10 +35,12 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root) this->suspiciousUserColor = QColor(sender.value("chat_color").toString()); - std::vector badges; + std::vector badges; for (const auto &badge : sender.value("badges").toArray()) { - badges.emplace_back(badge.toObject()); + const auto badgeObj = badge.toObject(); + badges.emplace_back(Badge{badgeObj.value("id").toString(), + badgeObj.value("version").toString()}); } this->senderBadges = badges; diff --git a/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp b/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp index 84ca577d791..3679f6e1aed 100644 --- a/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp +++ b/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp @@ -1,5 +1,7 @@ #pragma once +#include "providers/twitch/TwitchBadge.hpp" + #include #include #include @@ -8,17 +10,6 @@ namespace chatterino { -struct LowTrustUserChatBadge { - QString id; - QString version; - - explicit LowTrustUserChatBadge(const QJsonObject &obj) - : id(obj.value("id").toString()) - , version(obj.value("version").toString()) - { - } -}; - struct PubSubLowTrustUsersMessage { /** * The type of low trust message update @@ -130,7 +121,7 @@ struct PubSubLowTrustUsersMessage { * A list of badges of the user who sent the message. * Only used for the UserMessage type. */ - std::vector senderBadges; + std::vector senderBadges; /** * Stores the string value of `type` From 4609b2b9d333f8dc7535fa2a1a7107f9547c8975 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Sun, 31 Dec 2023 21:41:21 -0800 Subject: [PATCH 02/10] feat: display emotes in suspicious user messages --- src/providers/twitch/TwitchMessageBuilder.cpp | 19 ++++++++++++++++-- .../twitch/pubsubmessages/LowTrustUsers.cpp | 10 ++++++++-- .../twitch/pubsubmessages/LowTrustUsers.hpp | 20 +++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index e7ced2a6020..6d2dbadf2d5 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -2123,8 +2123,23 @@ std::pair TwitchMessageBuilder::makeLowTrustUserMessage( ->setLink({Link::UserInfo, action.suspiciousUserLogin}); // sender's message caught by AutoMod - builder2.emplace(action.text, MessageElementFlag::Text, - MessageColor::Text); + for (const auto &fragment : action.fragments) + { + if (fragment.emoteID.isEmpty()) + { + builder2.emplace( + fragment.text, MessageElementFlag::Text, MessageColor::Text); + } + else + { + const auto emotePtr = + getIApp()->getEmotes()->getTwitchEmotes()->getOrCreateEmote( + EmoteId{fragment.emoteID}, EmoteName{fragment.text}); + builder2.emplace( + emotePtr, MessageElementFlag::TwitchEmote, MessageColor::Text); + } + } + auto text = QString("%1: %2").arg(action.suspiciousUserDisplayName, action.text); builder2.message().messageText = text; diff --git a/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp b/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp index dc60b997a1d..a367530bb55 100644 --- a/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp +++ b/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp @@ -21,8 +21,14 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root) { this->msgID = data.value("message_id").toString(); this->sentAt = data.value("sent_at").toString(); - this->text = - data.value("message_content").toObject().value("text").toString(); + const auto content = data.value("message_content").toObject(); + this->text = content.value("text").toString(); + std::vector parts; + for (const auto &part : content.value("fragments").toArray()) + { + parts.emplace_back(part.toObject()); + } + this->fragments = parts; // the rest of the data is within a nested object data = data.value("low_trust_user").toObject(); diff --git a/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp b/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp index 3679f6e1aed..e266628136b 100644 --- a/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp +++ b/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp @@ -11,6 +11,20 @@ namespace chatterino { struct PubSubLowTrustUsersMessage { + struct Fragment { + QString text; + QString emoteID; + + explicit Fragment(const QJsonObject &obj) + : text(obj.value("text").toString()) + , emoteID(obj.value("emoticon") + .toObject() + .value("emoticonID") + .toString()) + { + } + }; + /** * The type of low trust message update */ @@ -93,6 +107,12 @@ struct PubSubLowTrustUsersMessage { */ QString text; + /** + * Pre-parsed components of the message. + * Only used for the UserMessage type. + */ + std::vector fragments; + /** * ID of the message. * Only used for the UserMessage type. From 5b9417d011c4b598ad060c799def29870b4d1efb Mon Sep 17 00:00:00 2001 From: iProdigy Date: Sun, 31 Dec 2023 22:20:28 -0800 Subject: [PATCH 03/10] feat: add search filters for suspicious messages --- src/controllers/filters/lang/Filter.cpp | 2 ++ src/messages/Message.hpp | 2 ++ src/messages/search/MessageFlagsPredicate.cpp | 8 ++++++++ src/providers/twitch/TwitchMessageBuilder.cpp | 2 ++ 4 files changed, 14 insertions(+) diff --git a/src/controllers/filters/lang/Filter.cpp b/src/controllers/filters/lang/Filter.cpp index 9f31ec1e6e3..9cb15813e30 100644 --- a/src/controllers/filters/lang/Filter.cpp +++ b/src/controllers/filters/lang/Filter.cpp @@ -101,6 +101,8 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel) {"flags.whisper", m->flags.has(MessageFlag::Whisper)}, {"flags.reply", m->flags.has(MessageFlag::ReplyMessage)}, {"flags.automod", m->flags.has(MessageFlag::AutoMod)}, + {"flags.restricted", m->flags.has(MessageFlag::RestrictedMessage)}, + {"flags.monitored", m->flags.has(MessageFlag::MonitoredMessage)}, {"message.content", m->messageText}, {"message.length", m->messageText.length()}, diff --git a/src/messages/Message.hpp b/src/messages/Message.hpp index 30c1308a437..34256b85f75 100644 --- a/src/messages/Message.hpp +++ b/src/messages/Message.hpp @@ -53,6 +53,8 @@ enum class MessageFlag : int64_t { /// The message caught by AutoMod containing the user who sent the message & its contents AutoModOffendingMessage = (1LL << 31), LowTrustUsers = (1LL << 32), + RestrictedMessage = (1LL << 33), + MonitoredMessage = (1LL << 34), }; using MessageFlags = FlagsEnum; diff --git a/src/messages/search/MessageFlagsPredicate.cpp b/src/messages/search/MessageFlagsPredicate.cpp index c15257699ac..76e32de7209 100644 --- a/src/messages/search/MessageFlagsPredicate.cpp +++ b/src/messages/search/MessageFlagsPredicate.cpp @@ -52,6 +52,14 @@ MessageFlagsPredicate::MessageFlagsPredicate(const QString &flags, bool negate) { this->flags_.set(MessageFlag::ReplyMessage); } + else if (flag == "restricted") + { + this->flags_.set(MessageFlag::RestrictedMessage); + } + else if (flag == "monitored") + { + this->flags_.set(MessageFlag::MonitoredMessage); + } } } diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 6d2dbadf2d5..656bde9f210 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -2045,10 +2045,12 @@ std::pair TwitchMessageBuilder::makeLowTrustUserMessage( if (action.treatment == PubSubLowTrustUsersMessage::Treatment::Restricted) { headerMessage = "Restricted"; + builder2.message().flags.set(MessageFlag::RestrictedMessage); } else { headerMessage = "Monitored"; + builder2.message().flags.set(MessageFlag::MonitoredMessage); } if (action.restrictionTypes.has( From 20395f22861de17960ac62ab2141d97b8aebdba1 Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Sun, 31 Dec 2023 22:29:10 -0800 Subject: [PATCH 04/10] chore: update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 717cc71f827..2324e26df3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Major: Allow use of Twitch follower emotes in other channels if subscribed. (#4922) - Major: Add `/automod` split to track automod caught messages across all open channels the user moderates. (#4986, #5026) -- Major: Show restricted chat messages and suspicious treatment updates. (#5056) +- Major: Show restricted chat messages and suspicious treatment updates. (#5056, #5060) - Minor: Migrate to the new Get Channel Followers Helix endpoint, fixing follower count not showing up in usercards. (#4809) - Minor: The account switcher is now styled to match your theme. (#4817) - Minor: Add an invisible resize handle to the bottom of frameless user info popups and reply thread popups. (#4795) From 69d5efab716c68352f7277a90b83a0819cf06f50 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Mon, 1 Jan 2024 23:58:33 -0800 Subject: [PATCH 05/10] refactor: resolve initial nits --- src/Application.cpp | 16 +- src/providers/twitch/TwitchMessageBuilder.cpp | 235 +++++++++--------- src/providers/twitch/TwitchMessageBuilder.hpp | 8 +- .../twitch/pubsubmessages/LowTrustUsers.cpp | 19 +- 4 files changed, 132 insertions(+), 146 deletions(-) diff --git a/src/Application.cpp b/src/Application.cpp index 0ecefefee3c..021cd1ffe45 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -507,26 +507,26 @@ void Application::initPubSub() return; } - auto channel = + auto chan = this->twitch->getChannelOrEmptyByID(action.channelID); - if (channel->isEmpty()) + if (chan->isEmpty()) { return; } - auto *chan = dynamic_cast(channel.get()); - if (!chan) + auto *twitchChannel = dynamic_cast(chan.get()); + if (!twitchChannel) { return; } - postToThread([chan, action] { + postToThread([twitchChannel, action] { const auto p = TwitchMessageBuilder::makeLowTrustUserMessage( - action, chan->getName(), chan); - chan->addMessage(p.first); - chan->addMessage(p.second); + action, twitchChannel->getName(), twitchChannel); + twitchChannel->addMessage(p.first); + twitchChannel->addMessage(p.second); }); }); diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 656bde9f210..b583d034e0c 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -153,6 +153,119 @@ namespace { } } + std::optional getTwitchBadge(const Badge &badge, + const TwitchChannel *twitchChannel) + { + if (auto channelBadge = + twitchChannel->twitchBadge(badge.key_, badge.value_)) + { + return channelBadge; + } + + if (auto globalBadge = + TwitchBadges::instance()->badge(badge.key_, badge.value_)) + { + return globalBadge; + } + + return std::nullopt; + } + + void appendBadges(MessageBuilder *builder, const std::vector &badges, + const std::unordered_map &badgeInfos, + const TwitchChannel *twitchChannel) + { + if (twitchChannel == nullptr) + { + return; + } + + for (const auto &badge : badges) + { + auto badgeEmote = getTwitchBadge(badge, twitchChannel); + if (!badgeEmote) + { + continue; + } + auto tooltip = (*badgeEmote)->tooltip.string; + + if (badge.key_ == "bits") + { + const auto &cheerAmount = badge.value_; + tooltip = QString("Twitch cheer %0").arg(cheerAmount); + } + else if (badge.key_ == "moderator" && + getSettings()->useCustomFfzModeratorBadges) + { + if (auto customModBadge = twitchChannel->ffzCustomModBadge()) + { + builder + ->emplace( + *customModBadge, + MessageElementFlag::BadgeChannelAuthority) + ->setTooltip((*customModBadge)->tooltip.string); + // early out, since we have to add a custom badge element here + continue; + } + } + else if (badge.key_ == "vip" && + getSettings()->useCustomFfzVipBadges) + { + if (auto customVipBadge = twitchChannel->ffzCustomVipBadge()) + { + builder + ->emplace( + *customVipBadge, + MessageElementFlag::BadgeChannelAuthority) + ->setTooltip((*customVipBadge)->tooltip.string); + // early out, since we have to add a custom badge element here + continue; + } + } + else if (badge.flag_ == MessageElementFlag::BadgeSubscription) + { + auto badgeInfoIt = badgeInfos.find(badge.key_); + if (badgeInfoIt != badgeInfos.end()) + { + // badge.value_ is 4 chars long if user is subbed on higher tier + // (tier + amount of months with leading zero if less than 100) + // e.g. 3054 - tier 3 4,5-year sub. 2108 - tier 2 9-year sub + const auto &subTier = + badge.value_.length() > 3 ? badge.value_.at(0) : '1'; + const auto &subMonths = badgeInfoIt->second; + tooltip += QString(" (%1%2 months)") + .arg(subTier != '1' + ? QString("Tier %1, ").arg(subTier) + : "") + .arg(subMonths); + } + } + else if (badge.flag_ == MessageElementFlag::BadgePredictions) + { + auto badgeInfoIt = badgeInfos.find(badge.key_); + if (badgeInfoIt != badgeInfos.end()) + { + auto infoValue = badgeInfoIt->second; + auto predictionText = + infoValue + .replace(R"(\s)", " ") // standard IRC escapes + .replace(R"(\:)", ";") + .replace(R"(\\)", R"(\)") + .replace("⸝", ","); // twitch's comma escape + // Careful, the first character is RIGHT LOW PARAPHRASE BRACKET or U+2E1D, which just looks like a comma + + tooltip = QString("Predicted %1").arg(predictionText); + } + } + + builder->emplace(*badgeEmote, badge.flag_) + ->setTooltip(tooltip); + } + + builder->message().badges = badges; + builder->message().badgeInfos = badgeInfos; + } + } // namespace TwitchMessageBuilder::TwitchMessageBuilder( @@ -1113,23 +1226,6 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name) return Failure; } -std::optional TwitchMessageBuilder::getTwitchBadge( - const Badge &badge, const TwitchChannel *chan) -{ - if (auto channelBadge = chan->twitchBadge(badge.key_, badge.value_)) - { - return channelBadge; - } - - if (auto globalBadge = - TwitchBadges::instance()->badge(badge.key_, badge.value_)) - { - return globalBadge; - } - - return std::nullopt; -} - std::unordered_map TwitchMessageBuilder::parseBadgeInfoTag( const QVariantMap &tags) { @@ -1188,104 +1284,8 @@ void TwitchMessageBuilder::appendTwitchBadges() } auto badgeInfos = TwitchMessageBuilder::parseBadgeInfoTag(this->tags); - auto badges = this->parseBadgeTag(this->tags); - TwitchMessageBuilder::appendTwitchBadges(this, badges, badgeInfos, - this->twitchChannel); -} - -void TwitchMessageBuilder::appendTwitchBadges( - MessageBuilder *builder, const std::vector &badges, - const std::unordered_map &badgeInfos, - const TwitchChannel *chan) -{ - if (chan == nullptr) - { - return; - } - - for (const auto &badge : badges) - { - auto badgeEmote = getTwitchBadge(badge, chan); - if (!badgeEmote) - { - continue; - } - auto tooltip = (*badgeEmote)->tooltip.string; - - if (badge.key_ == "bits") - { - const auto &cheerAmount = badge.value_; - tooltip = QString("Twitch cheer %0").arg(cheerAmount); - } - else if (badge.key_ == "moderator" && - getSettings()->useCustomFfzModeratorBadges) - { - if (auto customModBadge = chan->ffzCustomModBadge()) - { - builder - ->emplace( - *customModBadge, - MessageElementFlag::BadgeChannelAuthority) - ->setTooltip((*customModBadge)->tooltip.string); - // early out, since we have to add a custom badge element here - continue; - } - } - else if (badge.key_ == "vip" && getSettings()->useCustomFfzVipBadges) - { - if (auto customVipBadge = chan->ffzCustomVipBadge()) - { - builder - ->emplace( - *customVipBadge, - MessageElementFlag::BadgeChannelAuthority) - ->setTooltip((*customVipBadge)->tooltip.string); - // early out, since we have to add a custom badge element here - continue; - } - } - else if (badge.flag_ == MessageElementFlag::BadgeSubscription) - { - auto badgeInfoIt = badgeInfos.find(badge.key_); - if (badgeInfoIt != badgeInfos.end()) - { - // badge.value_ is 4 chars long if user is subbed on higher tier - // (tier + amount of months with leading zero if less than 100) - // e.g. 3054 - tier 3 4,5-year sub. 2108 - tier 2 9-year sub - const auto &subTier = - badge.value_.length() > 3 ? badge.value_.at(0) : '1'; - const auto &subMonths = badgeInfoIt->second; - tooltip += - QString(" (%1%2 months)") - .arg(subTier != '1' ? QString("Tier %1, ").arg(subTier) - : "") - .arg(subMonths); - } - } - else if (badge.flag_ == MessageElementFlag::BadgePredictions) - { - auto badgeInfoIt = badgeInfos.find(badge.key_); - if (badgeInfoIt != badgeInfos.end()) - { - auto infoValue = badgeInfoIt->second; - auto predictionText = - infoValue - .replace(R"(\s)", " ") // standard IRC escapes - .replace(R"(\:)", ";") - .replace(R"(\\)", R"(\)") - .replace("⸝", ","); // twitch's comma escape - // Careful, the first character is RIGHT LOW PARAPHRASE BRACKET or U+2E1D, which just looks like a comma - - tooltip = QString("Predicted %1").arg(predictionText); - } - } - - builder->emplace(*badgeEmote, badge.flag_) - ->setTooltip(tooltip); - } - - builder->message().badges = badges; - builder->message().badgeInfos = badgeInfos; + auto badges = TwitchMessageBuilder::parseBadgeTag(this->tags); + appendBadges(this, badges, badgeInfos, this->twitchChannel); } void TwitchMessageBuilder::appendChatterinoBadges() @@ -2022,7 +2022,7 @@ MessagePtr TwitchMessageBuilder::makeLowTrustUpdateMessage( std::pair TwitchMessageBuilder::makeLowTrustUserMessage( const PubSubLowTrustUsersMessage &action, const QString &channelName, - const TwitchChannel *chan) + const TwitchChannel *twitchChannel) { MessageBuilder builder, builder2; @@ -2108,8 +2108,7 @@ std::pair TwitchMessageBuilder::makeLowTrustUserMessage( builder2.message().flags.set(MessageFlag::LowTrustUsers); // sender badges - TwitchMessageBuilder::appendTwitchBadges(&builder2, action.senderBadges, {}, - chan); + appendBadges(&builder2, action.senderBadges, {}, twitchChannel); // sender username builder2 diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index 7c93f79a911..a9fb15f4f96 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -96,7 +96,7 @@ class TwitchMessageBuilder : public SharedMessageBuilder static std::pair makeLowTrustUserMessage( const PubSubLowTrustUsersMessage &action, const QString &channelName, - const TwitchChannel *chan); + const TwitchChannel *twitchChannel); static MessagePtr makeLowTrustUpdateMessage( const PubSubLowTrustUsersMessage &action); @@ -120,8 +120,6 @@ class TwitchMessageBuilder : public SharedMessageBuilder void runIgnoreReplaces(std::vector &twitchEmotes); - static std::optional getTwitchBadge(const Badge &badge, - const TwitchChannel *chan); Outcome tryAppendEmote(const EmoteName &name) override; void addWords(const QStringList &words, @@ -130,10 +128,6 @@ class TwitchMessageBuilder : public SharedMessageBuilder void addTextOrEmoji(const QString &value) override; void appendTwitchBadges(); - static void appendTwitchBadges( - MessageBuilder *builder, const std::vector &badges, - const std::unordered_map &badgeInfos, - const TwitchChannel *chan); void appendChatterinoBadges(); void appendFfzBadges(); void appendSeventvBadges(); diff --git a/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp b/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp index a367530bb55..2a7fd6f50fc 100644 --- a/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp +++ b/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp @@ -23,12 +23,10 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root) this->sentAt = data.value("sent_at").toString(); const auto content = data.value("message_content").toObject(); this->text = content.value("text").toString(); - std::vector parts; for (const auto &part : content.value("fragments").toArray()) { - parts.emplace_back(part.toObject()); + this->fragments.emplace_back(part.toObject()); } - this->fragments = parts; // the rest of the data is within a nested object data = data.value("low_trust_user").toObject(); @@ -41,25 +39,22 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root) this->suspiciousUserColor = QColor(sender.value("chat_color").toString()); - std::vector badges; for (const auto &badge : sender.value("badges").toArray()) { const auto badgeObj = badge.toObject(); - badges.emplace_back(Badge{badgeObj.value("id").toString(), - badgeObj.value("version").toString()}); + const auto badgeID = badgeObj.value("id").toString(); + const auto badgeVersion = badgeObj.value("version").toString(); + this->senderBadges.emplace_back(Badge{badgeID, badgeVersion}); } - this->senderBadges = badges; const auto sharedValue = data.value("shared_ban_channel_ids"); - std::vector sharedIDs; if (!sharedValue.isNull()) { for (const auto &id : sharedValue.toArray()) { - sharedIDs.emplace_back(id.toString()); + this->sharedBanChannelIDs.emplace_back(id.toString()); } } - this->sharedBanChannelIDs = sharedIDs; } else { @@ -96,17 +91,15 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root) this->evasionEvaluation = oEvaluation.value(); } - FlagsEnum restrictions; for (const auto &rType : data.value("types").toArray()) { if (const auto oRestriction = magic_enum::enum_cast( rType.toString().toStdString()); oRestriction.has_value()) { - restrictions.set(oRestriction.value()); + this->restrictionTypes.set(oRestriction.value()); } } - this->restrictionTypes = restrictions; } } // namespace chatterino From e9796768236d1ef4a3f0b26f97fd01f7cec4c56a Mon Sep 17 00:00:00 2001 From: iProdigy Date: Tue, 2 Jan 2024 00:10:08 -0800 Subject: [PATCH 06/10] fix: finish adding new filter identifier --- src/controllers/filters/lang/Filter.cpp | 2 ++ src/controllers/filters/lang/Filter.hpp | 2 ++ src/controllers/filters/lang/Tokenizer.hpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/controllers/filters/lang/Filter.cpp b/src/controllers/filters/lang/Filter.cpp index 9cb15813e30..7ae61991a90 100644 --- a/src/controllers/filters/lang/Filter.cpp +++ b/src/controllers/filters/lang/Filter.cpp @@ -44,6 +44,8 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel) * flags.whisper * flags.reply * flags.automod + * flags.restricted + * flags.monitored * * message.content * message.length diff --git a/src/controllers/filters/lang/Filter.hpp b/src/controllers/filters/lang/Filter.hpp index 6ebfdb944a8..c8afbd76916 100644 --- a/src/controllers/filters/lang/Filter.hpp +++ b/src/controllers/filters/lang/Filter.hpp @@ -44,6 +44,8 @@ static const QMap MESSAGE_TYPING_CONTEXT = { {"flags.whisper", Type::Bool}, {"flags.reply", Type::Bool}, {"flags.automod", Type::Bool}, + {"flags.restricted", Type::Bool}, + {"flags.monitored", Type::Bool}, {"message.content", Type::String}, {"message.length", Type::Int}, }; diff --git a/src/controllers/filters/lang/Tokenizer.hpp b/src/controllers/filters/lang/Tokenizer.hpp index 567c8d1323c..2fbc5fd9536 100644 --- a/src/controllers/filters/lang/Tokenizer.hpp +++ b/src/controllers/filters/lang/Tokenizer.hpp @@ -32,6 +32,8 @@ static const QMap validIdentifiersMap = { {"flags.whisper", "whisper message?"}, {"flags.reply", "reply message?"}, {"flags.automod", "automod message?"}, + {"flags.restricted", "restricted message?"}, + {"flags.monitored", "monitored message?"}, {"message.content", "message text"}, {"message.length", "message length"}}; From 90913970eb10792d355229ca51ae12c469c4795c Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 6 Jan 2024 13:08:02 +0100 Subject: [PATCH 07/10] Comment the new message flags --- src/messages/Message.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/messages/Message.hpp b/src/messages/Message.hpp index 34256b85f75..82de23fe674 100644 --- a/src/messages/Message.hpp +++ b/src/messages/Message.hpp @@ -53,7 +53,9 @@ enum class MessageFlag : int64_t { /// The message caught by AutoMod containing the user who sent the message & its contents AutoModOffendingMessage = (1LL << 31), LowTrustUsers = (1LL << 32), + /// The message is sent by a user marked as restricted with Twitch's "Low Trust"/"Suspicious User" feature RestrictedMessage = (1LL << 33), + /// The message is sent by a user marked as monitor with Twitch's "Low Trust"/"Suspicious User" feature MonitoredMessage = (1LL << 34), }; using MessageFlags = FlagsEnum; From a9d690d1e84d51486307860fe1833048d6eaa39d Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 6 Jan 2024 13:12:55 +0100 Subject: [PATCH 08/10] Add a list of known issues to low trust update messages --- src/providers/twitch/TwitchMessageBuilder.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index b583d034e0c..47fcf93f251 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -1951,6 +1951,12 @@ std::pair TwitchMessageBuilder::makeAutomodMessage( MessagePtr TwitchMessageBuilder::makeLowTrustUpdateMessage( const PubSubLowTrustUsersMessage &action) { + /** + * Known issues: + * - Non-Twitch badges are not shown + * - Non-Twitch emotes are not shown + */ + MessageBuilder builder; builder.emplace(); builder.message().flags.set(MessageFlag::System); From e3eb8dbe5c734a62e934b59cd854c5273f04e78a Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 6 Jan 2024 13:14:09 +0100 Subject: [PATCH 09/10] fix: Keep shared-pointerness of the channel Without this change, we would have the possibility of using the TwitchChannel after the Channel itself has gone out of scope, albeit not realistically since we just post this to a thread and parse it - there's no networking or big delays involved. but this shows the intent better --- src/Application.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Application.cpp b/src/Application.cpp index feaa773ea73..884ec24d836 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -515,7 +515,7 @@ void Application::initPubSub() return; } - auto *twitchChannel = dynamic_cast(chan.get()); + auto twitchChannel = dynamic_pointer_cast(chan); if (!twitchChannel) { return; @@ -524,7 +524,8 @@ void Application::initPubSub() postToThread([twitchChannel, action] { const auto p = TwitchMessageBuilder::makeLowTrustUserMessage( - action, twitchChannel->getName(), twitchChannel); + action, twitchChannel->getName(), + twitchChannel.get()); twitchChannel->addMessage(p.first); twitchChannel->addMessage(p.second); }); From 857d173f477cfdbb3d1de5672c925ddedf579791 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 6 Jan 2024 14:03:43 +0100 Subject: [PATCH 10/10] fix dynamic pointer cast missing namespace --- src/Application.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Application.cpp b/src/Application.cpp index b6ad8b0f317..e13624c7e99 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -525,7 +525,8 @@ void Application::initPubSub() return; } - auto twitchChannel = dynamic_pointer_cast(chan); + auto twitchChannel = + std::dynamic_pointer_cast(chan); if (!twitchChannel) { return;