From 4b725c4d6b52bbeb1c86bc542ade8f0c366fcc73 Mon Sep 17 00:00:00 2001 From: nerix Date: Sat, 2 Nov 2024 22:32:49 +0100 Subject: [PATCH 01/12] fix(7TV): ignore `entitlement.reset` (#5685) --- CHANGELOG.md | 1 + src/providers/seventv/SeventvEventAPI.cpp | 4 ++++ src/providers/seventv/eventapi/Subscription.hpp | 3 +++ 3 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95466ae9363..8c433308be8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ - Dev: Refactored IRC message building. (#5663) - Dev: Fixed some compiler warnings. (#5672) - Dev: Unified parsing of historic and live IRC messages. (#5678) +- Dev: 7TV's `entitlement.reset` is now explicitly ignored. (#5685) ## 2.5.1 diff --git a/src/providers/seventv/SeventvEventAPI.cpp b/src/providers/seventv/SeventvEventAPI.cpp index fcb99ce123a..0a7797eb7d4 100644 --- a/src/providers/seventv/SeventvEventAPI.cpp +++ b/src/providers/seventv/SeventvEventAPI.cpp @@ -233,6 +233,10 @@ void SeventvEventAPI::handleDispatch(const Dispatch &dispatch) } } break; + case SubscriptionType::ResetEntitlement: { + // unhandled (not clear what we'd do here yet) + } + break; default: { qCDebug(chatterinoSeventvEventAPI) << "Unknown subscription type:" diff --git a/src/providers/seventv/eventapi/Subscription.hpp b/src/providers/seventv/eventapi/Subscription.hpp index 65cf0354443..c6767b139d1 100644 --- a/src/providers/seventv/eventapi/Subscription.hpp +++ b/src/providers/seventv/eventapi/Subscription.hpp @@ -27,6 +27,7 @@ enum class SubscriptionType { CreateEntitlement, UpdateEntitlement, DeleteEntitlement, + ResetEntitlement, INVALID, }; @@ -119,6 +120,8 @@ constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name< return "entitlement.update"; case SubscriptionType::DeleteEntitlement: return "entitlement.delete"; + case SubscriptionType::ResetEntitlement: + return "entitlement.reset"; default: return default_tag; From 403fc6d3c4397ac0a9459a499f975f09d44f4c8c Mon Sep 17 00:00:00 2001 From: pajlada Date: Sun, 3 Nov 2024 11:49:00 +0100 Subject: [PATCH 02/12] fix: ensure timer doesn't run if the QuickSwitcherPopup is dead (#5687) --- CHANGELOG.md | 1 + src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c433308be8..a6601351048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ - Bugfix: Fixed event emotes not showing up in autocomplete and popups. (#5239, #5580, #5582, #5632) - Bugfix: Fixed tab visibility being controllable in the emote popup. (#5530) - Bugfix: Fixed account switch not being saved if no other settings were changed. (#5558) +- Bugfix: Fixed a crash that could occur when handling the quick switcher popup really quickly. (#5687) - Bugfix: Fixed 7TV badges being inadvertently animated. (#5674) - Bugfix: Fixed some tooltips not being readable. (#5578) - Bugfix: Fixed log files being locked longer than needed. (#5592) diff --git a/src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp b/src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp index 7841d06b4e4..b0294c45a3e 100644 --- a/src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp +++ b/src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp @@ -141,7 +141,7 @@ void QuickSwitcherPopup::updateSuggestions(const QString &text) * Timeout interval 0 means the call will be delayed until all window events * have been processed (cf. https://doc.qt.io/qt-5/qtimer.html#interval-prop). */ - QTimer::singleShot(0, [this] { + QTimer::singleShot(0, this, [this] { this->adjustSize(); }); } From 8220a1fbd4880f7b9f6fc2b599696444b2363968 Mon Sep 17 00:00:00 2001 From: nerix Date: Sun, 3 Nov 2024 12:17:16 +0100 Subject: [PATCH 03/12] feat: make usernames in USERNOTICEs clickable (#5686) --- CHANGELOG.md | 1 + src/messages/MessageBuilder.cpp | 103 +++++++++++++++--- src/messages/MessageBuilder.hpp | 15 ++- src/providers/twitch/IrcMessageHandler.cpp | 75 +++++++------ .../IrcMessageHandler/bitsbadge.json | 9 +- .../IrcMessageHandler/sub-first-gift.json | 29 ++++- .../IrcMessageHandler/sub-first-time.json | 9 +- .../snapshots/IrcMessageHandler/sub-gift.json | 29 ++++- .../IrcMessageHandler/sub-message.json | 9 +- .../sub-multi-month-anon-gift.json | 20 +++- .../sub-multi-month-gift-count.json | 29 ++++- .../sub-multi-month-gift.json | 29 ++++- .../sub-multi-month-resub.json | 9 +- .../IrcMessageHandler/sub-multi-month.json | 9 +- .../IrcMessageHandler/sub-no-message.json | 18 ++- 15 files changed, 297 insertions(+), 96 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6601351048..6ceaf7551cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ - 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) +- Minor: Made usernames in bits and sub messages clickable. (#5686) - Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426, #5612) - 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/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp index cee5a7c87bd..f88a7c4993d 100644 --- a/src/messages/MessageBuilder.cpp +++ b/src/messages/MessageBuilder.cpp @@ -75,6 +75,8 @@ const QRegularExpression mentionRegex("^@" + regexHelpString); // if findAllUsernames setting is enabled, matches strings like in the examples above, but without @ symbol at the beginning const QRegularExpression allUsernamesMentionRegex("^" + regexHelpString); +const QRegularExpression SPACE_REGEX("\\s"); + const QSet zeroWidthEmotes{ "SoSnowy", "IceCold", "SantaHat", "TopHat", "ReinDeer", "CandyCane", "cvMask", "cvHazmat", @@ -512,7 +514,7 @@ MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text, // check system message for links // (e.g. needed for sub ticket message in sub only mode) const QStringList textFragments = - text.split(QRegularExpression("\\s"), Qt::SkipEmptyParts); + text.split(SPACE_REGEX, Qt::SkipEmptyParts); for (const auto &word : textFragments) { auto link = linkparser::parse(word); @@ -531,33 +533,100 @@ MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text, this->message().searchText = text; } -MessageBuilder::MessageBuilder(RaidEntryMessageTag, const QString &text, - const QString &loginName, - const QString &displayName, - const MessageColor &userColor, const QTime &time) - : MessageBuilder() +MessagePtrMut MessageBuilder::makeSystemMessageWithUser( + const QString &text, const QString &loginName, const QString &displayName, + const MessageColor &userColor, const QTime &time) { - this->emplace(time); + MessageBuilder builder; + builder.emplace(time); - const QStringList textFragments = - text.split(QRegularExpression("\\s"), Qt::SkipEmptyParts); + const auto textFragments = text.split(SPACE_REGEX, Qt::SkipEmptyParts); for (const auto &word : textFragments) { if (word == displayName) { - this->emplace(displayName, loginName, - MessageColor::System, userColor); + builder.emplace(displayName, loginName, + MessageColor::System, userColor); continue; } - this->emplace(word, MessageElementFlag::Text, - MessageColor::System); + builder.emplace(word, MessageElementFlag::Text, + MessageColor::System); } - this->message().flags.set(MessageFlag::System); - this->message().flags.set(MessageFlag::DoNotTriggerNotification); - this->message().messageText = text; - this->message().searchText = text; + builder->flags.set(MessageFlag::System); + builder->flags.set(MessageFlag::DoNotTriggerNotification); + builder->messageText = text; + builder->searchText = text; + + return builder.release(); +} + +MessagePtrMut MessageBuilder::makeSubgiftMessage(const QString &text, + const QVariantMap &tags, + const QTime &time) +{ + MessageBuilder builder; + builder.emplace(time); + + auto gifterLogin = tags.value("login").toString(); + auto gifterDisplayName = tags.value("display-name").toString(); + if (gifterDisplayName.isEmpty()) + { + gifterDisplayName = gifterLogin; + } + MessageColor gifterColor = MessageColor::System; + if (auto colorTag = tags.value("color").value(); colorTag.isValid()) + { + gifterColor = MessageColor(colorTag); + } + + auto recipientLogin = + tags.value("msg-param-recipient-user-name").toString(); + if (recipientLogin.isEmpty()) + { + recipientLogin = tags.value("msg-param-recipient-name").toString(); + } + auto recipientDisplayName = + tags.value("msg-param-recipient-display-name").toString(); + if (recipientDisplayName.isEmpty()) + { + recipientDisplayName = recipientLogin; + } + + const auto textFragments = text.split(SPACE_REGEX, Qt::SkipEmptyParts); + for (const auto &word : textFragments) + { + if (word == gifterDisplayName) + { + builder.emplace(gifterDisplayName, gifterLogin, + MessageColor::System, gifterColor); + continue; + } + if (word.endsWith('!') && + word.size() == recipientDisplayName.size() + 1 && + word.startsWith(recipientDisplayName)) + { + builder + .emplace(recipientDisplayName, recipientLogin, + MessageColor::System, + MessageColor::System) + ->setTrailingSpace(false); + builder.emplace(u"!"_s, MessageElementFlag::Text, + MessageColor::System); + continue; + } + + builder.emplace(word, MessageElementFlag::Text, + MessageColor::System); + } + + builder->flags.set(MessageFlag::System); + builder->flags.set(MessageFlag::DoNotTriggerNotification); + builder->messageText = text; + builder->searchText = text; + + return builder.release(); } MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &timeoutUser, diff --git a/src/messages/MessageBuilder.hpp b/src/messages/MessageBuilder.hpp index 45b65095d99..122348b06cf 100644 --- a/src/messages/MessageBuilder.hpp +++ b/src/messages/MessageBuilder.hpp @@ -52,8 +52,6 @@ namespace linkparser { struct SystemMessageTag { }; -struct RaidEntryMessageTag { -}; struct TimeoutMessageTag { }; struct LiveUpdatesUpdateEmoteMessageTag { @@ -69,7 +67,6 @@ struct ImageUploaderResultTag { // NOLINTBEGIN(readability-identifier-naming) const SystemMessageTag systemMessage{}; -const RaidEntryMessageTag raidEntryMessage{}; const TimeoutMessageTag timeoutMessage{}; const LiveUpdatesUpdateEmoteMessageTag liveUpdatesUpdateEmoteMessage{}; const LiveUpdatesRemoveEmoteMessageTag liveUpdatesRemoveEmoteMessage{}; @@ -109,9 +106,6 @@ class MessageBuilder MessageBuilder(SystemMessageTag, const QString &text, const QTime &time = QTime::currentTime()); - MessageBuilder(RaidEntryMessageTag, const QString &text, - const QString &loginName, const QString &displayName, - const MessageColor &userColor, const QTime &time); MessageBuilder(TimeoutMessageTag, const QString &timeoutUser, const QString &sourceUser, const QString &systemMessageText, int times, const QTime &time = QTime::currentTime()); @@ -255,6 +249,15 @@ class MessageBuilder const std::shared_ptr &thread = {}, const MessagePtr &parent = {}); + static MessagePtrMut makeSystemMessageWithUser( + const QString &text, const QString &loginName, + const QString &displayName, const MessageColor &userColor, + const QTime &time); + + static MessagePtrMut makeSubgiftMessage(const QString &text, + const QVariantMap &tags, + const QTime &time); + private: struct TextState { TwitchChannel *twitchChannel = nullptr; diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 7b05fea4882..9864b36442f 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -699,35 +699,6 @@ void IrcMessageHandler::parseUserNoticeMessageInto(Communi::IrcMessage *message, { messageText = "Announcement"; } - else if (msgType == "raid") - { - auto login = tags.value("login").toString(); - auto displayName = tags.value("msg-param-displayName").toString(); - - if (!login.isEmpty() && !displayName.isEmpty()) - { - MessageColor color = MessageColor::System; - if (auto colorTag = tags.value("color").value(); - colorTag.isValid()) - { - color = MessageColor(colorTag); - } - - auto b = MessageBuilder( - raidEntryMessage, parseTagString(messageText), login, - displayName, color, calculateMessageTime(message).time()); - - b->flags.set(MessageFlag::Subscription); - if (mirrored) - { - b->flags.set(MessageFlag::SharedMessage); - } - auto newMessage = b.release(); - - sink.addMessage(newMessage, MessageContext::Original); - return; - } - } else if (msgType == "subgift") { if (auto monthsIt = tags.find("msg-param-gift-months"); @@ -762,6 +733,20 @@ void IrcMessageHandler::parseUserNoticeMessageInto(Communi::IrcMessage *message, } } } + + // subgifts are special because they include two users + auto msg = MessageBuilder::makeSubgiftMessage( + parseTagString(messageText), tags, + calculateMessageTime(message).time()); + + msg->flags.set(MessageFlag::Subscription); + if (mirrored) + { + msg->flags.set(MessageFlag::SharedMessage); + } + + sink.addMessage(msg, MessageContext::Original); + return; } else if (msgType == "sub" || msgType == "resub") { @@ -795,17 +780,37 @@ void IrcMessageHandler::parseUserNoticeMessageInto(Communi::IrcMessage *message, } } - auto b = MessageBuilder(systemMessage, parseTagString(messageText), - calculateMessageTime(message).time()); + auto displayName = [&] { + if (msgType == u"raid") + { + return tags.value("msg-param-displayName").toString(); + } + return tags.value("display-name").toString(); + }(); + auto login = tags.value("login").toString(); + if (displayName.isEmpty()) + { + displayName = login; + } + + MessageColor userColor = MessageColor::System; + if (auto colorTag = tags.value("color").value(); + colorTag.isValid()) + { + userColor = MessageColor(colorTag); + } + + auto msg = MessageBuilder::makeSystemMessageWithUser( + parseTagString(messageText), login, displayName, userColor, + calculateMessageTime(message).time()); - b->flags.set(MessageFlag::Subscription); + msg->flags.set(MessageFlag::Subscription); if (mirrored) { - b->flags.set(MessageFlag::SharedMessage); + msg->flags.set(MessageFlag::SharedMessage); } - auto newMessage = b.release(); - sink.addMessage(newMessage, MessageContext::Original); + sink.addMessage(msg, MessageContext::Original); } } diff --git a/tests/snapshots/IrcMessageHandler/bitsbadge.json b/tests/snapshots/IrcMessageHandler/bitsbadge.json index bd2b353246e..0ebbeb0ec4d 100644 --- a/tests/snapshots/IrcMessageHandler/bitsbadge.json +++ b/tests/snapshots/IrcMessageHandler/bitsbadge.json @@ -38,8 +38,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -47,7 +48,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ffff4500", + "userLoginName": "whoopiix", "words": [ "whoopiix" ] diff --git a/tests/snapshots/IrcMessageHandler/sub-first-gift.json b/tests/snapshots/IrcMessageHandler/sub-first-gift.json index 25ed044e1f9..9ad6339fae6 100644 --- a/tests/snapshots/IrcMessageHandler/sub-first-gift.json +++ b/tests/snapshots/IrcMessageHandler/sub-first-gift.json @@ -38,8 +38,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -47,7 +48,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ff00ff7f", + "userLoginName": "hyperbolicxd", "words": [ "hyperbolicxd" ] @@ -142,6 +145,24 @@ "to" ] }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": false, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "quote_if_nam", + "words": [ + "quote_if_nam" + ] + }, { "color": "System", "flags": "Text", @@ -154,7 +175,7 @@ "trailingSpace": true, "type": "TextElement", "words": [ - "quote_if_nam!" + "!" ] }, { diff --git a/tests/snapshots/IrcMessageHandler/sub-first-time.json b/tests/snapshots/IrcMessageHandler/sub-first-time.json index e1427a8b009..8fd22c06a98 100644 --- a/tests/snapshots/IrcMessageHandler/sub-first-time.json +++ b/tests/snapshots/IrcMessageHandler/sub-first-time.json @@ -38,8 +38,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -47,7 +48,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ff0000ff", + "userLoginName": "byebyeheart", "words": [ "byebyeheart" ] diff --git a/tests/snapshots/IrcMessageHandler/sub-gift.json b/tests/snapshots/IrcMessageHandler/sub-gift.json index 738fe14e4e7..f7c77da850e 100644 --- a/tests/snapshots/IrcMessageHandler/sub-gift.json +++ b/tests/snapshots/IrcMessageHandler/sub-gift.json @@ -38,8 +38,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -47,7 +48,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ff0000ff", + "userLoginName": "tww2", "words": [ "TWW2" ] @@ -142,6 +145,24 @@ "to" ] }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": false, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "mr_woodchuck", + "words": [ + "Mr_Woodchuck" + ] + }, { "color": "System", "flags": "Text", @@ -154,7 +175,7 @@ "trailingSpace": true, "type": "TextElement", "words": [ - "Mr_Woodchuck!" + "!" ] } ], diff --git a/tests/snapshots/IrcMessageHandler/sub-message.json b/tests/snapshots/IrcMessageHandler/sub-message.json index 98c78764771..8afca027299 100644 --- a/tests/snapshots/IrcMessageHandler/sub-message.json +++ b/tests/snapshots/IrcMessageHandler/sub-message.json @@ -290,8 +290,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -299,7 +300,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ff008000", + "userLoginName": "ronni", "words": [ "ronni" ] diff --git a/tests/snapshots/IrcMessageHandler/sub-multi-month-anon-gift.json b/tests/snapshots/IrcMessageHandler/sub-multi-month-anon-gift.json index a9857556559..02fd37398f7 100644 --- a/tests/snapshots/IrcMessageHandler/sub-multi-month-anon-gift.json +++ b/tests/snapshots/IrcMessageHandler/sub-multi-month-anon-gift.json @@ -217,6 +217,24 @@ "to" ] }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": false, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "mohammadrezadh", + "words": [ + "MohammadrezaDH" + ] + }, { "color": "System", "flags": "Text", @@ -229,7 +247,7 @@ "trailingSpace": true, "type": "TextElement", "words": [ - "MohammadrezaDH!" + "!" ] } ], diff --git a/tests/snapshots/IrcMessageHandler/sub-multi-month-gift-count.json b/tests/snapshots/IrcMessageHandler/sub-multi-month-gift-count.json index 3b9d3b106c0..030f2984551 100644 --- a/tests/snapshots/IrcMessageHandler/sub-multi-month-gift-count.json +++ b/tests/snapshots/IrcMessageHandler/sub-multi-month-gift-count.json @@ -38,8 +38,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -47,7 +48,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ffff8ea3", + "userLoginName": "inatsufn", "words": [ "iNatsuFN" ] @@ -187,6 +190,24 @@ "to" ] }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": false, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "kimmi_tm", + "words": [ + "kimmi_tm" + ] + }, { "color": "System", "flags": "Text", @@ -199,7 +220,7 @@ "trailingSpace": true, "type": "TextElement", "words": [ - "kimmi_tm!" + "!" ] }, { diff --git a/tests/snapshots/IrcMessageHandler/sub-multi-month-gift.json b/tests/snapshots/IrcMessageHandler/sub-multi-month-gift.json index 65246a879c6..94eead08810 100644 --- a/tests/snapshots/IrcMessageHandler/sub-multi-month-gift.json +++ b/tests/snapshots/IrcMessageHandler/sub-multi-month-gift.json @@ -38,8 +38,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -47,7 +48,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ffeb078d", + "userLoginName": "lucidfoxx", "words": [ "Lucidfoxx" ] @@ -187,6 +190,24 @@ "to" ] }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": false, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "ogprodigy", + "words": [ + "OGprodigy" + ] + }, { "color": "System", "flags": "Text", @@ -199,7 +220,7 @@ "trailingSpace": true, "type": "TextElement", "words": [ - "OGprodigy!" + "!" ] } ], diff --git a/tests/snapshots/IrcMessageHandler/sub-multi-month-resub.json b/tests/snapshots/IrcMessageHandler/sub-multi-month-resub.json index 4878d3f4ea6..ffa926d54a5 100644 --- a/tests/snapshots/IrcMessageHandler/sub-multi-month-resub.json +++ b/tests/snapshots/IrcMessageHandler/sub-multi-month-resub.json @@ -38,8 +38,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -47,7 +48,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ff0000ff", + "userLoginName": "calm__like_a_tom", "words": [ "calm__like_a_tom" ] diff --git a/tests/snapshots/IrcMessageHandler/sub-multi-month.json b/tests/snapshots/IrcMessageHandler/sub-multi-month.json index 98f7e34a462..4adf9f17ae3 100644 --- a/tests/snapshots/IrcMessageHandler/sub-multi-month.json +++ b/tests/snapshots/IrcMessageHandler/sub-multi-month.json @@ -38,8 +38,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -47,7 +48,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "System", + "userLoginName": "foly__", "words": [ "foly__" ] diff --git a/tests/snapshots/IrcMessageHandler/sub-no-message.json b/tests/snapshots/IrcMessageHandler/sub-no-message.json index 01d589b9077..7b866c3aea9 100644 --- a/tests/snapshots/IrcMessageHandler/sub-no-message.json +++ b/tests/snapshots/IrcMessageHandler/sub-no-message.json @@ -38,8 +38,9 @@ "type": "TimestampElement" }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -47,7 +48,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ffcc00c2", + "userLoginName": "cspice", "words": [ "cspice" ] @@ -158,8 +161,9 @@ ] }, { - "color": "System", - "flags": "Text", + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", "link": { "type": "None", "value": "" @@ -167,7 +171,9 @@ "style": "ChatMedium", "tooltip": "", "trailingSpace": true, - "type": "TextElement", + "type": "MentionElement", + "userColor": "#ffcc00c2", + "userLoginName": "cspice", "words": [ "cspice" ] From d3000ba5975d8823c04f1b223711088cf49388e8 Mon Sep 17 00:00:00 2001 From: nerix Date: Sun, 3 Nov 2024 13:29:57 +0100 Subject: [PATCH 04/12] feat: show warning before blocking followed channel (#5615) --- CHANGELOG.md | 1 + mocks/include/mocks/Helix.hpp | 2 +- src/providers/twitch/TwitchChannel.cpp | 2 +- src/providers/twitch/api/Helix.cpp | 3 +- src/providers/twitch/api/Helix.hpp | 4 +- src/widgets/DraggablePopup.cpp | 11 +++ src/widgets/DraggablePopup.hpp | 8 ++ src/widgets/dialogs/UserInfoPopup.cpp | 110 +++++++++++++++---------- 8 files changed, 91 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ceaf7551cd..1eea6413102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - Minor: Moderators can now see which mods start and cancel raids. (#5563) - Minor: The emote popup now reloads when Twitch emotes are reloaded. (#5580) - 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: Proxy URL information is now included in the `/debug-env` command. (#5648) - Minor: Make raid entry message usernames clickable. (#5651) diff --git a/mocks/include/mocks/Helix.hpp b/mocks/include/mocks/Helix.hpp index cfb2d71b6fe..02913efed5a 100644 --- a/mocks/include/mocks/Helix.hpp +++ b/mocks/include/mocks/Helix.hpp @@ -422,7 +422,7 @@ class Helix : public IHelix // get followed channel MOCK_METHOD( void, getFollowedChannel, - (QString userID, QString broadcasterID, + (QString userID, QString broadcasterID, const QObject *caller, ResultCallback> successCallback, FailureCallback failureCallback), (override)); diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 7c7cc130401..0371fd2953b 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -274,7 +274,7 @@ void TwitchChannel::refreshTwitchChannelEmotes(bool manualRefresh) getHelix()->getFollowedChannel( getApp()->getAccounts()->twitch.getCurrent()->getUserId(), - this->roomId(), + this->roomId(), nullptr, [weak{this->weak_from_this()}, makeEmotes](const auto &chan) { auto self = std::dynamic_pointer_cast(weak.lock()); if (!self || !chan) diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index 593c6837f16..977cbd5f675 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -3138,7 +3138,7 @@ void Helix::getUserEmotes( } void Helix::getFollowedChannel( - QString userID, QString broadcasterID, + QString userID, QString broadcasterID, const QObject *caller, ResultCallback> successCallback, FailureCallback failureCallback) { @@ -3147,6 +3147,7 @@ void Helix::getFollowedChannel( {u"user_id"_s, userID}, {u"broadcaster_id"_s, broadcasterID}, }) + .caller(caller) .onSuccess([successCallback](auto result) { if (result.status() != 200) { diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp index 3a81a9908f8..e62920682a9 100644 --- a/src/providers/twitch/api/Helix.hpp +++ b/src/providers/twitch/api/Helix.hpp @@ -1143,7 +1143,7 @@ class IHelix /// https://dev.twitch.tv/docs/api/reference/#get-followed-channels /// (non paginated) virtual void getFollowedChannel( - QString userID, QString broadcasterID, + QString userID, QString broadcasterID, const QObject *caller, ResultCallback> successCallback, FailureCallback failureCallback) = 0; @@ -1486,7 +1486,7 @@ class Helix final : public IHelix /// https://dev.twitch.tv/docs/api/reference/#get-followed-channels /// (non paginated) void getFollowedChannel( - QString userID, QString broadcasterID, + QString userID, QString broadcasterID, const QObject *caller, ResultCallback> successCallback, FailureCallback failureCallback) final; diff --git a/src/widgets/DraggablePopup.cpp b/src/widgets/DraggablePopup.cpp index 6b7e9eaf990..b680e35bfd5 100644 --- a/src/widgets/DraggablePopup.cpp +++ b/src/widgets/DraggablePopup.cpp @@ -41,6 +41,7 @@ DraggablePopup::DraggablePopup(bool closeAutomatically, QWidget *parent) BaseWindow::ClearBuffersOnDpiChange, parent) , lifetimeHack_(std::make_shared(false)) + , closeAutomatically_(closeAutomatically) , dragTimer_(this) { @@ -128,4 +129,14 @@ Button *DraggablePopup::createPinButton() return this->pinButton_; } +bool DraggablePopup::ensurePinned() +{ + if (this->closeAutomatically_ && !this->isPinned_) + { + this->togglePinned(); + return true; + } + return false; +} + } // namespace chatterino diff --git a/src/widgets/DraggablePopup.hpp b/src/widgets/DraggablePopup.hpp index bf82af0082c..97217c0df14 100644 --- a/src/widgets/DraggablePopup.hpp +++ b/src/widgets/DraggablePopup.hpp @@ -38,10 +38,18 @@ class DraggablePopup : public BaseWindow // button pixmap void togglePinned(); + /// Ensures that this popup is pinned (if it's expected to close automatically) + /// + /// @returns `true` if the popup was pinned as a result (i.e. if the popup + /// was unpinned and said to automatically close before) + bool ensurePinned(); + private: // isMoving_ is set to true if the user is holding the left mouse button down and has moved the mouse a small amount away from the original click point (startPosDrag_) bool isMoving_ = false; + bool closeAutomatically_ = false; + // startPosDrag_ is the coordinates where the user originally pressed the mouse button down to start dragging QPoint startPosDrag_; diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index f8bec9b1b09..3087da586e2 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -2,6 +2,7 @@ #include "Application.hpp" #include "common/Channel.hpp" +#include "common/Literals.hpp" #include "common/network/NetworkRequest.hpp" #include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" @@ -37,6 +38,8 @@ #include #include +#include +#include #include #include #include @@ -141,6 +144,8 @@ int calculateTimeoutDuration(TimeoutButton timeout) namespace chatterino { +using namespace literals; + UserInfoPopup::UserInfoPopup(bool closeAutomatically, Split *split) : DraggablePopup(closeAutomatically, split) , split_(split) @@ -623,57 +628,72 @@ void UserInfoPopup::installEvents() return; } - switch (newState) + if (newState == Qt::Unchecked) { - case Qt::CheckState::Unchecked: { - this->ui_.block->setEnabled(false); - - getApp()->getAccounts()->twitch.getCurrent()->unblockUser( - this->userId_, this, - [this, reenableBlockCheckbox, currentUser] { - this->channel_->addSystemMessage( - QString("You successfully unblocked user %1") - .arg(this->userName_)); - reenableBlockCheckbox(); - }, - [this, reenableBlockCheckbox] { - this->channel_->addSystemMessage( - QString( - "User %1 couldn't be unblocked, an unknown " + this->ui_.block->setEnabled(false); + + getApp()->getAccounts()->twitch.getCurrent()->unblockUser( + this->userId_, this, + [this, reenableBlockCheckbox, currentUser] { + this->channel_->addSystemMessage( + QString("You successfully unblocked user %1") + .arg(this->userName_)); + reenableBlockCheckbox(); + }, + [this, reenableBlockCheckbox] { + this->channel_->addSystemMessage( + QString("User %1 couldn't be unblocked, an unknown " "error occurred!") - .arg(this->userName_)); - reenableBlockCheckbox(); - }); - } - break; + .arg(this->userName_)); + reenableBlockCheckbox(); + }); + return; + } - case Qt::CheckState::PartiallyChecked: { - // We deliberately ignore this state + if (newState == Qt::Checked) + { + this->ui_.block->setEnabled(false); + + bool wasPinned = this->ensurePinned(); + auto btn = QMessageBox::warning( + this, u"Blocking " % this->userName_, + u"Blocking %1 can cause unintended side-effects like unfollowing.\n\n"_s + "Are you sure you want to block %1?".arg(this->userName_), + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel); + if (wasPinned) + { + this->togglePinned(); } - break; - - case Qt::CheckState::Checked: { - this->ui_.block->setEnabled(false); - - getApp()->getAccounts()->twitch.getCurrent()->blockUser( - this->userId_, this, - [this, reenableBlockCheckbox, currentUser] { - this->channel_->addSystemMessage( - QString("You successfully blocked user %1") - .arg(this->userName_)); - reenableBlockCheckbox(); - }, - [this, reenableBlockCheckbox] { - this->channel_->addSystemMessage( - QString( - "User %1 couldn't be blocked, an unknown " - "error occurred!") - .arg(this->userName_)); - reenableBlockCheckbox(); - }); + if (btn != QMessageBox::Yes) + { + reenableBlockCheckbox(); + QSignalBlocker blocker(this->ui_.block); + this->ui_.block->setCheckState(Qt::Unchecked); + return; } - break; + + getApp()->getAccounts()->twitch.getCurrent()->blockUser( + this->userId_, this, + [this, reenableBlockCheckbox, currentUser] { + this->channel_->addSystemMessage( + QString("You successfully blocked user %1") + .arg(this->userName_)); + reenableBlockCheckbox(); + }, + [this, reenableBlockCheckbox] { + this->channel_->addSystemMessage( + QString("User %1 couldn't be blocked, an " + "unknown error occurred!") + .arg(this->userName_)); + reenableBlockCheckbox(); + }); + return; } + + qCWarning(chatterinoWidget) + << "Unexpected check-state when blocking" << this->userName_ + << QMetaEnum::fromType().valueToKey(newState); }); // ignore highlights From 68304272a26c12b7d72fa3d95a732471341e3d15 Mon Sep 17 00:00:00 2001 From: nerix Date: Sun, 3 Nov 2024 19:23:53 +0100 Subject: [PATCH 05/12] fix: handle multiline selection starting at trailing space (#5691) --- CHANGELOG.md | 1 + .../layouts/MessageLayoutContainer.cpp | 38 ++++++++++++------- .../layouts/MessageLayoutContainer.hpp | 7 +++- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eea6413102..ee859fad2b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ - Bugfix: Fixed emotes starting with ":" not tab-completing. (#5603) - Bugfix: Fixed 7TV emotes messing with Qt's HTML. (#5677) - Bugfix: Fixed incorrect messages getting replaced visually. (#5683) +- Bugfix: Fixed rendering of multi-line selection that starts at a trailing space. (#5691) - Dev: Update Windows build from Qt 6.5.0 to Qt 6.7.1. (#5420) - Dev: Update vcpkg build Qt from 6.5.0 to 6.7.0, boost from 1.83.0 to 1.85.0, openssl from 3.1.3 to 3.3.0. (#5422) - Dev: Unsingletonize `ISoundController`. (#5462) diff --git a/src/messages/layouts/MessageLayoutContainer.cpp b/src/messages/layouts/MessageLayoutContainer.cpp index dbf04fb7735..20569a8b81b 100644 --- a/src/messages/layouts/MessageLayoutContainer.cpp +++ b/src/messages/layouts/MessageLayoutContainer.cpp @@ -854,6 +854,18 @@ std::optional MessageLayoutContainer::paintSelectionStart( { const auto selectionColor = getTheme()->messages.selection; + auto paintRemainingLines = [&](size_t startIndex) { + for (size_t i = startIndex; i < this->lines_.size(); i++) + { + const auto &line = this->lines_[i]; + auto left = this->elements_[line.startIndex]->getRect().left(); + auto right = this->elements_[line.endIndex - 1]->getRect().right(); + + this->paintSelectionRect(painter, line, left, right, yOffset, + selectionColor); + } + }; + // The selection starts in this message for (size_t lineIndex = 0; lineIndex < this->lines_.size(); lineIndex++) { @@ -874,7 +886,17 @@ std::optional MessageLayoutContainer::paintSelectionStart( auto right = this->elements_[line.endIndex - 1]->getRect().right(); this->paintSelectionRect(painter, line, right, right, yOffset, selectionColor); - return std::nullopt; + + if (selection.selectionMax.messageIndex != messageIndex) + { + // The selection does not end in this message + paintRemainingLines(lineIndex + 1); + + return std::nullopt; + } + + // The selection starts in this line, but ends in some next line or message + return {lineIndex + 1}; } int x = this->elements_[line.startIndex]->getRect().left(); @@ -923,21 +945,9 @@ std::optional MessageLayoutContainer::paintSelectionStart( if (selection.selectionMax.messageIndex != messageIndex) { // The selection does not end in this message - for (size_t lineIndex2 = lineIndex + 1; - lineIndex2 < this->lines_.size(); lineIndex2++) - { - const auto &line2 = this->lines_[lineIndex2]; - auto left = - this->elements_[line2.startIndex]->getRect().left(); - auto right = - this->elements_[line2.endIndex - 1]->getRect().right(); - - this->paintSelectionRect(painter, line2, left, right, - yOffset, selectionColor); - } - this->paintSelectionRect(painter, line, x, r, yOffset, selectionColor); + paintRemainingLines(lineIndex + 1); return std::nullopt; } diff --git a/src/messages/layouts/MessageLayoutContainer.hpp b/src/messages/layouts/MessageLayoutContainer.hpp index 1cd7f6af2c2..e59e539224b 100644 --- a/src/messages/layouts/MessageLayoutContainer.hpp +++ b/src/messages/layouts/MessageLayoutContainer.hpp @@ -283,7 +283,12 @@ struct MessageLayoutContainer { /** * Paint the selection start * - * Returns a line index if this message should also paint the selection end + * @returns A line index if the selection ends within this message but start + * and end are on different lines. The returned index is the index + * of the first line where the selection starts at the beginning of + * the line. This index should be passed to paintSelectionEnd(). + * If `std::nullopt` is returned, no further call to + * paintSelectionEnd() is necessary. */ std::optional paintSelectionStart(QPainter &painter, size_t messageIndex, From 03cdd98c2edec699e13f7bc0a975519de82d5d4f Mon Sep 17 00:00:00 2001 From: Brian <18603393+brian6932@users.noreply.github.com> Date: Mon, 4 Nov 2024 03:49:32 -0500 Subject: [PATCH 06/12] chore: clarify Lua submodule version (#5693) --- .gitmodules | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitmodules b/.gitmodules index e15a27575b3..219a91bfaab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -38,6 +38,7 @@ [submodule "lib/lua/src"] path = lib/lua/src url = https://github.com/lua/lua + branch = v5.4 [submodule "tools/crash-handler"] path = tools/crash-handler url = https://github.com/Chatterino/crash-handler diff --git a/CHANGELOG.md b/CHANGELOG.md index ee859fad2b4..c4baf3f5378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ - Dev: `GIFTimer` is no longer initialized in tests. (#5608) - Dev: Emojis now use flags instead of a set of strings for capabilities. (#5616) - Dev: Move plugins to Sol2. (#5622, #5682) +- Dev: Clarified our Lua dependency's version. (#5693) - Dev: Refactored static `MessageBuilder` helpers to standalone functions. (#5652) - Dev: Decoupled reply parsing from `MessageBuilder`. (#5660, #5668) - Dev: Refactored IRC message building. (#5663) From e68f205e046d34bc63e1d8b988cf30587fc3ee58 Mon Sep 17 00:00:00 2001 From: Brian <18603393+brian6932@users.noreply.github.com> Date: Tue, 5 Nov 2024 04:13:20 -0500 Subject: [PATCH 07/12] chore: Specify qtkeychain branch to `chatterino-cmake` (#5695) --- .gitmodules | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitmodules b/.gitmodules index 219a91bfaab..36f795b8b77 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,6 +20,7 @@ [submodule "lib/qtkeychain"] path = lib/qtkeychain url = https://github.com/Chatterino/qtkeychain + branch = chatterino-cmake [submodule "lib/websocketpp"] path = lib/websocketpp url = https://github.com/zaphoyd/websocketpp diff --git a/CHANGELOG.md b/CHANGELOG.md index c4baf3f5378..bf2cf960e4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ - Dev: Emojis now use flags instead of a set of strings for capabilities. (#5616) - Dev: Move plugins to Sol2. (#5622, #5682) - Dev: Clarified our Lua dependency's version. (#5693) +- Dev: Specified qtkeychain dependency version. (#5695) - Dev: Refactored static `MessageBuilder` helpers to standalone functions. (#5652) - Dev: Decoupled reply parsing from `MessageBuilder`. (#5660, #5668) - Dev: Refactored IRC message building. (#5663) From d92b24b8a198a770a257500750defe4c3906a4b7 Mon Sep 17 00:00:00 2001 From: Felanbird <41973452+Felanbird@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:53:43 -0500 Subject: [PATCH 08/12] chore: standardize references to FrankerFaceZ & BetterTTV in settings (#5698) --- CHANGELOG.md | 1 + src/widgets/settingspages/GeneralPage.cpp | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf2cf960e4b..036b53e5770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Minor: Make raid entry message usernames clickable. (#5651) - Minor: Tabs unhighlight when their content is read in other tabs. (#5649) - Minor: Made usernames in bits and sub messages clickable. (#5686) +- Minor: Mentions of FrankerFaceZ and BetterTTV in settings are standardized as such. (#5698) - Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426, #5612) - 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/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index 7103fb52739..84ebaaf1b52 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -614,12 +614,16 @@ void GeneralPage::initLayout(GeneralPageView &layout) "Google", }, s.emojiSet); - layout.addCheckbox("Show BTTV global emotes", s.enableBTTVGlobalEmotes); - layout.addCheckbox("Show BTTV channel emotes", s.enableBTTVChannelEmotes); - layout.addCheckbox("Enable BTTV live emote updates (requires restart)", + layout.addCheckbox("Show BetterTTV global emotes", + s.enableBTTVGlobalEmotes); + layout.addCheckbox("Show BetterTTV channel emotes", + s.enableBTTVChannelEmotes); + layout.addCheckbox("Enable BetterTTV live emote updates (requires restart)", s.enableBTTVLiveUpdates); - layout.addCheckbox("Show FFZ global emotes", s.enableFFZGlobalEmotes); - layout.addCheckbox("Show FFZ channel emotes", s.enableFFZChannelEmotes); + layout.addCheckbox("Show FrankerFaceZ global emotes", + s.enableFFZGlobalEmotes); + layout.addCheckbox("Show FrankerFaceZ channel emotes", + s.enableFFZChannelEmotes); layout.addCheckbox("Show 7TV global emotes", s.enableSevenTVGlobalEmotes); layout.addCheckbox("Show 7TV channel emotes", s.enableSevenTVChannelEmotes); layout.addCheckbox("Enable 7TV live emote updates (requires restart)", From 46c1f18ae7b041947400a8b811dc1b0d7964282d Mon Sep 17 00:00:00 2001 From: pajlada Date: Tue, 5 Nov 2024 21:04:45 +0100 Subject: [PATCH 09/12] feat: add setting to hide blocked term automod messages (#5690) --- CHANGELOG.md | 1 + src/common/ChatterinoSetting.hpp | 7 ++++ src/messages/MessageBuilder.cpp | 7 ++++ src/messages/MessageFlag.hpp | 2 ++ src/messages/layouts/MessageLayout.cpp | 15 ++++++++ src/providers/twitch/PubSubActions.hpp | 3 ++ src/providers/twitch/PubSubManager.cpp | 34 ------------------- src/providers/twitch/TwitchIrcServer.cpp | 1 + .../twitch/pubsubmessages/AutoMod.cpp | 4 +++ .../twitch/pubsubmessages/AutoMod.hpp | 28 ++++++++++++++- src/singletons/Settings.hpp | 11 ++++++ src/singletons/WindowManager.cpp | 2 ++ src/widgets/settingspages/GeneralPage.cpp | 8 +++++ 13 files changed, 88 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 036b53e5770..e087eeef6ee 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 f2006e6d7b5..9db04def426 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 f88a7c4993d..00b5eedbf62 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 60c213f036c..306587a0982 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 81c75c6b471..dc51113f668 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 5f83b40515e..58e1a35e3fa 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 89a08327b18..9a2f0384838 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 9e2fe2fbfca..901ac0525f2 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 697db1e32c7..3dd3d77df02 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 9f40d39da67..3179abae3fb 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 94366dabe3e..fa039184eac 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 @@ class Settings 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 88d5ec56566..75d09bde7bc 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 84ebaaf1b52..58cc373193d 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, From f3a5f81fa02b6377c1d5132f2e817184b31ebd80 Mon Sep 17 00:00:00 2001 From: nerix Date: Tue, 5 Nov 2024 22:11:14 +0100 Subject: [PATCH 10/12] deps: use upstream qtkeychain (#5697) --- .gitmodules | 1 - CHANGELOG.md | 2 +- CMakeLists.txt | 9 +++++++++ lib/qtkeychain | 2 +- src/common/Credentials.cpp | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index 36f795b8b77..219a91bfaab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,7 +20,6 @@ [submodule "lib/qtkeychain"] path = lib/qtkeychain url = https://github.com/Chatterino/qtkeychain - branch = chatterino-cmake [submodule "lib/websocketpp"] path = lib/websocketpp url = https://github.com/zaphoyd/websocketpp diff --git a/CHANGELOG.md b/CHANGELOG.md index e087eeef6ee..ca72b9ab939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,7 +120,7 @@ - Dev: Emojis now use flags instead of a set of strings for capabilities. (#5616) - Dev: Move plugins to Sol2. (#5622, #5682) - Dev: Clarified our Lua dependency's version. (#5693) -- Dev: Specified qtkeychain dependency version. (#5695) +- Dev: Specified qtkeychain dependency version. (#5695, #5697) - Dev: Refactored static `MessageBuilder` helpers to standalone functions. (#5652) - Dev: Decoupled reply parsing from `MessageBuilder`. (#5660, #5668) - Dev: Refactored IRC message building. (#5663) diff --git a/CMakeLists.txt b/CMakeLists.txt index 603c9c42bc7..37c8835b812 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,10 +160,19 @@ if (BUILD_WITH_QTKEYCHAIN) message(FATAL_ERROR "Submodules probably not loaded, unable to find lib/qtkeychain/CMakeLists.txt") endif() + set(_prev_testing ${BUILD_TESTING}) + set(BUILD_TESTING Off) add_subdirectory("${QTKEYCHAIN_ROOT_LIB_FOLDER}" EXCLUDE_FROM_ALL) + set(BUILD_TESTING ${_prev_testing}) + if (NOT TARGET qt${MAJOR_QT_VERSION}keychain) message(FATAL_ERROR "qt${MAJOR_QT_VERSION}keychain target was not created :@") endif() + if (MSVC AND "${MAJOR_QT_VERSION}" STREQUAL "5") + target_compile_definitions(qt5keychain PRIVATE UNICODE) + target_compile_options(qt5keychain PRIVATE /utf-8) + set_target_properties(qt5keychain PROPERTIES CXX_STANDARD 17) + endif() endif() endif() diff --git a/lib/qtkeychain b/lib/qtkeychain index e5b070831cf..73c3772d643 160000 --- a/lib/qtkeychain +++ b/lib/qtkeychain @@ -1 +1 @@ -Subproject commit e5b070831cf1ea3cb98c95f97fcb7439f8d79bd6 +Subproject commit 73c3772d6432280df83e1ab3299c2a8f1e4ea47f diff --git a/src/common/Credentials.cpp b/src/common/Credentials.cpp index 1effa8f9b9d..7e5e5655eb3 100644 --- a/src/common/Credentials.cpp +++ b/src/common/Credentials.cpp @@ -24,7 +24,7 @@ # include "qt5keychain/keychain.h" # endif # else -# include "keychain.h" +# include # endif #endif From 5b1ce32a4e0afaa160ca4b6918914216483884b5 Mon Sep 17 00:00:00 2001 From: pajlada Date: Wed, 6 Nov 2024 15:07:33 +0100 Subject: [PATCH 11/12] fix: don't spam debug output about 7TV CreateEmoteSet (#5700) --- src/providers/seventv/SeventvEventAPI.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/providers/seventv/SeventvEventAPI.cpp b/src/providers/seventv/SeventvEventAPI.cpp index 0a7797eb7d4..41f21963957 100644 --- a/src/providers/seventv/SeventvEventAPI.cpp +++ b/src/providers/seventv/SeventvEventAPI.cpp @@ -237,6 +237,10 @@ void SeventvEventAPI::handleDispatch(const Dispatch &dispatch) // unhandled (not clear what we'd do here yet) } break; + case SubscriptionType::CreateEmoteSet: { + // unhandled (c2 does not support custom emote sets) + } + break; default: { qCDebug(chatterinoSeventvEventAPI) << "Unknown subscription type:" From fc8d27b7e2241b54be3e8d782ad75330110ef9c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 13:46:22 +0100 Subject: [PATCH 12/12] chore(deps): bump ZedThree/clang-tidy-review from 0.19.0 to 0.20.0 (#5701) --- .github/workflows/clang-tidy.yml | 4 ++-- .github/workflows/post-clang-tidy-review.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index d382e972cca..8f9214ab9c1 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -41,7 +41,7 @@ jobs: - name: clang-tidy review timeout-minutes: 20 - uses: ZedThree/clang-tidy-review@v0.19.0 + uses: ZedThree/clang-tidy-review@v0.20.0 with: build_dir: build-clang-tidy config_file: ".clang-tidy" @@ -63,4 +63,4 @@ jobs: libxkbcommon-x11-0, libxcb-xkb-dev, libxcb-cursor0 - name: clang-tidy-review upload - uses: ZedThree/clang-tidy-review/upload@v0.19.0 + uses: ZedThree/clang-tidy-review/upload@v0.20.0 diff --git a/.github/workflows/post-clang-tidy-review.yml b/.github/workflows/post-clang-tidy-review.yml index c55b3a3d9f5..df8ce219c2c 100644 --- a/.github/workflows/post-clang-tidy-review.yml +++ b/.github/workflows/post-clang-tidy-review.yml @@ -14,7 +14,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - - uses: ZedThree/clang-tidy-review/post@v0.19.0 + - uses: ZedThree/clang-tidy-review/post@v0.20.0 with: lgtm_comment_body: "" num_comments_as_exitcode: false