feat: stack /clear messages (#5806)
This commit is contained in:
@@ -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<MessagePtr> snapshot = this->getMessageSnapshot();
|
||||
|
||||
@@ -92,6 +92,7 @@ public:
|
||||
void fillInMissingMessages(const std::vector<MessagePtr> &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);
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QStringBuilder>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -1986,6 +1987,46 @@ MessagePtr MessageBuilder::makeLowTrustUpdateMessage(
|
||||
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(
|
||||
/* mutable */ Channel *channel, const Communi::IrcMessage *ircMessage,
|
||||
const MessageParseArgs &args, /* mutable */ QString content,
|
||||
|
||||
@@ -258,6 +258,12 @@ public:
|
||||
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;
|
||||
|
||||
@@ -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<MessageFlag>;
|
||||
|
||||
|
||||
@@ -48,6 +48,11 @@ public:
|
||||
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;
|
||||
|
||||
|
||||
@@ -197,9 +197,8 @@ std::optional<ClearChatMessage> 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());
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -15,6 +15,7 @@ public:
|
||||
MessagePtr message, MessageContext ctx,
|
||||
std::optional<MessageFlags> overridingFlags = std::nullopt) override;
|
||||
void addOrReplaceTimeout(MessagePtr clearchatMessage, QTime now) override;
|
||||
void addOrReplaceClearChat(MessagePtr clearchatMessage, QTime now) override;
|
||||
|
||||
void disableAllMessages() override;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user