Skip to content

Commit

Permalink
Show historic timeouts and bans in usercard (Chatterino#4760)
Browse files Browse the repository at this point in the history
Co-authored-by: Rasmus Karlsson <[email protected]>
  • Loading branch information
Nerixyz and pajlada authored Aug 13, 2023
1 parent 1e35391 commit e7281b0
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 170 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- Bugfix: Fixed crash that could occurr when closing the usercard too quickly after blocking or unblocking a user. (#4711)
- Bugfix: Fixed highlights sometimes not working after changing sound device, or switching users in your operating system. (#4729)
- Bugfix: Fixed key bindings not showing in context menus on Mac. (#4722)
- Bugfix: Fixed timeouts from history messages not behaving consistently. (#4760)
- Bugfix: Fixed tab completion rarely completing the wrong word. (#4735)
- Bugfix: Fixed an issue where Subscriptions & Announcements that contained ignored phrases would still appear if the Block option was enabled. (#4748)
- Dev: Added command to set Qt's logging filter/rules at runtime (`/c2-set-logging-rules`). (#4637)
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ set(SOURCE_FILES
util/AttachToConsole.cpp
util/AttachToConsole.hpp
util/CancellationToken.hpp
util/ChannelHelpers.hpp
util/Clipboard.cpp
util/Clipboard.hpp
util/ConcurrentMap.hpp
Expand Down
99 changes: 10 additions & 89 deletions src/common/Channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "singletons/Logging.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp"
#include "util/ChannelHelpers.hpp"

#include <QJsonArray>
#include <QJsonDocument>
Expand Down Expand Up @@ -113,95 +114,15 @@ void Channel::addMessage(MessagePtr message,

void Channel::addOrReplaceTimeout(MessagePtr message)
{
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
int snapshotLength = snapshot.size();

int end = std::max(0, snapshotLength - 20);

bool addMessage = true;

QTime minimumTime = QTime::currentTime().addSecs(-5);

auto timeoutStackStyle = static_cast<TimeoutStackStyle>(
getSettings()->timeoutStackStyle.getValue());

for (int i = snapshotLength - 1; i >= end; --i)
{
auto &s = snapshot[i];

if (s->parseTime < minimumTime)
{
break;
}

if (s->flags.has(MessageFlag::Untimeout) &&
s->timeoutUser == message->timeoutUser)
{
break;
}

if (timeoutStackStyle == TimeoutStackStyle::DontStackBeyondUserMessage)
{
if (s->loginName == message->timeoutUser &&
s->flags.hasNone({MessageFlag::Disabled, MessageFlag::Timeout,
MessageFlag::Untimeout}))
{
break;
}
}

if (s->flags.has(MessageFlag::Timeout) &&
s->timeoutUser == message->timeoutUser)
{
if (message->flags.has(MessageFlag::PubSub) &&
!s->flags.has(MessageFlag::PubSub))
{
this->replaceMessage(s, message);
addMessage = false;
break;
}
if (!message->flags.has(MessageFlag::PubSub) &&
s->flags.has(MessageFlag::PubSub))
{
addMessage = timeoutStackStyle == TimeoutStackStyle::DontStack;
break;
}

int count = s->count + 1;

MessageBuilder replacement(timeoutMessage, message->timeoutUser,
message->loginName, message->searchText,
count);

replacement->timeoutUser = message->timeoutUser;
replacement->count = count;
replacement->flags = message->flags;

this->replaceMessage(s, replacement.release());

addMessage = false;
break;
}
}

// disable the messages from the user
for (int i = 0; i < snapshotLength; i++)
{
auto &s = snapshot[i];
if (s->loginName == message->timeoutUser &&
s->flags.hasNone({MessageFlag::Timeout, MessageFlag::Untimeout,
MessageFlag::Whisper}))
{
// FOURTF: disabled for now
// PAJLADA: Shitty solution described in Message.hpp
s->flags.set(MessageFlag::Disabled);
}
}

if (addMessage)
{
this->addMessage(message);
}
addOrReplaceChannelTimeout(
this->getMessageSnapshot(), std::move(message), QTime::currentTime(),
[this](auto /*idx*/, auto msg, auto replacement) {
this->replaceMessage(msg, replacement);
},
[this](auto msg) {
this->addMessage(msg);
},
true);

// XXX: Might need the following line
// WindowManager::instance().repaintVisibleChatWidgets(this);
Expand Down
51 changes: 0 additions & 51 deletions src/providers/recentmessages/Impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,52 +20,6 @@ const auto &LOG = chatterinoRecentMessages;

namespace chatterino::recentmessages::detail {

// convertClearchatToNotice takes a Communi::IrcMessage that is a CLEARCHAT
// command and converts it to a readable NOTICE message. This has
// historically been done in the Recent Messages API, but this functionality
// has been moved to Chatterino instead.
Communi::IrcMessage *convertClearchatToNotice(Communi::IrcMessage *message)
{
auto channelName = message->parameter(0);
QString noticeMessage{};
if (message->tags().contains("target-user-id"))
{
auto target = message->parameter(1);

if (message->tags().contains("ban-duration"))
{
// User was timed out
noticeMessage =
QString("%1 has been timed out for %2.")
.arg(target)
.arg(formatTime(message->tag("ban-duration").toString()));
}
else
{
// User was permanently banned
noticeMessage =
QString("%1 has been permanently banned.").arg(target);
}
}
else
{
// Chat was cleared
noticeMessage = "Chat has been cleared by a moderator.";
}

// rebuild the raw IRC message so we can convert it back to an ircmessage again!
// this could probably be done in a smarter way

auto s = QString(":tmi.twitch.tv NOTICE %1 :%2")
.arg(channelName)
.arg(noticeMessage);

auto *newMessage = Communi::IrcMessage::fromData(s.toUtf8(), nullptr);
newMessage->setTags(message->tags());

return newMessage;
}

// Parse the IRC messages returned in JSON form into Communi messages
std::vector<Communi::IrcMessage *> parseRecentMessages(
const QJsonObject &jsonRoot)
Expand All @@ -89,11 +43,6 @@ std::vector<Communi::IrcMessage *> parseRecentMessages(
auto *message =
Communi::IrcMessage::fromData(content.toUtf8(), nullptr);

if (message->command() == "CLEARCHAT")
{
message = convertClearchatToNotice(message);
}

messages.emplace_back(message);
}

Expand Down
6 changes: 0 additions & 6 deletions src/providers/recentmessages/Impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@

namespace chatterino::recentmessages::detail {

// convertClearchatToNotice takes a Communi::IrcMessage that is a CLEARCHAT
// command and converts it to a readable NOTICE message. This has
// historically been done in the Recent Messages API, but this functionality
// has been moved to Chatterino instead.
Communi::IrcMessage *convertClearchatToNotice(Communi::IrcMessage *message);

// Parse the IRC messages returned in JSON form into Communi messages
std::vector<Communi::IrcMessage *> parseRecentMessages(
const QJsonObject &jsonRoot);
Expand Down
97 changes: 74 additions & 23 deletions src/providers/twitch/IrcMessageHandler.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "IrcMessageHandler.hpp"
#include "providers/twitch/IrcMessageHandler.hpp"

#include "Application.hpp"
#include "common/Literals.hpp"
Expand All @@ -22,6 +22,7 @@
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp"
#include "util/ChannelHelpers.hpp"
#include "util/FormatTime.hpp"
#include "util/Helpers.hpp"
#include "util/IrcHelpers.hpp"
Expand Down Expand Up @@ -377,7 +378,7 @@ void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,

std::vector<MessagePtr> IrcMessageHandler::parseMessageWithReply(
Channel *channel, Communi::IrcMessage *message,
const std::vector<MessagePtr> &otherLoaded)
std::vector<MessagePtr> &otherLoaded)
{
std::vector<MessagePtr> builtMessages;

Expand Down Expand Up @@ -416,6 +417,33 @@ std::vector<MessagePtr> IrcMessageHandler::parseMessageWithReply(
return this->parseNoticeMessage(
static_cast<Communi::IrcNoticeMessage *>(message));
}
else if (command == u"CLEARCHAT"_s)
{
auto cc = this->parseClearChatMessage(message);
if (!cc)
{
return builtMessages;
}
auto &clearChat = *cc;
if (clearChat.disableAllMessages)
{
builtMessages.emplace_back(std::move(clearChat.message));
}
else
{
addOrReplaceChannelTimeout(
otherLoaded, std::move(clearChat.message),
calculateMessageTime(message).time(),
[&](auto idx, auto /*msg*/, auto &&replacement) {
replacement->flags.set(MessageFlag::RecentMessage);
otherLoaded[idx] = replacement;
},
[&](auto &&msg) {
builtMessages.emplace_back(msg);
},
false);
}
}

return builtMessages;
}
Expand Down Expand Up @@ -662,13 +690,51 @@ void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
twitchChannel->roomModesChanged.invoke();
}

void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
std::optional<ClearChatMessage> IrcMessageHandler::parseClearChatMessage(
Communi::IrcMessage *message)
{
// check parameter count
if (message->parameters().length() < 1)
{
return std::nullopt;
}

// check if the chat has been cleared by a moderator
if (message->parameters().length() == 1)
{
return ClearChatMessage{
.message =
makeSystemMessage("Chat has been cleared by a moderator.",
calculateMessageTime(message).time()),
.disableAllMessages = true,
};
}

// get username, duration and message of the timed out user
QString username = message->parameter(1);
QString durationInSeconds;
QVariant v = message->tag("ban-duration");
if (v.isValid())
{
durationInSeconds = v.toString();
}

auto timeoutMsg =
MessageBuilder(timeoutMessage, username, durationInSeconds, false,
calculateMessageTime(message).time())
.release();

return ClearChatMessage{.message = timeoutMsg, .disableAllMessages = false};
}

void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
{
auto cc = this->parseClearChatMessage(message);
if (!cc)
{
return;
}
auto &clearChat = *cc;

QString chanName;
if (!trimChannelName(message->parameter(0), chanName))
Expand All @@ -682,36 +748,21 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
if (chan->isEmpty())
{
qCDebug(chatterinoTwitch)
<< "[IrcMessageHandler:handleClearChatMessage] Twitch channel"
<< "[IrcMessageHandler::handleClearChatMessage] Twitch channel"
<< chanName << "not found";
return;
}

// check if the chat has been cleared by a moderator
if (message->parameters().length() == 1)
// chat has been cleared by a moderator
if (clearChat.disableAllMessages)
{
chan->disableAllMessages();
chan->addMessage(
makeSystemMessage("Chat has been cleared by a moderator.",
calculateMessageTime(message).time()));
chan->addMessage(std::move(clearChat.message));

return;
}

// get username, duration and message of the timed out user
QString username = message->parameter(1);
QString durationInSeconds;
QVariant v = message->tag("ban-duration");
if (v.isValid())
{
durationInSeconds = v.toString();
}

auto timeoutMsg =
MessageBuilder(timeoutMessage, username, durationInSeconds, false,
calculateMessageTime(message).time())
.release();
chan->addOrReplaceTimeout(timeoutMsg);
chan->addOrReplaceTimeout(std::move(clearChat.message));

// refresh all
getApp()->windows->repaintVisibleChatWidgets(chan.get());
Expand Down
10 changes: 9 additions & 1 deletion src/providers/twitch/IrcMessageHandler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <IrcMessage>

#include <optional>
#include <vector>

namespace chatterino {
Expand All @@ -16,6 +17,11 @@ using MessagePtr = std::shared_ptr<const Message>;
class TwitchChannel;
class TwitchMessageBuilder;

struct ClearChatMessage {
MessagePtr message;
bool disableAllMessages;
};

class IrcMessageHandler
{
IrcMessageHandler() = default;
Expand All @@ -29,7 +35,7 @@ class IrcMessageHandler

std::vector<MessagePtr> parseMessageWithReply(
Channel *channel, Communi::IrcMessage *message,
const std::vector<MessagePtr> &otherLoaded);
std::vector<MessagePtr> &otherLoaded);

// parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
std::vector<MessagePtr> parsePrivMessage(
Expand All @@ -38,6 +44,8 @@ class IrcMessageHandler
TwitchIrcServer &server);

void handleRoomStateMessage(Communi::IrcMessage *message);
std::optional<ClearChatMessage> parseClearChatMessage(
Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message);
void handleClearMessageMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message);
Expand Down
Loading

0 comments on commit e7281b0

Please sign in to comment.