From 3485aacfdbecfe7c3fe27cb6d713de45cdb44101 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 8 Jan 2025 22:27:31 +0100 Subject: [PATCH 1/2] feat: stack `/clear` messages --- src/common/Channel.cpp | 12 + src/common/Channel.hpp | 1 + src/messages/MessageBuilder.cpp | 40 +++ src/messages/MessageBuilder.hpp | 6 + src/messages/MessageFlag.hpp | 2 + src/messages/MessageSink.hpp | 5 + src/providers/twitch/IrcMessageHandler.cpp | 20 +- src/providers/twitch/TwitchIrcServer.cpp | 9 +- src/util/ChannelHelpers.hpp | 77 ++++++ src/util/VectorMessageSink.cpp | 14 ++ src/util/VectorMessageSink.hpp | 1 + .../clearchat-stack-always.json | 238 ++++++++++++++++++ .../clearchat-stack-never.json | 86 +++++++ .../clearchat-stack-no-user.json | 98 ++++++++ .../IrcMessageHandler/clearchat.json | 102 +------- tests/src/IrcMessageHandler.cpp | 6 +- 16 files changed, 606 insertions(+), 111 deletions(-) create mode 100644 tests/snapshots/IrcMessageHandler/clearchat-stack-always.json create mode 100644 tests/snapshots/IrcMessageHandler/clearchat-stack-never.json create mode 100644 tests/snapshots/IrcMessageHandler/clearchat-stack-no-user.json diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp index 6396ef00c04..7923c706731 100644 --- a/src/common/Channel.cpp +++ b/src/common/Channel.cpp @@ -139,6 +139,18 @@ void Channel::addOrReplaceTimeout(MessagePtr message, QTime now) // WindowManager::instance().repaintVisibleChatWidgets(this); } +void Channel::addOrReplaceClearChat(MessagePtr message, QTime now) +{ + addOrReplaceChannelClear( + this->getMessageSnapshot(), std::move(message), now, + [this](auto /*idx*/, auto msg, auto replacement) { + this->replaceMessage(msg, replacement); + }, + [this](auto msg) { + this->addMessage(msg, MessageContext::Original); + }); +} + void Channel::disableAllMessages() { LimitedQueueSnapshot snapshot = this->getMessageSnapshot(); diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp index c7d006f1be2..f6626f12647 100644 --- a/src/common/Channel.hpp +++ b/src/common/Channel.hpp @@ -92,6 +92,7 @@ class Channel : public std::enable_shared_from_this, public MessageSink void fillInMissingMessages(const std::vector &messages); void addOrReplaceTimeout(MessagePtr message, QTime now) final; + void addOrReplaceClearChat(MessagePtr message, QTime now) final; void disableAllMessages() final; void replaceMessage(const MessagePtr &message, const MessagePtr &replacement); diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp index 38d2170bc3e..a97d126f548 100644 --- a/src/messages/MessageBuilder.cpp +++ b/src/messages/MessageBuilder.cpp @@ -1986,6 +1986,46 @@ MessagePtr MessageBuilder::makeLowTrustUpdateMessage( return builder.release(); } +MessagePtrMut MessageBuilder::makeClearChatMessage(QTime now, + const QString &actor, + uint32_t count) +{ + MessageBuilder builder; + builder.emplace(now); + builder->count = count; + builder->parseTime = now; + builder.message().flags.set(MessageFlag::System, + MessageFlag::DoNotTriggerNotification, + MessageFlag::ClearChat); + + QString messageText; + if (actor.isEmpty()) + { + builder.emplaceSystemTextAndUpdate( + "Chat has been cleared by a moderator.", messageText); + } + else + { + builder.message().flags.set(MessageFlag::PubSub); + builder.emplace(actor, actor, MessageColor::System, + MessageColor::System); + messageText = actor + ' '; + builder.emplaceSystemTextAndUpdate("cleared the chat.", messageText); + builder->timeoutUser = actor; + } + + if (count > 1) + { + builder.emplaceSystemTextAndUpdate( + '(' % QString::number(count) % u" times)", messageText); + } + + builder->messageText = messageText; + builder->searchText = messageText; + + return builder.release(); +} + std::pair MessageBuilder::makeIrcMessage( /* mutable */ Channel *channel, const Communi::IrcMessage *ircMessage, const MessageParseArgs &args, /* mutable */ QString content, diff --git a/src/messages/MessageBuilder.hpp b/src/messages/MessageBuilder.hpp index 122348b06cf..76f0ea47bcf 100644 --- a/src/messages/MessageBuilder.hpp +++ b/src/messages/MessageBuilder.hpp @@ -258,6 +258,12 @@ class MessageBuilder const QVariantMap &tags, const QTime &time); + /// "Chat has been cleared by a moderator." or "{actor} cleared the chat." + /// @param actor The user who cleared the chat (empty if unknown) + /// @param count How many times this message has been received already + static MessagePtrMut makeClearChatMessage(QTime now, const QString &actor, + uint32_t count = 1); + private: struct TextState { TwitchChannel *twitchChannel = nullptr; diff --git a/src/messages/MessageFlag.hpp b/src/messages/MessageFlag.hpp index 306587a0982..317cf5a9754 100644 --- a/src/messages/MessageFlag.hpp +++ b/src/messages/MessageFlag.hpp @@ -54,6 +54,8 @@ enum class MessageFlag : std::int64_t { SharedMessage = (1LL << 37), /// AutoMod message that showed up due to containing a blocked term in the channel AutoModBlockedTerm = (1LL << 38), + /// The message is a full clear chat message (/clear) + ClearChat = (1LL << 39), }; using MessageFlags = FlagsEnum; diff --git a/src/messages/MessageSink.hpp b/src/messages/MessageSink.hpp index e720a1867e0..0a96f1217aa 100644 --- a/src/messages/MessageSink.hpp +++ b/src/messages/MessageSink.hpp @@ -48,6 +48,11 @@ class MessageSink virtual void addOrReplaceTimeout(MessagePtr clearchatMessage, QTime now) = 0; + /// Adds a clear chat message (for the entire chat) or merges it into an + /// existing one + virtual void addOrReplaceClearChat(MessagePtr clearchatMessage, + QTime now) = 0; + /// Flags all messages as `Disabled` virtual void disableAllMessages() = 0; diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 9864b36442f..075ef6f8795 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -197,9 +197,8 @@ std::optional parseClearChatMessage( if (message->parameters().length() == 1) { return ClearChatMessage{ - .message = - makeSystemMessage("Chat has been cleared by a moderator.", - calculateMessageTime(message).time()), + .message = MessageBuilder::makeClearChatMessage( + calculateMessageTime(message).time(), {}), .disableAllMessages = true, }; } @@ -320,15 +319,14 @@ void IrcMessageHandler::parseMessageInto(Communi::IrcMessage *message, return; } auto &clearChat = *cc; + auto time = calculateMessageTime(message).time(); if (clearChat.disableAllMessages) { - sink.addMessage(std::move(clearChat.message), - MessageContext::Original); + sink.addOrReplaceClearChat(std::move(clearChat.message), time); } else { - sink.addOrReplaceTimeout(std::move(clearChat.message), - calculateMessageTime(message).time()); + sink.addOrReplaceTimeout(std::move(clearChat.message), time); } } } @@ -464,18 +462,16 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) return; } + auto time = calculateMessageTime(message).time(); // chat has been cleared by a moderator if (clearChat.disableAllMessages) { chan->disableAllMessages(); - chan->addMessage(std::move(clearChat.message), - MessageContext::Original); - + chan->addOrReplaceClearChat(std::move(clearChat.message), time); return; } - chan->addOrReplaceTimeout(std::move(clearChat.message), - calculateMessageTime(message).time()); + chan->addOrReplaceTimeout(std::move(clearChat.message), time); // refresh all getApp()->getWindows()->repaintVisibleChatWidgets(chan.get()); diff --git a/src/providers/twitch/TwitchIrcServer.cpp b/src/providers/twitch/TwitchIrcServer.cpp index 69d53a78736..6e07346b010 100644 --- a/src/providers/twitch/TwitchIrcServer.cpp +++ b/src/providers/twitch/TwitchIrcServer.cpp @@ -247,11 +247,10 @@ void TwitchIrcServer::initialize() return; } - QString text = - QString("%1 cleared the chat.").arg(action.source.login); - - postToThread([chan, text] { - chan->addSystemMessage(text); + postToThread([chan, actor{action.source.login}] { + auto now = QTime::currentTime(); + chan->addOrReplaceClearChat( + MessageBuilder::makeClearChatMessage(now, actor), now); }); }); diff --git a/src/util/ChannelHelpers.hpp b/src/util/ChannelHelpers.hpp index f53f3aa2332..2dbe39b0901 100644 --- a/src/util/ChannelHelpers.hpp +++ b/src/util/ChannelHelpers.hpp @@ -118,4 +118,81 @@ void addOrReplaceChannelTimeout(const Buf &buffer, MessagePtr message, } } +/// Adds a clear message or replaces a previous one sent in the last 20 messages and in the last 5s. +/// This function accepts any buffer to store the messsages in. +/// @param replaceMessage A function of type `void (int index, MessagePtr toReplace, MessagePtr replacement)` +/// - replace `buffer[i]` (=toReplace) with `replacement` +/// @param addMessage A function of type `void (MessagePtr message)` +/// - adds the `message`. +template +void addOrReplaceChannelClear(const Buffer &buffer, MessagePtr message, + QTime now, Replace replaceMessage, Add addMessage) +{ + // NOTE: This function uses the messages PARSE time to figure out whether they should be replaced + // This works as expected for incoming messages, but not for historic messages. + // This has never worked before, but would be nice in the future. + // For this to work, we need to make sure *all* messages have a "server received time". + auto snapshotLength = static_cast(buffer.size()); + auto end = std::max(0, snapshotLength - 20); + bool shouldAddMessage = true; + QTime minimumTime = now.addSecs(-5); + auto timeoutStackStyle = static_cast( + getSettings()->timeoutStackStyle.getValue()); + + if (timeoutStackStyle == TimeoutStackStyle::DontStack) + { + addMessage(message); + return; + } + + for (auto i = snapshotLength - 1; i >= end; --i) + { + const MessagePtr &s = buffer[i]; + + if (s->parseTime < minimumTime) + { + break; + } + + bool isClearChat = s->flags.has(MessageFlag::ClearChat); + + if (timeoutStackStyle == + TimeoutStackStyle::DontStackBeyondUserMessage && + !isClearChat) + { + break; + } + + if (!isClearChat || message->flags.has(MessageFlag::PubSub) != + s->flags.has(MessageFlag::PubSub)) + { + continue; + } + + if (timeoutStackStyle == + TimeoutStackStyle::DontStackBeyondUserMessage && + s->flags.has(MessageFlag::PubSub) && + s->timeoutUser != message->timeoutUser) + { + break; + } + + uint32_t count = s->count + 1; + + auto replacement = MessageBuilder::makeClearChatMessage( + message->parseTime, message->timeoutUser, count); + replacement->flags = message->flags; + + replaceMessage(i, s, replacement); + + shouldAddMessage = false; + break; + } + + if (shouldAddMessage) + { + addMessage(message); + } +} + } // namespace chatterino diff --git a/src/util/VectorMessageSink.cpp b/src/util/VectorMessageSink.cpp index 3911fee890d..16aa618c994 100644 --- a/src/util/VectorMessageSink.cpp +++ b/src/util/VectorMessageSink.cpp @@ -38,6 +38,20 @@ void VectorMessageSink::addOrReplaceTimeout(MessagePtr clearchatMessage, false); } +void VectorMessageSink::addOrReplaceClearChat(MessagePtr clearchatMessage, + QTime now) +{ + addOrReplaceChannelClear( + this->messages_, std::move(clearchatMessage), now, + [&](auto idx, auto /*msg*/, auto &&replacement) { + replacement->flags.set(this->additionalFlags); + this->messages_[idx] = replacement; + }, + [&](auto &&msg) { + this->messages_.emplace_back(msg); + }); +} + void VectorMessageSink::disableAllMessages() { if (this->additionalFlags.has(MessageFlag::RecentMessage)) diff --git a/src/util/VectorMessageSink.hpp b/src/util/VectorMessageSink.hpp index c4ffcfa9c09..98ab241999a 100644 --- a/src/util/VectorMessageSink.hpp +++ b/src/util/VectorMessageSink.hpp @@ -15,6 +15,7 @@ class VectorMessageSink final : public MessageSink MessagePtr message, MessageContext ctx, std::optional overridingFlags = std::nullopt) override; void addOrReplaceTimeout(MessagePtr clearchatMessage, QTime now) override; + void addOrReplaceClearChat(MessagePtr clearchatMessage, QTime now) override; void disableAllMessages() override; diff --git a/tests/snapshots/IrcMessageHandler/clearchat-stack-always.json b/tests/snapshots/IrcMessageHandler/clearchat-stack-always.json new file mode 100644 index 00000000000..0e0b1d1142c --- /dev/null +++ b/tests/snapshots/IrcMessageHandler/clearchat-stack-always.json @@ -0,0 +1,238 @@ +{ + "input": "@tmi-sent-ts=1736369068441;rm-received-ts=1736369068532;historical=1;room-id=111448817 :tmi.twitch.tv CLEARCHAT #pajlada", + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "", + "count": 4, + "displayName": "", + "elements": [ + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "20:44" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "20:44:28", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Chat", + "has", + "been", + "cleared", + "by", + "a", + "moderator." + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "(4", + "times)" + ] + } + ], + "flags": "System|DoNotTriggerNotification|ClearChat", + "id": "", + "localizedName": "", + "loginName": "", + "messageText": "Chat has been cleared by a moderator. (4 times) ", + "searchText": "Chat has been cleared by a moderator. (4 times) ", + "serverReceivedTime": "", + "timeoutUser": "", + "usernameColor": "#ff000000" + }, + { + "badgeInfos": { + }, + "badges": [ + "broadcaster" + ], + "channelName": "pajlada", + "count": 1, + "displayName": "nerixyz", + "elements": [ + { + "color": "System", + "flags": "ChannelName", + "link": { + "type": "JumpToChannel", + "value": "pajlada" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "#pajlada" + ] + }, + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "20:44" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "20:44:24", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "flags": "ModeratorTools", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "", + "trailingSpace": true, + "type": "TwitchModerationElement" + }, + { + "emote": { + "images": { + "1x": "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/1", + "2x": "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/2", + "3x": "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/3" + }, + "name": "", + "tooltip": "Broadcaster" + }, + "flags": "BadgeChannelAuthority", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "Broadcaster", + "trailingSpace": true, + "type": "BadgeElement" + }, + { + "color": "#ffff0000", + "flags": "Username", + "link": { + "type": "UserInfo", + "value": "nerixyz" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "nerixyz:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "def" + ] + }, + { + "background": "#ffa0a0a4", + "flags": "ReplyButton", + "link": { + "type": "ReplyToMessage", + "value": "50e33ac9-4e54-4503-9a35-a621ef114b82" + }, + "padding": 2, + "tooltip": "", + "trailingSpace": true, + "type": "CircularImageElement", + "url": "" + } + ], + "flags": "Disabled|Collapsed", + "id": "50e33ac9-4e54-4503-9a35-a621ef114b82", + "localizedName": "", + "loginName": "nerixyz", + "messageText": "def", + "searchText": "nerixyz nerixyz: def ", + "serverReceivedTime": "2025-01-08T20:44:24Z", + "timeoutUser": "", + "usernameColor": "#ffff0000" + } + ], + "params": { + "nAdditional": 2, + "prevMessages": [ + "@room-id=111448817;rm-received-ts=1736369059298;rm-deleted=1;tmi-sent-ts=1736369059196;historical=1 :tmi.twitch.tv CLEARCHAT #pajlada", + "@historical=1;tmi-sent-ts=1736369062841;room-id=111448817;rm-received-ts=1736369062932;rm-deleted=1 :tmi.twitch.tv CLEARCHAT #pajlada", + "@room-id=111448817;rm-deleted=1;returning-chatter=0;rm-received-ts=1736369064768;subscriber=0;tmi-sent-ts=1736369064631;color=#FF0000;flags=;turbo=0;user-type=;emotes=;id=50e33ac9-4e54-4503-9a35-a621ef114b82;mod=0;historical=1;user-id=129546453;first-msg=0;badge-info=;badges=broadcaster/1;display-name=nerixyz :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada def", + "@historical=1;room-id=111448817;rm-received-ts=1736369067491;rm-deleted=1;tmi-sent-ts=1736369067400 :tmi.twitch.tv CLEARCHAT #pajlada" + ] + }, + "settings": { + "moderation": { + "timeoutStackStyle": 0 + } + } +} diff --git a/tests/snapshots/IrcMessageHandler/clearchat-stack-never.json b/tests/snapshots/IrcMessageHandler/clearchat-stack-never.json new file mode 100644 index 00000000000..7efa97cee09 --- /dev/null +++ b/tests/snapshots/IrcMessageHandler/clearchat-stack-never.json @@ -0,0 +1,86 @@ +{ + "input": "@tmi-sent-ts=1736369068441;rm-received-ts=1736369068532;historical=1;room-id=111448817 :tmi.twitch.tv CLEARCHAT #pajlada", + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "", + "count": 1, + "displayName": "", + "elements": [ + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "20:44" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "20:44:28", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Chat", + "has", + "been", + "cleared", + "by", + "a", + "moderator." + ] + } + ], + "flags": "System|DoNotTriggerNotification|ClearChat", + "id": "", + "localizedName": "", + "loginName": "", + "messageText": "Chat has been cleared by a moderator. ", + "searchText": "Chat has been cleared by a moderator. ", + "serverReceivedTime": "", + "timeoutUser": "", + "usernameColor": "#ff000000" + } + ], + "params": { + "prevMessages": [ + "@room-id=111448817;rm-received-ts=1736369059298;rm-deleted=1;tmi-sent-ts=1736369059196;historical=1 :tmi.twitch.tv CLEARCHAT #pajlada", + "@historical=1;tmi-sent-ts=1736369062841;room-id=111448817;rm-received-ts=1736369062932;rm-deleted=1 :tmi.twitch.tv CLEARCHAT #pajlada", + "@room-id=111448817;rm-deleted=1;returning-chatter=0;rm-received-ts=1736369064768;subscriber=0;tmi-sent-ts=1736369064631;color=#FF0000;flags=;turbo=0;user-type=;emotes=;id=50e33ac9-4e54-4503-9a35-a621ef114b82;mod=0;historical=1;user-id=129546453;first-msg=0;badge-info=;badges=broadcaster/1;display-name=nerixyz :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada def", + "@historical=1;room-id=111448817;rm-received-ts=1736369067491;rm-deleted=1;tmi-sent-ts=1736369067400 :tmi.twitch.tv CLEARCHAT #pajlada" + ] + }, + "settings": { + "moderation": { + "timeoutStackStyle": 2 + } + } +} diff --git a/tests/snapshots/IrcMessageHandler/clearchat-stack-no-user.json b/tests/snapshots/IrcMessageHandler/clearchat-stack-no-user.json new file mode 100644 index 00000000000..b159d154640 --- /dev/null +++ b/tests/snapshots/IrcMessageHandler/clearchat-stack-no-user.json @@ -0,0 +1,98 @@ +{ + "input": "@tmi-sent-ts=1736369068441;rm-received-ts=1736369068532;historical=1;room-id=111448817 :tmi.twitch.tv CLEARCHAT #pajlada", + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "", + "count": 2, + "displayName": "", + "elements": [ + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "20:44" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "20:44:28", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Chat", + "has", + "been", + "cleared", + "by", + "a", + "moderator." + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "(2", + "times)" + ] + } + ], + "flags": "System|DoNotTriggerNotification|ClearChat", + "id": "", + "localizedName": "", + "loginName": "", + "messageText": "Chat has been cleared by a moderator. (2 times) ", + "searchText": "Chat has been cleared by a moderator. (2 times) ", + "serverReceivedTime": "", + "timeoutUser": "", + "usernameColor": "#ff000000" + } + ], + "params": { + "nAdditional": 1, + "prevMessages": [ + "@room-id=111448817;rm-received-ts=1736369059298;rm-deleted=1;tmi-sent-ts=1736369059196;historical=1 :tmi.twitch.tv CLEARCHAT #pajlada", + "@historical=1;tmi-sent-ts=1736369062841;room-id=111448817;rm-received-ts=1736369062932;rm-deleted=1 :tmi.twitch.tv CLEARCHAT #pajlada", + "@room-id=111448817;rm-deleted=1;returning-chatter=0;rm-received-ts=1736369064768;subscriber=0;tmi-sent-ts=1736369064631;color=#FF0000;flags=;turbo=0;user-type=;emotes=;id=50e33ac9-4e54-4503-9a35-a621ef114b82;mod=0;historical=1;user-id=129546453;first-msg=0;badge-info=;badges=broadcaster/1;display-name=nerixyz :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada def", + "@historical=1;room-id=111448817;rm-received-ts=1736369067491;rm-deleted=1;tmi-sent-ts=1736369067400 :tmi.twitch.tv CLEARCHAT #pajlada" + ] + } +} diff --git a/tests/snapshots/IrcMessageHandler/clearchat.json b/tests/snapshots/IrcMessageHandler/clearchat.json index d40f9e7042b..904d746d7d0 100644 --- a/tests/snapshots/IrcMessageHandler/clearchat.json +++ b/tests/snapshots/IrcMessageHandler/clearchat.json @@ -49,106 +49,22 @@ "trailingSpace": true, "type": "TextElement", "words": [ - "Chat" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "has" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "been" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "cleared" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "by" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "a" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ + "Chat", + "has", + "been", + "cleared", + "by", + "a", "moderator." ] } ], - "flags": "System|DoNotTriggerNotification", + "flags": "System|DoNotTriggerNotification|ClearChat", "id": "", "localizedName": "", "loginName": "", - "messageText": "Chat has been cleared by a moderator.", - "searchText": "Chat has been cleared by a moderator.", + "messageText": "Chat has been cleared by a moderator. ", + "searchText": "Chat has been cleared by a moderator. ", "serverReceivedTime": "", "timeoutUser": "", "usernameColor": "#ff000000" diff --git a/tests/src/IrcMessageHandler.cpp b/tests/src/IrcMessageHandler.cpp index 8ed4ee568ab..8a0013e50b9 100644 --- a/tests/src/IrcMessageHandler.cpp +++ b/tests/src/IrcMessageHandler.cpp @@ -569,6 +569,7 @@ class TestIrcMessageHandlerP : public ::testing::TestWithParam /// - `prevMessages`: An array of past messages (used for replies) /// - `findAllUsernames`: A boolean controlling the equally named setting /// (default: false) +/// - `nAdditional`: Include n additional built messages (from `prevMessages`) TEST_P(TestIrcMessageHandlerP, Run) { auto channel = makeMockTwitchChannel(u"pajlada"_s, *snapshot); @@ -588,7 +589,10 @@ TEST_P(TestIrcMessageHandlerP, Run) Communi::IrcMessage::fromData(snapshot->inputUtf8(), nullptr); ASSERT_NE(ircMessage, nullptr); - auto firstAddedMsg = sink.messages().size(); + auto nAdditionalMessages = snapshot->param("nAdditional").toInt(0); + ASSERT_GE(sink.messages().size(), nAdditionalMessages); + + auto firstAddedMsg = sink.messages().size() - nAdditionalMessages; IrcMessageHandler::parseMessageInto(ircMessage, sink, channel.get()); QJsonArray got; From 9fb14551c2a5be4066da8ee75935c4cd7e046443 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 8 Jan 2025 22:36:42 +0100 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f38527c575d..a2d5ff65304 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unversioned +- Minor: `/clear` messages are now stacked like timeouts. (#5806) - Bugfix: Fixed a crash relating to Lua HTTP. (#5800) ## 2.5.2