diff --git a/src/application.cpp b/src/application.cpp index 271dbc4ac..370a1e32d 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -65,7 +65,7 @@ Application::Application() action.source.name); }); - pubsub.sig.moderation.userTimedOut.connect([&](const auto &action) { + pubsub.sig.moderation.userBanned.connect([&](const auto &action) { auto &server = providers::twitch::TwitchServer::getInstance(); auto chan = server.getChannelOrEmptyByID(action.roomID); @@ -73,31 +73,22 @@ Application::Application() return; } - auto msg = messages::Message::createSystemMessage( - QString("User %1(%2) was timed out by %3 for %4 seconds with reason: '%5'") - .arg(action.target.name) - .arg(action.target.id) - .arg(action.source.name) - .arg(action.duration) - .arg(action.reason)); + auto msg = messages::Message::createTimeoutMessage(action); util::postToThread([chan, msg] { chan->addMessage(msg); }); - - debug::Log("User {}({}) was timed out by {} for {} seconds with reason: '{}'", - action.target.name, action.target.id, action.source.name, action.duration, - action.reason); - }); - - pubsub.sig.moderation.userBanned.connect([&](const auto &action) { - debug::Log("User {}({}) was banned by {} with reason: '{}'", action.target.name, - action.target.id, action.source.name, action.reason); }); pubsub.sig.moderation.userUnbanned.connect([&](const auto &action) { - debug::Log( - "User {}({}) was unbanned by {}. User was previously {}", action.target.name, - action.target.id, action.source.name, - action.previousState == singletons::UnbanAction::Banned ? "banned" : "timed out"); + auto &server = providers::twitch::TwitchServer::getInstance(); + auto chan = server.getChannelOrEmptyByID(action.roomID); + + if (chan->isEmpty()) { + return; + } + + auto msg = messages::Message::createUntimeoutMessage(action); + + util::postToThread([chan, msg] { chan->addMessage(msg); }); }); auto &accountManager = singletons::AccountManager::getInstance(); diff --git a/src/channel.cpp b/src/channel.cpp index 18054d2a2..be679a9f7 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -57,15 +57,57 @@ void Channel::addMessage(MessagePtr message) { MessagePtr deleted; - const QString &username = message->loginName; + bool isTimeout = (message->flags & Message::Timeout) != 0; - if (!username.isEmpty()) { - // TODO: Add recent chatters display name. This should maybe be a setting - this->addRecentChatter(message); + if (!isTimeout) { + const QString &username = message->loginName; + if (!username.isEmpty()) { + // TODO: Add recent chatters display name. This should maybe be a setting + this->addRecentChatter(message); + } } singletons::LoggingManager::getInstance().addMessage(this->name, message); + if (isTimeout) { + LimitedQueueSnapshot snapshot = this->getMessageSnapshot(); + bool addMessage = true; + int snapshotLength = snapshot.getLength(); + + int end = std::max(0, snapshotLength - 20); + + for (int i = snapshotLength - 1; i >= end; --i) { + auto &s = snapshot[i]; + if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) { + break; + } + + if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) { + assert(message->banAction != nullptr); + MessagePtr replacement( + Message::createTimeoutMessage(*(message->banAction), s->count + 1)); + this->replaceMessage(s, replacement); + addMessage = false; + } + } + + // disable the messages from the user + for (int i = 0; i < snapshotLength; i++) { + auto &s = snapshot[i]; + if ((s->flags & (Message::Timeout | Message::Untimeout)) == 0 && + s->loginName == message->timeoutUser) { + s->flags.EnableFlag(Message::Disabled); + } + } + + // XXX: Might need the following line + // WindowManager::getInstance().repaintVisibleChatWidgets(this); + + if (!addMessage) { + return; + } + } + if (this->messages.pushBack(message, deleted)) { this->messageRemovedFromStart.invoke(deleted); } diff --git a/src/messages/message.cpp b/src/messages/message.cpp index 93d564faf..02e535553 100644 --- a/src/messages/message.cpp +++ b/src/messages/message.cpp @@ -1,5 +1,6 @@ #include "messages/message.hpp" #include "messageelement.hpp" +#include "singletons/helper/pubsubactions.hpp" #include "util/irchelpers.hpp" using SBHighlight = chatterino::widgets::ScrollbarHighlight; @@ -78,5 +79,85 @@ MessagePtr Message::createTimeoutMessage(const QString &username, const QString return message; } +MessagePtr Message::createTimeoutMessage(const singletons::BanAction &action, uint32_t count) +{ + MessagePtr msg(new Message); + + msg->addElement(new TimestampElement(QTime::currentTime())); + msg->flags.EnableFlag(MessageFlags::System); + msg->flags.EnableFlag(MessageFlags::Timeout); + + msg->timeoutUser = action.target.name; + msg->count = count; + msg->banAction.reset(new singletons::BanAction(action)); + + QString text; + + if (action.isBan()) { + if (action.reason.isEmpty()) { + text = QString("%1 banned %2.") // + .arg(action.source.name) + .arg(action.target.name); + } else { + text = QString("%1 banned %2: \"%3\".") // + .arg(action.source.name) + .arg(action.target.name) + .arg(action.reason); + } + } else { + if (action.reason.isEmpty()) { + text = QString("%1 timed out %2 for %3 seconds.") // + .arg(action.source.name) + .arg(action.target.name) + .arg(action.duration); + } else { + text = QString("%1 timed out %2 for %3 seconds: \"%4\".") // + .arg(action.source.name) + .arg(action.target.name) + .arg(action.duration) + .arg(action.reason); + } + + if (count > 1) { + text.append(QString(" (%1 times)").arg(count)); + } + } + + msg->addElement(new messages::TextElement(text, messages::MessageElement::Text, + messages::MessageColor::System)); + msg->searchText = text; + + return msg; +} + +MessagePtr Message::createUntimeoutMessage(const singletons::UnbanAction &action) +{ + MessagePtr msg(new Message); + + msg->addElement(new TimestampElement(QTime::currentTime())); + msg->flags.EnableFlag(MessageFlags::System); + msg->flags.EnableFlag(MessageFlags::Untimeout); + + msg->timeoutUser = action.target.name; + + QString text; + + if (action.wasBan()) { + text = QString("%1 unbanned %2.") // + .arg(action.source.name) + .arg(action.target.name); + } else { + text = QString("%1 untimedout %2.") // + .arg(action.source.name) + .arg(action.target.name); + } + + msg->addElement(new messages::TextElement(text, messages::MessageElement::Text, + messages::MessageColor::System)); + msg->searchText = text; + + return msg; +} + } // namespace messages } // namespace chatterino diff --git a/src/messages/message.hpp b/src/messages/message.hpp index 2d3ee6970..f039c0210 100644 --- a/src/messages/message.hpp +++ b/src/messages/message.hpp @@ -1,6 +1,7 @@ #pragma once #include "messages/messageelement.hpp" +#include "singletons/helper/pubsubactions.hpp" #include "util/flagsenum.hpp" #include "widgets/helper/scrollbarhighlight.hpp" @@ -37,6 +38,7 @@ struct Message { DisableCompactEmotes = (1 << 6), Collapsed = (1 << 7), DisconnectedMessage = (1 << 8), + Untimeout = (1 << 9), }; util::FlagsEnum flags; @@ -48,6 +50,9 @@ struct Message { QString localizedName; QString timeoutUser; + std::unique_ptr banAction; + uint32_t count = 1; + // Messages should not be added after the message is done initializing. void addElement(MessageElement *element); const std::vector> &getElements() const; @@ -64,6 +69,10 @@ public: static std::shared_ptr createTimeoutMessage(const QString &username, const QString &durationInSeconds, const QString &reason, bool multipleTimes); + + static std::shared_ptr createTimeoutMessage(const singletons::BanAction &action, + uint32_t count = 1); + static std::shared_ptr createUntimeoutMessage(const singletons::UnbanAction &action); }; using MessagePtr = std::shared_ptr; diff --git a/src/providers/twitch/ircmessagehandler.cpp b/src/providers/twitch/ircmessagehandler.cpp index 4d62cdce2..bcb7f6e19 100644 --- a/src/providers/twitch/ircmessagehandler.cpp +++ b/src/providers/twitch/ircmessagehandler.cpp @@ -56,6 +56,7 @@ void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message) void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) { + return; // check parameter count if (message->parameters().length() < 1) { return; diff --git a/src/singletons/helper/pubsubactions.hpp b/src/singletons/helper/pubsubactions.hpp index ea769747d..fe0796e7e 100644 --- a/src/singletons/helper/pubsubactions.hpp +++ b/src/singletons/helper/pubsubactions.hpp @@ -46,21 +46,19 @@ struct ModeChangedAction : PubSubAction { } args; }; -struct TimeoutAction : PubSubAction { - using PubSubAction::PubSubAction; - - ActionUser target; - - QString reason; - uint32_t duration; -}; - struct BanAction : PubSubAction { using PubSubAction::PubSubAction; ActionUser target; QString reason; + + uint32_t duration = 0; + + bool isBan() const + { + return this->duration == 0; + } }; struct UnbanAction : PubSubAction { @@ -72,6 +70,11 @@ struct UnbanAction : PubSubAction { Banned, TimedOut, } previousState; + + bool wasBan() const + { + return this->previousState == Banned; + } }; struct ClearChatAction : PubSubAction { diff --git a/src/singletons/pubsubmanager.cpp b/src/singletons/pubsubmanager.cpp index bc84ede1b..38df480ac 100644 --- a/src/singletons/pubsubmanager.cpp +++ b/src/singletons/pubsubmanager.cpp @@ -300,7 +300,7 @@ PubSubManager::PubSubManager() }; this->moderationActionHandlers["timeout"] = [this](const auto &data, const auto &roomID) { - TimeoutAction action(data, roomID); + BanAction action(data, roomID); getCreatedByUser(data, action.source); getTargetUser(data, action.target); @@ -329,7 +329,7 @@ PubSubManager::PubSubManager() } } - this->sig.moderation.userTimedOut.invoke(action); + this->sig.moderation.userBanned.invoke(action); } catch (const std::runtime_error &ex) { debug::Log("Error parsing moderation action: {}", ex.what()); } diff --git a/src/singletons/pubsubmanager.hpp b/src/singletons/pubsubmanager.hpp index e9a5bf00b..ed3da54ff 100644 --- a/src/singletons/pubsubmanager.hpp +++ b/src/singletons/pubsubmanager.hpp @@ -102,7 +102,6 @@ public: Signal modeChanged; Signal moderationStateChanged; - Signal userTimedOut; Signal userBanned; Signal userUnbanned; } moderation;