feat: add countdown for slow mode and timeouts (#6782)

This commit is contained in:
Ava Chow
2026-02-01 19:54:24 +08:00
committed by GitHub
parent 301417488f
commit f61cd1068c
12 changed files with 228 additions and 18 deletions
+72 -1
View File
@@ -222,7 +222,9 @@ std::optional<ClearChatMessage> parseClearChatMessage(
calculateMessageTime(message))
.release();
return ClearChatMessage{.message = timeoutMsg, .disableAllMessages = false};
return ClearChatMessage{.message = timeoutMsg,
.disableAllMessages = false,
.username = username};
}
/**
@@ -403,6 +405,22 @@ void IrcMessageHandler::parsePrivMessageInto(
channel->setVIP(parsedBadges.contains("vip"));
channel->setStaff(parsedBadges.contains("staff"));
}
if (!channel->isLoadingRecentMessages())
{
// Clear the send wait timer when we are able to send a message
channel->setSendWait(0);
// Update send wait timer with slow mode timeout if this user is not a mod or vip.
if (!channel->hasHighRateLimit())
{
auto roomModes = *channel->accessRoomModes();
if (roomModes.slowMode > 0)
{
channel->setSendWait(roomModes.slowMode);
}
}
}
}
IrcMessageHandler::addMessage(
@@ -510,6 +528,25 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
}
else
{
// Set send wait timer when the user is timed out
const auto currentUsername =
getApp()->getAccounts()->twitch.getCurrent()->getUserName();
if (currentUsername == clearChat.username)
{
bool ok = false;
int remainingTime =
message->tags().value("ban-duration").toInt(&ok);
if (ok)
{
auto *tc = dynamic_cast<TwitchChannel *>(chan.get());
assert(tc != nullptr);
if (tc != nullptr)
{
tc->setSendWait(remainingTime);
}
}
}
chan->addOrReplaceTimeout(std::move(clearChat.message), time);
}
@@ -617,6 +654,13 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
tc->setMod(modTag == "1");
}
}
// When a user is updated to mod or vip status, any previous
// slow mode or timeout timers are no longer in effect.
if (tc->hasHighRateLimit())
{
tc->setSendWait(0);
}
}
}
@@ -979,6 +1023,33 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
{
channel->addMessage(msg, MessageContext::Original);
}
auto handleSendWait = [&channel](const QString &remaining) {
bool ok = false;
int seconds = remaining.toInt(&ok);
if (ok)
{
auto *tc = dynamic_cast<TwitchChannel *>(channel.get());
assert(tc != nullptr);
if (tc != nullptr)
{
tc->setSendWait(seconds);
}
}
};
if (tags == "msg_slowmode")
{
// Notice received when the user sends a message too quickly during slow mode.
// @msg-id=msg_slowmode :tmi.twitch.tv NOTICE #channel :This room is in slow mode and you are sending messages too quickly. You will be able to talk again in 10 seconds.
handleSendWait(message->content().split(u' ').value(21));
}
else if (tags == "msg_timedout")
{
// Notice received when the user sends a message while timed out.
// @msg-id=msg_timedout :tmi.twitch.tv NOTICE #twitch :You are timed out for 3600 more seconds.
handleSendWait(message->content().split(u' ').value(5));
}
}
void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
@@ -23,6 +23,7 @@ class MessageSink;
struct ClearChatMessage {
MessagePtr message;
bool disableAllMessages;
std::optional<QString> username;
};
class IrcMessageHandler
+57
View File
@@ -46,6 +46,7 @@
#include "singletons/StreamerMode.hpp"
#include "singletons/Toasts.hpp"
#include "singletons/WindowManager.hpp"
#include "util/FormatTime.hpp"
#include "util/Helpers.hpp"
#include "util/PostToThread.hpp"
#include "util/QStringHash.hpp"
@@ -65,6 +66,7 @@
namespace chatterino {
using namespace literals;
using namespace std::chrono_literals;
namespace detail {
@@ -212,6 +214,11 @@ TwitchChannel::TwitchChannel(const QString &name)
}
});
QObject::connect(&this->sendWaitTimer_, &QTimer::timeout,
&this->lifetimeGuard_, [this] {
this->syncSendWaitTimer();
});
// debugging
#if 0
for (int i = 0; i < 1000; i++) {
@@ -997,6 +1004,12 @@ void TwitchChannel::setRoomModes(const RoomModes &newRoomModes)
{
this->roomModes = newRoomModes;
// Clear send wait timer when slow mode is disabled
if (newRoomModes.slowMode == 0)
{
this->setSendWait(newRoomModes.slowMode);
}
this->roomModesChanged.invoke();
}
@@ -2342,4 +2355,48 @@ void TwitchChannel::listenSevenTVCosmetics() const
}
}
void TwitchChannel::syncSendWaitTimer()
{
auto now = std::chrono::steady_clock::now();
const auto remaining =
this->sendWaitEnd_.has_value()
? std::chrono::duration_cast<std::chrono::seconds>(
this->sendWaitEnd_.value() - now)
: 0s;
if (remaining <= 0s)
{
this->sendWaitTimer_.stop();
this->sendWaitUpdate.invoke("");
}
else
{
this->sendWaitUpdate.invoke(formatTime(remaining, 2));
}
}
void TwitchChannel::setSendWait(int seconds)
{
if (seconds <= 0)
{
if (this->sendWaitEnd_.has_value())
{
this->sendWaitEnd_ = std::nullopt;
this->syncSendWaitTimer();
}
return;
}
this->sendWaitEnd_ =
std::chrono::steady_clock::now() + std::chrono::seconds(seconds);
if (!this->sendWaitTimer_.isActive())
{
this->sendWaitTimer_.start(1s);
this->syncSendWaitTimer();
}
}
bool TwitchChannel::isLoadingRecentMessages() const
{
return this->loadingRecentMessages_.test();
}
} // namespace chatterino
+21
View File
@@ -338,6 +338,8 @@ public:
pajlada::Signals::NoArgSignal destroyed;
pajlada::Signals::Signal<const QString &> sendWaitUpdate;
// Channel point rewards
void addQueuedRedemption(const QString &rewardId,
const QString &originalContent,
@@ -368,6 +370,21 @@ public:
const QString &getDisplayName() const override;
void updateDisplayName(const QString &displayName);
/**
* Sync the text of the send wait timer to the actual time remaining.
*/
void syncSendWaitTimer();
/**
* Set the send wait timer to given number of seconds
*
* When a channel is in slow mode or the user is timed out, calling
* this function sets the timer to show how long the user will need
* to wait before they can send again.
*/
void setSendWait(int seconds);
bool isLoadingRecentMessages() const;
private:
struct NameOptions {
// displayName is the non-CJK-display name for this user
@@ -572,6 +589,10 @@ private:
friend class Commands_E2E_Test;
friend class ::TestIrcMessageHandlerP;
friend class ::TestEventSubMessagesP;
QTimer sendWaitTimer_;
// Timepoint at which the user can send messages again
std::optional<std::chrono::steady_clock::time_point> sendWaitEnd_;
};
} // namespace chatterino