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: Conan will no longer generate a `CMakeUserPresets.json` file. (#6117)
|
||||||
- Dev: Pass `--force-openssl` when installing from CMake in Qt 6.8+. (#6129)
|
- 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: Fixed `<build-tool> clean` not working correctly with generated sources. (#6154)
|
||||||
|
- Dev: Removed authenticated PubSub implementation. (#6158)
|
||||||
- Dev: Save settings in `aboutToQuit`. (#6159)
|
- Dev: Save settings in `aboutToQuit`. (#6159)
|
||||||
|
|
||||||
## 2.5.3
|
## 2.5.3
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
#include "providers/seventv/SeventvBadges.hpp"
|
#include "providers/seventv/SeventvBadges.hpp"
|
||||||
#include "providers/seventv/SeventvEventAPI.hpp"
|
#include "providers/seventv/SeventvEventAPI.hpp"
|
||||||
#include "providers/twitch/ChannelPointReward.hpp"
|
#include "providers/twitch/ChannelPointReward.hpp"
|
||||||
#include "providers/twitch/PubSubActions.hpp"
|
|
||||||
#include "providers/twitch/PubSubManager.hpp"
|
#include "providers/twitch/PubSubManager.hpp"
|
||||||
#include "providers/twitch/PubSubMessages.hpp"
|
#include "providers/twitch/PubSubMessages.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
|||||||
@@ -404,8 +404,6 @@ set(SOURCE_FILES
|
|||||||
providers/twitch/ChannelPointReward.hpp
|
providers/twitch/ChannelPointReward.hpp
|
||||||
providers/twitch/IrcMessageHandler.cpp
|
providers/twitch/IrcMessageHandler.cpp
|
||||||
providers/twitch/IrcMessageHandler.hpp
|
providers/twitch/IrcMessageHandler.hpp
|
||||||
providers/twitch/PubSubActions.cpp
|
|
||||||
providers/twitch/PubSubActions.hpp
|
|
||||||
providers/twitch/PubSubClient.cpp
|
providers/twitch/PubSubClient.cpp
|
||||||
providers/twitch/PubSubClient.hpp
|
providers/twitch/PubSubClient.hpp
|
||||||
providers/twitch/PubSubClientOptions.hpp
|
providers/twitch/PubSubClientOptions.hpp
|
||||||
@@ -452,18 +450,12 @@ set(SOURCE_FILES
|
|||||||
providers/twitch/eventsub/SubscriptionRequest.cpp
|
providers/twitch/eventsub/SubscriptionRequest.cpp
|
||||||
providers/twitch/eventsub/SubscriptionRequest.hpp
|
providers/twitch/eventsub/SubscriptionRequest.hpp
|
||||||
|
|
||||||
providers/twitch/pubsubmessages/AutoMod.cpp
|
|
||||||
providers/twitch/pubsubmessages/AutoMod.hpp
|
|
||||||
providers/twitch/pubsubmessages/Base.cpp
|
providers/twitch/pubsubmessages/Base.cpp
|
||||||
providers/twitch/pubsubmessages/Base.hpp
|
providers/twitch/pubsubmessages/Base.hpp
|
||||||
providers/twitch/pubsubmessages/ChannelPoints.cpp
|
providers/twitch/pubsubmessages/ChannelPoints.cpp
|
||||||
providers/twitch/pubsubmessages/ChannelPoints.hpp
|
providers/twitch/pubsubmessages/ChannelPoints.hpp
|
||||||
providers/twitch/pubsubmessages/ChatModeratorAction.cpp
|
|
||||||
providers/twitch/pubsubmessages/ChatModeratorAction.hpp
|
|
||||||
providers/twitch/pubsubmessages/Listen.cpp
|
providers/twitch/pubsubmessages/Listen.cpp
|
||||||
providers/twitch/pubsubmessages/Listen.hpp
|
providers/twitch/pubsubmessages/Listen.hpp
|
||||||
providers/twitch/pubsubmessages/LowTrustUsers.cpp
|
|
||||||
providers/twitch/pubsubmessages/LowTrustUsers.hpp
|
|
||||||
providers/twitch/pubsubmessages/Message.hpp
|
providers/twitch/pubsubmessages/Message.hpp
|
||||||
providers/twitch/pubsubmessages/Unlisten.cpp
|
providers/twitch/pubsubmessages/Unlisten.cpp
|
||||||
providers/twitch/pubsubmessages/Unlisten.hpp
|
providers/twitch/pubsubmessages/Unlisten.hpp
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/twitch/PubSubActions.hpp"
|
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
@@ -149,27 +148,7 @@ QString debugTest(const CommandContext &ctx)
|
|||||||
|
|
||||||
const auto command = ctx.words.value(1);
|
const auto command = ctx.words.value(1);
|
||||||
|
|
||||||
if (command == "timeout-pubsub")
|
if (command == "timeout-irc")
|
||||||
{
|
|
||||||
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")
|
|
||||||
{
|
{
|
||||||
auto nowMillis = QDateTime::currentDateTime().toSecsSinceEpoch();
|
auto nowMillis = QDateTime::currentDateTime().toSecsSinceEpoch();
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,6 @@
|
|||||||
#include "providers/seventv/SeventvEmotes.hpp"
|
#include "providers/seventv/SeventvEmotes.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "providers/twitch/ChannelPointReward.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/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchBadge.hpp"
|
#include "providers/twitch/TwitchBadge.hpp"
|
||||||
#include "providers/twitch/TwitchBadges.hpp"
|
#include "providers/twitch/TwitchBadges.hpp"
|
||||||
@@ -376,15 +374,6 @@ bool doesWordContainATwitchEmote(
|
|||||||
return true;
|
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,
|
EmotePtr makeSharedChatBadge(const QString &sourceName,
|
||||||
const QString &sourceProfileURL,
|
const QString &sourceProfileURL,
|
||||||
const QString &sourceLogin)
|
const QString &sourceLogin)
|
||||||
@@ -773,239 +762,6 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
|
|||||||
this->message().serverReceivedTime = time;
|
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*/,
|
MessageBuilder::MessageBuilder(LiveUpdatesAddEmoteMessageTag /*unused*/,
|
||||||
const QString &platform, const QString &actor,
|
const QString &platform, const QString &actor,
|
||||||
const std::vector<QString> &emoteNames)
|
const std::vector<QString> &emoteNames)
|
||||||
@@ -1531,50 +1287,6 @@ MessagePtr MessageBuilder::makeDeletionMessageFromIRC(
|
|||||||
return builder.release();
|
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,
|
MessagePtr MessageBuilder::makeListOfUsersMessage(QString prefix,
|
||||||
QStringList users,
|
QStringList users,
|
||||||
Channel *channel,
|
Channel *channel,
|
||||||
@@ -1729,368 +1441,6 @@ MessagePtr MessageBuilder::buildHypeChatMessage(
|
|||||||
return builder.release();
|
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(
|
MessagePtrMut MessageBuilder::makeMissingScopesMessage(
|
||||||
const QString &missingScopes)
|
const QString &missingScopes)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,29 +4,20 @@
|
|||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "messages/MessageColor.hpp"
|
#include "messages/MessageColor.hpp"
|
||||||
#include "messages/MessageFlag.hpp"
|
#include "messages/MessageFlag.hpp"
|
||||||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp"
|
|
||||||
|
|
||||||
#include <IrcMessage>
|
#include <IrcMessage>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTime>
|
#include <QTime>
|
||||||
|
#include <QUrl>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
struct BanAction;
|
|
||||||
struct UnbanAction;
|
|
||||||
struct WarnAction;
|
|
||||||
struct RaidAction;
|
|
||||||
struct UnraidAction;
|
|
||||||
struct AutomodAction;
|
|
||||||
struct AutomodUserAction;
|
|
||||||
struct AutomodInfoAction;
|
|
||||||
struct Message;
|
struct Message;
|
||||||
using MessagePtr = std::shared_ptr<const Message>;
|
using MessagePtr = std::shared_ptr<const Message>;
|
||||||
using MessagePtrMut = std::shared_ptr<Message>;
|
using MessagePtrMut = std::shared_ptr<Message>;
|
||||||
@@ -43,7 +34,6 @@ class IgnorePhrase;
|
|||||||
struct HelixVip;
|
struct HelixVip;
|
||||||
using HelixModerator = HelixVip;
|
using HelixModerator = HelixVip;
|
||||||
struct ChannelPointReward;
|
struct ChannelPointReward;
|
||||||
struct DeleteAction;
|
|
||||||
struct TwitchEmoteOccurrence;
|
struct TwitchEmoteOccurrence;
|
||||||
|
|
||||||
namespace linkparser {
|
namespace linkparser {
|
||||||
@@ -113,13 +103,6 @@ public:
|
|||||||
MessageBuilder(TimeoutMessageTag, const QString &username,
|
MessageBuilder(TimeoutMessageTag, const QString &username,
|
||||||
const QString &durationInSeconds, bool multipleTimes,
|
const QString &durationInSeconds, bool multipleTimes,
|
||||||
const QDateTime &time);
|
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,
|
MessageBuilder(LiveUpdatesAddEmoteMessageTag, const QString &platform,
|
||||||
const QString &actor,
|
const QString &actor,
|
||||||
@@ -201,7 +184,6 @@ public:
|
|||||||
bool hostOn);
|
bool hostOn);
|
||||||
static MessagePtr makeDeletionMessageFromIRC(
|
static MessagePtr makeDeletionMessageFromIRC(
|
||||||
const MessagePtr &originalMessage);
|
const MessagePtr &originalMessage);
|
||||||
static MessagePtr makeDeletionMessageFromPubSub(const DeleteAction &action);
|
|
||||||
static MessagePtr makeListOfUsersMessage(QString prefix, QStringList users,
|
static MessagePtr makeListOfUsersMessage(QString prefix, QStringList users,
|
||||||
Channel *channel,
|
Channel *channel,
|
||||||
MessageFlags extraFlags = {});
|
MessageFlags extraFlags = {});
|
||||||
@@ -211,16 +193,6 @@ public:
|
|||||||
|
|
||||||
static MessagePtr buildHypeChatMessage(Communi::IrcPrivateMessage *message);
|
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`.
|
/// @brief Builds a message out of an `ircMessage`.
|
||||||
///
|
///
|
||||||
/// Building a message won't cause highlights to be triggered. They will
|
/// 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 "providers/twitch/PubSubClient.hpp"
|
||||||
|
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "providers/twitch/PubSubActions.hpp"
|
|
||||||
#include "providers/twitch/PubSubHelpers.hpp"
|
#include "providers/twitch/PubSubHelpers.hpp"
|
||||||
#include "providers/twitch/PubSubMessages.hpp"
|
#include "providers/twitch/PubSubMessages.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
#include "util/Helpers.hpp"
|
|
||||||
#include "util/RapidjsonHelpers.hpp"
|
|
||||||
|
|
||||||
#include <exception>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,11 @@
|
|||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
|
||||||
#include "providers/NetworkConfigurationProvider.hpp"
|
#include "providers/NetworkConfigurationProvider.hpp"
|
||||||
#include "providers/twitch/PubSubActions.hpp"
|
|
||||||
#include "providers/twitch/PubSubClient.hpp"
|
#include "providers/twitch/PubSubClient.hpp"
|
||||||
#include "providers/twitch/PubSubHelpers.hpp"
|
#include "providers/twitch/PubSubHelpers.hpp"
|
||||||
#include "providers/twitch/PubSubMessages.hpp"
|
#include "providers/twitch/PubSubMessages.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
|
||||||
#include "singletons/Settings.hpp"
|
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
#include "util/Helpers.hpp"
|
|
||||||
#include "util/RapidjsonHelpers.hpp"
|
|
||||||
#include "util/RenameThread.hpp"
|
#include "util/RenameThread.hpp"
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
@@ -20,7 +14,6 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -37,449 +30,6 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
|
|||||||
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.set_access_channels(websocketpp::log::alevel::all);
|
||||||
this->websocketClient.clear_access_channels(
|
this->websocketClient.clear_access_channels(
|
||||||
websocketpp::log::alevel::frame_payload |
|
websocketpp::log::alevel::frame_payload |
|
||||||
@@ -509,24 +59,6 @@ PubSub::~PubSub()
|
|||||||
void PubSub::initialize()
|
void PubSub::initialize()
|
||||||
{
|
{
|
||||||
this->start();
|
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()
|
void PubSub::addClient()
|
||||||
@@ -617,108 +149,6 @@ void PubSub::stop()
|
|||||||
this->thread->detach();
|
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)
|
void PubSub::listenToChannelPointRewards(const QString &channelID)
|
||||||
{
|
{
|
||||||
static const QString topicFormat("community-points-channel-v1.%1");
|
static const QString topicFormat("community-points-channel-v1.%1");
|
||||||
@@ -918,7 +348,6 @@ void PubSub::onConnectionOpen(WebsocketHandle hdl)
|
|||||||
this->requests.begin() + topicsToTake);
|
this->requests.begin() + topicsToTake);
|
||||||
|
|
||||||
PubSubListenMessage msg(newTopics);
|
PubSubListenMessage msg(newTopics);
|
||||||
msg.setToken(this->token_);
|
|
||||||
|
|
||||||
if (auto success = client->listen(msg); !success)
|
if (auto success = client->listen(msg); !success)
|
||||||
{
|
{
|
||||||
@@ -1100,70 +529,7 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
|
|||||||
{
|
{
|
||||||
QString topic = message.topic;
|
QString topic = message.topic;
|
||||||
|
|
||||||
if (topic.startsWith("chat_moderator_actions."))
|
if (topic.startsWith("community-points-channel-v1."))
|
||||||
{
|
|
||||||
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."))
|
|
||||||
{
|
{
|
||||||
auto oInnerMessage =
|
auto oInnerMessage =
|
||||||
message.toInner<PubSubCommunityPointsChannelV1Message>();
|
message.toInner<PubSubCommunityPointsChannelV1Message>();
|
||||||
@@ -1193,55 +559,6 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
|
|||||||
break;
|
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
|
else
|
||||||
{
|
{
|
||||||
qCDebug(chatterinoPubSub) << "Unknown topic:" << topic;
|
qCDebug(chatterinoPubSub) << "Unknown topic:" << topic;
|
||||||
@@ -1258,13 +575,7 @@ void PubSub::runThread()
|
|||||||
|
|
||||||
void PubSub::listenToTopic(const QString &topic)
|
void PubSub::listenToTopic(const QString &topic)
|
||||||
{
|
{
|
||||||
PubSubListenMessage msg({topic});
|
this->listen(PubSubListenMessage({topic}));
|
||||||
if (!topic.startsWith("community-points-channel-v1."))
|
|
||||||
{
|
|
||||||
msg.setToken(this->token_);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->listen(std::move(msg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -35,22 +34,6 @@ namespace chatterino {
|
|||||||
class TwitchAccount;
|
class TwitchAccount;
|
||||||
class PubSubClient;
|
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 PubSubListenMessage;
|
||||||
struct PubSubMessage;
|
struct PubSubMessage;
|
||||||
struct PubSubMessageMessage;
|
struct PubSubMessageMessage;
|
||||||
@@ -83,11 +66,6 @@ class PubSub
|
|||||||
WebsocketClient websocketClient;
|
WebsocketClient websocketClient;
|
||||||
std::unique_ptr<std::thread> thread;
|
std::unique_ptr<std::thread> thread;
|
||||||
|
|
||||||
// Account credentials
|
|
||||||
// Set from setAccount
|
|
||||||
QString token_;
|
|
||||||
QString userID_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PubSub(const QString &host,
|
PubSub(const QString &host,
|
||||||
std::chrono::seconds pingInterval = std::chrono::seconds(15));
|
std::chrono::seconds pingInterval = std::chrono::seconds(15));
|
||||||
@@ -101,74 +79,10 @@ public:
|
|||||||
/// Set up connections between itself & other parts of the application
|
/// Set up connections between itself & other parts of the application
|
||||||
void initialize();
|
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 {
|
struct {
|
||||||
Signal<const QJsonObject &> redeemed;
|
Signal<const QJsonObject &> redeemed;
|
||||||
} pointReward;
|
} 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.
|
* Listen to incoming channel point redemptions in the given channel.
|
||||||
* This topic is relevant for everyone.
|
* This topic is relevant for everyone.
|
||||||
@@ -190,8 +104,6 @@ public:
|
|||||||
} diag;
|
} diag;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setAccount(std::shared_ptr<TwitchAccount> account);
|
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
@@ -218,14 +130,6 @@ private:
|
|||||||
std::owner_less<WebsocketHandle>>
|
std::owner_less<WebsocketHandle>>
|
||||||
clients;
|
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 onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr msg);
|
||||||
void onConnectionOpen(websocketpp::connection_hdl hdl);
|
void onConnectionOpen(websocketpp::connection_hdl hdl);
|
||||||
void onConnectionFail(websocketpp::connection_hdl hdl);
|
void onConnectionFail(websocketpp::connection_hdl hdl);
|
||||||
@@ -266,12 +170,6 @@ private:
|
|||||||
FRIEND_TEST(TwitchPubSubClient, DisconnectedAfter1s);
|
FRIEND_TEST(TwitchPubSubClient, DisconnectedAfter1s);
|
||||||
FRIEND_TEST(TwitchPubSubClient, ExceedTopicLimit);
|
FRIEND_TEST(TwitchPubSubClient, ExceedTopicLimit);
|
||||||
FRIEND_TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep);
|
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
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
#pragma once
|
#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/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/Listen.hpp" // IWYU pragma: export
|
|
||||||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp" // IWYU pragma: export
|
|
||||||
#include "providers/twitch/pubsubmessages/Message.hpp" // IWYU pragma: export
|
#include "providers/twitch/pubsubmessages/Message.hpp" // IWYU pragma: export
|
||||||
#include "providers/twitch/pubsubmessages/Unlisten.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();
|
auto currentAccount = getApp()->getAccounts()->twitch.getCurrent();
|
||||||
|
|
||||||
getApp()->getTwitchPubSub()->listenToChannelModerationActions(roomId);
|
|
||||||
if (this->hasModRights())
|
if (this->hasModRights())
|
||||||
{
|
{
|
||||||
getApp()->getTwitchPubSub()->listenToAutomod(roomId);
|
|
||||||
getApp()->getTwitchPubSub()->listenToLowTrustUsers(roomId);
|
|
||||||
|
|
||||||
this->eventSubChannelModerateHandle =
|
this->eventSubChannelModerateHandle =
|
||||||
getApp()->getEventSub()->subscribe(eventsub::SubscriptionRequest{
|
getApp()->getEventSub()->subscribe(eventsub::SubscriptionRequest{
|
||||||
.subscriptionType = "channel.moderate",
|
.subscriptionType = "channel.moderate",
|
||||||
|
|||||||
@@ -17,13 +17,10 @@
|
|||||||
#include "providers/seventv/SeventvEventAPI.hpp"
|
#include "providers/seventv/SeventvEventAPI.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||||
#include "providers/twitch/PubSubActions.hpp"
|
|
||||||
#include "providers/twitch/PubSubManager.hpp"
|
#include "providers/twitch/PubSubManager.hpp"
|
||||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/StreamerMode.hpp"
|
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
#include "util/RatelimitBucket.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(
|
this->connections_.managedConnect(
|
||||||
getApp()->getTwitchPubSub()->pointReward.redeemed, [this](auto &data) {
|
getApp()->getTwitchPubSub()->pointReward.redeemed, [this](auto &data) {
|
||||||
QString channelId = data.value("channel_id").toString();
|
QString channelId = data.value("channel_id").toString();
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "providers/twitch/eventsub/Controller.hpp"
|
#include "providers/twitch/eventsub/Controller.hpp"
|
||||||
#include "providers/twitch/eventsub/MessageBuilder.hpp"
|
#include "providers/twitch/eventsub/MessageBuilder.hpp"
|
||||||
#include "providers/twitch/eventsub/MessageHandlers.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/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/Literals.hpp"
|
#include "common/Literals.hpp"
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
|
#include "messages/Image.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "messages/MessageElement.hpp"
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/StreamerMode.hpp"
|
#include "singletons/StreamerMode.hpp"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/twitch/eventsub/MessageBuilder.hpp"
|
#include "providers/twitch/eventsub/MessageBuilder.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "singletons/Settings.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 "mocks/BaseApplication.hpp"
|
||||||
#include "providers/twitch/PubSubActions.hpp"
|
|
||||||
#include "providers/twitch/PubSubClient.hpp"
|
#include "providers/twitch/PubSubClient.hpp"
|
||||||
#include "providers/twitch/PubSubManager.hpp"
|
#include "providers/twitch/PubSubManager.hpp"
|
||||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "Test.hpp"
|
#include "Test.hpp"
|
||||||
|
|
||||||
@@ -20,11 +18,7 @@ using namespace std::chrono_literals;
|
|||||||
* Server randomly disconnects us, we should reconnect (COMPLETE)
|
* Server randomly disconnects us, we should reconnect (COMPLETE)
|
||||||
* Client listens to more than 50 topics, so it opens 2 connections (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)
|
* 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 ChannelPoints message
|
||||||
* Incoming ChatModeratorAction message (COMPLETE)
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
#define RUN_PUBSUB_TESTS
|
#define RUN_PUBSUB_TESTS
|
||||||
@@ -85,23 +79,18 @@ const QString TEST_SETTINGS = R"(
|
|||||||
class FTest : public PubSub
|
class FTest : public PubSub
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit FTest(const char *path, std::chrono::seconds pingInterval,
|
explicit FTest(const char *path, std::chrono::seconds pingInterval)
|
||||||
QString token = "token")
|
|
||||||
: PubSub(QString("wss://127.0.0.1:9050%1").arg(path), 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
|
class MockApplication : public mock::BaseApplication
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MockApplication(const char *path, std::chrono::seconds pingInterval,
|
MockApplication(const char *path, std::chrono::seconds pingInterval)
|
||||||
QString token = "token")
|
|
||||||
: mock::BaseApplication(TEST_SETTINGS)
|
: 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.connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
||||||
|
|
||||||
pubSub.listenToChannelModerationActions("123456");
|
pubSub.listenToChannelPointRewards("123456");
|
||||||
|
|
||||||
std::this_thread::sleep_for(150ms);
|
std::this_thread::sleep_for(150ms);
|
||||||
|
|
||||||
@@ -159,7 +148,7 @@ TEST(TwitchPubSubClient, ServerDoesntRespondToPings)
|
|||||||
auto &pubSub = a.pubSub;
|
auto &pubSub = a.pubSub;
|
||||||
|
|
||||||
pubSub.start();
|
pubSub.start();
|
||||||
pubSub.listenToChannelModerationActions("123456");
|
pubSub.listenToChannelPointRewards("123456");
|
||||||
|
|
||||||
std::this_thread::sleep_for(750ms);
|
std::this_thread::sleep_for(750ms);
|
||||||
|
|
||||||
@@ -198,7 +187,7 @@ TEST(TwitchPubSubClient, DisconnectedAfter1s)
|
|||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
||||||
ASSERT_EQ(pubSub.diag.listenResponses, 0);
|
ASSERT_EQ(pubSub.diag.listenResponses, 0);
|
||||||
|
|
||||||
pubSub.listenToChannelModerationActions("123456");
|
pubSub.listenToChannelPointRewards("123456");
|
||||||
|
|
||||||
std::this_thread::sleep_for(500ms);
|
std::this_thread::sleep_for(500ms);
|
||||||
|
|
||||||
@@ -233,7 +222,7 @@ TEST(TwitchPubSubClient, ExceedTopicLimit)
|
|||||||
|
|
||||||
for (auto i = 0; i < PubSubClient::MAX_LISTENS; ++i)
|
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);
|
std::this_thread::sleep_for(50ms);
|
||||||
@@ -244,7 +233,7 @@ TEST(TwitchPubSubClient, ExceedTopicLimit)
|
|||||||
|
|
||||||
for (auto i = 0; i < PubSubClient::MAX_LISTENS; ++i)
|
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);
|
std::this_thread::sleep_for(50ms);
|
||||||
@@ -274,7 +263,7 @@ TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep)
|
|||||||
|
|
||||||
for (auto i = 0; i < PubSubClient::MAX_LISTENS * 2; ++i)
|
for (auto i = 0; i < PubSubClient::MAX_LISTENS * 2; ++i)
|
||||||
{
|
{
|
||||||
pubSub.listenToChannelModerationActions("123456");
|
pubSub.listenToChannelPointRewards("123456");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(150ms);
|
std::this_thread::sleep_for(150ms);
|
||||||
@@ -290,167 +279,6 @@ TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep)
|
|||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
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
|
} // namespace chatterino
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user