refactor: trim down PubSub implementation (#6158)
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
- Dev: Conan will no longer generate a `CMakeUserPresets.json` file. (#6117)
|
||||
- Dev: Pass `--force-openssl` when installing from CMake in Qt 6.8+. (#6129)
|
||||
- Dev: Fixed `<build-tool> clean` not working correctly with generated sources. (#6154)
|
||||
- Dev: Removed authenticated PubSub implementation. (#6158)
|
||||
- Dev: Save settings in `aboutToQuit`. (#6159)
|
||||
|
||||
## 2.5.3
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
#include "providers/seventv/SeventvBadges.hpp"
|
||||
#include "providers/seventv/SeventvEventAPI.hpp"
|
||||
#include "providers/twitch/ChannelPointReward.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubManager.hpp"
|
||||
#include "providers/twitch/PubSubMessages.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
|
||||
@@ -404,8 +404,6 @@ set(SOURCE_FILES
|
||||
providers/twitch/ChannelPointReward.hpp
|
||||
providers/twitch/IrcMessageHandler.cpp
|
||||
providers/twitch/IrcMessageHandler.hpp
|
||||
providers/twitch/PubSubActions.cpp
|
||||
providers/twitch/PubSubActions.hpp
|
||||
providers/twitch/PubSubClient.cpp
|
||||
providers/twitch/PubSubClient.hpp
|
||||
providers/twitch/PubSubClientOptions.hpp
|
||||
@@ -452,18 +450,12 @@ set(SOURCE_FILES
|
||||
providers/twitch/eventsub/SubscriptionRequest.cpp
|
||||
providers/twitch/eventsub/SubscriptionRequest.hpp
|
||||
|
||||
providers/twitch/pubsubmessages/AutoMod.cpp
|
||||
providers/twitch/pubsubmessages/AutoMod.hpp
|
||||
providers/twitch/pubsubmessages/Base.cpp
|
||||
providers/twitch/pubsubmessages/Base.hpp
|
||||
providers/twitch/pubsubmessages/ChannelPoints.cpp
|
||||
providers/twitch/pubsubmessages/ChannelPoints.hpp
|
||||
providers/twitch/pubsubmessages/ChatModeratorAction.cpp
|
||||
providers/twitch/pubsubmessages/ChatModeratorAction.hpp
|
||||
providers/twitch/pubsubmessages/Listen.cpp
|
||||
providers/twitch/pubsubmessages/Listen.hpp
|
||||
providers/twitch/pubsubmessages/LowTrustUsers.cpp
|
||||
providers/twitch/pubsubmessages/LowTrustUsers.hpp
|
||||
providers/twitch/pubsubmessages/Message.hpp
|
||||
providers/twitch/pubsubmessages/Unlisten.cpp
|
||||
providers/twitch/pubsubmessages/Unlisten.hpp
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
@@ -149,27 +148,7 @@ QString debugTest(const CommandContext &ctx)
|
||||
|
||||
const auto command = ctx.words.value(1);
|
||||
|
||||
if (command == "timeout-pubsub")
|
||||
{
|
||||
QJsonObject data;
|
||||
data["created_by_user_id"] = ctx.twitchChannel->roomId();
|
||||
data["created_by"] = ctx.twitchChannel->getName();
|
||||
|
||||
BanAction action(data, ctx.twitchChannel->roomId());
|
||||
|
||||
action.source.id = ctx.twitchChannel->roomId();
|
||||
action.source.login = ctx.twitchChannel->getName();
|
||||
|
||||
action.target.id = "11148817";
|
||||
action.target.login = "pajlada";
|
||||
action.duration = 10;
|
||||
|
||||
MessageBuilder msg(action, QDateTime::currentDateTime());
|
||||
msg->flags.set(MessageFlag::PubSub);
|
||||
ctx.channel->addOrReplaceTimeout(msg.release(),
|
||||
QDateTime::currentDateTime());
|
||||
}
|
||||
else if (command == "timeout-irc")
|
||||
if (command == "timeout-irc")
|
||||
{
|
||||
auto nowMillis = QDateTime::currentDateTime().toSecsSinceEpoch();
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
#include "providers/seventv/SeventvEmotes.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/ChannelPointReward.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
#include "providers/twitch/TwitchBadges.hpp"
|
||||
@@ -376,15 +374,6 @@ bool doesWordContainATwitchEmote(
|
||||
return true;
|
||||
}
|
||||
|
||||
EmotePtr makeAutoModBadge()
|
||||
{
|
||||
return std::make_shared<Emote>(Emote{
|
||||
EmoteName{},
|
||||
ImageSet{Image::fromResourcePixmap(getResources().twitch.automod)},
|
||||
Tooltip{"AutoMod"},
|
||||
Url{"https://dashboard.twitch.tv/settings/moderation/automod"}});
|
||||
}
|
||||
|
||||
EmotePtr makeSharedChatBadge(const QString &sourceName,
|
||||
const QString &sourceProfileURL,
|
||||
const QString &sourceLogin)
|
||||
@@ -773,239 +762,6 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
|
||||
this->message().serverReceivedTime = time;
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(const BanAction &action, const QDateTime &time,
|
||||
uint32_t count)
|
||||
: MessageBuilder()
|
||||
{
|
||||
auto current = getApp()->getAccounts()->twitch.getCurrent();
|
||||
|
||||
this->emplace<TimestampElement>();
|
||||
this->message().flags.set(MessageFlag::System);
|
||||
this->message().flags.set(MessageFlag::Timeout);
|
||||
this->message().flags.set(MessageFlag::ModerationAction);
|
||||
this->message().timeoutUser = action.target.login;
|
||||
this->message().loginName = action.source.login;
|
||||
this->message().count = count;
|
||||
|
||||
QString text;
|
||||
|
||||
if (action.target.id == current->getUserId())
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate("You", text)
|
||||
->setLink({Link::UserInfo, current->getUserName()});
|
||||
this->emplaceSystemTextAndUpdate("were", text);
|
||||
if (action.isBan())
|
||||
{
|
||||
this->appendOrEmplaceSystemTextAndUpdate("banned", text);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->appendOrEmplaceSystemTextAndUpdate(
|
||||
QString("timed out for %1").arg(formatTime(action.duration)),
|
||||
text);
|
||||
}
|
||||
|
||||
if (!action.source.login.isEmpty())
|
||||
{
|
||||
this->appendOrEmplaceSystemTextAndUpdate("by", text);
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
action.source.login + (action.reason.isEmpty() ? "." : ":"),
|
||||
text)
|
||||
->setLink({Link::UserInfo, action.source.login});
|
||||
}
|
||||
|
||||
if (!action.reason.isEmpty())
|
||||
{
|
||||
this->appendOrEmplaceSystemTextAndUpdate(
|
||||
QString("\"%1\".").arg(action.reason), text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (action.isBan())
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate(action.source.login, text)
|
||||
->setLink({Link::UserInfo, action.source.login});
|
||||
this->emplaceSystemTextAndUpdate("banned", text);
|
||||
if (action.reason.isEmpty())
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate(action.target.login + ".",
|
||||
text)
|
||||
->setLink({Link::UserInfo, action.target.login});
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate(action.target.login + ":",
|
||||
text)
|
||||
->setLink({Link::UserInfo, action.target.login});
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("\"%1\".").arg(action.reason), text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate(action.source.login, text)
|
||||
->setLink({Link::UserInfo, action.source.login});
|
||||
this->emplaceSystemTextAndUpdate("timed out", text);
|
||||
this->emplaceSystemTextAndUpdate(action.target.login, text)
|
||||
->setLink({Link::UserInfo, action.target.login});
|
||||
if (action.reason.isEmpty())
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("for %1.").arg(formatTime(action.duration)), text);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("for %1: \"%2\".")
|
||||
.arg(formatTime(action.duration))
|
||||
.arg(action.reason),
|
||||
text);
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
this->appendOrEmplaceSystemTextAndUpdate(
|
||||
QString("(%1 times)").arg(count), text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
|
||||
this->message().serverReceivedTime = time;
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(const UnbanAction &action, const QDateTime &time)
|
||||
: MessageBuilder()
|
||||
{
|
||||
this->emplace<TimestampElement>();
|
||||
this->message().flags.set(MessageFlag::System);
|
||||
this->message().flags.set(MessageFlag::Untimeout);
|
||||
|
||||
this->message().timeoutUser = action.target.login;
|
||||
|
||||
QString text;
|
||||
|
||||
this->emplaceSystemTextAndUpdate(action.source.login, text)
|
||||
->setLink({Link::UserInfo, action.source.login});
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
action.wasBan() ? "unbanned" : "untimedout", text);
|
||||
this->emplaceSystemTextAndUpdate(action.target.login + ".", text)
|
||||
->setLink({Link::UserInfo, action.target.login});
|
||||
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
|
||||
this->message().serverReceivedTime = time;
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(const WarnAction &action)
|
||||
: MessageBuilder()
|
||||
{
|
||||
this->emplace<TimestampElement>();
|
||||
this->message().flags.set(MessageFlag::System);
|
||||
|
||||
QString text;
|
||||
|
||||
// TODO: Use MentionElement here, once WarnAction includes username/displayname
|
||||
this->emplaceSystemTextAndUpdate("A moderator", text)
|
||||
->setLink({Link::UserInfo, "id:" + action.source.id});
|
||||
this->emplaceSystemTextAndUpdate("warned", text);
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
action.target.login + (action.reasons.isEmpty() ? "." : ":"), text)
|
||||
->setLink({Link::UserInfo, action.target.login});
|
||||
|
||||
if (!action.reasons.isEmpty())
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate(action.reasons.join(", "), text);
|
||||
}
|
||||
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(const RaidAction &action)
|
||||
: MessageBuilder()
|
||||
{
|
||||
this->emplace<TimestampElement>();
|
||||
this->message().flags.set(MessageFlag::System);
|
||||
|
||||
QString text;
|
||||
|
||||
this->emplaceSystemTextAndUpdate(action.source.login, text)
|
||||
->setLink({Link::UserInfo, "id:" + action.source.id});
|
||||
this->emplaceSystemTextAndUpdate("initiated a raid to", text);
|
||||
this->emplaceSystemTextAndUpdate(action.target + ".", text)
|
||||
->setLink({Link::UserInfo, action.target});
|
||||
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(const UnraidAction &action)
|
||||
: MessageBuilder()
|
||||
{
|
||||
this->emplace<TimestampElement>();
|
||||
this->message().flags.set(MessageFlag::System);
|
||||
|
||||
QString text;
|
||||
|
||||
this->emplaceSystemTextAndUpdate(action.source.login, text)
|
||||
->setLink({Link::UserInfo, "id:" + action.source.id});
|
||||
this->emplaceSystemTextAndUpdate("canceled the raid.", text);
|
||||
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(const AutomodUserAction &action)
|
||||
: MessageBuilder()
|
||||
{
|
||||
this->emplace<TimestampElement>();
|
||||
this->message().flags.set(MessageFlag::System);
|
||||
|
||||
QString text;
|
||||
switch (action.type)
|
||||
{
|
||||
case AutomodUserAction::AddPermitted: {
|
||||
text = QString("%1 added \"%2\" as a permitted term on AutoMod.")
|
||||
.arg(action.source.login, action.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case AutomodUserAction::AddBlocked: {
|
||||
text = QString("%1 added \"%2\" as a blocked term on AutoMod.")
|
||||
.arg(action.source.login, action.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case AutomodUserAction::RemovePermitted: {
|
||||
text = QString("%1 removed \"%2\" as a permitted term on AutoMod.")
|
||||
.arg(action.source.login, action.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case AutomodUserAction::RemoveBlocked: {
|
||||
text = QString("%1 removed \"%2\" as a blocked term on AutoMod.")
|
||||
.arg(action.source.login, action.message);
|
||||
}
|
||||
break;
|
||||
|
||||
case AutomodUserAction::Properties: {
|
||||
text = QString("%1 modified the AutoMod properties.")
|
||||
.arg(action.source.login);
|
||||
}
|
||||
break;
|
||||
}
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
|
||||
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(LiveUpdatesAddEmoteMessageTag /*unused*/,
|
||||
const QString &platform, const QString &actor,
|
||||
const std::vector<QString> &emoteNames)
|
||||
@@ -1531,50 +1287,6 @@ MessagePtr MessageBuilder::makeDeletionMessageFromIRC(
|
||||
return builder.release();
|
||||
}
|
||||
|
||||
MessagePtr MessageBuilder::makeDeletionMessageFromPubSub(
|
||||
const DeleteAction &action)
|
||||
{
|
||||
MessageBuilder builder;
|
||||
|
||||
builder.emplace<TimestampElement>();
|
||||
builder.message().flags.set(MessageFlag::System);
|
||||
builder.message().flags.set(MessageFlag::DoNotTriggerNotification);
|
||||
builder.message().flags.set(MessageFlag::ModerationAction);
|
||||
|
||||
builder
|
||||
.emplace<TextElement>(action.source.login, MessageElementFlag::Username,
|
||||
MessageColor::System, FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, action.source.login});
|
||||
// TODO(mm2pl): If or when jumping to a single message gets implemented a link,
|
||||
// add a link to the originalMessage
|
||||
builder.emplace<TextElement>(
|
||||
"deleted message from", MessageElementFlag::Text, MessageColor::System);
|
||||
builder
|
||||
.emplace<TextElement>(action.target.login, MessageElementFlag::Username,
|
||||
MessageColor::System, FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, action.target.login});
|
||||
builder.emplace<TextElement>("saying:", MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
if (action.messageText.length() > 50)
|
||||
{
|
||||
builder
|
||||
.emplace<TextElement>(action.messageText.left(50) + "…",
|
||||
MessageElementFlag::Text, MessageColor::Text)
|
||||
->setLink({Link::JumpToMessage, action.messageId});
|
||||
}
|
||||
else
|
||||
{
|
||||
builder
|
||||
.emplace<TextElement>(action.messageText, MessageElementFlag::Text,
|
||||
MessageColor::Text)
|
||||
->setLink({Link::JumpToMessage, action.messageId});
|
||||
}
|
||||
builder.message().timeoutUser = "msg:" + action.messageId;
|
||||
builder.message().flags.set(MessageFlag::PubSub);
|
||||
|
||||
return builder.release();
|
||||
}
|
||||
|
||||
MessagePtr MessageBuilder::makeListOfUsersMessage(QString prefix,
|
||||
QStringList users,
|
||||
Channel *channel,
|
||||
@@ -1729,368 +1441,6 @@ MessagePtr MessageBuilder::buildHypeChatMessage(
|
||||
return builder.release();
|
||||
}
|
||||
|
||||
std::pair<MessagePtr, MessagePtr> MessageBuilder::makeAutomodMessage(
|
||||
const AutomodAction &action, const QString &channelName)
|
||||
{
|
||||
MessageBuilder builder, builder2;
|
||||
|
||||
if (action.reasonCode == PubSubAutoModQueueMessage::Reason::BlockedTerm)
|
||||
{
|
||||
builder.message().flags.set(MessageFlag::AutoModBlockedTerm);
|
||||
builder2.message().flags.set(MessageFlag::AutoModBlockedTerm);
|
||||
}
|
||||
|
||||
//
|
||||
// Builder for AutoMod message with explanation
|
||||
builder.message().id = "automod_" + action.msgID;
|
||||
builder.message().loginName = "automod";
|
||||
builder.message().channelName = channelName;
|
||||
builder.message().flags.set(MessageFlag::PubSub);
|
||||
builder.message().flags.set(MessageFlag::ModerationAction);
|
||||
builder.message().flags.set(MessageFlag::AutoMod);
|
||||
builder.message().flags.set(MessageFlag::AutoModOffendingMessageHeader);
|
||||
|
||||
// AutoMod shield badge
|
||||
builder.emplace<BadgeElement>(makeAutoModBadge(),
|
||||
MessageElementFlag::BadgeChannelAuthority);
|
||||
// AutoMod "username"
|
||||
builder.emplace<TextElement>("AutoMod:", MessageElementFlag::Text,
|
||||
AUTOMOD_USER_COLOR, FontStyle::ChatMediumBold);
|
||||
// AutoMod header message
|
||||
builder.emplace<TextElement>(
|
||||
("Held a message for reason: " + action.reason +
|
||||
". Allow will post it in chat. "),
|
||||
MessageElementFlag::Text, MessageColor::Text);
|
||||
// Allow link button
|
||||
builder
|
||||
.emplace<TextElement>("Allow", MessageElementFlag::Text,
|
||||
MessageColor(QColor("green")),
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::AutoModAllow, action.msgID});
|
||||
// Deny link button
|
||||
builder
|
||||
.emplace<TextElement>(" Deny", MessageElementFlag::Text,
|
||||
MessageColor(QColor("red")),
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::AutoModDeny, action.msgID});
|
||||
// ID of message caught by AutoMod
|
||||
// builder.emplace<TextElement>(action.msgID, MessageElementFlag::Text,
|
||||
// MessageColor::Text);
|
||||
auto text1 =
|
||||
QString("AutoMod: Held a message for reason: %1. Allow will post "
|
||||
"it in chat. Allow Deny")
|
||||
.arg(action.reason);
|
||||
builder.message().messageText = text1;
|
||||
builder.message().searchText = text1;
|
||||
|
||||
auto message1 = builder.release();
|
||||
|
||||
//
|
||||
// Builder for offender's message
|
||||
builder2.message().channelName = channelName;
|
||||
builder2
|
||||
.emplace<TextElement>("#" + channelName,
|
||||
MessageElementFlag::ChannelName,
|
||||
MessageColor::System)
|
||||
->setLink({Link::JumpToChannel, channelName});
|
||||
builder2.emplace<TimestampElement>();
|
||||
builder2.emplace<TwitchModerationElement>();
|
||||
builder2.message().loginName = action.target.login;
|
||||
builder2.message().flags.set(MessageFlag::PubSub);
|
||||
builder2.message().flags.set(MessageFlag::ModerationAction);
|
||||
builder2.message().flags.set(MessageFlag::AutoMod);
|
||||
builder2.message().flags.set(MessageFlag::AutoModOffendingMessage);
|
||||
|
||||
// sender username
|
||||
builder2.emplace<MentionElement>(action.target.displayName + ":",
|
||||
action.target.login, MessageColor::Text,
|
||||
action.target.color);
|
||||
// sender's message caught by AutoMod
|
||||
builder2.emplace<TextElement>(action.message, MessageElementFlag::Text,
|
||||
MessageColor::Text);
|
||||
auto text2 =
|
||||
QString("%1: %2").arg(action.target.displayName, action.message);
|
||||
builder2.message().messageText = text2;
|
||||
builder2.message().searchText = text2;
|
||||
|
||||
auto message2 = builder2.release();
|
||||
|
||||
// Normally highlights would be checked & triggered during the builder parse steps
|
||||
// and when the message is added to the channel
|
||||
// We do this a bit weird since the message comes in from PubSub and not the normal message route
|
||||
auto [highlighted, highlightResult] = getApp()->getHighlights()->check(
|
||||
{}, {}, action.target.login, action.message, message2->flags);
|
||||
if (highlighted)
|
||||
{
|
||||
actuallyTriggerHighlights(
|
||||
channelName, highlightResult.playSound,
|
||||
highlightResult.customSoundUrl.value_or(QUrl{}),
|
||||
highlightResult.alert);
|
||||
}
|
||||
|
||||
return std::make_pair(message1, message2);
|
||||
}
|
||||
|
||||
MessagePtr MessageBuilder::makeAutomodInfoMessage(
|
||||
const AutomodInfoAction &action)
|
||||
{
|
||||
auto builder = MessageBuilder();
|
||||
QString text("AutoMod: ");
|
||||
|
||||
builder.emplace<TimestampElement>();
|
||||
builder.message().flags.set(MessageFlag::PubSub);
|
||||
|
||||
// AutoMod shield badge
|
||||
builder.emplace<BadgeElement>(makeAutoModBadge(),
|
||||
MessageElementFlag::BadgeChannelAuthority);
|
||||
// AutoMod "username"
|
||||
builder.emplace<TextElement>("AutoMod:", MessageElementFlag::Text,
|
||||
AUTOMOD_USER_COLOR, FontStyle::ChatMediumBold);
|
||||
switch (action.type)
|
||||
{
|
||||
case AutomodInfoAction::OnHold: {
|
||||
QString info("Hey! Your message is being checked "
|
||||
"by mods and has not been sent.");
|
||||
text += info;
|
||||
builder.appendOrEmplaceText(info, MessageColor::Text);
|
||||
}
|
||||
break;
|
||||
case AutomodInfoAction::Denied: {
|
||||
QString info("Mods have removed your message.");
|
||||
text += info;
|
||||
builder.appendOrEmplaceText(info, MessageColor::Text);
|
||||
}
|
||||
break;
|
||||
case AutomodInfoAction::Approved: {
|
||||
QString info("Mods have accepted your message.");
|
||||
text += info;
|
||||
builder.appendOrEmplaceText(info, MessageColor::Text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
builder.message().flags.set(MessageFlag::AutoMod);
|
||||
builder.message().messageText = text;
|
||||
builder.message().searchText = text;
|
||||
|
||||
auto message = builder.release();
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
std::pair<MessagePtr, MessagePtr> MessageBuilder::makeLowTrustUserMessage(
|
||||
const PubSubLowTrustUsersMessage &action, const QString &channelName,
|
||||
const TwitchChannel *twitchChannel)
|
||||
{
|
||||
MessageBuilder builder, builder2;
|
||||
|
||||
// Builder for low trust user message with explanation
|
||||
builder.message().channelName = channelName;
|
||||
builder.message().flags.set(MessageFlag::PubSub);
|
||||
builder.message().flags.set(MessageFlag::LowTrustUsers);
|
||||
|
||||
// AutoMod shield badge
|
||||
builder.emplace<BadgeElement>(makeAutoModBadge(),
|
||||
MessageElementFlag::BadgeChannelAuthority);
|
||||
|
||||
// Suspicious user header message
|
||||
QString prefix = "Suspicious User:";
|
||||
builder.emplace<TextElement>(prefix, MessageElementFlag::Text,
|
||||
MessageColor(QColor("blue")),
|
||||
FontStyle::ChatMediumBold);
|
||||
|
||||
QString headerMessage;
|
||||
if (action.treatment == PubSubLowTrustUsersMessage::Treatment::Restricted)
|
||||
{
|
||||
headerMessage = "Restricted";
|
||||
builder2.message().flags.set(MessageFlag::RestrictedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
headerMessage = "Monitored";
|
||||
builder2.message().flags.set(MessageFlag::MonitoredMessage);
|
||||
}
|
||||
|
||||
if (action.restrictionTypes.has(
|
||||
PubSubLowTrustUsersMessage::RestrictionType::ManuallyAdded))
|
||||
{
|
||||
headerMessage += " by " + action.updatedByUserLogin;
|
||||
}
|
||||
|
||||
headerMessage += " at " + action.updatedAt;
|
||||
|
||||
if (action.restrictionTypes.has(
|
||||
PubSubLowTrustUsersMessage::RestrictionType::DetectedBanEvader))
|
||||
{
|
||||
QString evader;
|
||||
if (action.evasionEvaluation ==
|
||||
PubSubLowTrustUsersMessage::EvasionEvaluation::LikelyEvader)
|
||||
{
|
||||
evader = "likely";
|
||||
}
|
||||
else
|
||||
{
|
||||
evader = "possible";
|
||||
}
|
||||
|
||||
headerMessage += ". Detected as " + evader + " ban evader";
|
||||
}
|
||||
|
||||
if (action.restrictionTypes.has(
|
||||
PubSubLowTrustUsersMessage::RestrictionType::BannedInSharedChannel))
|
||||
{
|
||||
headerMessage += ". Banned in " +
|
||||
QString::number(action.sharedBanChannelIDs.size()) +
|
||||
" shared channels";
|
||||
}
|
||||
|
||||
builder.emplace<TextElement>(headerMessage, MessageElementFlag::Text,
|
||||
MessageColor::Text);
|
||||
builder.message().messageText = prefix + " " + headerMessage;
|
||||
builder.message().searchText = prefix + " " + headerMessage;
|
||||
|
||||
auto message1 = builder.release();
|
||||
|
||||
//
|
||||
// Builder for offender's message
|
||||
builder2.message().channelName = channelName;
|
||||
builder2
|
||||
.emplace<TextElement>("#" + channelName,
|
||||
MessageElementFlag::ChannelName,
|
||||
MessageColor::System)
|
||||
->setLink({Link::JumpToChannel, channelName});
|
||||
builder2.emplace<TimestampElement>();
|
||||
builder2.emplace<TwitchModerationElement>();
|
||||
builder2.message().loginName = action.suspiciousUserLogin;
|
||||
builder2.message().flags.set(MessageFlag::PubSub);
|
||||
builder2.message().flags.set(MessageFlag::LowTrustUsers);
|
||||
|
||||
// sender badges
|
||||
appendBadges(&builder2, action.senderBadges, {}, twitchChannel);
|
||||
|
||||
// sender username
|
||||
builder2.emplace<MentionElement>(
|
||||
action.suspiciousUserDisplayName + ":", action.suspiciousUserLogin,
|
||||
MessageColor::Text, action.suspiciousUserColor);
|
||||
|
||||
// sender's message caught by AutoMod
|
||||
for (const auto &fragment : action.fragments)
|
||||
{
|
||||
if (fragment.emoteID.isEmpty())
|
||||
{
|
||||
builder2.emplace<TextElement>(
|
||||
fragment.text, MessageElementFlag::Text, MessageColor::Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto emotePtr =
|
||||
getApp()->getEmotes()->getTwitchEmotes()->getOrCreateEmote(
|
||||
EmoteId{fragment.emoteID}, EmoteName{fragment.text});
|
||||
builder2.emplace<EmoteElement>(
|
||||
emotePtr, MessageElementFlag::TwitchEmote, MessageColor::Text);
|
||||
}
|
||||
}
|
||||
|
||||
auto text =
|
||||
QString("%1: %2").arg(action.suspiciousUserDisplayName, action.text);
|
||||
builder2.message().messageText = text;
|
||||
builder2.message().searchText = text;
|
||||
|
||||
auto message2 = builder2.release();
|
||||
|
||||
return std::make_pair(message1, message2);
|
||||
}
|
||||
|
||||
MessagePtr MessageBuilder::makeLowTrustUpdateMessage(
|
||||
const PubSubLowTrustUsersMessage &action)
|
||||
{
|
||||
/**
|
||||
* Known issues:
|
||||
* - Non-Twitch badges are not shown
|
||||
* - Non-Twitch emotes are not shown
|
||||
*/
|
||||
|
||||
MessageBuilder builder;
|
||||
builder.emplace<TimestampElement>();
|
||||
builder.message().flags.set(MessageFlag::System);
|
||||
builder.message().flags.set(MessageFlag::PubSub);
|
||||
builder.message().flags.set(MessageFlag::DoNotTriggerNotification);
|
||||
|
||||
builder
|
||||
.emplace<TextElement>(action.updatedByUserDisplayName,
|
||||
MessageElementFlag::Username,
|
||||
MessageColor::System, FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, action.updatedByUserLogin});
|
||||
|
||||
QString text;
|
||||
assert(action.treatment != PubSubLowTrustUsersMessage::Treatment::INVALID);
|
||||
switch (action.treatment)
|
||||
{
|
||||
case PubSubLowTrustUsersMessage::Treatment::NoTreatment: {
|
||||
builder.emplace<TextElement>("removed", MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
builder
|
||||
.emplace<TextElement>(action.suspiciousUserDisplayName,
|
||||
MessageElementFlag::Username,
|
||||
MessageColor::System,
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, action.suspiciousUserLogin});
|
||||
builder.emplace<TextElement>("from the suspicious user list.",
|
||||
MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
text = QString("%1 removed %2 from the suspicious user list.")
|
||||
.arg(action.updatedByUserDisplayName,
|
||||
action.suspiciousUserDisplayName);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubLowTrustUsersMessage::Treatment::ActiveMonitoring: {
|
||||
builder.emplace<TextElement>("added", MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
builder
|
||||
.emplace<TextElement>(action.suspiciousUserDisplayName,
|
||||
MessageElementFlag::Username,
|
||||
MessageColor::System,
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, action.suspiciousUserLogin});
|
||||
builder.emplace<TextElement>("as a monitored suspicious chatter.",
|
||||
MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
text = QString("%1 added %2 as a monitored suspicious chatter.")
|
||||
.arg(action.updatedByUserDisplayName,
|
||||
action.suspiciousUserDisplayName);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubLowTrustUsersMessage::Treatment::Restricted: {
|
||||
builder.emplace<TextElement>("added", MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
builder
|
||||
.emplace<TextElement>(action.suspiciousUserDisplayName,
|
||||
MessageElementFlag::Username,
|
||||
MessageColor::System,
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, action.suspiciousUserLogin});
|
||||
builder.emplace<TextElement>("as a restricted suspicious chatter.",
|
||||
MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
text = QString("%1 added %2 as a restricted suspicious chatter.")
|
||||
.arg(action.updatedByUserDisplayName,
|
||||
action.suspiciousUserDisplayName);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
qCDebug(chatterinoTwitch) << "Unexpected suspicious treatment: "
|
||||
<< action.treatmentString;
|
||||
break;
|
||||
}
|
||||
|
||||
builder->messageText = text;
|
||||
builder->searchText = text;
|
||||
return builder.release();
|
||||
}
|
||||
|
||||
MessagePtrMut MessageBuilder::makeMissingScopesMessage(
|
||||
const QString &missingScopes)
|
||||
{
|
||||
|
||||
@@ -4,29 +4,20 @@
|
||||
#include "common/Outcome.hpp"
|
||||
#include "messages/MessageColor.hpp"
|
||||
#include "messages/MessageFlag.hpp"
|
||||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QTime>
|
||||
#include <QUrl>
|
||||
#include <QVariant>
|
||||
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct BanAction;
|
||||
struct UnbanAction;
|
||||
struct WarnAction;
|
||||
struct RaidAction;
|
||||
struct UnraidAction;
|
||||
struct AutomodAction;
|
||||
struct AutomodUserAction;
|
||||
struct AutomodInfoAction;
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
using MessagePtrMut = std::shared_ptr<Message>;
|
||||
@@ -43,7 +34,6 @@ class IgnorePhrase;
|
||||
struct HelixVip;
|
||||
using HelixModerator = HelixVip;
|
||||
struct ChannelPointReward;
|
||||
struct DeleteAction;
|
||||
struct TwitchEmoteOccurrence;
|
||||
|
||||
namespace linkparser {
|
||||
@@ -113,13 +103,6 @@ public:
|
||||
MessageBuilder(TimeoutMessageTag, const QString &username,
|
||||
const QString &durationInSeconds, bool multipleTimes,
|
||||
const QDateTime &time);
|
||||
MessageBuilder(const BanAction &action, const QDateTime &time,
|
||||
uint32_t count = 1);
|
||||
MessageBuilder(const UnbanAction &action, const QDateTime &time);
|
||||
MessageBuilder(const WarnAction &action);
|
||||
MessageBuilder(const RaidAction &action);
|
||||
MessageBuilder(const UnraidAction &action);
|
||||
MessageBuilder(const AutomodUserAction &action);
|
||||
|
||||
MessageBuilder(LiveUpdatesAddEmoteMessageTag, const QString &platform,
|
||||
const QString &actor,
|
||||
@@ -201,7 +184,6 @@ public:
|
||||
bool hostOn);
|
||||
static MessagePtr makeDeletionMessageFromIRC(
|
||||
const MessagePtr &originalMessage);
|
||||
static MessagePtr makeDeletionMessageFromPubSub(const DeleteAction &action);
|
||||
static MessagePtr makeListOfUsersMessage(QString prefix, QStringList users,
|
||||
Channel *channel,
|
||||
MessageFlags extraFlags = {});
|
||||
@@ -211,16 +193,6 @@ public:
|
||||
|
||||
static MessagePtr buildHypeChatMessage(Communi::IrcPrivateMessage *message);
|
||||
|
||||
static std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
|
||||
const AutomodAction &action, const QString &channelName);
|
||||
static MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action);
|
||||
|
||||
static std::pair<MessagePtr, MessagePtr> makeLowTrustUserMessage(
|
||||
const PubSubLowTrustUsersMessage &action, const QString &channelName,
|
||||
const TwitchChannel *twitchChannel);
|
||||
static MessagePtr makeLowTrustUpdateMessage(
|
||||
const PubSubLowTrustUsersMessage &action);
|
||||
|
||||
/// @brief Builds a message out of an `ircMessage`.
|
||||
///
|
||||
/// Building a message won't cause highlights to be triggered. They will
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PubSubAction::PubSubAction(const QJsonObject &data, const QString &_roomID)
|
||||
: timestamp(std::chrono::steady_clock::now())
|
||||
, roomID(_roomID)
|
||||
{
|
||||
this->source.id = data.value("created_by_user_id").toString();
|
||||
this->source.login = data.value("created_by").toString();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,196 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
||||
|
||||
#include <QColor>
|
||||
#include <QDebug>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct ActionUser {
|
||||
QString id;
|
||||
QString login;
|
||||
// displayName should be in format "login(localizedName)" for non-ascii usernames
|
||||
QString displayName;
|
||||
QColor color;
|
||||
|
||||
inline bool operator==(const ActionUser &rhs) const
|
||||
{
|
||||
return this->id == rhs.id && this->login == rhs.login &&
|
||||
this->displayName == rhs.displayName && this->color == rhs.color;
|
||||
}
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug dbg, const ActionUser &user)
|
||||
{
|
||||
dbg.nospace() << "ActionUser(" << user.id << ", " << user.login << ", "
|
||||
<< user.displayName << ", " << user.color << ")";
|
||||
|
||||
return dbg.maybeSpace();
|
||||
}
|
||||
|
||||
struct PubSubAction {
|
||||
PubSubAction() = default;
|
||||
PubSubAction(const QJsonObject &data, const QString &_roomID);
|
||||
ActionUser source;
|
||||
|
||||
std::chrono::steady_clock::time_point timestamp;
|
||||
QString roomID;
|
||||
};
|
||||
|
||||
// Used when a chat mode (i.e. slowmode, subscribers only mode) is enabled or
|
||||
// disabled
|
||||
struct ModeChangedAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
enum Mode {
|
||||
Unknown,
|
||||
Slow,
|
||||
R9K,
|
||||
SubscribersOnly,
|
||||
EmoteOnly,
|
||||
} mode;
|
||||
|
||||
// Whether the mode was turned on or off
|
||||
enum State {
|
||||
Off,
|
||||
On,
|
||||
} state;
|
||||
|
||||
uint32_t duration = 0;
|
||||
|
||||
const char *getModeName() const
|
||||
{
|
||||
switch (this->mode)
|
||||
{
|
||||
case Mode::Slow:
|
||||
return "slow";
|
||||
case Mode::R9K:
|
||||
return "r9k";
|
||||
case Mode::SubscribersOnly:
|
||||
return "subscribers-only";
|
||||
case Mode::EmoteOnly:
|
||||
return "emote-only";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct BanAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
QString reason;
|
||||
|
||||
uint32_t duration = 0;
|
||||
|
||||
bool isBan() const
|
||||
{
|
||||
return this->duration == 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct DeleteAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
QString messageId;
|
||||
QString messageText;
|
||||
};
|
||||
|
||||
struct UnbanAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
enum {
|
||||
Banned,
|
||||
TimedOut,
|
||||
} previousState;
|
||||
|
||||
bool wasBan() const
|
||||
{
|
||||
return this->previousState == Banned;
|
||||
}
|
||||
};
|
||||
|
||||
struct ClearChatAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
};
|
||||
|
||||
struct ModerationStateAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
// true = modded
|
||||
// false = unmodded
|
||||
bool modded;
|
||||
};
|
||||
|
||||
struct AutomodAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
QString message;
|
||||
|
||||
QString reason;
|
||||
PubSubAutoModQueueMessage::Reason reasonCode;
|
||||
|
||||
QString msgID;
|
||||
};
|
||||
|
||||
struct AutomodUserAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
enum {
|
||||
AddPermitted,
|
||||
RemovePermitted,
|
||||
AddBlocked,
|
||||
RemoveBlocked,
|
||||
Properties,
|
||||
} type;
|
||||
|
||||
QString message;
|
||||
};
|
||||
|
||||
struct AutomodInfoAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
enum {
|
||||
OnHold,
|
||||
Denied,
|
||||
Approved,
|
||||
} type;
|
||||
};
|
||||
|
||||
struct RaidAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
QString target;
|
||||
};
|
||||
|
||||
struct UnraidAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
};
|
||||
|
||||
struct WarnAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
QStringList reasons;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,16 +1,9 @@
|
||||
#include "providers/twitch/PubSubClient.hpp"
|
||||
|
||||
#include "common/QLogging.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubHelpers.hpp"
|
||||
#include "providers/twitch/PubSubMessages.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
#include <exception>
|
||||
#include <thread>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "providers/NetworkConfigurationProvider.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubClient.hpp"
|
||||
#include "providers/twitch/PubSubHelpers.hpp"
|
||||
#include "providers/twitch/PubSubMessages.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
#include "util/RenameThread.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
@@ -20,7 +14,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
@@ -37,449 +30,6 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
|
||||
pingInterval,
|
||||
})
|
||||
{
|
||||
this->moderationActionHandlers["clear"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ClearChatAction action(data, roomID);
|
||||
|
||||
this->moderation.chatCleared.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["slowoff"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::Slow;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["slow"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::Slow;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.empty())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "Missing duration argument in slowmode on";
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
|
||||
action.duration = args.at(0).toString().toUInt(&ok, 10);
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["r9kbetaoff"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::R9K;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["r9kbeta"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::R9K;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["subscribersoff"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["subscribers"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["emoteonlyoff"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::EmoteOnly;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["emoteonly"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::EmoteOnly;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["unmod"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModerationStateAction action(data, roomID);
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
|
||||
action.modded = false;
|
||||
|
||||
this->moderation.moderationStateChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["mod"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModerationStateAction action(data, roomID);
|
||||
action.modded = true;
|
||||
|
||||
auto innerType = data.value("type").toString();
|
||||
if (innerType == "chat_login_moderation")
|
||||
{
|
||||
// Don't display the old message type
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
action.target.login = data.value("target_user_login").toString();
|
||||
|
||||
this->moderation.moderationStateChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["timeout"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
BanAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
bool ok;
|
||||
action.duration = args[1].toString().toUInt(&ok, 10);
|
||||
action.reason = args[2].toString(); // May be omitted
|
||||
|
||||
this->moderation.userBanned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["delete"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
DeleteAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.size() < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
action.messageText = args[1].toString();
|
||||
action.messageId = args[2].toString();
|
||||
|
||||
this->moderation.messageDeleted.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["ban"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
BanAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
action.reason = args[1].toString(); // May be omitted
|
||||
|
||||
this->moderation.userBanned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["unban"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
UnbanAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
action.previousState = UnbanAction::Banned;
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
|
||||
this->moderation.userUnbanned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["untimeout"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
UnbanAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
action.previousState = UnbanAction::TimedOut;
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
|
||||
this->moderation.userUnbanned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["warn"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
WarnAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login =
|
||||
data.value("created_by").toString(); // currently always empty
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
action.target.login = data.value("target_user_login").toString();
|
||||
|
||||
const auto reasons = data.value("args").toArray();
|
||||
bool firstArg = true;
|
||||
for (const auto &reasonValue : reasons)
|
||||
{
|
||||
if (firstArg)
|
||||
{
|
||||
// Skip first arg in the reasons array since it's not a reason
|
||||
firstArg = false;
|
||||
continue;
|
||||
}
|
||||
const auto &reason = reasonValue.toString();
|
||||
if (!reason.isEmpty())
|
||||
{
|
||||
action.reasons.append(reason);
|
||||
}
|
||||
}
|
||||
|
||||
this->moderation.userWarned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["raid"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
RaidAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target = args[0].toString();
|
||||
|
||||
this->moderation.raidStarted.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["unraid"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
UnraidAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
this->moderation.raidCanceled.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["automod_message_rejected"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
AutomodInfoAction action(data, roomID);
|
||||
action.type = AutomodInfoAction::OnHold;
|
||||
this->moderation.automodInfoMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["automod_message_denied"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
AutomodInfoAction action(data, roomID);
|
||||
action.type = AutomodInfoAction::Denied;
|
||||
this->moderation.automodInfoMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["automod_message_approved"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
AutomodInfoAction action(data, roomID);
|
||||
action.type = AutomodInfoAction::Approved;
|
||||
this->moderation.automodInfoMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->channelTermsActionHandlers["add_permitted_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got a pass through automod
|
||||
AutomodUserAction action(data, roomID);
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.type = AutomodUserAction::AddPermitted;
|
||||
action.message = data.value("text").toString();
|
||||
action.source.login = data.value("requester_login").toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->channelTermsActionHandlers["add_blocked_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// A term has been added
|
||||
AutomodUserAction action(data, roomID);
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.type = AutomodUserAction::AddBlocked;
|
||||
action.message = data.value("text").toString();
|
||||
action.source.login = data.value("requester_login").toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["delete_permitted_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got deleted
|
||||
AutomodUserAction action(data, roomID);
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
action.type = AutomodUserAction::RemovePermitted;
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.message = args[0].toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->channelTermsActionHandlers["delete_permitted_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got deleted
|
||||
AutomodUserAction action(data, roomID);
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.type = AutomodUserAction::RemovePermitted;
|
||||
action.message = data.value("text").toString();
|
||||
action.source.login = data.value("requester_login").toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["delete_blocked_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got deleted
|
||||
AutomodUserAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
action.type = AutomodUserAction::RemoveBlocked;
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.message = args[0].toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
this->channelTermsActionHandlers["delete_blocked_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got deleted
|
||||
AutomodUserAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.type = AutomodUserAction::RemoveBlocked;
|
||||
action.message = data.value("text").toString();
|
||||
action.source.login = data.value("requester_login").toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["denied_automod_message"] =
|
||||
[](const auto &data, const auto &roomID) {
|
||||
// This message got denied by a moderator
|
||||
// qCDebug(chatterinoPubSub) << rj::stringify(data);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["approved_automod_message"] =
|
||||
[](const auto &data, const auto &roomID) {
|
||||
// This message got approved by a moderator
|
||||
// qCDebug(chatterinoPubSub) << rj::stringify(data);
|
||||
};
|
||||
|
||||
this->websocketClient.set_access_channels(websocketpp::log::alevel::all);
|
||||
this->websocketClient.clear_access_channels(
|
||||
websocketpp::log::alevel::frame_payload |
|
||||
@@ -509,24 +59,6 @@ PubSub::~PubSub()
|
||||
void PubSub::initialize()
|
||||
{
|
||||
this->start();
|
||||
this->setAccount(getApp()->getAccounts()->twitch.getCurrent());
|
||||
|
||||
getApp()->getAccounts()->twitch.currentUserChanged.connect(
|
||||
[this] {
|
||||
this->unlistenChannelModerationActions();
|
||||
this->unlistenAutomod();
|
||||
this->unlistenLowTrustUsers();
|
||||
this->unlistenChannelPointRewards();
|
||||
|
||||
this->setAccount(getApp()->getAccounts()->twitch.getCurrent());
|
||||
},
|
||||
boost::signals2::at_front);
|
||||
}
|
||||
|
||||
void PubSub::setAccount(std::shared_ptr<TwitchAccount> account)
|
||||
{
|
||||
this->token_ = account->getOAuthToken();
|
||||
this->userID_ = account->getUserId();
|
||||
}
|
||||
|
||||
void PubSub::addClient()
|
||||
@@ -617,108 +149,6 @@ void PubSub::stop()
|
||||
this->thread->detach();
|
||||
}
|
||||
|
||||
void PubSub::listenToChannelModerationActions(const QString &channelID)
|
||||
{
|
||||
if (getSettings()->enableExperimentalEventSub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->userID_.isEmpty())
|
||||
{
|
||||
qCDebug(chatterinoPubSub) << "Unable to listen to moderation actions "
|
||||
"topic, no user logged in";
|
||||
return;
|
||||
}
|
||||
|
||||
static const QString topicFormat("chat_moderator_actions.%1.%2");
|
||||
assert(!channelID.isEmpty());
|
||||
|
||||
auto topic = topicFormat.arg(this->userID_, channelID);
|
||||
|
||||
if (this->isListeningToTopic(topic))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(chatterinoPubSub) << "Listen to topic" << topic;
|
||||
|
||||
this->listenToTopic(topic);
|
||||
}
|
||||
|
||||
void PubSub::unlistenChannelModerationActions()
|
||||
{
|
||||
this->unlistenPrefix("chat_moderator_actions.");
|
||||
}
|
||||
|
||||
void PubSub::listenToAutomod(const QString &channelID)
|
||||
{
|
||||
if (getSettings()->enableExperimentalEventSub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->userID_.isEmpty())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "Unable to listen to automod topic, no user logged in";
|
||||
return;
|
||||
}
|
||||
|
||||
static const QString topicFormat("automod-queue.%1.%2");
|
||||
assert(!channelID.isEmpty());
|
||||
|
||||
auto topic = topicFormat.arg(this->userID_, channelID);
|
||||
|
||||
if (this->isListeningToTopic(topic))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(chatterinoPubSub) << "Listen to topic" << topic;
|
||||
|
||||
this->listenToTopic(topic);
|
||||
}
|
||||
|
||||
void PubSub::unlistenAutomod()
|
||||
{
|
||||
this->unlistenPrefix("automod-queue.");
|
||||
}
|
||||
|
||||
void PubSub::listenToLowTrustUsers(const QString &channelID)
|
||||
{
|
||||
if (getSettings()->enableExperimentalEventSub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->userID_.isEmpty())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "Unable to listen to low trust users topic, no user logged in";
|
||||
return;
|
||||
}
|
||||
|
||||
static const QString topicFormat("low-trust-users.%1.%2");
|
||||
assert(!channelID.isEmpty());
|
||||
|
||||
auto topic = topicFormat.arg(this->userID_, channelID);
|
||||
|
||||
if (this->isListeningToTopic(topic))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(chatterinoPubSub) << "Listen to topic" << topic;
|
||||
|
||||
this->listenToTopic(topic);
|
||||
}
|
||||
|
||||
void PubSub::unlistenLowTrustUsers()
|
||||
{
|
||||
this->unlistenPrefix("low-trust-users.");
|
||||
}
|
||||
|
||||
void PubSub::listenToChannelPointRewards(const QString &channelID)
|
||||
{
|
||||
static const QString topicFormat("community-points-channel-v1.%1");
|
||||
@@ -918,7 +348,6 @@ void PubSub::onConnectionOpen(WebsocketHandle hdl)
|
||||
this->requests.begin() + topicsToTake);
|
||||
|
||||
PubSubListenMessage msg(newTopics);
|
||||
msg.setToken(this->token_);
|
||||
|
||||
if (auto success = client->listen(msg); !success)
|
||||
{
|
||||
@@ -1100,70 +529,7 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
|
||||
{
|
||||
QString topic = message.topic;
|
||||
|
||||
if (topic.startsWith("chat_moderator_actions."))
|
||||
{
|
||||
auto oInnerMessage =
|
||||
message.toInner<PubSubChatModeratorActionMessage>();
|
||||
if (!oInnerMessage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto innerMessage = *oInnerMessage;
|
||||
auto topicParts = topic.split(".");
|
||||
assert(topicParts.length() == 3);
|
||||
|
||||
// Channel ID where the moderator actions are coming from
|
||||
auto channelID = topicParts[2];
|
||||
|
||||
switch (innerMessage.type)
|
||||
{
|
||||
case PubSubChatModeratorActionMessage::Type::ModerationAction: {
|
||||
QString moderationAction =
|
||||
innerMessage.data.value("moderation_action").toString();
|
||||
|
||||
auto handlerIt =
|
||||
this->moderationActionHandlers.find(moderationAction);
|
||||
|
||||
if (handlerIt == this->moderationActionHandlers.end())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "No handler found for moderation action"
|
||||
<< moderationAction;
|
||||
return;
|
||||
}
|
||||
// Invoke handler function
|
||||
handlerIt->second(innerMessage.data, channelID);
|
||||
}
|
||||
break;
|
||||
case PubSubChatModeratorActionMessage::Type::ChannelTermsAction: {
|
||||
QString channelTermsAction =
|
||||
innerMessage.data.value("type").toString();
|
||||
|
||||
auto handlerIt =
|
||||
this->channelTermsActionHandlers.find(channelTermsAction);
|
||||
|
||||
if (handlerIt == this->channelTermsActionHandlers.end())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "No handler found for channel terms action"
|
||||
<< channelTermsAction;
|
||||
return;
|
||||
}
|
||||
// Invoke handler function
|
||||
handlerIt->second(innerMessage.data, channelID);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubChatModeratorActionMessage::Type::INVALID:
|
||||
default: {
|
||||
qCDebug(chatterinoPubSub) << "Invalid moderator action type:"
|
||||
<< innerMessage.typeString;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (topic.startsWith("community-points-channel-v1."))
|
||||
if (topic.startsWith("community-points-channel-v1."))
|
||||
{
|
||||
auto oInnerMessage =
|
||||
message.toInner<PubSubCommunityPointsChannelV1Message>();
|
||||
@@ -1193,55 +559,6 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (topic.startsWith("automod-queue."))
|
||||
{
|
||||
auto oInnerMessage = message.toInner<PubSubAutoModQueueMessage>();
|
||||
if (!oInnerMessage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto innerMessage = *oInnerMessage;
|
||||
|
||||
auto topicParts = topic.split(".");
|
||||
assert(topicParts.length() == 3);
|
||||
|
||||
// Channel ID where the moderator actions are coming from
|
||||
auto channelID = topicParts[2];
|
||||
|
||||
this->moderation.autoModMessageCaught.invoke(innerMessage, channelID);
|
||||
}
|
||||
else if (topic.startsWith("low-trust-users."))
|
||||
{
|
||||
auto oInnerMessage = message.toInner<PubSubLowTrustUsersMessage>();
|
||||
if (!oInnerMessage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto innerMessage = *oInnerMessage;
|
||||
|
||||
switch (innerMessage.type)
|
||||
{
|
||||
case PubSubLowTrustUsersMessage::Type::UserMessage: {
|
||||
this->moderation.suspiciousMessageReceived.invoke(innerMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubLowTrustUsersMessage::Type::TreatmentUpdate: {
|
||||
this->moderation.suspiciousTreatmentUpdated.invoke(
|
||||
innerMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubLowTrustUsersMessage::Type::INVALID: {
|
||||
qCWarning(chatterinoPubSub)
|
||||
<< "Invalid low trust users event type:"
|
||||
<< innerMessage.typeString;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qCDebug(chatterinoPubSub) << "Unknown topic:" << topic;
|
||||
@@ -1258,13 +575,7 @@ void PubSub::runThread()
|
||||
|
||||
void PubSub::listenToTopic(const QString &topic)
|
||||
{
|
||||
PubSubListenMessage msg({topic});
|
||||
if (!topic.startsWith("community-points-channel-v1."))
|
||||
{
|
||||
msg.setToken(this->token_);
|
||||
}
|
||||
|
||||
this->listen(std::move(msg));
|
||||
this->listen(PubSubListenMessage({topic}));
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -35,22 +34,6 @@ namespace chatterino {
|
||||
class TwitchAccount;
|
||||
class PubSubClient;
|
||||
|
||||
struct ClearChatAction;
|
||||
struct DeleteAction;
|
||||
struct ModeChangedAction;
|
||||
struct ModerationStateAction;
|
||||
struct BanAction;
|
||||
struct UnbanAction;
|
||||
struct PubSubAutoModQueueMessage;
|
||||
struct AutomodAction;
|
||||
struct AutomodUserAction;
|
||||
struct AutomodInfoAction;
|
||||
struct RaidAction;
|
||||
struct UnraidAction;
|
||||
struct WarnAction;
|
||||
struct PubSubLowTrustUsersMessage;
|
||||
struct PubSubWhisperMessage;
|
||||
|
||||
struct PubSubListenMessage;
|
||||
struct PubSubMessage;
|
||||
struct PubSubMessageMessage;
|
||||
@@ -83,11 +66,6 @@ class PubSub
|
||||
WebsocketClient websocketClient;
|
||||
std::unique_ptr<std::thread> thread;
|
||||
|
||||
// Account credentials
|
||||
// Set from setAccount
|
||||
QString token_;
|
||||
QString userID_;
|
||||
|
||||
public:
|
||||
PubSub(const QString &host,
|
||||
std::chrono::seconds pingInterval = std::chrono::seconds(15));
|
||||
@@ -101,74 +79,10 @@ public:
|
||||
/// Set up connections between itself & other parts of the application
|
||||
void initialize();
|
||||
|
||||
struct {
|
||||
Signal<ClearChatAction> chatCleared;
|
||||
Signal<DeleteAction> messageDeleted;
|
||||
Signal<ModeChangedAction> modeChanged;
|
||||
Signal<ModerationStateAction> moderationStateChanged;
|
||||
|
||||
Signal<RaidAction> raidStarted;
|
||||
Signal<UnraidAction> raidCanceled;
|
||||
|
||||
Signal<BanAction> userBanned;
|
||||
Signal<UnbanAction> userUnbanned;
|
||||
Signal<WarnAction> userWarned;
|
||||
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousMessageReceived;
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousTreatmentUpdated;
|
||||
|
||||
// Message caught by automod
|
||||
// channelID
|
||||
pajlada::Signals::Signal<PubSubAutoModQueueMessage, QString>
|
||||
autoModMessageCaught;
|
||||
|
||||
// Message blocked by moderator
|
||||
Signal<AutomodAction> autoModMessageBlocked;
|
||||
|
||||
Signal<AutomodUserAction> automodUserMessage;
|
||||
Signal<AutomodInfoAction> automodInfoMessage;
|
||||
} moderation;
|
||||
|
||||
struct {
|
||||
Signal<const QJsonObject &> redeemed;
|
||||
} pointReward;
|
||||
|
||||
/**
|
||||
* Listen to moderation actions in the given channel.
|
||||
* This topic is relevant for everyone.
|
||||
* For moderators, this topic includes blocked/permitted terms updates,
|
||||
* roomstate changes, general mod/vip updates, all bans/timeouts/deletions.
|
||||
* For normal users, this topic includes moderation actions that are targetted at the local user:
|
||||
* automod catching a user's sent message, a moderator approving or denying their caught messages,
|
||||
* the user gaining/losing mod/vip, the user receiving a ban/timeout/deletion.
|
||||
*
|
||||
* PubSub topic: chat_moderator_actions.{currentUserID}.{channelID}
|
||||
*/
|
||||
void listenToChannelModerationActions(const QString &channelID);
|
||||
void unlistenChannelModerationActions();
|
||||
|
||||
/**
|
||||
* Listen to Automod events in the given channel.
|
||||
* This topic is only relevant for moderators.
|
||||
* This will send events about incoming messages that
|
||||
* are caught by Automod.
|
||||
*
|
||||
* PubSub topic: automod-queue.{currentUserID}.{channelID}
|
||||
*/
|
||||
void listenToAutomod(const QString &channelID);
|
||||
void unlistenAutomod();
|
||||
|
||||
/**
|
||||
* Listen to Low Trust events in the given channel.
|
||||
* This topic is only relevant for moderators.
|
||||
* This will fire events about suspicious treatment updates
|
||||
* and messages sent by restricted/monitored users.
|
||||
*
|
||||
* PubSub topic: low-trust-users.{currentUserID}.{channelID}
|
||||
*/
|
||||
void listenToLowTrustUsers(const QString &channelID);
|
||||
void unlistenLowTrustUsers();
|
||||
|
||||
/**
|
||||
* Listen to incoming channel point redemptions in the given channel.
|
||||
* This topic is relevant for everyone.
|
||||
@@ -190,8 +104,6 @@ public:
|
||||
} diag;
|
||||
|
||||
private:
|
||||
void setAccount(std::shared_ptr<TwitchAccount> account);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
@@ -218,14 +130,6 @@ private:
|
||||
std::owner_less<WebsocketHandle>>
|
||||
clients;
|
||||
|
||||
std::unordered_map<
|
||||
QString, std::function<void(const QJsonObject &, const QString &)>>
|
||||
moderationActionHandlers;
|
||||
|
||||
std::unordered_map<
|
||||
QString, std::function<void(const QJsonObject &, const QString &)>>
|
||||
channelTermsActionHandlers;
|
||||
|
||||
void onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr msg);
|
||||
void onConnectionOpen(websocketpp::connection_hdl hdl);
|
||||
void onConnectionFail(websocketpp::connection_hdl hdl);
|
||||
@@ -266,12 +170,6 @@ private:
|
||||
FRIEND_TEST(TwitchPubSubClient, DisconnectedAfter1s);
|
||||
FRIEND_TEST(TwitchPubSubClient, ExceedTopicLimit);
|
||||
FRIEND_TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep);
|
||||
FRIEND_TEST(TwitchPubSubClient, ReceivedWhisper);
|
||||
FRIEND_TEST(TwitchPubSubClient, ModeratorActionsUserBanned);
|
||||
FRIEND_TEST(TwitchPubSubClient, MissingToken);
|
||||
FRIEND_TEST(TwitchPubSubClient, WrongToken);
|
||||
FRIEND_TEST(TwitchPubSubClient, CorrectToken);
|
||||
FRIEND_TEST(TwitchPubSubClient, AutoModMessageHeld);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Base.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Base.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/ChannelPoints.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/ChatModeratorAction.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Listen.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Listen.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Message.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Unlisten.hpp" // IWYU pragma: export
|
||||
|
||||
@@ -1510,12 +1510,8 @@ void TwitchChannel::refreshPubSub()
|
||||
|
||||
auto currentAccount = getApp()->getAccounts()->twitch.getCurrent();
|
||||
|
||||
getApp()->getTwitchPubSub()->listenToChannelModerationActions(roomId);
|
||||
if (this->hasModRights())
|
||||
{
|
||||
getApp()->getTwitchPubSub()->listenToAutomod(roomId);
|
||||
getApp()->getTwitchPubSub()->listenToLowTrustUsers(roomId);
|
||||
|
||||
this->eventSubChannelModerateHandle =
|
||||
getApp()->getEventSub()->subscribe(eventsub::SubscriptionRequest{
|
||||
.subscriptionType = "channel.moderate",
|
||||
|
||||
@@ -17,13 +17,10 @@
|
||||
#include "providers/seventv/SeventvEventAPI.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubManager.hpp"
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/StreamerMode.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
#include "util/RatelimitBucket.hpp"
|
||||
@@ -240,518 +237,6 @@ void TwitchIrcServer::initialize()
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.chatCleared,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, actor{action.source.login}] {
|
||||
auto now = QDateTime::currentDateTime();
|
||||
chan->addOrReplaceClearChat(
|
||||
MessageBuilder::makeClearChatMessage(now, actor), now);
|
||||
if (getSettings()->hideModerated)
|
||||
{
|
||||
// XXX: This is expensive. We could use a layout request if the layout
|
||||
// would store the previous message flags.
|
||||
getApp()->getWindows()->forceLayoutChannelViews();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.modeChanged,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QString text =
|
||||
QString("%1 turned %2 %3 mode.")
|
||||
.arg(action.source.login)
|
||||
.arg(action.state == ModeChangedAction::State::On ? "on"
|
||||
: "off")
|
||||
.arg(action.getModeName());
|
||||
|
||||
if (action.duration > 0)
|
||||
{
|
||||
text += QString(" (%1 seconds)").arg(action.duration);
|
||||
}
|
||||
|
||||
postToThread([chan, text] {
|
||||
chan->addSystemMessage(text);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.moderationStateChanged,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QString text;
|
||||
|
||||
text = QString("%1 %2 %3.")
|
||||
.arg(action.source.login,
|
||||
(action.modded ? "modded" : "unmodded"),
|
||||
action.target.login);
|
||||
|
||||
postToThread([chan, text] {
|
||||
chan->addSystemMessage(text);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.userBanned,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
// TODO: Can we utilize some pubsub time field? maybe not worth
|
||||
auto time = QDateTime::currentDateTime();
|
||||
MessageBuilder msg(action, time);
|
||||
msg->flags.set(MessageFlag::PubSub);
|
||||
chan->addOrReplaceTimeout(msg.release(),
|
||||
QDateTime::currentDateTime());
|
||||
if (getSettings()->hideModerated)
|
||||
{
|
||||
// XXX: This is expensive. We could use a layout request if the layout
|
||||
// would store the previous message flags.
|
||||
getApp()->getWindows()->forceLayoutChannelViews();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.userWarned,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Resolve the moderator's user ID into a full user here, so message can look better
|
||||
postToThread([chan, action] {
|
||||
MessageBuilder msg(action);
|
||||
msg->flags.set(MessageFlag::PubSub);
|
||||
chan->addMessage(msg.release(), MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.messageDeleted,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty() || getSettings()->hideDeletionActions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = MessageBuilder::makeDeletionMessageFromPubSub(action);
|
||||
|
||||
postToThread([chan, msg] {
|
||||
auto replaced = false;
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot =
|
||||
chan->getMessageSnapshot();
|
||||
int snapshotLength = snapshot.size();
|
||||
|
||||
// without parens it doesn't build on windows
|
||||
int end = (std::max)(0, snapshotLength - 200);
|
||||
|
||||
for (int i = snapshotLength - 1; i >= end; --i)
|
||||
{
|
||||
const auto &s = snapshot[i];
|
||||
if (!s->flags.has(MessageFlag::PubSub) &&
|
||||
s->timeoutUser == msg->timeoutUser)
|
||||
{
|
||||
chan->replaceMessage(s, msg);
|
||||
replaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!replaced)
|
||||
{
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.userUnbanned,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Can we utilize some pubsub time field? maybe not worth
|
||||
auto time = QDateTime::currentDateTime();
|
||||
auto msg = MessageBuilder(action, time).release();
|
||||
|
||||
postToThread([chan, msg] {
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.suspiciousMessageReceived,
|
||||
[this](const auto &action) {
|
||||
if (action.treatment ==
|
||||
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
||||
{
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "Received suspicious message with unknown "
|
||||
"treatment:"
|
||||
<< action.treatmentString;
|
||||
return;
|
||||
}
|
||||
|
||||
// monitored chats are received over irc; in the future, we will use pubsub instead
|
||||
if (action.treatment !=
|
||||
PubSubLowTrustUsersMessage::Treatment::Restricted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (getApp()->getStreamerMode()->shouldHideModActions())
|
||||
{
|
||||
// NOTE: This completely stops the building of this action, rathern than only hiding it.
|
||||
// If the user disabled streamer mode or the setting, there will be messages missing
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan = this->getChannelOrEmptyByID(action.channelID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(chan);
|
||||
if (!twitchChannel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([twitchChannel, action] {
|
||||
const auto p = MessageBuilder::makeLowTrustUserMessage(
|
||||
action, twitchChannel->getName(), twitchChannel.get());
|
||||
twitchChannel->addMessage(p.first, MessageContext::Original);
|
||||
twitchChannel->addMessage(p.second, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.suspiciousTreatmentUpdated,
|
||||
[this](const auto &action) {
|
||||
if (action.treatment ==
|
||||
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
||||
{
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "Received suspicious user update with unknown "
|
||||
"treatment:"
|
||||
<< action.treatmentString;
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.updatedByUserLogin.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (getApp()->getStreamerMode()->shouldHideModActions())
|
||||
{
|
||||
// NOTE: This completely stops the building of this action, rathern than only hiding it.
|
||||
// If the user disabled streamer mode or the setting, there will be messages missing
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan = this->getChannelOrEmptyByID(action.channelID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
auto msg = MessageBuilder::makeLowTrustUpdateMessage(action);
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.autoModMessageCaught,
|
||||
[this](const auto &msg, const QString &channelID) {
|
||||
auto chan = this->getChannelOrEmptyByID(channelID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.type)
|
||||
{
|
||||
case PubSubAutoModQueueMessage::Type::AutoModCaughtMessage: {
|
||||
if (msg.status == "PENDING")
|
||||
{
|
||||
AutomodAction action(msg.data, channelID);
|
||||
if (msg.reason ==
|
||||
PubSubAutoModQueueMessage::Reason::BlockedTerm)
|
||||
{
|
||||
auto numBlockedTermsMatched =
|
||||
msg.blockedTermsFound.size();
|
||||
auto hideBlockedTerms =
|
||||
getSettings()
|
||||
->streamerModeHideBlockedTermText &&
|
||||
getApp()->getStreamerMode()->isEnabled();
|
||||
if (!msg.blockedTermsFound.empty())
|
||||
{
|
||||
if (hideBlockedTerms)
|
||||
{
|
||||
action.reason =
|
||||
u"matches %1 blocked term%2"_s
|
||||
.arg(numBlockedTermsMatched)
|
||||
.arg(numBlockedTermsMatched > 1
|
||||
? u"s"
|
||||
: u"");
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList blockedTerms(
|
||||
msg.blockedTermsFound.begin(),
|
||||
msg.blockedTermsFound.end());
|
||||
action.reason =
|
||||
u"matches %1 blocked term%2 \"%3\""_s
|
||||
.arg(numBlockedTermsMatched)
|
||||
.arg(numBlockedTermsMatched > 1
|
||||
? u"s"
|
||||
: u"")
|
||||
.arg(blockedTerms.join(u"\", \""));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
action.reason = "blocked term usage";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
action.reason = QString("%1 level %2")
|
||||
.arg(msg.contentCategory)
|
||||
.arg(msg.contentLevel);
|
||||
}
|
||||
|
||||
action.msgID = msg.messageID;
|
||||
action.message = msg.messageText;
|
||||
action.reasonCode = msg.reason;
|
||||
|
||||
// this message also contains per-word automod data, which could be implemented
|
||||
|
||||
// extract sender data manually because Twitch loves not being consistent
|
||||
QString senderDisplayName =
|
||||
msg.senderUserDisplayName; // Might be transformed later
|
||||
bool hasLocalizedName = false;
|
||||
if (!msg.senderUserDisplayName.isEmpty())
|
||||
{
|
||||
// check for non-ascii display names
|
||||
if (QString::compare(msg.senderUserDisplayName,
|
||||
msg.senderUserLogin,
|
||||
Qt::CaseInsensitive) != 0)
|
||||
{
|
||||
hasLocalizedName = true;
|
||||
}
|
||||
}
|
||||
QColor senderColor = msg.senderUserChatColor;
|
||||
QString senderColor_;
|
||||
if (!senderColor.isValid() &&
|
||||
getSettings()->colorizeNicknames)
|
||||
{
|
||||
// color may be not present if user is a grey-name
|
||||
senderColor = getRandomColor(msg.senderUserID);
|
||||
}
|
||||
|
||||
// handle username style based on prefered setting
|
||||
switch (getSettings()->usernameDisplayMode.getValue())
|
||||
{
|
||||
case UsernameDisplayMode::Username: {
|
||||
if (hasLocalizedName)
|
||||
{
|
||||
senderDisplayName = msg.senderUserLogin;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UsernameDisplayMode::LocalizedName: {
|
||||
break;
|
||||
}
|
||||
case UsernameDisplayMode::
|
||||
UsernameAndLocalizedName: {
|
||||
if (hasLocalizedName)
|
||||
{
|
||||
senderDisplayName = QString("%1(%2)").arg(
|
||||
msg.senderUserLogin,
|
||||
msg.senderUserDisplayName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
action.target =
|
||||
ActionUser{msg.senderUserID, msg.senderUserLogin,
|
||||
senderDisplayName, senderColor};
|
||||
postToThread([chan, action] {
|
||||
const auto p = MessageBuilder::makeAutomodMessage(
|
||||
action, chan->getName());
|
||||
chan->addMessage(p.first, MessageContext::Original);
|
||||
chan->addMessage(p.second,
|
||||
MessageContext::Original);
|
||||
|
||||
getApp()
|
||||
->getTwitch()
|
||||
->getAutomodChannel()
|
||||
->addMessage(p.first, MessageContext::Original);
|
||||
getApp()
|
||||
->getTwitch()
|
||||
->getAutomodChannel()
|
||||
->addMessage(p.second,
|
||||
MessageContext::Original);
|
||||
|
||||
if (getSettings()->showAutomodInMentions)
|
||||
{
|
||||
getApp()
|
||||
->getTwitch()
|
||||
->getMentionsChannel()
|
||||
->addMessage(p.first,
|
||||
MessageContext::Original);
|
||||
getApp()
|
||||
->getTwitch()
|
||||
->getMentionsChannel()
|
||||
->addMessage(p.second,
|
||||
MessageContext::Original);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gray out approve/deny button upon "ALLOWED" and "DENIED" statuses
|
||||
// They are versions of automod_message_(denied|approved) but for mods.
|
||||
chan->disableMessage("automod_" + msg.messageID);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubAutoModQueueMessage::Type::INVALID:
|
||||
default: {
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.autoModMessageBlocked,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
const auto p =
|
||||
MessageBuilder::makeAutomodMessage(action, chan->getName());
|
||||
chan->addMessage(p.first, MessageContext::Original);
|
||||
chan->addMessage(p.second, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.automodUserMessage,
|
||||
[this](const auto &action) {
|
||||
if (getApp()->getStreamerMode()->shouldHideModActions())
|
||||
{
|
||||
// NOTE: This completely stops the building of this action, rathern than only hiding it.
|
||||
// If the user disabled streamer mode or the setting, there will be messages missing
|
||||
return;
|
||||
}
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = MessageBuilder(action).release();
|
||||
|
||||
postToThread([chan, msg] {
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.automodInfoMessage,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
const auto p = MessageBuilder::makeAutomodInfoMessage(action);
|
||||
chan->addMessage(p, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.raidStarted,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = MessageBuilder(action).release();
|
||||
|
||||
postToThread([chan, msg] {
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.raidCanceled,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = MessageBuilder(action).release();
|
||||
|
||||
postToThread([chan, msg] {
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->pointReward.redeemed, [this](auto &data) {
|
||||
QString channelId = data.value("channel_id").toString();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "providers/twitch/eventsub/Controller.hpp"
|
||||
#include "providers/twitch/eventsub/MessageBuilder.hpp"
|
||||
#include "providers/twitch/eventsub/MessageHandlers.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
#include "Application.hpp"
|
||||
#include "common/Literals.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/StreamerMode.hpp"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Application.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/twitch/eventsub/MessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
||||
|
||||
#include "util/QMagicEnum.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root)
|
||||
: typeString(root.value("type").toString())
|
||||
, data(root.value("data").toObject())
|
||||
, status(this->data.value("status").toString())
|
||||
{
|
||||
auto oType = qmagicenum::enumCast<Type>(this->typeString);
|
||||
if (oType.has_value())
|
||||
{
|
||||
this->type = oType.value();
|
||||
}
|
||||
|
||||
this->reason =
|
||||
qmagicenum::enumCast<Reason>(data.value("reason_code").toString())
|
||||
.value_or(Reason::INVALID);
|
||||
|
||||
auto contentClassification =
|
||||
data.value("content_classification").toObject();
|
||||
|
||||
this->contentCategory = contentClassification.value("category").toString();
|
||||
this->contentLevel = contentClassification.value("level").toInt();
|
||||
|
||||
auto message = data.value("message").toObject();
|
||||
|
||||
this->messageID = message.value("id").toString();
|
||||
|
||||
auto messageContent = message.value("content").toObject();
|
||||
|
||||
this->messageText = messageContent.value("text").toString();
|
||||
|
||||
auto messageSender = message.value("sender").toObject();
|
||||
|
||||
this->senderUserID = messageSender.value("user_id").toString();
|
||||
this->senderUserLogin = messageSender.value("login").toString();
|
||||
this->senderUserDisplayName =
|
||||
messageSender.value("display_name").toString();
|
||||
this->senderUserChatColor =
|
||||
QColor(messageSender.value("chat_color").toString());
|
||||
|
||||
if (this->reason == Reason::BlockedTerm)
|
||||
{
|
||||
// Attempt to read the blocked term(s) that caused this message to be blocked
|
||||
const auto caughtMessageReason =
|
||||
data.value("caught_message_reason").toObject();
|
||||
const auto blockedTermFailure =
|
||||
caughtMessageReason.value("blocked_term_failure").toObject();
|
||||
const auto termsFound =
|
||||
blockedTermFailure.value("terms_found").toArray();
|
||||
|
||||
for (const auto &termValue : termsFound)
|
||||
{
|
||||
const auto term = termValue.toObject();
|
||||
const auto termText = term.value("text").toString();
|
||||
if (!termText.isEmpty())
|
||||
{
|
||||
this->blockedTermsFound.insert(termText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,84 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <QColor>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct PubSubAutoModQueueMessage {
|
||||
enum class Type {
|
||||
AutoModCaughtMessage,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
enum class Reason {
|
||||
AutoMod,
|
||||
BlockedTerm,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
QString typeString;
|
||||
Type type = Type::INVALID;
|
||||
Reason reason = Reason::INVALID;
|
||||
|
||||
QJsonObject data;
|
||||
|
||||
QString status;
|
||||
|
||||
QString contentCategory;
|
||||
int contentLevel{};
|
||||
|
||||
QString messageID;
|
||||
QString messageText;
|
||||
|
||||
QString senderUserID;
|
||||
QString senderUserLogin;
|
||||
QString senderUserDisplayName;
|
||||
QColor senderUserChatColor;
|
||||
|
||||
std::set<QString> blockedTermsFound;
|
||||
|
||||
PubSubAutoModQueueMessage() = default;
|
||||
explicit PubSubAutoModQueueMessage(const QJsonObject &root);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubAutoModQueueMessage::Type>(
|
||||
chatterino::PubSubAutoModQueueMessage::Type value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case chatterino::PubSubAutoModQueueMessage::Type::AutoModCaughtMessage:
|
||||
return "automod_caught_message";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubAutoModQueueMessage::Reason>(
|
||||
chatterino::PubSubAutoModQueueMessage::Reason value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case chatterino::PubSubAutoModQueueMessage::Reason::AutoMod:
|
||||
return "AutoModCaughtMessageReason";
|
||||
case chatterino::PubSubAutoModQueueMessage::Reason::BlockedTerm:
|
||||
return "BlockedTermCaughtMessageReason";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#include "providers/twitch/pubsubmessages/ChatModeratorAction.hpp"
|
||||
|
||||
#include "util/QMagicEnum.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PubSubChatModeratorActionMessage::PubSubChatModeratorActionMessage(
|
||||
const QJsonObject &root)
|
||||
: typeString(root.value("type").toString())
|
||||
, data(root.value("data").toObject())
|
||||
{
|
||||
auto oType = qmagicenum::enumCast<Type>(this->typeString);
|
||||
if (oType.has_value())
|
||||
{
|
||||
this->type = oType.value();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct PubSubChatModeratorActionMessage {
|
||||
enum class Type {
|
||||
ModerationAction,
|
||||
ChannelTermsAction,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
QString typeString;
|
||||
Type type = Type::INVALID;
|
||||
|
||||
QJsonObject data;
|
||||
|
||||
PubSubChatModeratorActionMessage(const QJsonObject &root);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubChatModeratorActionMessage::Type>(
|
||||
chatterino::PubSubChatModeratorActionMessage::Type value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case chatterino::PubSubChatModeratorActionMessage::Type::
|
||||
ModerationAction:
|
||||
return "moderation_action";
|
||||
|
||||
case chatterino::PubSubChatModeratorActionMessage::Type::
|
||||
ChannelTermsAction:
|
||||
return "channel_terms_action";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp"
|
||||
|
||||
#include "util/QMagicEnum.hpp"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
|
||||
: typeString(root.value("type").toString())
|
||||
{
|
||||
if (const auto oType = qmagicenum::enumCast<Type>(this->typeString);
|
||||
oType.has_value())
|
||||
{
|
||||
this->type = oType.value();
|
||||
}
|
||||
|
||||
auto data = root.value("data").toObject();
|
||||
|
||||
if (this->type == Type::UserMessage)
|
||||
{
|
||||
this->msgID = data.value("message_id").toString();
|
||||
this->sentAt = data.value("sent_at").toString();
|
||||
const auto content = data.value("message_content").toObject();
|
||||
this->text = content.value("text").toString();
|
||||
for (const auto &part : content.value("fragments").toArray())
|
||||
{
|
||||
this->fragments.emplace_back(part.toObject());
|
||||
}
|
||||
|
||||
// the rest of the data is within a nested object
|
||||
data = data.value("low_trust_user").toObject();
|
||||
|
||||
const auto sender = data.value("sender").toObject();
|
||||
this->suspiciousUserID = sender.value("user_id").toString();
|
||||
this->suspiciousUserLogin = sender.value("login").toString();
|
||||
this->suspiciousUserDisplayName =
|
||||
sender.value("display_name").toString();
|
||||
this->suspiciousUserColor =
|
||||
QColor(sender.value("chat_color").toString());
|
||||
|
||||
for (const auto &badge : sender.value("badges").toArray())
|
||||
{
|
||||
const auto badgeObj = badge.toObject();
|
||||
const auto badgeID = badgeObj.value("id").toString();
|
||||
const auto badgeVersion = badgeObj.value("version").toString();
|
||||
this->senderBadges.emplace_back(Badge{badgeID, badgeVersion});
|
||||
}
|
||||
|
||||
const auto sharedValue = data.value("shared_ban_channel_ids");
|
||||
if (!sharedValue.isNull())
|
||||
{
|
||||
for (const auto &id : sharedValue.toArray())
|
||||
{
|
||||
this->sharedBanChannelIDs.emplace_back(id.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->suspiciousUserID = data.value("target_user_id").toString();
|
||||
this->suspiciousUserLogin = data.value("target_user").toString();
|
||||
this->suspiciousUserDisplayName = this->suspiciousUserLogin;
|
||||
}
|
||||
|
||||
this->channelID = data.value("channel_id").toString();
|
||||
this->updatedAtString = data.value("updated_at").toString();
|
||||
this->updatedAt = QDateTime::fromString(this->updatedAtString, Qt::ISODate)
|
||||
.toLocalTime()
|
||||
.toString("MMM d yyyy, h:mm ap");
|
||||
|
||||
const auto updatedBy = data.value("updated_by").toObject();
|
||||
this->updatedByUserID = updatedBy.value("id").toString();
|
||||
this->updatedByUserLogin = updatedBy.value("login").toString();
|
||||
this->updatedByUserDisplayName = updatedBy.value("display_name").toString();
|
||||
|
||||
this->treatmentString = data.value("treatment").toString();
|
||||
if (const auto oTreatment =
|
||||
qmagicenum::enumCast<Treatment>(this->treatmentString);
|
||||
oTreatment.has_value())
|
||||
{
|
||||
this->treatment = oTreatment.value();
|
||||
}
|
||||
|
||||
this->evasionEvaluationString =
|
||||
data.value("ban_evasion_evaluation").toString();
|
||||
if (const auto oEvaluation = qmagicenum::enumCast<EvasionEvaluation>(
|
||||
this->evasionEvaluationString);
|
||||
oEvaluation.has_value())
|
||||
{
|
||||
this->evasionEvaluation = oEvaluation.value();
|
||||
}
|
||||
|
||||
for (const auto &rType : data.value("types").toArray())
|
||||
{
|
||||
if (const auto oRestriction =
|
||||
qmagicenum::enumCast<RestrictionType>(rType.toString());
|
||||
oRestriction.has_value())
|
||||
{
|
||||
this->restrictionTypes.set(oRestriction.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,266 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
|
||||
#include <common/FlagsEnum.hpp>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <QColor>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct PubSubLowTrustUsersMessage {
|
||||
struct Fragment {
|
||||
QString text;
|
||||
QString emoteID;
|
||||
|
||||
explicit Fragment(const QJsonObject &obj)
|
||||
: text(obj.value("text").toString())
|
||||
, emoteID(obj.value("emoticon")
|
||||
.toObject()
|
||||
.value("emoticonID")
|
||||
.toString())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of low trust message update
|
||||
*/
|
||||
enum class Type {
|
||||
/**
|
||||
* An incoming message from someone marked as low trust
|
||||
*/
|
||||
UserMessage,
|
||||
|
||||
/**
|
||||
* An incoming update about a user's low trust status
|
||||
*/
|
||||
TreatmentUpdate,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
/**
|
||||
* The treatment set for the suspicious user
|
||||
*/
|
||||
enum class Treatment {
|
||||
NoTreatment,
|
||||
ActiveMonitoring,
|
||||
Restricted,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
/**
|
||||
* A ban evasion likelihood value (if any) that has been applied to the user
|
||||
* automatically by Twitch
|
||||
*/
|
||||
enum class EvasionEvaluation {
|
||||
UnknownEvader,
|
||||
UnlikelyEvader,
|
||||
LikelyEvader,
|
||||
PossibleEvader,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
/**
|
||||
* Restriction type (if any) that apply to the suspicious user
|
||||
*/
|
||||
enum class RestrictionType : uint8_t {
|
||||
UnknownType = 1 << 0,
|
||||
ManuallyAdded = 1 << 1,
|
||||
DetectedBanEvader = 1 << 2,
|
||||
BannedInSharedChannel = 1 << 3,
|
||||
|
||||
INVALID = 1 << 4,
|
||||
};
|
||||
|
||||
Type type = Type::INVALID;
|
||||
|
||||
Treatment treatment = Treatment::INVALID;
|
||||
|
||||
EvasionEvaluation evasionEvaluation = EvasionEvaluation::INVALID;
|
||||
|
||||
FlagsEnum<RestrictionType> restrictionTypes;
|
||||
|
||||
QString channelID;
|
||||
|
||||
QString suspiciousUserID;
|
||||
QString suspiciousUserLogin;
|
||||
QString suspiciousUserDisplayName;
|
||||
|
||||
QString updatedByUserID;
|
||||
QString updatedByUserLogin;
|
||||
QString updatedByUserDisplayName;
|
||||
|
||||
/**
|
||||
* Formatted timestamp of when the treatment was last updated for the suspicious user
|
||||
*/
|
||||
QString updatedAt;
|
||||
|
||||
/**
|
||||
* Plain text of the message sent.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
QString text;
|
||||
|
||||
/**
|
||||
* Pre-parsed components of the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
std::vector<Fragment> fragments;
|
||||
|
||||
/**
|
||||
* ID of the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
QString msgID;
|
||||
|
||||
/**
|
||||
* RFC3339 timestamp of when the message was sent.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
QString sentAt;
|
||||
|
||||
/**
|
||||
* Color of the user who sent the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
QColor suspiciousUserColor;
|
||||
|
||||
/**
|
||||
* A list of channel IDs where the suspicious user is also banned.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
std::vector<QString> sharedBanChannelIDs;
|
||||
|
||||
/**
|
||||
* A list of badges of the user who sent the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
std::vector<Badge> senderBadges;
|
||||
|
||||
/**
|
||||
* Stores the string value of `type`
|
||||
* Useful in case type shows up as invalid after being parsed
|
||||
*/
|
||||
QString typeString;
|
||||
|
||||
/**
|
||||
* Stores the string value of `treatment`
|
||||
* Useful in case treatment shows up as invalid after being parsed
|
||||
*/
|
||||
QString treatmentString;
|
||||
|
||||
/**
|
||||
* Stores the string value of `ban_evasion_evaluation`
|
||||
* Useful in case evasionEvaluation shows up as invalid after being parsed
|
||||
*/
|
||||
QString evasionEvaluationString;
|
||||
|
||||
/**
|
||||
* Stores the string value of `updated_at`
|
||||
* Useful in case formattedUpdatedAt doesn't parse correctly
|
||||
*/
|
||||
QString updatedAtString;
|
||||
|
||||
PubSubLowTrustUsersMessage() = default;
|
||||
explicit PubSubLowTrustUsersMessage(const QJsonObject &root);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubLowTrustUsersMessage::Type>(
|
||||
chatterino::PubSubLowTrustUsersMessage::Type value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case chatterino::PubSubLowTrustUsersMessage::Type::UserMessage:
|
||||
return "low_trust_user_new_message";
|
||||
|
||||
case chatterino::PubSubLowTrustUsersMessage::Type::TreatmentUpdate:
|
||||
return "low_trust_user_treatment_update";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubLowTrustUsersMessage::Treatment>(
|
||||
chatterino::PubSubLowTrustUsersMessage::Treatment value) noexcept
|
||||
{
|
||||
using Treatment = chatterino::PubSubLowTrustUsersMessage::Treatment;
|
||||
switch (value)
|
||||
{
|
||||
case Treatment::NoTreatment:
|
||||
return "NO_TREATMENT";
|
||||
|
||||
case Treatment::ActiveMonitoring:
|
||||
return "ACTIVE_MONITORING";
|
||||
|
||||
case Treatment::Restricted:
|
||||
return "RESTRICTED";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubLowTrustUsersMessage::EvasionEvaluation>(
|
||||
chatterino::PubSubLowTrustUsersMessage::EvasionEvaluation value) noexcept
|
||||
{
|
||||
using EvasionEvaluation =
|
||||
chatterino::PubSubLowTrustUsersMessage::EvasionEvaluation;
|
||||
switch (value)
|
||||
{
|
||||
case EvasionEvaluation::UnknownEvader:
|
||||
return "UNKNOWN_EVADER";
|
||||
|
||||
case EvasionEvaluation::UnlikelyEvader:
|
||||
return "UNLIKELY_EVADER";
|
||||
|
||||
case EvasionEvaluation::LikelyEvader:
|
||||
return "LIKELY_EVADER";
|
||||
|
||||
case EvasionEvaluation::PossibleEvader:
|
||||
return "POSSIBLE_EVADER";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubLowTrustUsersMessage::RestrictionType>(
|
||||
chatterino::PubSubLowTrustUsersMessage::RestrictionType value) noexcept
|
||||
{
|
||||
using RestrictionType =
|
||||
chatterino::PubSubLowTrustUsersMessage::RestrictionType;
|
||||
switch (value)
|
||||
{
|
||||
case RestrictionType::UnknownType:
|
||||
return "UNKNOWN_TYPE";
|
||||
|
||||
case RestrictionType::ManuallyAdded:
|
||||
return "MANUALLY_ADDED";
|
||||
|
||||
case RestrictionType::DetectedBanEvader:
|
||||
return "DETECTED_BAN_EVADER";
|
||||
|
||||
case RestrictionType::BannedInSharedChannel:
|
||||
return "BANNED_IN_SHARED_CHANNEL";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "mocks/BaseApplication.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubClient.hpp"
|
||||
#include "providers/twitch/PubSubManager.hpp"
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "Test.hpp"
|
||||
|
||||
@@ -20,11 +18,7 @@ using namespace std::chrono_literals;
|
||||
* Server randomly disconnects us, we should reconnect (COMPLETE)
|
||||
* Client listens to more than 50 topics, so it opens 2 connections (COMPLETE)
|
||||
* Server sends RECONNECT message to us, we should reconnect (INCOMPLETE, leaving for now since if we just ignore it and Twitch disconnects us we should already handle it properly)
|
||||
* Listen that required authentication, but authentication is missing (COMPLETE)
|
||||
* Listen that required authentication, but authentication is wrong (COMPLETE)
|
||||
* Incoming AutoMod message
|
||||
* Incoming ChannelPoints message
|
||||
* Incoming ChatModeratorAction message (COMPLETE)
|
||||
**/
|
||||
|
||||
#define RUN_PUBSUB_TESTS
|
||||
@@ -85,23 +79,18 @@ const QString TEST_SETTINGS = R"(
|
||||
class FTest : public PubSub
|
||||
{
|
||||
public:
|
||||
explicit FTest(const char *path, std::chrono::seconds pingInterval,
|
||||
QString token = "token")
|
||||
explicit FTest(const char *path, std::chrono::seconds pingInterval)
|
||||
: PubSub(QString("wss://127.0.0.1:9050%1").arg(path), pingInterval)
|
||||
{
|
||||
auto account = std::make_shared<TwitchAccount>("testaccount_420", token,
|
||||
"clientid", "123456");
|
||||
this->setAccount(account);
|
||||
}
|
||||
};
|
||||
|
||||
class MockApplication : public mock::BaseApplication
|
||||
{
|
||||
public:
|
||||
MockApplication(const char *path, std::chrono::seconds pingInterval,
|
||||
QString token = "token")
|
||||
MockApplication(const char *path, std::chrono::seconds pingInterval)
|
||||
: mock::BaseApplication(TEST_SETTINGS)
|
||||
, pubSub(path, pingInterval, token)
|
||||
, pubSub(path, pingInterval)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -127,7 +116,7 @@ TEST(TwitchPubSubClient, ServerRespondsToPings)
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
||||
|
||||
pubSub.listenToChannelModerationActions("123456");
|
||||
pubSub.listenToChannelPointRewards("123456");
|
||||
|
||||
std::this_thread::sleep_for(150ms);
|
||||
|
||||
@@ -159,7 +148,7 @@ TEST(TwitchPubSubClient, ServerDoesntRespondToPings)
|
||||
auto &pubSub = a.pubSub;
|
||||
|
||||
pubSub.start();
|
||||
pubSub.listenToChannelModerationActions("123456");
|
||||
pubSub.listenToChannelPointRewards("123456");
|
||||
|
||||
std::this_thread::sleep_for(750ms);
|
||||
|
||||
@@ -198,7 +187,7 @@ TEST(TwitchPubSubClient, DisconnectedAfter1s)
|
||||
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
||||
ASSERT_EQ(pubSub.diag.listenResponses, 0);
|
||||
|
||||
pubSub.listenToChannelModerationActions("123456");
|
||||
pubSub.listenToChannelPointRewards("123456");
|
||||
|
||||
std::this_thread::sleep_for(500ms);
|
||||
|
||||
@@ -233,7 +222,7 @@ TEST(TwitchPubSubClient, ExceedTopicLimit)
|
||||
|
||||
for (auto i = 0; i < PubSubClient::MAX_LISTENS; ++i)
|
||||
{
|
||||
pubSub.listenToChannelModerationActions(QString("1%1").arg(i));
|
||||
pubSub.listenToChannelPointRewards(QString("1%1").arg(i));
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
@@ -244,7 +233,7 @@ TEST(TwitchPubSubClient, ExceedTopicLimit)
|
||||
|
||||
for (auto i = 0; i < PubSubClient::MAX_LISTENS; ++i)
|
||||
{
|
||||
pubSub.listenToChannelModerationActions(QString("2%1").arg(i));
|
||||
pubSub.listenToChannelPointRewards(QString("2%1").arg(i));
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
@@ -274,7 +263,7 @@ TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep)
|
||||
|
||||
for (auto i = 0; i < PubSubClient::MAX_LISTENS * 2; ++i)
|
||||
{
|
||||
pubSub.listenToChannelModerationActions("123456");
|
||||
pubSub.listenToChannelPointRewards("123456");
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(150ms);
|
||||
@@ -290,167 +279,6 @@ TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep)
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
}
|
||||
|
||||
TEST(TwitchPubSubClient, ModeratorActionsUserBanned)
|
||||
{
|
||||
MockApplication a("/moderator-actions-user-banned", 1s);
|
||||
auto &pubSub = a.pubSub;
|
||||
|
||||
pubSub.start();
|
||||
|
||||
ReceivedMessage<BanAction> received;
|
||||
|
||||
std::ignore =
|
||||
pubSub.moderation.userBanned.connect([&received](const auto &action) {
|
||||
received = action;
|
||||
});
|
||||
|
||||
ASSERT_EQ(pubSub.diag.listenResponses, 0);
|
||||
|
||||
pubSub.listenToChannelModerationActions("123456");
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
ASSERT_EQ(pubSub.diag.messagesReceived, 3);
|
||||
ASSERT_EQ(pubSub.diag.listenResponses, 1);
|
||||
|
||||
ASSERT_TRUE(received);
|
||||
|
||||
ActionUser expectedTarget{"140114344", "1xelerate", "", QColor()};
|
||||
ActionUser expectedSource{"117691339", "mm2pl", "", QColor()};
|
||||
|
||||
ASSERT_EQ(received->reason, QString());
|
||||
ASSERT_EQ(received->duration, 0);
|
||||
ASSERT_EQ(received->target, expectedTarget);
|
||||
ASSERT_EQ(received->source, expectedSource);
|
||||
|
||||
pubSub.stop();
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
}
|
||||
|
||||
TEST(TwitchPubSubClient, MissingToken)
|
||||
{
|
||||
// The token that's required is "xD"
|
||||
MockApplication a("/authentication-required", 1s, "");
|
||||
auto &pubSub = a.pubSub;
|
||||
|
||||
pubSub.start();
|
||||
|
||||
pubSub.listenToChannelModerationActions("123456");
|
||||
|
||||
std::this_thread::sleep_for(150ms);
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
ASSERT_EQ(pubSub.diag.messagesReceived, 2);
|
||||
ASSERT_EQ(pubSub.diag.listenResponses, 0);
|
||||
ASSERT_EQ(pubSub.diag.failedListenResponses, 1);
|
||||
|
||||
pubSub.stop();
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
}
|
||||
|
||||
TEST(TwitchPubSubClient, WrongToken)
|
||||
{
|
||||
// The token that's required is "xD"
|
||||
MockApplication a("/authentication-required", 1s);
|
||||
auto &pubSub = a.pubSub;
|
||||
|
||||
pubSub.start();
|
||||
|
||||
pubSub.listenToChannelModerationActions("123456");
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
ASSERT_EQ(pubSub.diag.messagesReceived, 2);
|
||||
ASSERT_EQ(pubSub.diag.listenResponses, 0);
|
||||
ASSERT_EQ(pubSub.diag.failedListenResponses, 1);
|
||||
|
||||
pubSub.stop();
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
}
|
||||
|
||||
TEST(TwitchPubSubClient, CorrectToken)
|
||||
{
|
||||
// The token that's required is "xD"
|
||||
MockApplication a("/authentication-required", 1s, "xD");
|
||||
auto &pubSub = a.pubSub;
|
||||
|
||||
pubSub.start();
|
||||
|
||||
pubSub.listenToChannelModerationActions("123456");
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
ASSERT_EQ(pubSub.diag.messagesReceived, 2);
|
||||
ASSERT_EQ(pubSub.diag.listenResponses, 1);
|
||||
ASSERT_EQ(pubSub.diag.failedListenResponses, 0);
|
||||
|
||||
pubSub.stop();
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
}
|
||||
|
||||
TEST(TwitchPubSubClient, AutoModMessageHeld)
|
||||
{
|
||||
MockApplication a("/automod-held", 1s);
|
||||
auto &pubSub = a.pubSub;
|
||||
|
||||
pubSub.start();
|
||||
|
||||
ReceivedMessage<PubSubAutoModQueueMessage> received;
|
||||
ReceivedMessage<QString> channelID;
|
||||
|
||||
std::ignore = pubSub.moderation.autoModMessageCaught.connect(
|
||||
[&](const auto &msg, const QString &incomingChannelID) {
|
||||
received = msg;
|
||||
channelID = incomingChannelID;
|
||||
});
|
||||
|
||||
pubSub.listenToAutomod("117166826");
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
ASSERT_EQ(pubSub.diag.messagesReceived, 3);
|
||||
ASSERT_EQ(pubSub.diag.listenResponses, 1);
|
||||
ASSERT_EQ(pubSub.diag.failedListenResponses, 0);
|
||||
|
||||
ASSERT_TRUE(received);
|
||||
ASSERT_TRUE(channelID);
|
||||
|
||||
ASSERT_EQ(channelID, "117166826");
|
||||
ASSERT_EQ(received->messageText, "kurwa");
|
||||
|
||||
pubSub.stop();
|
||||
|
||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 1);
|
||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user