diff --git a/CHANGELOG.md b/CHANGELOG.md index a66013510..6ceaf7551 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 cee5a7c87..f88a7c499 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 45b65095d..122348b06 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 @@ public: 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 @@ public: 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 7b05fea48..9864b3644 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; + } - b->flags.set(MessageFlag::Subscription); + 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()); + + 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 bd2b35324..0ebbeb0ec 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 25ed044e1..9ad6339fa 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 e1427a8b0..8fd22c06a 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 738fe14e4..f7c77da85 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 98c787647..8afca0272 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 a98575565..02fd37398 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 3b9d3b106..030f29845 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 65246a879..94eead088 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 4878d3f4e..ffa926d54 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 98f7e34a4..4adf9f17a 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 01d589b90..7b866c3ae 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" ]