refactor: trim down PubSub implementation (#6158)
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PubSubAction::PubSubAction(const QJsonObject &data, const QString &_roomID)
|
||||
: timestamp(std::chrono::steady_clock::now())
|
||||
, roomID(_roomID)
|
||||
{
|
||||
this->source.id = data.value("created_by_user_id").toString();
|
||||
this->source.login = data.value("created_by").toString();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,196 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
||||
|
||||
#include <QColor>
|
||||
#include <QDebug>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct ActionUser {
|
||||
QString id;
|
||||
QString login;
|
||||
// displayName should be in format "login(localizedName)" for non-ascii usernames
|
||||
QString displayName;
|
||||
QColor color;
|
||||
|
||||
inline bool operator==(const ActionUser &rhs) const
|
||||
{
|
||||
return this->id == rhs.id && this->login == rhs.login &&
|
||||
this->displayName == rhs.displayName && this->color == rhs.color;
|
||||
}
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug dbg, const ActionUser &user)
|
||||
{
|
||||
dbg.nospace() << "ActionUser(" << user.id << ", " << user.login << ", "
|
||||
<< user.displayName << ", " << user.color << ")";
|
||||
|
||||
return dbg.maybeSpace();
|
||||
}
|
||||
|
||||
struct PubSubAction {
|
||||
PubSubAction() = default;
|
||||
PubSubAction(const QJsonObject &data, const QString &_roomID);
|
||||
ActionUser source;
|
||||
|
||||
std::chrono::steady_clock::time_point timestamp;
|
||||
QString roomID;
|
||||
};
|
||||
|
||||
// Used when a chat mode (i.e. slowmode, subscribers only mode) is enabled or
|
||||
// disabled
|
||||
struct ModeChangedAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
enum Mode {
|
||||
Unknown,
|
||||
Slow,
|
||||
R9K,
|
||||
SubscribersOnly,
|
||||
EmoteOnly,
|
||||
} mode;
|
||||
|
||||
// Whether the mode was turned on or off
|
||||
enum State {
|
||||
Off,
|
||||
On,
|
||||
} state;
|
||||
|
||||
uint32_t duration = 0;
|
||||
|
||||
const char *getModeName() const
|
||||
{
|
||||
switch (this->mode)
|
||||
{
|
||||
case Mode::Slow:
|
||||
return "slow";
|
||||
case Mode::R9K:
|
||||
return "r9k";
|
||||
case Mode::SubscribersOnly:
|
||||
return "subscribers-only";
|
||||
case Mode::EmoteOnly:
|
||||
return "emote-only";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct BanAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
QString reason;
|
||||
|
||||
uint32_t duration = 0;
|
||||
|
||||
bool isBan() const
|
||||
{
|
||||
return this->duration == 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct DeleteAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
QString messageId;
|
||||
QString messageText;
|
||||
};
|
||||
|
||||
struct UnbanAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
enum {
|
||||
Banned,
|
||||
TimedOut,
|
||||
} previousState;
|
||||
|
||||
bool wasBan() const
|
||||
{
|
||||
return this->previousState == Banned;
|
||||
}
|
||||
};
|
||||
|
||||
struct ClearChatAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
};
|
||||
|
||||
struct ModerationStateAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
// true = modded
|
||||
// false = unmodded
|
||||
bool modded;
|
||||
};
|
||||
|
||||
struct AutomodAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
QString message;
|
||||
|
||||
QString reason;
|
||||
PubSubAutoModQueueMessage::Reason reasonCode;
|
||||
|
||||
QString msgID;
|
||||
};
|
||||
|
||||
struct AutomodUserAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
enum {
|
||||
AddPermitted,
|
||||
RemovePermitted,
|
||||
AddBlocked,
|
||||
RemoveBlocked,
|
||||
Properties,
|
||||
} type;
|
||||
|
||||
QString message;
|
||||
};
|
||||
|
||||
struct AutomodInfoAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
enum {
|
||||
OnHold,
|
||||
Denied,
|
||||
Approved,
|
||||
} type;
|
||||
};
|
||||
|
||||
struct RaidAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
QString target;
|
||||
};
|
||||
|
||||
struct UnraidAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
};
|
||||
|
||||
struct WarnAction : PubSubAction {
|
||||
using PubSubAction::PubSubAction;
|
||||
|
||||
ActionUser target;
|
||||
|
||||
QStringList reasons;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,16 +1,9 @@
|
||||
#include "providers/twitch/PubSubClient.hpp"
|
||||
|
||||
#include "common/QLogging.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubHelpers.hpp"
|
||||
#include "providers/twitch/PubSubMessages.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
#include <exception>
|
||||
#include <thread>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "providers/NetworkConfigurationProvider.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubClient.hpp"
|
||||
#include "providers/twitch/PubSubHelpers.hpp"
|
||||
#include "providers/twitch/PubSubMessages.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
#include "util/RenameThread.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
@@ -20,7 +14,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
@@ -37,449 +30,6 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
|
||||
pingInterval,
|
||||
})
|
||||
{
|
||||
this->moderationActionHandlers["clear"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ClearChatAction action(data, roomID);
|
||||
|
||||
this->moderation.chatCleared.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["slowoff"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::Slow;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["slow"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::Slow;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.empty())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "Missing duration argument in slowmode on";
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
|
||||
action.duration = args.at(0).toString().toUInt(&ok, 10);
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["r9kbetaoff"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::R9K;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["r9kbeta"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::R9K;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["subscribersoff"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["subscribers"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["emoteonlyoff"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::EmoteOnly;
|
||||
action.state = ModeChangedAction::State::Off;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["emoteonly"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModeChangedAction action(data, roomID);
|
||||
|
||||
action.mode = ModeChangedAction::Mode::EmoteOnly;
|
||||
action.state = ModeChangedAction::State::On;
|
||||
|
||||
this->moderation.modeChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["unmod"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModerationStateAction action(data, roomID);
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
|
||||
action.modded = false;
|
||||
|
||||
this->moderation.moderationStateChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["mod"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
ModerationStateAction action(data, roomID);
|
||||
action.modded = true;
|
||||
|
||||
auto innerType = data.value("type").toString();
|
||||
if (innerType == "chat_login_moderation")
|
||||
{
|
||||
// Don't display the old message type
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
action.target.login = data.value("target_user_login").toString();
|
||||
|
||||
this->moderation.moderationStateChanged.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["timeout"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
BanAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
bool ok;
|
||||
action.duration = args[1].toString().toUInt(&ok, 10);
|
||||
action.reason = args[2].toString(); // May be omitted
|
||||
|
||||
this->moderation.userBanned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["delete"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
DeleteAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.size() < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
action.messageText = args[1].toString();
|
||||
action.messageId = args[2].toString();
|
||||
|
||||
this->moderation.messageDeleted.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["ban"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
BanAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
action.reason = args[1].toString(); // May be omitted
|
||||
|
||||
this->moderation.userBanned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["unban"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
UnbanAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
action.previousState = UnbanAction::Banned;
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
|
||||
this->moderation.userUnbanned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["untimeout"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
UnbanAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
|
||||
action.previousState = UnbanAction::TimedOut;
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target.login = args[0].toString();
|
||||
|
||||
this->moderation.userUnbanned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["warn"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
WarnAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login =
|
||||
data.value("created_by").toString(); // currently always empty
|
||||
|
||||
action.target.id = data.value("target_user_id").toString();
|
||||
action.target.login = data.value("target_user_login").toString();
|
||||
|
||||
const auto reasons = data.value("args").toArray();
|
||||
bool firstArg = true;
|
||||
for (const auto &reasonValue : reasons)
|
||||
{
|
||||
if (firstArg)
|
||||
{
|
||||
// Skip first arg in the reasons array since it's not a reason
|
||||
firstArg = false;
|
||||
continue;
|
||||
}
|
||||
const auto &reason = reasonValue.toString();
|
||||
if (!reason.isEmpty())
|
||||
{
|
||||
action.reasons.append(reason);
|
||||
}
|
||||
}
|
||||
|
||||
this->moderation.userWarned.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["raid"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
RaidAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.target = args[0].toString();
|
||||
|
||||
this->moderation.raidStarted.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["unraid"] = [this](const auto &data,
|
||||
const auto &roomID) {
|
||||
UnraidAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
this->moderation.raidCanceled.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["automod_message_rejected"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
AutomodInfoAction action(data, roomID);
|
||||
action.type = AutomodInfoAction::OnHold;
|
||||
this->moderation.automodInfoMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["automod_message_denied"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
AutomodInfoAction action(data, roomID);
|
||||
action.type = AutomodInfoAction::Denied;
|
||||
this->moderation.automodInfoMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["automod_message_approved"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
AutomodInfoAction action(data, roomID);
|
||||
action.type = AutomodInfoAction::Approved;
|
||||
this->moderation.automodInfoMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->channelTermsActionHandlers["add_permitted_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got a pass through automod
|
||||
AutomodUserAction action(data, roomID);
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.type = AutomodUserAction::AddPermitted;
|
||||
action.message = data.value("text").toString();
|
||||
action.source.login = data.value("requester_login").toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->channelTermsActionHandlers["add_blocked_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// A term has been added
|
||||
AutomodUserAction action(data, roomID);
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.type = AutomodUserAction::AddBlocked;
|
||||
action.message = data.value("text").toString();
|
||||
action.source.login = data.value("requester_login").toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["delete_permitted_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got deleted
|
||||
AutomodUserAction action(data, roomID);
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
action.type = AutomodUserAction::RemovePermitted;
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.message = args[0].toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->channelTermsActionHandlers["delete_permitted_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got deleted
|
||||
AutomodUserAction action(data, roomID);
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.type = AutomodUserAction::RemovePermitted;
|
||||
action.message = data.value("text").toString();
|
||||
action.source.login = data.value("requester_login").toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["delete_blocked_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got deleted
|
||||
AutomodUserAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
const auto args = data.value("args").toArray();
|
||||
action.type = AutomodUserAction::RemoveBlocked;
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.message = args[0].toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
this->channelTermsActionHandlers["delete_blocked_term"] =
|
||||
[this](const auto &data, const auto &roomID) {
|
||||
// This term got deleted
|
||||
AutomodUserAction action(data, roomID);
|
||||
|
||||
action.source.id = data.value("created_by_user_id").toString();
|
||||
action.source.login = data.value("created_by").toString();
|
||||
|
||||
action.type = AutomodUserAction::RemoveBlocked;
|
||||
action.message = data.value("text").toString();
|
||||
action.source.login = data.value("requester_login").toString();
|
||||
|
||||
this->moderation.automodUserMessage.invoke(action);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["denied_automod_message"] =
|
||||
[](const auto &data, const auto &roomID) {
|
||||
// This message got denied by a moderator
|
||||
// qCDebug(chatterinoPubSub) << rj::stringify(data);
|
||||
};
|
||||
|
||||
this->moderationActionHandlers["approved_automod_message"] =
|
||||
[](const auto &data, const auto &roomID) {
|
||||
// This message got approved by a moderator
|
||||
// qCDebug(chatterinoPubSub) << rj::stringify(data);
|
||||
};
|
||||
|
||||
this->websocketClient.set_access_channels(websocketpp::log::alevel::all);
|
||||
this->websocketClient.clear_access_channels(
|
||||
websocketpp::log::alevel::frame_payload |
|
||||
@@ -509,24 +59,6 @@ PubSub::~PubSub()
|
||||
void PubSub::initialize()
|
||||
{
|
||||
this->start();
|
||||
this->setAccount(getApp()->getAccounts()->twitch.getCurrent());
|
||||
|
||||
getApp()->getAccounts()->twitch.currentUserChanged.connect(
|
||||
[this] {
|
||||
this->unlistenChannelModerationActions();
|
||||
this->unlistenAutomod();
|
||||
this->unlistenLowTrustUsers();
|
||||
this->unlistenChannelPointRewards();
|
||||
|
||||
this->setAccount(getApp()->getAccounts()->twitch.getCurrent());
|
||||
},
|
||||
boost::signals2::at_front);
|
||||
}
|
||||
|
||||
void PubSub::setAccount(std::shared_ptr<TwitchAccount> account)
|
||||
{
|
||||
this->token_ = account->getOAuthToken();
|
||||
this->userID_ = account->getUserId();
|
||||
}
|
||||
|
||||
void PubSub::addClient()
|
||||
@@ -617,108 +149,6 @@ void PubSub::stop()
|
||||
this->thread->detach();
|
||||
}
|
||||
|
||||
void PubSub::listenToChannelModerationActions(const QString &channelID)
|
||||
{
|
||||
if (getSettings()->enableExperimentalEventSub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->userID_.isEmpty())
|
||||
{
|
||||
qCDebug(chatterinoPubSub) << "Unable to listen to moderation actions "
|
||||
"topic, no user logged in";
|
||||
return;
|
||||
}
|
||||
|
||||
static const QString topicFormat("chat_moderator_actions.%1.%2");
|
||||
assert(!channelID.isEmpty());
|
||||
|
||||
auto topic = topicFormat.arg(this->userID_, channelID);
|
||||
|
||||
if (this->isListeningToTopic(topic))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(chatterinoPubSub) << "Listen to topic" << topic;
|
||||
|
||||
this->listenToTopic(topic);
|
||||
}
|
||||
|
||||
void PubSub::unlistenChannelModerationActions()
|
||||
{
|
||||
this->unlistenPrefix("chat_moderator_actions.");
|
||||
}
|
||||
|
||||
void PubSub::listenToAutomod(const QString &channelID)
|
||||
{
|
||||
if (getSettings()->enableExperimentalEventSub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->userID_.isEmpty())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "Unable to listen to automod topic, no user logged in";
|
||||
return;
|
||||
}
|
||||
|
||||
static const QString topicFormat("automod-queue.%1.%2");
|
||||
assert(!channelID.isEmpty());
|
||||
|
||||
auto topic = topicFormat.arg(this->userID_, channelID);
|
||||
|
||||
if (this->isListeningToTopic(topic))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(chatterinoPubSub) << "Listen to topic" << topic;
|
||||
|
||||
this->listenToTopic(topic);
|
||||
}
|
||||
|
||||
void PubSub::unlistenAutomod()
|
||||
{
|
||||
this->unlistenPrefix("automod-queue.");
|
||||
}
|
||||
|
||||
void PubSub::listenToLowTrustUsers(const QString &channelID)
|
||||
{
|
||||
if (getSettings()->enableExperimentalEventSub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->userID_.isEmpty())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "Unable to listen to low trust users topic, no user logged in";
|
||||
return;
|
||||
}
|
||||
|
||||
static const QString topicFormat("low-trust-users.%1.%2");
|
||||
assert(!channelID.isEmpty());
|
||||
|
||||
auto topic = topicFormat.arg(this->userID_, channelID);
|
||||
|
||||
if (this->isListeningToTopic(topic))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(chatterinoPubSub) << "Listen to topic" << topic;
|
||||
|
||||
this->listenToTopic(topic);
|
||||
}
|
||||
|
||||
void PubSub::unlistenLowTrustUsers()
|
||||
{
|
||||
this->unlistenPrefix("low-trust-users.");
|
||||
}
|
||||
|
||||
void PubSub::listenToChannelPointRewards(const QString &channelID)
|
||||
{
|
||||
static const QString topicFormat("community-points-channel-v1.%1");
|
||||
@@ -918,7 +348,6 @@ void PubSub::onConnectionOpen(WebsocketHandle hdl)
|
||||
this->requests.begin() + topicsToTake);
|
||||
|
||||
PubSubListenMessage msg(newTopics);
|
||||
msg.setToken(this->token_);
|
||||
|
||||
if (auto success = client->listen(msg); !success)
|
||||
{
|
||||
@@ -1100,70 +529,7 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
|
||||
{
|
||||
QString topic = message.topic;
|
||||
|
||||
if (topic.startsWith("chat_moderator_actions."))
|
||||
{
|
||||
auto oInnerMessage =
|
||||
message.toInner<PubSubChatModeratorActionMessage>();
|
||||
if (!oInnerMessage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto innerMessage = *oInnerMessage;
|
||||
auto topicParts = topic.split(".");
|
||||
assert(topicParts.length() == 3);
|
||||
|
||||
// Channel ID where the moderator actions are coming from
|
||||
auto channelID = topicParts[2];
|
||||
|
||||
switch (innerMessage.type)
|
||||
{
|
||||
case PubSubChatModeratorActionMessage::Type::ModerationAction: {
|
||||
QString moderationAction =
|
||||
innerMessage.data.value("moderation_action").toString();
|
||||
|
||||
auto handlerIt =
|
||||
this->moderationActionHandlers.find(moderationAction);
|
||||
|
||||
if (handlerIt == this->moderationActionHandlers.end())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "No handler found for moderation action"
|
||||
<< moderationAction;
|
||||
return;
|
||||
}
|
||||
// Invoke handler function
|
||||
handlerIt->second(innerMessage.data, channelID);
|
||||
}
|
||||
break;
|
||||
case PubSubChatModeratorActionMessage::Type::ChannelTermsAction: {
|
||||
QString channelTermsAction =
|
||||
innerMessage.data.value("type").toString();
|
||||
|
||||
auto handlerIt =
|
||||
this->channelTermsActionHandlers.find(channelTermsAction);
|
||||
|
||||
if (handlerIt == this->channelTermsActionHandlers.end())
|
||||
{
|
||||
qCDebug(chatterinoPubSub)
|
||||
<< "No handler found for channel terms action"
|
||||
<< channelTermsAction;
|
||||
return;
|
||||
}
|
||||
// Invoke handler function
|
||||
handlerIt->second(innerMessage.data, channelID);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubChatModeratorActionMessage::Type::INVALID:
|
||||
default: {
|
||||
qCDebug(chatterinoPubSub) << "Invalid moderator action type:"
|
||||
<< innerMessage.typeString;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (topic.startsWith("community-points-channel-v1."))
|
||||
if (topic.startsWith("community-points-channel-v1."))
|
||||
{
|
||||
auto oInnerMessage =
|
||||
message.toInner<PubSubCommunityPointsChannelV1Message>();
|
||||
@@ -1193,55 +559,6 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (topic.startsWith("automod-queue."))
|
||||
{
|
||||
auto oInnerMessage = message.toInner<PubSubAutoModQueueMessage>();
|
||||
if (!oInnerMessage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto innerMessage = *oInnerMessage;
|
||||
|
||||
auto topicParts = topic.split(".");
|
||||
assert(topicParts.length() == 3);
|
||||
|
||||
// Channel ID where the moderator actions are coming from
|
||||
auto channelID = topicParts[2];
|
||||
|
||||
this->moderation.autoModMessageCaught.invoke(innerMessage, channelID);
|
||||
}
|
||||
else if (topic.startsWith("low-trust-users."))
|
||||
{
|
||||
auto oInnerMessage = message.toInner<PubSubLowTrustUsersMessage>();
|
||||
if (!oInnerMessage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto innerMessage = *oInnerMessage;
|
||||
|
||||
switch (innerMessage.type)
|
||||
{
|
||||
case PubSubLowTrustUsersMessage::Type::UserMessage: {
|
||||
this->moderation.suspiciousMessageReceived.invoke(innerMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubLowTrustUsersMessage::Type::TreatmentUpdate: {
|
||||
this->moderation.suspiciousTreatmentUpdated.invoke(
|
||||
innerMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubLowTrustUsersMessage::Type::INVALID: {
|
||||
qCWarning(chatterinoPubSub)
|
||||
<< "Invalid low trust users event type:"
|
||||
<< innerMessage.typeString;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qCDebug(chatterinoPubSub) << "Unknown topic:" << topic;
|
||||
@@ -1258,13 +575,7 @@ void PubSub::runThread()
|
||||
|
||||
void PubSub::listenToTopic(const QString &topic)
|
||||
{
|
||||
PubSubListenMessage msg({topic});
|
||||
if (!topic.startsWith("community-points-channel-v1."))
|
||||
{
|
||||
msg.setToken(this->token_);
|
||||
}
|
||||
|
||||
this->listen(std::move(msg));
|
||||
this->listen(PubSubListenMessage({topic}));
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -35,22 +34,6 @@ namespace chatterino {
|
||||
class TwitchAccount;
|
||||
class PubSubClient;
|
||||
|
||||
struct ClearChatAction;
|
||||
struct DeleteAction;
|
||||
struct ModeChangedAction;
|
||||
struct ModerationStateAction;
|
||||
struct BanAction;
|
||||
struct UnbanAction;
|
||||
struct PubSubAutoModQueueMessage;
|
||||
struct AutomodAction;
|
||||
struct AutomodUserAction;
|
||||
struct AutomodInfoAction;
|
||||
struct RaidAction;
|
||||
struct UnraidAction;
|
||||
struct WarnAction;
|
||||
struct PubSubLowTrustUsersMessage;
|
||||
struct PubSubWhisperMessage;
|
||||
|
||||
struct PubSubListenMessage;
|
||||
struct PubSubMessage;
|
||||
struct PubSubMessageMessage;
|
||||
@@ -83,11 +66,6 @@ class PubSub
|
||||
WebsocketClient websocketClient;
|
||||
std::unique_ptr<std::thread> thread;
|
||||
|
||||
// Account credentials
|
||||
// Set from setAccount
|
||||
QString token_;
|
||||
QString userID_;
|
||||
|
||||
public:
|
||||
PubSub(const QString &host,
|
||||
std::chrono::seconds pingInterval = std::chrono::seconds(15));
|
||||
@@ -101,74 +79,10 @@ public:
|
||||
/// Set up connections between itself & other parts of the application
|
||||
void initialize();
|
||||
|
||||
struct {
|
||||
Signal<ClearChatAction> chatCleared;
|
||||
Signal<DeleteAction> messageDeleted;
|
||||
Signal<ModeChangedAction> modeChanged;
|
||||
Signal<ModerationStateAction> moderationStateChanged;
|
||||
|
||||
Signal<RaidAction> raidStarted;
|
||||
Signal<UnraidAction> raidCanceled;
|
||||
|
||||
Signal<BanAction> userBanned;
|
||||
Signal<UnbanAction> userUnbanned;
|
||||
Signal<WarnAction> userWarned;
|
||||
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousMessageReceived;
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousTreatmentUpdated;
|
||||
|
||||
// Message caught by automod
|
||||
// channelID
|
||||
pajlada::Signals::Signal<PubSubAutoModQueueMessage, QString>
|
||||
autoModMessageCaught;
|
||||
|
||||
// Message blocked by moderator
|
||||
Signal<AutomodAction> autoModMessageBlocked;
|
||||
|
||||
Signal<AutomodUserAction> automodUserMessage;
|
||||
Signal<AutomodInfoAction> automodInfoMessage;
|
||||
} moderation;
|
||||
|
||||
struct {
|
||||
Signal<const QJsonObject &> redeemed;
|
||||
} pointReward;
|
||||
|
||||
/**
|
||||
* Listen to moderation actions in the given channel.
|
||||
* This topic is relevant for everyone.
|
||||
* For moderators, this topic includes blocked/permitted terms updates,
|
||||
* roomstate changes, general mod/vip updates, all bans/timeouts/deletions.
|
||||
* For normal users, this topic includes moderation actions that are targetted at the local user:
|
||||
* automod catching a user's sent message, a moderator approving or denying their caught messages,
|
||||
* the user gaining/losing mod/vip, the user receiving a ban/timeout/deletion.
|
||||
*
|
||||
* PubSub topic: chat_moderator_actions.{currentUserID}.{channelID}
|
||||
*/
|
||||
void listenToChannelModerationActions(const QString &channelID);
|
||||
void unlistenChannelModerationActions();
|
||||
|
||||
/**
|
||||
* Listen to Automod events in the given channel.
|
||||
* This topic is only relevant for moderators.
|
||||
* This will send events about incoming messages that
|
||||
* are caught by Automod.
|
||||
*
|
||||
* PubSub topic: automod-queue.{currentUserID}.{channelID}
|
||||
*/
|
||||
void listenToAutomod(const QString &channelID);
|
||||
void unlistenAutomod();
|
||||
|
||||
/**
|
||||
* Listen to Low Trust events in the given channel.
|
||||
* This topic is only relevant for moderators.
|
||||
* This will fire events about suspicious treatment updates
|
||||
* and messages sent by restricted/monitored users.
|
||||
*
|
||||
* PubSub topic: low-trust-users.{currentUserID}.{channelID}
|
||||
*/
|
||||
void listenToLowTrustUsers(const QString &channelID);
|
||||
void unlistenLowTrustUsers();
|
||||
|
||||
/**
|
||||
* Listen to incoming channel point redemptions in the given channel.
|
||||
* This topic is relevant for everyone.
|
||||
@@ -190,8 +104,6 @@ public:
|
||||
} diag;
|
||||
|
||||
private:
|
||||
void setAccount(std::shared_ptr<TwitchAccount> account);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
@@ -218,14 +130,6 @@ private:
|
||||
std::owner_less<WebsocketHandle>>
|
||||
clients;
|
||||
|
||||
std::unordered_map<
|
||||
QString, std::function<void(const QJsonObject &, const QString &)>>
|
||||
moderationActionHandlers;
|
||||
|
||||
std::unordered_map<
|
||||
QString, std::function<void(const QJsonObject &, const QString &)>>
|
||||
channelTermsActionHandlers;
|
||||
|
||||
void onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr msg);
|
||||
void onConnectionOpen(websocketpp::connection_hdl hdl);
|
||||
void onConnectionFail(websocketpp::connection_hdl hdl);
|
||||
@@ -266,12 +170,6 @@ private:
|
||||
FRIEND_TEST(TwitchPubSubClient, DisconnectedAfter1s);
|
||||
FRIEND_TEST(TwitchPubSubClient, ExceedTopicLimit);
|
||||
FRIEND_TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep);
|
||||
FRIEND_TEST(TwitchPubSubClient, ReceivedWhisper);
|
||||
FRIEND_TEST(TwitchPubSubClient, ModeratorActionsUserBanned);
|
||||
FRIEND_TEST(TwitchPubSubClient, MissingToken);
|
||||
FRIEND_TEST(TwitchPubSubClient, WrongToken);
|
||||
FRIEND_TEST(TwitchPubSubClient, CorrectToken);
|
||||
FRIEND_TEST(TwitchPubSubClient, AutoModMessageHeld);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Base.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Base.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/ChannelPoints.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/ChatModeratorAction.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Listen.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Listen.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Message.hpp" // IWYU pragma: export
|
||||
#include "providers/twitch/pubsubmessages/Unlisten.hpp" // IWYU pragma: export
|
||||
|
||||
@@ -1510,12 +1510,8 @@ void TwitchChannel::refreshPubSub()
|
||||
|
||||
auto currentAccount = getApp()->getAccounts()->twitch.getCurrent();
|
||||
|
||||
getApp()->getTwitchPubSub()->listenToChannelModerationActions(roomId);
|
||||
if (this->hasModRights())
|
||||
{
|
||||
getApp()->getTwitchPubSub()->listenToAutomod(roomId);
|
||||
getApp()->getTwitchPubSub()->listenToLowTrustUsers(roomId);
|
||||
|
||||
this->eventSubChannelModerateHandle =
|
||||
getApp()->getEventSub()->subscribe(eventsub::SubscriptionRequest{
|
||||
.subscriptionType = "channel.moderate",
|
||||
|
||||
@@ -17,13 +17,10 @@
|
||||
#include "providers/seventv/SeventvEventAPI.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubManager.hpp"
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/StreamerMode.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
#include "util/RatelimitBucket.hpp"
|
||||
@@ -240,518 +237,6 @@ void TwitchIrcServer::initialize()
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.chatCleared,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, actor{action.source.login}] {
|
||||
auto now = QDateTime::currentDateTime();
|
||||
chan->addOrReplaceClearChat(
|
||||
MessageBuilder::makeClearChatMessage(now, actor), now);
|
||||
if (getSettings()->hideModerated)
|
||||
{
|
||||
// XXX: This is expensive. We could use a layout request if the layout
|
||||
// would store the previous message flags.
|
||||
getApp()->getWindows()->forceLayoutChannelViews();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.modeChanged,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QString text =
|
||||
QString("%1 turned %2 %3 mode.")
|
||||
.arg(action.source.login)
|
||||
.arg(action.state == ModeChangedAction::State::On ? "on"
|
||||
: "off")
|
||||
.arg(action.getModeName());
|
||||
|
||||
if (action.duration > 0)
|
||||
{
|
||||
text += QString(" (%1 seconds)").arg(action.duration);
|
||||
}
|
||||
|
||||
postToThread([chan, text] {
|
||||
chan->addSystemMessage(text);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.moderationStateChanged,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QString text;
|
||||
|
||||
text = QString("%1 %2 %3.")
|
||||
.arg(action.source.login,
|
||||
(action.modded ? "modded" : "unmodded"),
|
||||
action.target.login);
|
||||
|
||||
postToThread([chan, text] {
|
||||
chan->addSystemMessage(text);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.userBanned,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
// TODO: Can we utilize some pubsub time field? maybe not worth
|
||||
auto time = QDateTime::currentDateTime();
|
||||
MessageBuilder msg(action, time);
|
||||
msg->flags.set(MessageFlag::PubSub);
|
||||
chan->addOrReplaceTimeout(msg.release(),
|
||||
QDateTime::currentDateTime());
|
||||
if (getSettings()->hideModerated)
|
||||
{
|
||||
// XXX: This is expensive. We could use a layout request if the layout
|
||||
// would store the previous message flags.
|
||||
getApp()->getWindows()->forceLayoutChannelViews();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.userWarned,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Resolve the moderator's user ID into a full user here, so message can look better
|
||||
postToThread([chan, action] {
|
||||
MessageBuilder msg(action);
|
||||
msg->flags.set(MessageFlag::PubSub);
|
||||
chan->addMessage(msg.release(), MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.messageDeleted,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty() || getSettings()->hideDeletionActions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = MessageBuilder::makeDeletionMessageFromPubSub(action);
|
||||
|
||||
postToThread([chan, msg] {
|
||||
auto replaced = false;
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot =
|
||||
chan->getMessageSnapshot();
|
||||
int snapshotLength = snapshot.size();
|
||||
|
||||
// without parens it doesn't build on windows
|
||||
int end = (std::max)(0, snapshotLength - 200);
|
||||
|
||||
for (int i = snapshotLength - 1; i >= end; --i)
|
||||
{
|
||||
const auto &s = snapshot[i];
|
||||
if (!s->flags.has(MessageFlag::PubSub) &&
|
||||
s->timeoutUser == msg->timeoutUser)
|
||||
{
|
||||
chan->replaceMessage(s, msg);
|
||||
replaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!replaced)
|
||||
{
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.userUnbanned,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Can we utilize some pubsub time field? maybe not worth
|
||||
auto time = QDateTime::currentDateTime();
|
||||
auto msg = MessageBuilder(action, time).release();
|
||||
|
||||
postToThread([chan, msg] {
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.suspiciousMessageReceived,
|
||||
[this](const auto &action) {
|
||||
if (action.treatment ==
|
||||
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
||||
{
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "Received suspicious message with unknown "
|
||||
"treatment:"
|
||||
<< action.treatmentString;
|
||||
return;
|
||||
}
|
||||
|
||||
// monitored chats are received over irc; in the future, we will use pubsub instead
|
||||
if (action.treatment !=
|
||||
PubSubLowTrustUsersMessage::Treatment::Restricted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (getApp()->getStreamerMode()->shouldHideModActions())
|
||||
{
|
||||
// NOTE: This completely stops the building of this action, rathern than only hiding it.
|
||||
// If the user disabled streamer mode or the setting, there will be messages missing
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan = this->getChannelOrEmptyByID(action.channelID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(chan);
|
||||
if (!twitchChannel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([twitchChannel, action] {
|
||||
const auto p = MessageBuilder::makeLowTrustUserMessage(
|
||||
action, twitchChannel->getName(), twitchChannel.get());
|
||||
twitchChannel->addMessage(p.first, MessageContext::Original);
|
||||
twitchChannel->addMessage(p.second, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.suspiciousTreatmentUpdated,
|
||||
[this](const auto &action) {
|
||||
if (action.treatment ==
|
||||
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
||||
{
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "Received suspicious user update with unknown "
|
||||
"treatment:"
|
||||
<< action.treatmentString;
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.updatedByUserLogin.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (getApp()->getStreamerMode()->shouldHideModActions())
|
||||
{
|
||||
// NOTE: This completely stops the building of this action, rathern than only hiding it.
|
||||
// If the user disabled streamer mode or the setting, there will be messages missing
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan = this->getChannelOrEmptyByID(action.channelID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
auto msg = MessageBuilder::makeLowTrustUpdateMessage(action);
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.autoModMessageCaught,
|
||||
[this](const auto &msg, const QString &channelID) {
|
||||
auto chan = this->getChannelOrEmptyByID(channelID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.type)
|
||||
{
|
||||
case PubSubAutoModQueueMessage::Type::AutoModCaughtMessage: {
|
||||
if (msg.status == "PENDING")
|
||||
{
|
||||
AutomodAction action(msg.data, channelID);
|
||||
if (msg.reason ==
|
||||
PubSubAutoModQueueMessage::Reason::BlockedTerm)
|
||||
{
|
||||
auto numBlockedTermsMatched =
|
||||
msg.blockedTermsFound.size();
|
||||
auto hideBlockedTerms =
|
||||
getSettings()
|
||||
->streamerModeHideBlockedTermText &&
|
||||
getApp()->getStreamerMode()->isEnabled();
|
||||
if (!msg.blockedTermsFound.empty())
|
||||
{
|
||||
if (hideBlockedTerms)
|
||||
{
|
||||
action.reason =
|
||||
u"matches %1 blocked term%2"_s
|
||||
.arg(numBlockedTermsMatched)
|
||||
.arg(numBlockedTermsMatched > 1
|
||||
? u"s"
|
||||
: u"");
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList blockedTerms(
|
||||
msg.blockedTermsFound.begin(),
|
||||
msg.blockedTermsFound.end());
|
||||
action.reason =
|
||||
u"matches %1 blocked term%2 \"%3\""_s
|
||||
.arg(numBlockedTermsMatched)
|
||||
.arg(numBlockedTermsMatched > 1
|
||||
? u"s"
|
||||
: u"")
|
||||
.arg(blockedTerms.join(u"\", \""));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
action.reason = "blocked term usage";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
action.reason = QString("%1 level %2")
|
||||
.arg(msg.contentCategory)
|
||||
.arg(msg.contentLevel);
|
||||
}
|
||||
|
||||
action.msgID = msg.messageID;
|
||||
action.message = msg.messageText;
|
||||
action.reasonCode = msg.reason;
|
||||
|
||||
// this message also contains per-word automod data, which could be implemented
|
||||
|
||||
// extract sender data manually because Twitch loves not being consistent
|
||||
QString senderDisplayName =
|
||||
msg.senderUserDisplayName; // Might be transformed later
|
||||
bool hasLocalizedName = false;
|
||||
if (!msg.senderUserDisplayName.isEmpty())
|
||||
{
|
||||
// check for non-ascii display names
|
||||
if (QString::compare(msg.senderUserDisplayName,
|
||||
msg.senderUserLogin,
|
||||
Qt::CaseInsensitive) != 0)
|
||||
{
|
||||
hasLocalizedName = true;
|
||||
}
|
||||
}
|
||||
QColor senderColor = msg.senderUserChatColor;
|
||||
QString senderColor_;
|
||||
if (!senderColor.isValid() &&
|
||||
getSettings()->colorizeNicknames)
|
||||
{
|
||||
// color may be not present if user is a grey-name
|
||||
senderColor = getRandomColor(msg.senderUserID);
|
||||
}
|
||||
|
||||
// handle username style based on prefered setting
|
||||
switch (getSettings()->usernameDisplayMode.getValue())
|
||||
{
|
||||
case UsernameDisplayMode::Username: {
|
||||
if (hasLocalizedName)
|
||||
{
|
||||
senderDisplayName = msg.senderUserLogin;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UsernameDisplayMode::LocalizedName: {
|
||||
break;
|
||||
}
|
||||
case UsernameDisplayMode::
|
||||
UsernameAndLocalizedName: {
|
||||
if (hasLocalizedName)
|
||||
{
|
||||
senderDisplayName = QString("%1(%2)").arg(
|
||||
msg.senderUserLogin,
|
||||
msg.senderUserDisplayName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
action.target =
|
||||
ActionUser{msg.senderUserID, msg.senderUserLogin,
|
||||
senderDisplayName, senderColor};
|
||||
postToThread([chan, action] {
|
||||
const auto p = MessageBuilder::makeAutomodMessage(
|
||||
action, chan->getName());
|
||||
chan->addMessage(p.first, MessageContext::Original);
|
||||
chan->addMessage(p.second,
|
||||
MessageContext::Original);
|
||||
|
||||
getApp()
|
||||
->getTwitch()
|
||||
->getAutomodChannel()
|
||||
->addMessage(p.first, MessageContext::Original);
|
||||
getApp()
|
||||
->getTwitch()
|
||||
->getAutomodChannel()
|
||||
->addMessage(p.second,
|
||||
MessageContext::Original);
|
||||
|
||||
if (getSettings()->showAutomodInMentions)
|
||||
{
|
||||
getApp()
|
||||
->getTwitch()
|
||||
->getMentionsChannel()
|
||||
->addMessage(p.first,
|
||||
MessageContext::Original);
|
||||
getApp()
|
||||
->getTwitch()
|
||||
->getMentionsChannel()
|
||||
->addMessage(p.second,
|
||||
MessageContext::Original);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gray out approve/deny button upon "ALLOWED" and "DENIED" statuses
|
||||
// They are versions of automod_message_(denied|approved) but for mods.
|
||||
chan->disableMessage("automod_" + msg.messageID);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PubSubAutoModQueueMessage::Type::INVALID:
|
||||
default: {
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.autoModMessageBlocked,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
const auto p =
|
||||
MessageBuilder::makeAutomodMessage(action, chan->getName());
|
||||
chan->addMessage(p.first, MessageContext::Original);
|
||||
chan->addMessage(p.second, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.automodUserMessage,
|
||||
[this](const auto &action) {
|
||||
if (getApp()->getStreamerMode()->shouldHideModActions())
|
||||
{
|
||||
// NOTE: This completely stops the building of this action, rathern than only hiding it.
|
||||
// If the user disabled streamer mode or the setting, there will be messages missing
|
||||
return;
|
||||
}
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = MessageBuilder(action).release();
|
||||
|
||||
postToThread([chan, msg] {
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.automodInfoMessage,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
const auto p = MessageBuilder::makeAutomodInfoMessage(action);
|
||||
chan->addMessage(p, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.raidStarted,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = MessageBuilder(action).release();
|
||||
|
||||
postToThread([chan, msg] {
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->moderation.raidCanceled,
|
||||
[this](const auto &action) {
|
||||
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = MessageBuilder(action).release();
|
||||
|
||||
postToThread([chan, msg] {
|
||||
chan->addMessage(msg, MessageContext::Original);
|
||||
});
|
||||
});
|
||||
|
||||
this->connections_.managedConnect(
|
||||
getApp()->getTwitchPubSub()->pointReward.redeemed, [this](auto &data) {
|
||||
QString channelId = data.value("channel_id").toString();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "providers/twitch/eventsub/Controller.hpp"
|
||||
#include "providers/twitch/eventsub/MessageBuilder.hpp"
|
||||
#include "providers/twitch/eventsub/MessageHandlers.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
#include "Application.hpp"
|
||||
#include "common/Literals.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/StreamerMode.hpp"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Application.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/twitch/eventsub/MessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
||||
|
||||
#include "util/QMagicEnum.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root)
|
||||
: typeString(root.value("type").toString())
|
||||
, data(root.value("data").toObject())
|
||||
, status(this->data.value("status").toString())
|
||||
{
|
||||
auto oType = qmagicenum::enumCast<Type>(this->typeString);
|
||||
if (oType.has_value())
|
||||
{
|
||||
this->type = oType.value();
|
||||
}
|
||||
|
||||
this->reason =
|
||||
qmagicenum::enumCast<Reason>(data.value("reason_code").toString())
|
||||
.value_or(Reason::INVALID);
|
||||
|
||||
auto contentClassification =
|
||||
data.value("content_classification").toObject();
|
||||
|
||||
this->contentCategory = contentClassification.value("category").toString();
|
||||
this->contentLevel = contentClassification.value("level").toInt();
|
||||
|
||||
auto message = data.value("message").toObject();
|
||||
|
||||
this->messageID = message.value("id").toString();
|
||||
|
||||
auto messageContent = message.value("content").toObject();
|
||||
|
||||
this->messageText = messageContent.value("text").toString();
|
||||
|
||||
auto messageSender = message.value("sender").toObject();
|
||||
|
||||
this->senderUserID = messageSender.value("user_id").toString();
|
||||
this->senderUserLogin = messageSender.value("login").toString();
|
||||
this->senderUserDisplayName =
|
||||
messageSender.value("display_name").toString();
|
||||
this->senderUserChatColor =
|
||||
QColor(messageSender.value("chat_color").toString());
|
||||
|
||||
if (this->reason == Reason::BlockedTerm)
|
||||
{
|
||||
// Attempt to read the blocked term(s) that caused this message to be blocked
|
||||
const auto caughtMessageReason =
|
||||
data.value("caught_message_reason").toObject();
|
||||
const auto blockedTermFailure =
|
||||
caughtMessageReason.value("blocked_term_failure").toObject();
|
||||
const auto termsFound =
|
||||
blockedTermFailure.value("terms_found").toArray();
|
||||
|
||||
for (const auto &termValue : termsFound)
|
||||
{
|
||||
const auto term = termValue.toObject();
|
||||
const auto termText = term.value("text").toString();
|
||||
if (!termText.isEmpty())
|
||||
{
|
||||
this->blockedTermsFound.insert(termText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,84 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <QColor>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct PubSubAutoModQueueMessage {
|
||||
enum class Type {
|
||||
AutoModCaughtMessage,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
enum class Reason {
|
||||
AutoMod,
|
||||
BlockedTerm,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
QString typeString;
|
||||
Type type = Type::INVALID;
|
||||
Reason reason = Reason::INVALID;
|
||||
|
||||
QJsonObject data;
|
||||
|
||||
QString status;
|
||||
|
||||
QString contentCategory;
|
||||
int contentLevel{};
|
||||
|
||||
QString messageID;
|
||||
QString messageText;
|
||||
|
||||
QString senderUserID;
|
||||
QString senderUserLogin;
|
||||
QString senderUserDisplayName;
|
||||
QColor senderUserChatColor;
|
||||
|
||||
std::set<QString> blockedTermsFound;
|
||||
|
||||
PubSubAutoModQueueMessage() = default;
|
||||
explicit PubSubAutoModQueueMessage(const QJsonObject &root);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubAutoModQueueMessage::Type>(
|
||||
chatterino::PubSubAutoModQueueMessage::Type value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case chatterino::PubSubAutoModQueueMessage::Type::AutoModCaughtMessage:
|
||||
return "automod_caught_message";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubAutoModQueueMessage::Reason>(
|
||||
chatterino::PubSubAutoModQueueMessage::Reason value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case chatterino::PubSubAutoModQueueMessage::Reason::AutoMod:
|
||||
return "AutoModCaughtMessageReason";
|
||||
case chatterino::PubSubAutoModQueueMessage::Reason::BlockedTerm:
|
||||
return "BlockedTermCaughtMessageReason";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#include "providers/twitch/pubsubmessages/ChatModeratorAction.hpp"
|
||||
|
||||
#include "util/QMagicEnum.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PubSubChatModeratorActionMessage::PubSubChatModeratorActionMessage(
|
||||
const QJsonObject &root)
|
||||
: typeString(root.value("type").toString())
|
||||
, data(root.value("data").toObject())
|
||||
{
|
||||
auto oType = qmagicenum::enumCast<Type>(this->typeString);
|
||||
if (oType.has_value())
|
||||
{
|
||||
this->type = oType.value();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct PubSubChatModeratorActionMessage {
|
||||
enum class Type {
|
||||
ModerationAction,
|
||||
ChannelTermsAction,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
QString typeString;
|
||||
Type type = Type::INVALID;
|
||||
|
||||
QJsonObject data;
|
||||
|
||||
PubSubChatModeratorActionMessage(const QJsonObject &root);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubChatModeratorActionMessage::Type>(
|
||||
chatterino::PubSubChatModeratorActionMessage::Type value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case chatterino::PubSubChatModeratorActionMessage::Type::
|
||||
ModerationAction:
|
||||
return "moderation_action";
|
||||
|
||||
case chatterino::PubSubChatModeratorActionMessage::Type::
|
||||
ChannelTermsAction:
|
||||
return "channel_terms_action";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp"
|
||||
|
||||
#include "util/QMagicEnum.hpp"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
|
||||
: typeString(root.value("type").toString())
|
||||
{
|
||||
if (const auto oType = qmagicenum::enumCast<Type>(this->typeString);
|
||||
oType.has_value())
|
||||
{
|
||||
this->type = oType.value();
|
||||
}
|
||||
|
||||
auto data = root.value("data").toObject();
|
||||
|
||||
if (this->type == Type::UserMessage)
|
||||
{
|
||||
this->msgID = data.value("message_id").toString();
|
||||
this->sentAt = data.value("sent_at").toString();
|
||||
const auto content = data.value("message_content").toObject();
|
||||
this->text = content.value("text").toString();
|
||||
for (const auto &part : content.value("fragments").toArray())
|
||||
{
|
||||
this->fragments.emplace_back(part.toObject());
|
||||
}
|
||||
|
||||
// the rest of the data is within a nested object
|
||||
data = data.value("low_trust_user").toObject();
|
||||
|
||||
const auto sender = data.value("sender").toObject();
|
||||
this->suspiciousUserID = sender.value("user_id").toString();
|
||||
this->suspiciousUserLogin = sender.value("login").toString();
|
||||
this->suspiciousUserDisplayName =
|
||||
sender.value("display_name").toString();
|
||||
this->suspiciousUserColor =
|
||||
QColor(sender.value("chat_color").toString());
|
||||
|
||||
for (const auto &badge : sender.value("badges").toArray())
|
||||
{
|
||||
const auto badgeObj = badge.toObject();
|
||||
const auto badgeID = badgeObj.value("id").toString();
|
||||
const auto badgeVersion = badgeObj.value("version").toString();
|
||||
this->senderBadges.emplace_back(Badge{badgeID, badgeVersion});
|
||||
}
|
||||
|
||||
const auto sharedValue = data.value("shared_ban_channel_ids");
|
||||
if (!sharedValue.isNull())
|
||||
{
|
||||
for (const auto &id : sharedValue.toArray())
|
||||
{
|
||||
this->sharedBanChannelIDs.emplace_back(id.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->suspiciousUserID = data.value("target_user_id").toString();
|
||||
this->suspiciousUserLogin = data.value("target_user").toString();
|
||||
this->suspiciousUserDisplayName = this->suspiciousUserLogin;
|
||||
}
|
||||
|
||||
this->channelID = data.value("channel_id").toString();
|
||||
this->updatedAtString = data.value("updated_at").toString();
|
||||
this->updatedAt = QDateTime::fromString(this->updatedAtString, Qt::ISODate)
|
||||
.toLocalTime()
|
||||
.toString("MMM d yyyy, h:mm ap");
|
||||
|
||||
const auto updatedBy = data.value("updated_by").toObject();
|
||||
this->updatedByUserID = updatedBy.value("id").toString();
|
||||
this->updatedByUserLogin = updatedBy.value("login").toString();
|
||||
this->updatedByUserDisplayName = updatedBy.value("display_name").toString();
|
||||
|
||||
this->treatmentString = data.value("treatment").toString();
|
||||
if (const auto oTreatment =
|
||||
qmagicenum::enumCast<Treatment>(this->treatmentString);
|
||||
oTreatment.has_value())
|
||||
{
|
||||
this->treatment = oTreatment.value();
|
||||
}
|
||||
|
||||
this->evasionEvaluationString =
|
||||
data.value("ban_evasion_evaluation").toString();
|
||||
if (const auto oEvaluation = qmagicenum::enumCast<EvasionEvaluation>(
|
||||
this->evasionEvaluationString);
|
||||
oEvaluation.has_value())
|
||||
{
|
||||
this->evasionEvaluation = oEvaluation.value();
|
||||
}
|
||||
|
||||
for (const auto &rType : data.value("types").toArray())
|
||||
{
|
||||
if (const auto oRestriction =
|
||||
qmagicenum::enumCast<RestrictionType>(rType.toString());
|
||||
oRestriction.has_value())
|
||||
{
|
||||
this->restrictionTypes.set(oRestriction.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,266 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
|
||||
#include <common/FlagsEnum.hpp>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <QColor>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct PubSubLowTrustUsersMessage {
|
||||
struct Fragment {
|
||||
QString text;
|
||||
QString emoteID;
|
||||
|
||||
explicit Fragment(const QJsonObject &obj)
|
||||
: text(obj.value("text").toString())
|
||||
, emoteID(obj.value("emoticon")
|
||||
.toObject()
|
||||
.value("emoticonID")
|
||||
.toString())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of low trust message update
|
||||
*/
|
||||
enum class Type {
|
||||
/**
|
||||
* An incoming message from someone marked as low trust
|
||||
*/
|
||||
UserMessage,
|
||||
|
||||
/**
|
||||
* An incoming update about a user's low trust status
|
||||
*/
|
||||
TreatmentUpdate,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
/**
|
||||
* The treatment set for the suspicious user
|
||||
*/
|
||||
enum class Treatment {
|
||||
NoTreatment,
|
||||
ActiveMonitoring,
|
||||
Restricted,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
/**
|
||||
* A ban evasion likelihood value (if any) that has been applied to the user
|
||||
* automatically by Twitch
|
||||
*/
|
||||
enum class EvasionEvaluation {
|
||||
UnknownEvader,
|
||||
UnlikelyEvader,
|
||||
LikelyEvader,
|
||||
PossibleEvader,
|
||||
|
||||
INVALID,
|
||||
};
|
||||
|
||||
/**
|
||||
* Restriction type (if any) that apply to the suspicious user
|
||||
*/
|
||||
enum class RestrictionType : uint8_t {
|
||||
UnknownType = 1 << 0,
|
||||
ManuallyAdded = 1 << 1,
|
||||
DetectedBanEvader = 1 << 2,
|
||||
BannedInSharedChannel = 1 << 3,
|
||||
|
||||
INVALID = 1 << 4,
|
||||
};
|
||||
|
||||
Type type = Type::INVALID;
|
||||
|
||||
Treatment treatment = Treatment::INVALID;
|
||||
|
||||
EvasionEvaluation evasionEvaluation = EvasionEvaluation::INVALID;
|
||||
|
||||
FlagsEnum<RestrictionType> restrictionTypes;
|
||||
|
||||
QString channelID;
|
||||
|
||||
QString suspiciousUserID;
|
||||
QString suspiciousUserLogin;
|
||||
QString suspiciousUserDisplayName;
|
||||
|
||||
QString updatedByUserID;
|
||||
QString updatedByUserLogin;
|
||||
QString updatedByUserDisplayName;
|
||||
|
||||
/**
|
||||
* Formatted timestamp of when the treatment was last updated for the suspicious user
|
||||
*/
|
||||
QString updatedAt;
|
||||
|
||||
/**
|
||||
* Plain text of the message sent.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
QString text;
|
||||
|
||||
/**
|
||||
* Pre-parsed components of the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
std::vector<Fragment> fragments;
|
||||
|
||||
/**
|
||||
* ID of the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
QString msgID;
|
||||
|
||||
/**
|
||||
* RFC3339 timestamp of when the message was sent.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
QString sentAt;
|
||||
|
||||
/**
|
||||
* Color of the user who sent the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
QColor suspiciousUserColor;
|
||||
|
||||
/**
|
||||
* A list of channel IDs where the suspicious user is also banned.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
std::vector<QString> sharedBanChannelIDs;
|
||||
|
||||
/**
|
||||
* A list of badges of the user who sent the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
std::vector<Badge> senderBadges;
|
||||
|
||||
/**
|
||||
* Stores the string value of `type`
|
||||
* Useful in case type shows up as invalid after being parsed
|
||||
*/
|
||||
QString typeString;
|
||||
|
||||
/**
|
||||
* Stores the string value of `treatment`
|
||||
* Useful in case treatment shows up as invalid after being parsed
|
||||
*/
|
||||
QString treatmentString;
|
||||
|
||||
/**
|
||||
* Stores the string value of `ban_evasion_evaluation`
|
||||
* Useful in case evasionEvaluation shows up as invalid after being parsed
|
||||
*/
|
||||
QString evasionEvaluationString;
|
||||
|
||||
/**
|
||||
* Stores the string value of `updated_at`
|
||||
* Useful in case formattedUpdatedAt doesn't parse correctly
|
||||
*/
|
||||
QString updatedAtString;
|
||||
|
||||
PubSubLowTrustUsersMessage() = default;
|
||||
explicit PubSubLowTrustUsersMessage(const QJsonObject &root);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubLowTrustUsersMessage::Type>(
|
||||
chatterino::PubSubLowTrustUsersMessage::Type value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case chatterino::PubSubLowTrustUsersMessage::Type::UserMessage:
|
||||
return "low_trust_user_new_message";
|
||||
|
||||
case chatterino::PubSubLowTrustUsersMessage::Type::TreatmentUpdate:
|
||||
return "low_trust_user_treatment_update";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubLowTrustUsersMessage::Treatment>(
|
||||
chatterino::PubSubLowTrustUsersMessage::Treatment value) noexcept
|
||||
{
|
||||
using Treatment = chatterino::PubSubLowTrustUsersMessage::Treatment;
|
||||
switch (value)
|
||||
{
|
||||
case Treatment::NoTreatment:
|
||||
return "NO_TREATMENT";
|
||||
|
||||
case Treatment::ActiveMonitoring:
|
||||
return "ACTIVE_MONITORING";
|
||||
|
||||
case Treatment::Restricted:
|
||||
return "RESTRICTED";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubLowTrustUsersMessage::EvasionEvaluation>(
|
||||
chatterino::PubSubLowTrustUsersMessage::EvasionEvaluation value) noexcept
|
||||
{
|
||||
using EvasionEvaluation =
|
||||
chatterino::PubSubLowTrustUsersMessage::EvasionEvaluation;
|
||||
switch (value)
|
||||
{
|
||||
case EvasionEvaluation::UnknownEvader:
|
||||
return "UNKNOWN_EVADER";
|
||||
|
||||
case EvasionEvaluation::UnlikelyEvader:
|
||||
return "UNLIKELY_EVADER";
|
||||
|
||||
case EvasionEvaluation::LikelyEvader:
|
||||
return "LIKELY_EVADER";
|
||||
|
||||
case EvasionEvaluation::PossibleEvader:
|
||||
return "POSSIBLE_EVADER";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
||||
chatterino::PubSubLowTrustUsersMessage::RestrictionType>(
|
||||
chatterino::PubSubLowTrustUsersMessage::RestrictionType value) noexcept
|
||||
{
|
||||
using RestrictionType =
|
||||
chatterino::PubSubLowTrustUsersMessage::RestrictionType;
|
||||
switch (value)
|
||||
{
|
||||
case RestrictionType::UnknownType:
|
||||
return "UNKNOWN_TYPE";
|
||||
|
||||
case RestrictionType::ManuallyAdded:
|
||||
return "MANUALLY_ADDED";
|
||||
|
||||
case RestrictionType::DetectedBanEvader:
|
||||
return "DETECTED_BAN_EVADER";
|
||||
|
||||
case RestrictionType::BannedInSharedChannel:
|
||||
return "BANNED_IN_SHARED_CHANNEL";
|
||||
|
||||
default:
|
||||
return default_tag;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user