mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
feat: show restricted chats and suspicious treatment updates (#5056)
Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
69a54d944d
commit
036a5f3f21
|
@ -4,6 +4,7 @@
|
|||
|
||||
- Major: Allow use of Twitch follower emotes in other channels if subscribed. (#4922)
|
||||
- Major: Add `/automod` split to track automod caught messages across all open channels the user moderates. (#4986, #5026)
|
||||
- Major: Show restricted chat messages and suspicious treatment updates. (#5056)
|
||||
- Minor: Migrate to the new Get Channel Followers Helix endpoint, fixing follower count not showing up in usercards. (#4809)
|
||||
- Minor: The account switcher is now styled to match your theme. (#4817)
|
||||
- Minor: Add an invisible resize handle to the bottom of frameless user info popups and reply thread popups. (#4795)
|
||||
|
@ -59,6 +60,7 @@
|
|||
- Bugfix: Fixes to section deletion in text input fields. (#5013)
|
||||
- Bugfix: Show user text input within watch streak notices. (#5029)
|
||||
- Bugfix: Fixed avatar in usercard and moderation button triggering when releasing the mouse outside their area. (#5052)
|
||||
- Bugfix: Fixed moderator-only topics being subscribed to for non-moderators. (#5056)
|
||||
- Dev: Run miniaudio in a separate thread, and simplify it to not manage the device ourselves. There's a chance the simplification is a bad idea. (#4978)
|
||||
- Dev: Change clang-format from v14 to v16. (#4929)
|
||||
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubManager.hpp"
|
||||
#include "providers/twitch/PubSubMessages.hpp"
|
||||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
|
@ -473,6 +474,87 @@ void Application::initPubSub()
|
|||
});
|
||||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.suspiciousMessageReceived
|
||||
.connect([&](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 (getSettings()->streamerModeHideModActions &&
|
||||
isInStreamerMode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan =
|
||||
this->twitch->getChannelOrEmptyByID(action.channelID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
const auto p =
|
||||
TwitchMessageBuilder::makeLowTrustUserMessage(
|
||||
action, chan->getName());
|
||||
chan->addMessage(p.first);
|
||||
chan->addMessage(p.second);
|
||||
});
|
||||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.suspiciousTreatmentUpdated
|
||||
.connect([&](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 (getSettings()->streamerModeHideModActions &&
|
||||
isInStreamerMode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan =
|
||||
this->twitch->getChannelOrEmptyByID(action.channelID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
auto msg =
|
||||
TwitchMessageBuilder::makeLowTrustUpdateMessage(action);
|
||||
chan->addMessage(msg);
|
||||
});
|
||||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.autoModMessageCaught.connect(
|
||||
[&](const auto &msg, const QString &channelID) {
|
||||
|
@ -672,6 +754,7 @@ void Application::initPubSub()
|
|||
[this] {
|
||||
this->twitch->pubsub->unlistenAllModerationActions();
|
||||
this->twitch->pubsub->unlistenAutomod();
|
||||
this->twitch->pubsub->unlistenLowTrustUsers();
|
||||
this->twitch->pubsub->unlistenWhispers();
|
||||
},
|
||||
boost::signals2::at_front);
|
||||
|
|
|
@ -412,6 +412,8 @@ set(SOURCE_FILES
|
|||
providers/twitch/pubsubmessages/ChatModeratorAction.hpp
|
||||
providers/twitch/pubsubmessages/Listen.cpp
|
||||
providers/twitch/pubsubmessages/Listen.hpp
|
||||
providers/twitch/pubsubmessages/LowTrustUsers.cpp
|
||||
providers/twitch/pubsubmessages/LowTrustUsers.hpp
|
||||
providers/twitch/pubsubmessages/Message.hpp
|
||||
providers/twitch/pubsubmessages/Unlisten.cpp
|
||||
providers/twitch/pubsubmessages/Unlisten.hpp
|
||||
|
|
|
@ -315,7 +315,6 @@ bool Channel::isBroadcaster() const
|
|||
|
||||
bool Channel::hasModRights() const
|
||||
{
|
||||
// fourtf: check if staff
|
||||
return this->isMod() || this->isBroadcaster();
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ enum class MessageFlag : int64_t {
|
|||
LiveUpdatesUpdate = (1LL << 30),
|
||||
/// The message caught by AutoMod containing the user who sent the message & its contents
|
||||
AutoModOffendingMessage = (1LL << 31),
|
||||
LowTrustUsers = (1LL << 32),
|
||||
};
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
|
|
|
@ -367,7 +367,8 @@ void MessageLayout::updateBuffer(QPixmap *buffer,
|
|||
blendColors(backgroundColor,
|
||||
*ctx.colorProvider.color(ColorType::RedeemedHighlight));
|
||||
}
|
||||
else if (this->message_->flags.has(MessageFlag::AutoMod))
|
||||
else if (this->message_->flags.has(MessageFlag::AutoMod) ||
|
||||
this->message_->flags.has(MessageFlag::LowTrustUsers))
|
||||
{
|
||||
backgroundColor = QColor("#404040");
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "providers/twitch/PubSubHelpers.hpp"
|
||||
#include "providers/twitch/PubSubMessages.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "pubsubmessages/LowTrustUsers.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
@ -585,6 +586,25 @@ void PubSub::unlistenAutomod()
|
|||
}
|
||||
}
|
||||
|
||||
void PubSub::unlistenLowTrustUsers()
|
||||
{
|
||||
for (const auto &p : this->clients)
|
||||
{
|
||||
const auto &client = p.second;
|
||||
if (const auto &[topics, nonce] =
|
||||
client->unlistenPrefix("low-trust-users.");
|
||||
!topics.empty())
|
||||
{
|
||||
this->registerNonce(nonce, {
|
||||
client,
|
||||
"UNLISTEN",
|
||||
topics,
|
||||
topics.size(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PubSub::unlistenWhispers()
|
||||
{
|
||||
for (const auto &p : this->clients)
|
||||
|
@ -670,6 +690,30 @@ void PubSub::listenToAutomod(const QString &channelID)
|
|||
this->listenToTopic(topic);
|
||||
}
|
||||
|
||||
void PubSub::listenToLowTrustUsers(const QString &channelID)
|
||||
{
|
||||
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::listenToChannelPointRewards(const QString &channelID)
|
||||
{
|
||||
static const QString topicFormat("community-points-channel-v1.%1");
|
||||
|
@ -1169,6 +1213,38 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
|
|||
this->signals_.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->signals_.moderation.suspiciousMessageReceived.invoke(
|
||||
innerMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubLowTrustUsersMessage::Type::TreatmentUpdate: {
|
||||
this->signals_.moderation.suspiciousTreatmentUpdated.invoke(
|
||||
innerMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubLowTrustUsersMessage::Type::INVALID: {
|
||||
qCWarning(chatterinoPubSub)
|
||||
<< "Invalid low trust users event type:"
|
||||
<< innerMessage.typeString;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qCDebug(chatterinoPubSub) << "Unknown topic:" << topic;
|
||||
|
|
|
@ -34,6 +34,7 @@ struct PubSubAutoModQueueMessage;
|
|||
struct AutomodAction;
|
||||
struct AutomodUserAction;
|
||||
struct AutomodInfoAction;
|
||||
struct PubSubLowTrustUsersMessage;
|
||||
struct PubSubWhisperMessage;
|
||||
|
||||
struct PubSubListenMessage;
|
||||
|
@ -67,9 +68,6 @@ class PubSub
|
|||
QString userID_;
|
||||
|
||||
public:
|
||||
// The max amount of connections we may open
|
||||
static constexpr int maxConnections = 10;
|
||||
|
||||
PubSub(const QString &host,
|
||||
std::chrono::seconds pingInterval = std::chrono::seconds(15));
|
||||
|
||||
|
@ -100,6 +98,9 @@ public:
|
|||
Signal<BanAction> userBanned;
|
||||
Signal<UnbanAction> userUnbanned;
|
||||
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousMessageReceived;
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousTreatmentUpdated;
|
||||
|
||||
// Message caught by automod
|
||||
// channelID
|
||||
pajlada::Signals::Signal<PubSubAutoModQueueMessage, QString>
|
||||
|
@ -126,12 +127,56 @@ public:
|
|||
|
||||
void unlistenAllModerationActions();
|
||||
void unlistenAutomod();
|
||||
void unlistenLowTrustUsers();
|
||||
void unlistenWhispers();
|
||||
|
||||
/**
|
||||
* Listen to incoming whispers for the currently logged in user.
|
||||
* This topic is relevant for everyone.
|
||||
*
|
||||
* PubSub topic: whispers.{currentUserID}
|
||||
*/
|
||||
bool listenToWhispers();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Listen to incoming channel point redemptions in the given channel.
|
||||
* This topic is relevant for everyone.
|
||||
*
|
||||
* PubSub topic: community-points-channel-v1.{channelID}
|
||||
*/
|
||||
void listenToChannelPointRewards(const QString &channelID);
|
||||
|
||||
std::vector<QString> requests;
|
||||
|
|
|
@ -1253,7 +1253,11 @@ void TwitchChannel::refreshPubSub()
|
|||
getApp()->twitch->pubsub->setAccount(currentAccount);
|
||||
|
||||
getApp()->twitch->pubsub->listenToChannelModerationActions(roomId);
|
||||
if (this->hasModRights())
|
||||
{
|
||||
getApp()->twitch->pubsub->listenToAutomod(roomId);
|
||||
getApp()->twitch->pubsub->listenToLowTrustUsers(roomId);
|
||||
}
|
||||
getApp()->twitch->pubsub->listenToChannelPointRewards(roomId);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,13 +25,11 @@
|
|||
|
||||
#include <cassert>
|
||||
|
||||
// using namespace Communi;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
#define TWITCH_PUBSUB_URL "wss://pubsub-edge.twitch.tv"
|
||||
|
||||
namespace {
|
||||
|
||||
const QString TWITCH_PUBSUB_URL = "wss://pubsub-edge.twitch.tv";
|
||||
const QString BTTV_LIVE_UPDATES_URL = "wss://sockets.betterttv.net/ws";
|
||||
const QString SEVENTV_EVENTAPI_URL = "wss://events.7tv.io/v3";
|
||||
|
||||
|
@ -45,11 +43,10 @@ TwitchIrcServer::TwitchIrcServer()
|
|||
, liveChannel(new Channel("/live", Channel::Type::TwitchLive))
|
||||
, automodChannel(new Channel("/automod", Channel::Type::TwitchAutomod))
|
||||
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
||||
, pubsub(new PubSub(TWITCH_PUBSUB_URL))
|
||||
{
|
||||
this->initializeIrc();
|
||||
|
||||
this->pubsub = new PubSub(TWITCH_PUBSUB_URL);
|
||||
|
||||
if (getSettings()->enableBTTVLiveUpdates &&
|
||||
getSettings()->enableBTTVChannelEmotes)
|
||||
{
|
||||
|
|
|
@ -80,6 +80,7 @@ public:
|
|||
const ChannelPtr automodChannel;
|
||||
IndirectChannel watchingChannel;
|
||||
|
||||
// NOTE: We currently leak this
|
||||
PubSub *pubsub;
|
||||
std::unique_ptr<BttvLiveUpdates> bttvLiveUpdates;
|
||||
std::unique_ptr<SeventvEventAPI> seventvEventAPI;
|
||||
|
|
|
@ -1933,6 +1933,188 @@ std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeAutomodMessage(
|
|||
return std::make_pair(message1, message2);
|
||||
}
|
||||
|
||||
MessagePtr TwitchMessageBuilder::makeLowTrustUpdateMessage(
|
||||
const PubSubLowTrustUsersMessage &action)
|
||||
{
|
||||
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});
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
qCDebug(chatterinoTwitch) << "Unexpected suspicious treatment: "
|
||||
<< action.treatmentString;
|
||||
break;
|
||||
}
|
||||
|
||||
return builder.release();
|
||||
}
|
||||
|
||||
std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeLowTrustUserMessage(
|
||||
const PubSubLowTrustUsersMessage &action, const QString &channelName)
|
||||
{
|
||||
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";
|
||||
}
|
||||
else
|
||||
{
|
||||
headerMessage = "Monitored";
|
||||
}
|
||||
|
||||
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 username
|
||||
builder2
|
||||
.emplace<TextElement>(action.suspiciousUserDisplayName + ":",
|
||||
MessageElementFlag::BoldUsername,
|
||||
MessageColor(action.suspiciousUserColor),
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, action.suspiciousUserLogin});
|
||||
builder2
|
||||
.emplace<TextElement>(action.suspiciousUserDisplayName + ":",
|
||||
MessageElementFlag::NonBoldUsername,
|
||||
MessageColor(action.suspiciousUserColor))
|
||||
->setLink({Link::UserInfo, action.suspiciousUserLogin});
|
||||
|
||||
// sender's message caught by AutoMod
|
||||
builder2.emplace<TextElement>(action.text, MessageElementFlag::Text,
|
||||
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);
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::setThread(std::shared_ptr<MessageThread> thread)
|
||||
{
|
||||
this->thread_ = std::move(thread);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "messages/SharedMessageBuilder.hpp"
|
||||
#include "pubsubmessages/LowTrustUsers.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
#include <QString>
|
||||
|
@ -93,6 +94,11 @@ public:
|
|||
const AutomodAction &action, const QString &channelName);
|
||||
static MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action);
|
||||
|
||||
static std::pair<MessagePtr, MessagePtr> makeLowTrustUserMessage(
|
||||
const PubSubLowTrustUsersMessage &action, const QString &channelName);
|
||||
static MessagePtr makeLowTrustUpdateMessage(
|
||||
const PubSubLowTrustUsersMessage &action);
|
||||
|
||||
// Shares some common logic from SharedMessageBuilder::parseBadgeTag
|
||||
static std::unordered_map<QString, QString> parseBadgeInfoTag(
|
||||
const QVariantMap &tags);
|
||||
|
|
104
src/providers/twitch/pubsubmessages/LowTrustUsers.cpp
Normal file
104
src/providers/twitch/pubsubmessages/LowTrustUsers.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
|
||||
: typeString(root.value("type").toString())
|
||||
{
|
||||
if (const auto oType =
|
||||
magic_enum::enum_cast<Type>(this->typeString.toStdString());
|
||||
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();
|
||||
this->text =
|
||||
data.value("message_content").toObject().value("text").toString();
|
||||
|
||||
// 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());
|
||||
|
||||
std::vector<LowTrustUserChatBadge> badges;
|
||||
for (const auto &badge : sender.value("badges").toArray())
|
||||
{
|
||||
badges.emplace_back(badge.toObject());
|
||||
}
|
||||
this->senderBadges = badges;
|
||||
|
||||
const auto sharedValue = data.value("shared_ban_channel_ids");
|
||||
std::vector<QString> sharedIDs;
|
||||
if (!sharedValue.isNull())
|
||||
{
|
||||
for (const auto &id : sharedValue.toArray())
|
||||
{
|
||||
sharedIDs.emplace_back(id.toString());
|
||||
}
|
||||
}
|
||||
this->sharedBanChannelIDs = sharedIDs;
|
||||
}
|
||||
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 = magic_enum::enum_cast<Treatment>(
|
||||
this->treatmentString.toStdString());
|
||||
oTreatment.has_value())
|
||||
{
|
||||
this->treatment = oTreatment.value();
|
||||
}
|
||||
|
||||
this->evasionEvaluationString =
|
||||
data.value("ban_evasion_evaluation").toString();
|
||||
if (const auto oEvaluation = magic_enum::enum_cast<EvasionEvaluation>(
|
||||
this->evasionEvaluationString.toStdString());
|
||||
oEvaluation.has_value())
|
||||
{
|
||||
this->evasionEvaluation = oEvaluation.value();
|
||||
}
|
||||
|
||||
FlagsEnum<RestrictionType> restrictions;
|
||||
for (const auto &rType : data.value("types").toArray())
|
||||
{
|
||||
if (const auto oRestriction = magic_enum::enum_cast<RestrictionType>(
|
||||
rType.toString().toStdString());
|
||||
oRestriction.has_value())
|
||||
{
|
||||
restrictions.set(oRestriction.value());
|
||||
}
|
||||
}
|
||||
this->restrictionTypes = restrictions;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
255
src/providers/twitch/pubsubmessages/LowTrustUsers.hpp
Normal file
255
src/providers/twitch/pubsubmessages/LowTrustUsers.hpp
Normal file
|
@ -0,0 +1,255 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/FlagsEnum.hpp>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <QColor>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct LowTrustUserChatBadge {
|
||||
QString id;
|
||||
QString version;
|
||||
|
||||
explicit LowTrustUserChatBadge(const QJsonObject &obj)
|
||||
: id(obj.value("id").toString())
|
||||
, version(obj.value("version").toString())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct PubSubLowTrustUsersMessage {
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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<LowTrustUserChatBadge> 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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue