diff --git a/CHANGELOG.md b/CHANGELOG.md index 18753a26f7a..e615972191e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unversioned - 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) +- Major: Add `/automod` split to track automod caught messages across all open channels the user moderates. (#4986, #5026) - 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) diff --git a/src/Application.cpp b/src/Application.cpp index 7cfef3e34db..c28e36c621b 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -552,7 +552,8 @@ void Application::initPubSub() senderDisplayName, senderColor}; postToThread([chan, action] { const auto p = - makeAutomodMessage(action, chan->getName()); + TwitchMessageBuilder::makeAutomodMessage( + action, chan->getName()); chan->addMessage(p.first); chan->addMessage(p.second); @@ -584,7 +585,8 @@ void Application::initPubSub() } postToThread([chan, action] { - const auto p = makeAutomodMessage(action, chan->getName()); + const auto p = TwitchMessageBuilder::makeAutomodMessage( + action, chan->getName()); chan->addMessage(p.first); chan->addMessage(p.second); }); @@ -626,7 +628,8 @@ void Application::initPubSub() } postToThread([chan, action] { - const auto p = makeAutomodInfoMessage(action); + const auto p = + TwitchMessageBuilder::makeAutomodInfoMessage(action); chan->addMessage(p); }); }); diff --git a/src/controllers/highlights/HighlightController.cpp b/src/controllers/highlights/HighlightController.cpp index 6e91f706b71..9dee5504712 100644 --- a/src/controllers/highlights/HighlightController.cpp +++ b/src/controllers/highlights/HighlightController.cpp @@ -204,6 +204,41 @@ void rebuildMessageHighlights(Settings &settings, { checks.emplace_back(highlightPhraseCheck(highlight)); } + + if (settings.enableAutomodHighlight) + { + const auto highlightSound = + settings.enableAutomodHighlightSound.getValue(); + const auto highlightAlert = + settings.enableAutomodHighlightTaskbar.getValue(); + const auto highlightSoundUrlValue = + settings.automodHighlightSoundUrl.getValue(); + + checks.emplace_back(HighlightCheck{ + [=](const auto & /*args*/, const auto & /*badges*/, + const auto & /*senderName*/, const auto & /*originalMessage*/, + const auto &flags, + const auto /*self*/) -> std::optional { + if (!flags.has(MessageFlag::AutoModOffendingMessage)) + { + return std::nullopt; + } + + std::optional highlightSoundUrl; + if (!highlightSoundUrlValue.isEmpty()) + { + highlightSoundUrl = highlightSoundUrlValue; + } + + return HighlightResult{ + highlightAlert, // alert + highlightSound, // playSound + highlightSoundUrl, // customSoundUrl + nullptr, // color + false, // showInMentions + }; + }}); + } } void rebuildUserHighlights(Settings &settings, @@ -434,6 +469,11 @@ void HighlightController::initialize(Settings &settings, Paths & /*paths*/) this->rebuildListener_.addSetting(settings.threadHighlightSoundUrl); this->rebuildListener_.addSetting(settings.showThreadHighlightInMentions); + this->rebuildListener_.addSetting(settings.enableAutomodHighlight); + this->rebuildListener_.addSetting(settings.enableAutomodHighlightSound); + this->rebuildListener_.addSetting(settings.enableAutomodHighlightTaskbar); + this->rebuildListener_.addSetting(settings.automodHighlightSoundUrl); + this->rebuildListener_.setCB([this, &settings] { qCDebug(chatterinoHighlights) << "Rebuild checks because a setting changed"; diff --git a/src/controllers/highlights/HighlightModel.cpp b/src/controllers/highlights/HighlightModel.cpp index 7c7b08e9bd9..d7b76adf347 100644 --- a/src/controllers/highlights/HighlightModel.cpp +++ b/src/controllers/highlights/HighlightModel.cpp @@ -234,6 +234,30 @@ void HighlightModel::afterInit() this->insertCustomRow(threadMessageRow, HighlightRowIndexes::ThreadMessageRow); + + // Highlight settings for automod caught messages + const std::vector automodRow = this->createRow(); + setBoolItem(automodRow[Column::Pattern], + getSettings()->enableAutomodHighlight.getValue(), true, false); + automodRow[Column::Pattern]->setData("AutoMod Caught Messages", + Qt::DisplayRole); + automodRow[Column::ShowInMentions]->setFlags({}); + setBoolItem(automodRow[Column::FlashTaskbar], + getSettings()->enableAutomodHighlightTaskbar.getValue(), true, + false); + setBoolItem(automodRow[Column::PlaySound], + getSettings()->enableAutomodHighlightSound.getValue(), true, + false); + automodRow[Column::UseRegex]->setFlags({}); + automodRow[Column::CaseSensitive]->setFlags({}); + + const auto automodSound = + QUrl(getSettings()->automodHighlightSoundUrl.getValue()); + setFilePathItem(automodRow[Column::SoundPath], automodSound, false); + + automodRow[Column::Color]->setFlags(Qt::ItemFlag::NoItemFlags); + + this->insertCustomRow(automodRow, HighlightRowIndexes::AutomodRow); } void HighlightModel::customRowSetData(const std::vector &row, @@ -278,6 +302,11 @@ void HighlightModel::customRowSetData(const std::vector &row, getSettings()->enableThreadHighlight.setValue( value.toBool()); } + else if (rowIndex == HighlightRowIndexes::AutomodRow) + { + getSettings()->enableAutomodHighlight.setValue( + value.toBool()); + } } } break; @@ -336,6 +365,11 @@ void HighlightModel::customRowSetData(const std::vector &row, getSettings()->enableThreadHighlightTaskbar.setValue( value.toBool()); } + else if (rowIndex == HighlightRowIndexes::AutomodRow) + { + getSettings()->enableAutomodHighlightTaskbar.setValue( + value.toBool()); + } } } break; @@ -377,6 +411,11 @@ void HighlightModel::customRowSetData(const std::vector &row, getSettings()->enableThreadHighlightSound.setValue( value.toBool()); } + else if (rowIndex == HighlightRowIndexes::AutomodRow) + { + getSettings()->enableAutomodHighlightSound.setValue( + value.toBool()); + } } } break; @@ -412,6 +451,11 @@ void HighlightModel::customRowSetData(const std::vector &row, getSettings()->threadHighlightSoundUrl.setValue( value.toString()); } + else if (rowIndex == HighlightRowIndexes::AutomodRow) + { + getSettings()->automodHighlightSoundUrl.setValue( + value.toString()); + } } } break; diff --git a/src/controllers/highlights/HighlightModel.hpp b/src/controllers/highlights/HighlightModel.hpp index 0ee5192cf16..be807d33e54 100644 --- a/src/controllers/highlights/HighlightModel.hpp +++ b/src/controllers/highlights/HighlightModel.hpp @@ -34,6 +34,7 @@ class HighlightModel : public SignalVectorModel FirstMessageRow = 4, ElevatedMessageRow = 5, ThreadMessageRow = 6, + AutomodRow = 7, }; enum UserHighlightRowIndexes { diff --git a/src/messages/Message.hpp b/src/messages/Message.hpp index a503ea2757a..88d0d09345c 100644 --- a/src/messages/Message.hpp +++ b/src/messages/Message.hpp @@ -50,6 +50,8 @@ enum class MessageFlag : int64_t { LiveUpdatesAdd = (1LL << 28), LiveUpdatesRemove = (1LL << 29), LiveUpdatesUpdate = (1LL << 30), + /// The message caught by AutoMod containing the user who sent the message & its contents + AutoModOffendingMessage = (1LL << 31), }; using MessageFlags = FlagsEnum; diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp index b605544b904..d5ba0ff79fe 100644 --- a/src/messages/MessageBuilder.cpp +++ b/src/messages/MessageBuilder.cpp @@ -78,159 +78,6 @@ MessagePtr makeSystemMessage(const QString &text, const QTime &time) return MessageBuilder(systemMessage, text, time).release(); } -EmotePtr makeAutoModBadge() -{ - return std::make_shared(Emote{ - EmoteName{}, - ImageSet{Image::fromResourcePixmap(getResources().twitch.automod)}, - Tooltip{"AutoMod"}, - Url{"https://dashboard.twitch.tv/settings/moderation/automod"}}); -} - -MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action) -{ - auto builder = MessageBuilder(); - QString text("AutoMod: "); - - builder.emplace(); - builder.message().flags.set(MessageFlag::PubSub); - - // AutoMod shield badge - builder.emplace(makeAutoModBadge(), - MessageElementFlag::BadgeChannelAuthority); - // AutoMod "username" - builder.emplace("AutoMod:", MessageElementFlag::BoldUsername, - MessageColor(QColor("blue")), - FontStyle::ChatMediumBold); - builder.emplace( - "AutoMod:", MessageElementFlag::NonBoldUsername, - MessageColor(QColor("blue"))); - switch (action.type) - { - case AutomodInfoAction::OnHold: { - QString info("Hey! Your message is being checked " - "by mods and has not been sent."); - text += info; - builder.emplace(info, MessageElementFlag::Text, - MessageColor::Text); - } - break; - case AutomodInfoAction::Denied: { - QString info("Mods have removed your message."); - text += info; - builder.emplace(info, MessageElementFlag::Text, - MessageColor::Text); - } - break; - case AutomodInfoAction::Approved: { - QString info("Mods have accepted your message."); - text += info; - builder.emplace(info, MessageElementFlag::Text, - MessageColor::Text); - } - break; - } - - builder.message().flags.set(MessageFlag::AutoMod); - builder.message().messageText = text; - builder.message().searchText = text; - - auto message = builder.release(); - - return message; -} - -std::pair makeAutomodMessage( - const AutomodAction &action, const QString &channelName) -{ - MessageBuilder builder, builder2; - - // - // Builder for AutoMod message with explanation - builder.message().loginName = "automod"; - builder.message().channelName = channelName; - builder.message().flags.set(MessageFlag::PubSub); - builder.message().flags.set(MessageFlag::Timeout); - builder.message().flags.set(MessageFlag::AutoMod); - - // AutoMod shield badge - builder.emplace(makeAutoModBadge(), - MessageElementFlag::BadgeChannelAuthority); - // AutoMod "username" - builder.emplace("AutoMod:", MessageElementFlag::BoldUsername, - MessageColor(QColor("blue")), - FontStyle::ChatMediumBold); - builder.emplace( - "AutoMod:", MessageElementFlag::NonBoldUsername, - MessageColor(QColor("blue"))); - // AutoMod header message - builder.emplace( - ("Held a message for reason: " + action.reason + - ". Allow will post it in chat. "), - MessageElementFlag::Text, MessageColor::Text); - // Allow link button - builder - .emplace("Allow", MessageElementFlag::Text, - MessageColor(QColor("green")), - FontStyle::ChatMediumBold) - ->setLink({Link::AutoModAllow, action.msgID}); - // Deny link button - builder - .emplace(" Deny", MessageElementFlag::Text, - MessageColor(QColor("red")), - FontStyle::ChatMediumBold) - ->setLink({Link::AutoModDeny, action.msgID}); - // ID of message caught by AutoMod - // builder.emplace(action.msgID, MessageElementFlag::Text, - // MessageColor::Text); - auto text1 = - QString("AutoMod: Held a message for reason: %1. Allow will post " - "it in chat. Allow Deny") - .arg(action.reason); - builder.message().messageText = text1; - builder.message().searchText = text1; - - auto message1 = builder.release(); - - // - // Builder for offender's message - builder2.message().channelName = channelName; - builder2 - .emplace("#" + channelName, - MessageElementFlag::ChannelName, - MessageColor::System) - ->setLink({Link::JumpToChannel, channelName}); - builder2.emplace(); - builder2.emplace(); - builder2.message().loginName = action.target.login; - builder2.message().flags.set(MessageFlag::PubSub); - builder2.message().flags.set(MessageFlag::Timeout); - builder2.message().flags.set(MessageFlag::AutoMod); - - // sender username - builder2 - .emplace( - action.target.displayName + ":", MessageElementFlag::BoldUsername, - MessageColor(action.target.color), FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, action.target.login}); - builder2 - .emplace(action.target.displayName + ":", - MessageElementFlag::NonBoldUsername, - MessageColor(action.target.color)) - ->setLink({Link::UserInfo, action.target.login}); - // sender's message caught by AutoMod - builder2.emplace(action.message, MessageElementFlag::Text, - MessageColor::Text); - auto text2 = - QString("%1: %2").arg(action.target.displayName, action.message); - builder2.message().messageText = text2; - builder2.message().searchText = text2; - - auto message2 = builder2.release(); - - return std::make_pair(message1, message2); -} - MessageBuilder::MessageBuilder() : message_(std::make_shared()) { diff --git a/src/messages/MessageBuilder.hpp b/src/messages/MessageBuilder.hpp index 333d2346904..c7277997a27 100644 --- a/src/messages/MessageBuilder.hpp +++ b/src/messages/MessageBuilder.hpp @@ -53,9 +53,6 @@ const ImageUploaderResultTag imageUploaderResultMessage{}; MessagePtr makeSystemMessage(const QString &text); MessagePtr makeSystemMessage(const QString &text, const QTime &time); -std::pair makeAutomodMessage( - const AutomodAction &action, const QString &channelName); -MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action); struct MessageParseArgs { bool disablePingSounds = false; diff --git a/src/messages/SharedMessageBuilder.cpp b/src/messages/SharedMessageBuilder.cpp index 719ec1bedd4..addeb05b8a3 100644 --- a/src/messages/SharedMessageBuilder.cpp +++ b/src/messages/SharedMessageBuilder.cpp @@ -18,6 +18,8 @@ #include +#include + namespace { using namespace chatterino; @@ -170,18 +172,10 @@ void SharedMessageBuilder::parseHighlights() this->highlightAlert_ = highlightResult.alert; this->highlightSound_ = highlightResult.playSound; + this->highlightSoundCustomUrl_ = highlightResult.customSoundUrl; this->message().highlightColor = highlightResult.color; - if (highlightResult.customSoundUrl) - { - this->highlightSoundUrl_ = *highlightResult.customSoundUrl; - } - else - { - this->highlightSoundUrl_ = getFallbackHighlightSound(); - } - if (highlightResult.showInMentions) { this->message().flags.set(MessageFlag::ShowInMentions); @@ -199,6 +193,15 @@ void SharedMessageBuilder::appendChannelName() } void SharedMessageBuilder::triggerHighlights() +{ + SharedMessageBuilder::triggerHighlights( + this->channel->getName(), this->highlightSound_, + this->highlightSoundCustomUrl_, this->highlightAlert_); +} + +void SharedMessageBuilder::triggerHighlights( + const QString &channelName, bool playSound, + const std::optional &customSoundUrl, bool windowAlert) { if (isInStreamerMode() && getSettings()->streamerModeMuteMentions) { @@ -206,21 +209,32 @@ void SharedMessageBuilder::triggerHighlights() return; } - if (getSettings()->isMutedChannel(this->channel->getName())) + if (getSettings()->isMutedChannel(channelName)) { // Do nothing. Pings are muted in this channel. return; } - bool hasFocus = (QApplication::focusWidget() != nullptr); - bool resolveFocus = !hasFocus || getSettings()->highlightAlwaysPlaySound; + const bool hasFocus = (QApplication::focusWidget() != nullptr); + const bool resolveFocus = + !hasFocus || getSettings()->highlightAlwaysPlaySound; - if (this->highlightSound_ && resolveFocus) + if (playSound && resolveFocus) { - getIApp()->getSound()->play(this->highlightSoundUrl_); + // TODO(C++23): optional or_else + QUrl soundUrl; + if (customSoundUrl) + { + soundUrl = *customSoundUrl; + } + else + { + soundUrl = getFallbackHighlightSound(); + } + getIApp()->getSound()->play(soundUrl); } - if (this->highlightAlert_) + if (windowAlert) { getApp()->windows->sendAlert(); } diff --git a/src/messages/SharedMessageBuilder.hpp b/src/messages/SharedMessageBuilder.hpp index 5dce1ad9f95..b6e99e61bca 100644 --- a/src/messages/SharedMessageBuilder.hpp +++ b/src/messages/SharedMessageBuilder.hpp @@ -8,6 +8,8 @@ #include #include +#include + namespace chatterino { class Badge; @@ -57,6 +59,9 @@ class SharedMessageBuilder : public MessageBuilder // parseHighlights only updates the visual state of the message, but leaves the playing of alerts and sounds to the triggerHighlights function virtual void parseHighlights(); + static void triggerHighlights(const QString &channelName, bool playSound, + const std::optional &customSoundUrl, + bool windowAlert); void appendChannelName(); @@ -72,8 +77,7 @@ class SharedMessageBuilder : public MessageBuilder bool highlightAlert_ = false; bool highlightSound_ = false; - - QUrl highlightSoundUrl_; + std::optional highlightSoundCustomUrl_{}; }; } // namespace chatterino diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index 160ea51ca5b..0e8228485e9 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -342,9 +342,13 @@ void MessageLayout::updateBuffer(QPixmap *buffer, this->message_->flags.has(MessageFlag::HighlightedWhisper)) && !this->flags.has(MessageLayoutFlag::IgnoreHighlights)) { - // Blend highlight color with usual background color - backgroundColor = - blendColors(backgroundColor, *this->message_->highlightColor); + assert(this->message_->highlightColor); + if (this->message_->highlightColor) + { + // Blend highlight color with usual background color + backgroundColor = + blendColors(backgroundColor, *this->message_->highlightColor); + } } else if (this->message_->flags.has(MessageFlag::Subscription) && ctx.preferences.enableSubHighlight) diff --git a/src/messages/layouts/MessageLayoutContext.cpp b/src/messages/layouts/MessageLayoutContext.cpp index 82b158485fe..98c963919d5 100644 --- a/src/messages/layouts/MessageLayoutContext.cpp +++ b/src/messages/layouts/MessageLayoutContext.cpp @@ -47,6 +47,12 @@ void MessagePreferences::connectSettings(Settings *settings, }, holder); + settings->enableAutomodHighlight.connect( + [this](const auto &newValue) { + this->enableAutomodHighlight = newValue; + }, + holder); + settings->alternateMessages.connect( [this](const auto &newValue) { this->alternateMessages = newValue; diff --git a/src/messages/layouts/MessageLayoutContext.hpp b/src/messages/layouts/MessageLayoutContext.hpp index a64d98bb448..d8f08ab3abf 100644 --- a/src/messages/layouts/MessageLayoutContext.hpp +++ b/src/messages/layouts/MessageLayoutContext.hpp @@ -39,6 +39,7 @@ struct MessagePreferences { bool enableElevatedMessageHighlight{}; bool enableFirstMessageHighlight{}; bool enableSubHighlight{}; + bool enableAutomodHighlight{}; bool alternateMessages{}; bool separateMessages{}; diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 8ef6bcc66e2..e5f003a05a3 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -5,6 +5,7 @@ #include "common/Literals.hpp" #include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" +#include "controllers/highlights/HighlightController.hpp" #include "controllers/ignores/IgnoreController.hpp" #include "controllers/ignores/IgnorePhrase.hpp" #include "controllers/userdata/UserDataController.hpp" @@ -1765,6 +1766,173 @@ MessagePtr TwitchMessageBuilder::buildHypeChatMessage( return builder.release(); } +EmotePtr makeAutoModBadge() +{ + return std::make_shared(Emote{ + EmoteName{}, + ImageSet{Image::fromResourcePixmap(getResources().twitch.automod)}, + Tooltip{"AutoMod"}, + Url{"https://dashboard.twitch.tv/settings/moderation/automod"}}); +} + +MessagePtr TwitchMessageBuilder::makeAutomodInfoMessage( + const AutomodInfoAction &action) +{ + auto builder = MessageBuilder(); + QString text("AutoMod: "); + + builder.emplace(); + builder.message().flags.set(MessageFlag::PubSub); + + // AutoMod shield badge + builder.emplace(makeAutoModBadge(), + MessageElementFlag::BadgeChannelAuthority); + // AutoMod "username" + builder.emplace("AutoMod:", MessageElementFlag::BoldUsername, + MessageColor(QColor("blue")), + FontStyle::ChatMediumBold); + builder.emplace( + "AutoMod:", MessageElementFlag::NonBoldUsername, + MessageColor(QColor("blue"))); + switch (action.type) + { + case AutomodInfoAction::OnHold: { + QString info("Hey! Your message is being checked " + "by mods and has not been sent."); + text += info; + builder.emplace(info, MessageElementFlag::Text, + MessageColor::Text); + } + break; + case AutomodInfoAction::Denied: { + QString info("Mods have removed your message."); + text += info; + builder.emplace(info, MessageElementFlag::Text, + MessageColor::Text); + } + break; + case AutomodInfoAction::Approved: { + QString info("Mods have accepted your message."); + text += info; + builder.emplace(info, MessageElementFlag::Text, + MessageColor::Text); + } + break; + } + + builder.message().flags.set(MessageFlag::AutoMod); + builder.message().messageText = text; + builder.message().searchText = text; + + auto message = builder.release(); + + return message; +} + +std::pair TwitchMessageBuilder::makeAutomodMessage( + const AutomodAction &action, const QString &channelName) +{ + MessageBuilder builder, builder2; + + // + // Builder for AutoMod message with explanation + builder.message().loginName = "automod"; + builder.message().channelName = channelName; + builder.message().flags.set(MessageFlag::PubSub); + builder.message().flags.set(MessageFlag::Timeout); + builder.message().flags.set(MessageFlag::AutoMod); + + // AutoMod shield badge + builder.emplace(makeAutoModBadge(), + MessageElementFlag::BadgeChannelAuthority); + // AutoMod "username" + builder.emplace("AutoMod:", MessageElementFlag::BoldUsername, + MessageColor(QColor("blue")), + FontStyle::ChatMediumBold); + builder.emplace( + "AutoMod:", MessageElementFlag::NonBoldUsername, + MessageColor(QColor("blue"))); + // AutoMod header message + builder.emplace( + ("Held a message for reason: " + action.reason + + ". Allow will post it in chat. "), + MessageElementFlag::Text, MessageColor::Text); + // Allow link button + builder + .emplace("Allow", MessageElementFlag::Text, + MessageColor(QColor("green")), + FontStyle::ChatMediumBold) + ->setLink({Link::AutoModAllow, action.msgID}); + // Deny link button + builder + .emplace(" Deny", MessageElementFlag::Text, + MessageColor(QColor("red")), + FontStyle::ChatMediumBold) + ->setLink({Link::AutoModDeny, action.msgID}); + // ID of message caught by AutoMod + // builder.emplace(action.msgID, MessageElementFlag::Text, + // MessageColor::Text); + auto text1 = + QString("AutoMod: Held a message for reason: %1. Allow will post " + "it in chat. Allow Deny") + .arg(action.reason); + builder.message().messageText = text1; + builder.message().searchText = text1; + + auto message1 = builder.release(); + + // + // Builder for offender's message + builder2.message().channelName = channelName; + builder2 + .emplace("#" + channelName, + MessageElementFlag::ChannelName, + MessageColor::System) + ->setLink({Link::JumpToChannel, channelName}); + builder2.emplace(); + builder2.emplace(); + builder2.message().loginName = action.target.login; + builder2.message().flags.set(MessageFlag::PubSub); + builder2.message().flags.set(MessageFlag::Timeout); + builder2.message().flags.set(MessageFlag::AutoMod); + builder2.message().flags.set(MessageFlag::AutoModOffendingMessage); + + // sender username + builder2 + .emplace( + action.target.displayName + ":", MessageElementFlag::BoldUsername, + MessageColor(action.target.color), FontStyle::ChatMediumBold) + ->setLink({Link::UserInfo, action.target.login}); + builder2 + .emplace(action.target.displayName + ":", + MessageElementFlag::NonBoldUsername, + MessageColor(action.target.color)) + ->setLink({Link::UserInfo, action.target.login}); + // sender's message caught by AutoMod + builder2.emplace(action.message, MessageElementFlag::Text, + MessageColor::Text); + auto text2 = + QString("%1: %2").arg(action.target.displayName, action.message); + builder2.message().messageText = text2; + builder2.message().searchText = text2; + + auto message2 = builder2.release(); + + // Normally highlights would be checked & triggered during the builder parse steps + // and when the message is added to the channel + // We do this a bit weird since the message comes in from PubSub and not the normal message route + auto [highlighted, highlightResult] = getIApp()->getHighlights()->check( + {}, {}, action.target.login, action.message, message2->flags); + if (highlighted) + { + SharedMessageBuilder::triggerHighlights( + channelName, highlightResult.playSound, + highlightResult.customSoundUrl, highlightResult.alert); + } + + return std::make_pair(message1, message2); +} + void TwitchMessageBuilder::setThread(std::shared_ptr thread) { this->thread_ = std::move(thread); diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index cc1681acb1b..92f4dc3fc6b 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -89,6 +89,10 @@ class TwitchMessageBuilder : public SharedMessageBuilder static MessagePtr buildHypeChatMessage(Communi::IrcPrivateMessage *message); + static std::pair makeAutomodMessage( + const AutomodAction &action, const QString &channelName); + static MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action); + // Shares some common logic from SharedMessageBuilder::parseBadgeTag static std::unordered_map parseBadgeInfoTag( const QVariantMap &tags); diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 612f8ebefde..c66fcb2187d 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -375,6 +375,23 @@ class Settings ""}; QStringSetting subHighlightColor = {"/highlighting/subHighlightColor", ""}; + BoolSetting enableAutomodHighlight = { + "/highlighting/automod/enabled", + true, + }; + BoolSetting enableAutomodHighlightSound = { + "/highlighting/automod/enableSound", + false, + }; + BoolSetting enableAutomodHighlightTaskbar = { + "/highlighting/automod/enableTaskbarFlashing", + false, + }; + QStringSetting automodHighlightSoundUrl = { + "/highlighting/automod/soundUrl", + "", + }; + BoolSetting enableThreadHighlight = { "/highlighting/thread/nameIsHighlightKeyword", true}; BoolSetting showThreadHighlightInMentions = { diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 748220b7d46..fa5e1bb943d 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -1093,12 +1093,13 @@ void ChannelView::messageAppended(MessagePtr &message, if (!messageFlags->has(MessageFlag::DoNotTriggerNotification)) { - if (messageFlags->has(MessageFlag::Highlighted) && - messageFlags->has(MessageFlag::ShowInMentions) && - !messageFlags->has(MessageFlag::Subscription) && - (getSettings()->highlightMentions || - this->channel_->getType() != Channel::Type::TwitchMentions)) - + if ((messageFlags->has(MessageFlag::Highlighted) && + messageFlags->has(MessageFlag::ShowInMentions) && + !messageFlags->has(MessageFlag::Subscription) && + (getSettings()->highlightMentions || + this->channel_->getType() != Channel::Type::TwitchMentions)) || + (this->channel_->getType() == Channel::Type::TwitchAutomod && + getSettings()->enableAutomodHighlight)) { this->tabHighlightRequested.invoke(HighlightState::Highlighted); } diff --git a/src/widgets/helper/ScrollbarHighlight.cpp b/src/widgets/helper/ScrollbarHighlight.cpp index efd50e43db1..5216f2d66b7 100644 --- a/src/widgets/helper/ScrollbarHighlight.cpp +++ b/src/widgets/helper/ScrollbarHighlight.cpp @@ -26,6 +26,7 @@ ScrollbarHighlight::ScrollbarHighlight(const std::shared_ptr color, QColor ScrollbarHighlight::getColor() const { + assert(this->color_); return *this->color_; } diff --git a/src/widgets/settingspages/HighlightingPage.cpp b/src/widgets/settingspages/HighlightingPage.cpp index 1b6e61f7305..fbad47561cf 100644 --- a/src/widgets/settingspages/HighlightingPage.cpp +++ b/src/widgets/settingspages/HighlightingPage.cpp @@ -370,21 +370,22 @@ void HighlightingPage::tableCellClicked(const QModelIndex &clicked, EditableModelView *view, HighlightTab tab) { + if (!clicked.flags().testFlag(Qt::ItemIsEnabled)) + { + return; + } + switch (tab) { case HighlightTab::Messages: case HighlightTab::Users: { using Column = HighlightModel::Column; - bool restrictColorRow = - (tab == HighlightTab::Messages && - clicked.row() == - HighlightModel::HighlightRowIndexes::WhisperRow); - if (clicked.column() == Column::SoundPath && - clicked.flags().testFlag(Qt::ItemIsEnabled)) + + if (clicked.column() == Column::SoundPath) { this->openSoundDialog(clicked, view, Column::SoundPath); } - else if (clicked.column() == Column::Color && !restrictColorRow) + else if (clicked.column() == Column::Color) { this->openColorDialog(clicked, view, tab); }