feat: stack /clear messages (#5806)
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Unversioned
|
## Unversioned
|
||||||
|
|
||||||
|
- Minor: `/clear` messages are now stacked like timeouts. (#5806)
|
||||||
- Minor: Treat all browsers starting with `firefox` as a Firefox browser. (#5805)
|
- Minor: Treat all browsers starting with `firefox` as a Firefox browser. (#5805)
|
||||||
- Minor: Remove incognito browser support for `opera/launcher` (this should no longer be a thing). (#5805)
|
- Minor: Remove incognito browser support for `opera/launcher` (this should no longer be a thing). (#5805)
|
||||||
- Minor: Remove incognito browser support for `iexplore`, because internet explorer is EOL. (#5810)
|
- Minor: Remove incognito browser support for `iexplore`, because internet explorer is EOL. (#5810)
|
||||||
|
|||||||
@@ -139,6 +139,18 @@ void Channel::addOrReplaceTimeout(MessagePtr message, QTime now)
|
|||||||
// WindowManager::instance().repaintVisibleChatWidgets(this);
|
// 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()
|
void Channel::disableAllMessages()
|
||||||
{
|
{
|
||||||
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
|
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ public:
|
|||||||
void fillInMissingMessages(const std::vector<MessagePtr> &messages);
|
void fillInMissingMessages(const std::vector<MessagePtr> &messages);
|
||||||
|
|
||||||
void addOrReplaceTimeout(MessagePtr message, QTime now) final;
|
void addOrReplaceTimeout(MessagePtr message, QTime now) final;
|
||||||
|
void addOrReplaceClearChat(MessagePtr message, QTime now) final;
|
||||||
void disableAllMessages() final;
|
void disableAllMessages() final;
|
||||||
void replaceMessage(const MessagePtr &message,
|
void replaceMessage(const MessagePtr &message,
|
||||||
const MessagePtr &replacement);
|
const MessagePtr &replacement);
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QStringBuilder>
|
||||||
#include <QTimeZone>
|
#include <QTimeZone>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -1986,6 +1987,46 @@ MessagePtr MessageBuilder::makeLowTrustUpdateMessage(
|
|||||||
return builder.release();
|
return builder.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessagePtrMut MessageBuilder::makeClearChatMessage(QTime now,
|
||||||
|
const QString &actor,
|
||||||
|
uint32_t count)
|
||||||
|
{
|
||||||
|
MessageBuilder builder;
|
||||||
|
builder.emplace<TimestampElement>(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<MentionElement>(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<MessagePtrMut, HighlightAlert> MessageBuilder::makeIrcMessage(
|
std::pair<MessagePtrMut, HighlightAlert> MessageBuilder::makeIrcMessage(
|
||||||
/* mutable */ Channel *channel, const Communi::IrcMessage *ircMessage,
|
/* mutable */ Channel *channel, const Communi::IrcMessage *ircMessage,
|
||||||
const MessageParseArgs &args, /* mutable */ QString content,
|
const MessageParseArgs &args, /* mutable */ QString content,
|
||||||
|
|||||||
@@ -258,6 +258,12 @@ public:
|
|||||||
const QVariantMap &tags,
|
const QVariantMap &tags,
|
||||||
const QTime &time);
|
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:
|
private:
|
||||||
struct TextState {
|
struct TextState {
|
||||||
TwitchChannel *twitchChannel = nullptr;
|
TwitchChannel *twitchChannel = nullptr;
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ enum class MessageFlag : std::int64_t {
|
|||||||
SharedMessage = (1LL << 37),
|
SharedMessage = (1LL << 37),
|
||||||
/// AutoMod message that showed up due to containing a blocked term in the channel
|
/// AutoMod message that showed up due to containing a blocked term in the channel
|
||||||
AutoModBlockedTerm = (1LL << 38),
|
AutoModBlockedTerm = (1LL << 38),
|
||||||
|
/// The message is a full clear chat message (/clear)
|
||||||
|
ClearChat = (1LL << 39),
|
||||||
};
|
};
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ public:
|
|||||||
virtual void addOrReplaceTimeout(MessagePtr clearchatMessage,
|
virtual void addOrReplaceTimeout(MessagePtr clearchatMessage,
|
||||||
QTime now) = 0;
|
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`
|
/// Flags all messages as `Disabled`
|
||||||
virtual void disableAllMessages() = 0;
|
virtual void disableAllMessages() = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -197,9 +197,8 @@ std::optional<ClearChatMessage> parseClearChatMessage(
|
|||||||
if (message->parameters().length() == 1)
|
if (message->parameters().length() == 1)
|
||||||
{
|
{
|
||||||
return ClearChatMessage{
|
return ClearChatMessage{
|
||||||
.message =
|
.message = MessageBuilder::makeClearChatMessage(
|
||||||
makeSystemMessage("Chat has been cleared by a moderator.",
|
calculateMessageTime(message).time(), {}),
|
||||||
calculateMessageTime(message).time()),
|
|
||||||
.disableAllMessages = true,
|
.disableAllMessages = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -320,15 +319,14 @@ void IrcMessageHandler::parseMessageInto(Communi::IrcMessage *message,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto &clearChat = *cc;
|
auto &clearChat = *cc;
|
||||||
|
auto time = calculateMessageTime(message).time();
|
||||||
if (clearChat.disableAllMessages)
|
if (clearChat.disableAllMessages)
|
||||||
{
|
{
|
||||||
sink.addMessage(std::move(clearChat.message),
|
sink.addOrReplaceClearChat(std::move(clearChat.message), time);
|
||||||
MessageContext::Original);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sink.addOrReplaceTimeout(std::move(clearChat.message),
|
sink.addOrReplaceTimeout(std::move(clearChat.message), time);
|
||||||
calculateMessageTime(message).time());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,18 +462,16 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto time = calculateMessageTime(message).time();
|
||||||
// chat has been cleared by a moderator
|
// chat has been cleared by a moderator
|
||||||
if (clearChat.disableAllMessages)
|
if (clearChat.disableAllMessages)
|
||||||
{
|
{
|
||||||
chan->disableAllMessages();
|
chan->disableAllMessages();
|
||||||
chan->addMessage(std::move(clearChat.message),
|
chan->addOrReplaceClearChat(std::move(clearChat.message), time);
|
||||||
MessageContext::Original);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
chan->addOrReplaceTimeout(std::move(clearChat.message),
|
chan->addOrReplaceTimeout(std::move(clearChat.message), time);
|
||||||
calculateMessageTime(message).time());
|
|
||||||
|
|
||||||
// refresh all
|
// refresh all
|
||||||
getApp()->getWindows()->repaintVisibleChatWidgets(chan.get());
|
getApp()->getWindows()->repaintVisibleChatWidgets(chan.get());
|
||||||
|
|||||||
@@ -247,11 +247,10 @@ void TwitchIrcServer::initialize()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString text =
|
postToThread([chan, actor{action.source.login}] {
|
||||||
QString("%1 cleared the chat.").arg(action.source.login);
|
auto now = QTime::currentTime();
|
||||||
|
chan->addOrReplaceClearChat(
|
||||||
postToThread([chan, text] {
|
MessageBuilder::makeClearChatMessage(now, actor), now);
|
||||||
chan->addSystemMessage(text);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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 <typename Buffer, typename Replace, typename Add>
|
||||||
|
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<qsizetype>(buffer.size());
|
||||||
|
auto end = std::max<qsizetype>(0, snapshotLength - 20);
|
||||||
|
bool shouldAddMessage = true;
|
||||||
|
QTime minimumTime = now.addSecs(-5);
|
||||||
|
auto timeoutStackStyle = static_cast<TimeoutStackStyle>(
|
||||||
|
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
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -38,6 +38,20 @@ void VectorMessageSink::addOrReplaceTimeout(MessagePtr clearchatMessage,
|
|||||||
false);
|
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()
|
void VectorMessageSink::disableAllMessages()
|
||||||
{
|
{
|
||||||
if (this->additionalFlags.has(MessageFlag::RecentMessage))
|
if (this->additionalFlags.has(MessageFlag::RecentMessage))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public:
|
|||||||
MessagePtr message, MessageContext ctx,
|
MessagePtr message, MessageContext ctx,
|
||||||
std::optional<MessageFlags> overridingFlags = std::nullopt) override;
|
std::optional<MessageFlags> overridingFlags = std::nullopt) override;
|
||||||
void addOrReplaceTimeout(MessagePtr clearchatMessage, QTime now) override;
|
void addOrReplaceTimeout(MessagePtr clearchatMessage, QTime now) override;
|
||||||
|
void addOrReplaceClearChat(MessagePtr clearchatMessage, QTime now) override;
|
||||||
|
|
||||||
void disableAllMessages() override;
|
void disableAllMessages() override;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
{
|
||||||
|
"input": "@tmi-sent-ts=1736678514028;room-id=11148817;badges=;badge-info=;flags=;user-type=;emotes= :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": [
|
||||||
|
"10:41"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"format": "",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"time": "10:41:54",
|
||||||
|
"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": {
|
||||||
|
"subscriber": "108"
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
"broadcaster",
|
||||||
|
"subscriber",
|
||||||
|
"partner"
|
||||||
|
],
|
||||||
|
"channelName": "pajlada",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "pajlada",
|
||||||
|
"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": [
|
||||||
|
"10:41"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"format": "",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"time": "10:41:52",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TimestampElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emote": {
|
||||||
|
"images": {
|
||||||
|
"1x": "https://chatterino.com/tb-1",
|
||||||
|
"2x": "https://chatterino.com/tb-2",
|
||||||
|
"3x": "https://chatterino.com/tb-3"
|
||||||
|
},
|
||||||
|
"name": "",
|
||||||
|
"tooltip": "Subscriber"
|
||||||
|
},
|
||||||
|
"flags": "BadgeSubscription",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "Subscriber (Tier 3, 108 months)",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "BadgeElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emote": {
|
||||||
|
"homePage": "https://blog.twitch.tv/2017/04/24/the-verified-badge-is-here-13381bc05735",
|
||||||
|
"images": {
|
||||||
|
"1x": "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/1",
|
||||||
|
"2x": "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/2",
|
||||||
|
"3x": "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/3"
|
||||||
|
},
|
||||||
|
"name": "",
|
||||||
|
"tooltip": "Verified"
|
||||||
|
},
|
||||||
|
"flags": "BadgeVanity",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "Verified",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "BadgeElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#ffcc44ff",
|
||||||
|
"flags": "Username",
|
||||||
|
"link": {
|
||||||
|
"type": "UserInfo",
|
||||||
|
"value": "pajlada"
|
||||||
|
},
|
||||||
|
"style": "ChatMediumBold",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"pajlada:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"asd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background": "#ffa0a0a4",
|
||||||
|
"flags": "ReplyButton",
|
||||||
|
"link": {
|
||||||
|
"type": "ReplyToMessage",
|
||||||
|
"value": "b4f3ac1d-6335-4567-bb8d-e76dfd83f7e3"
|
||||||
|
},
|
||||||
|
"padding": 2,
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "CircularImageElement",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flags": "Collapsed",
|
||||||
|
"id": "b4f3ac1d-6335-4567-bb8d-e76dfd83f7e3",
|
||||||
|
"localizedName": "",
|
||||||
|
"loginName": "pajlada",
|
||||||
|
"messageText": "asd",
|
||||||
|
"searchText": "pajlada pajlada: asd ",
|
||||||
|
"serverReceivedTime": "2025-01-12T10:41:52Z",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"usernameColor": "#ffcc44ff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"params": {
|
||||||
|
"nAdditional": 2,
|
||||||
|
"prevMessages": [
|
||||||
|
"@tmi-sent-ts=1736678508224;room-id=11148817;badges=;badge-info=;flags=;user-type=;emotes= :tmi.twitch.tv CLEARCHAT #pajlada",
|
||||||
|
"@tmi-sent-ts=1736678509397;room-id=11148817;badges=;badge-info=;flags=;user-type=;emotes= :tmi.twitch.tv CLEARCHAT #pajlada",
|
||||||
|
"@tmi-sent-ts=1736678511185;room-id=11148817;badges=;badge-info=;flags=;user-type=;emotes= :tmi.twitch.tv CLEARCHAT #pajlada",
|
||||||
|
"@tmi-sent-ts=1736678512806;subscriber=1;id=b4f3ac1d-6335-4567-bb8d-e76dfd83f7e3;room-id=11148817;user-id=11148817;display-name=pajlada;badges=broadcaster/1,subscriber/3072,partner/1;badge-info=subscriber/108;color=#CC44FF;flags=;user-type=;emotes= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada :asd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"moderation": {
|
||||||
|
"timeoutStackStyle": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,106 +49,22 @@
|
|||||||
"trailingSpace": true,
|
"trailingSpace": true,
|
||||||
"type": "TextElement",
|
"type": "TextElement",
|
||||||
"words": [
|
"words": [
|
||||||
"Chat"
|
"Chat",
|
||||||
]
|
"has",
|
||||||
},
|
"been",
|
||||||
{
|
"cleared",
|
||||||
"color": "System",
|
"by",
|
||||||
"flags": "Text",
|
"a",
|
||||||
"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": [
|
|
||||||
"moderator."
|
"moderator."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"flags": "System|DoNotTriggerNotification",
|
"flags": "System|DoNotTriggerNotification|ClearChat",
|
||||||
"id": "",
|
"id": "",
|
||||||
"localizedName": "",
|
"localizedName": "",
|
||||||
"loginName": "",
|
"loginName": "",
|
||||||
"messageText": "Chat has been cleared by a moderator.",
|
"messageText": "Chat has been cleared by a moderator. ",
|
||||||
"searchText": "Chat has been cleared by a moderator.",
|
"searchText": "Chat has been cleared by a moderator. ",
|
||||||
"serverReceivedTime": "",
|
"serverReceivedTime": "",
|
||||||
"timeoutUser": "",
|
"timeoutUser": "",
|
||||||
"usernameColor": "#ff000000"
|
"usernameColor": "#ff000000"
|
||||||
|
|||||||
@@ -569,6 +569,7 @@ public:
|
|||||||
/// - `prevMessages`: An array of past messages (used for replies)
|
/// - `prevMessages`: An array of past messages (used for replies)
|
||||||
/// - `findAllUsernames`: A boolean controlling the equally named setting
|
/// - `findAllUsernames`: A boolean controlling the equally named setting
|
||||||
/// (default: false)
|
/// (default: false)
|
||||||
|
/// - `nAdditional`: Include n additional built messages (from `prevMessages`)
|
||||||
TEST_P(TestIrcMessageHandlerP, Run)
|
TEST_P(TestIrcMessageHandlerP, Run)
|
||||||
{
|
{
|
||||||
auto channel = makeMockTwitchChannel(u"pajlada"_s, *snapshot);
|
auto channel = makeMockTwitchChannel(u"pajlada"_s, *snapshot);
|
||||||
@@ -588,7 +589,10 @@ TEST_P(TestIrcMessageHandlerP, Run)
|
|||||||
Communi::IrcMessage::fromData(snapshot->inputUtf8(), nullptr);
|
Communi::IrcMessage::fromData(snapshot->inputUtf8(), nullptr);
|
||||||
ASSERT_NE(ircMessage, 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());
|
IrcMessageHandler::parseMessageInto(ircMessage, sink, channel.get());
|
||||||
|
|
||||||
QJsonArray got;
|
QJsonArray got;
|
||||||
|
|||||||
Reference in New Issue
Block a user