diff --git a/CHANGELOG.md b/CHANGELOG.md index 718ed116..fbe7b854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Dev: Conan will no longer generate a `CMakeUserPresets.json` file. (#6117) - Dev: Pass `--force-openssl` when installing from CMake in Qt 6.8+. (#6129) - Dev: Fixed ` clean` not working correctly with generated sources. (#6154) +- Dev: Removed authenticated PubSub implementation. (#6158) - Dev: Save settings in `aboutToQuit`. (#6159) ## 2.5.3 diff --git a/src/Application.cpp b/src/Application.cpp index 1809ee8f..afd96485 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -39,7 +39,6 @@ #include "providers/seventv/SeventvBadges.hpp" #include "providers/seventv/SeventvEventAPI.hpp" #include "providers/twitch/ChannelPointReward.hpp" -#include "providers/twitch/PubSubActions.hpp" #include "providers/twitch/PubSubManager.hpp" #include "providers/twitch/PubSubMessages.hpp" #include "providers/twitch/TwitchChannel.hpp" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 24e516a2..a808a5bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -404,8 +404,6 @@ set(SOURCE_FILES providers/twitch/ChannelPointReward.hpp providers/twitch/IrcMessageHandler.cpp providers/twitch/IrcMessageHandler.hpp - providers/twitch/PubSubActions.cpp - providers/twitch/PubSubActions.hpp providers/twitch/PubSubClient.cpp providers/twitch/PubSubClient.hpp providers/twitch/PubSubClientOptions.hpp @@ -452,18 +450,12 @@ set(SOURCE_FILES providers/twitch/eventsub/SubscriptionRequest.cpp providers/twitch/eventsub/SubscriptionRequest.hpp - providers/twitch/pubsubmessages/AutoMod.cpp - providers/twitch/pubsubmessages/AutoMod.hpp providers/twitch/pubsubmessages/Base.cpp providers/twitch/pubsubmessages/Base.hpp providers/twitch/pubsubmessages/ChannelPoints.cpp providers/twitch/pubsubmessages/ChannelPoints.hpp - providers/twitch/pubsubmessages/ChatModeratorAction.cpp - providers/twitch/pubsubmessages/ChatModeratorAction.hpp providers/twitch/pubsubmessages/Listen.cpp providers/twitch/pubsubmessages/Listen.hpp - providers/twitch/pubsubmessages/LowTrustUsers.cpp - providers/twitch/pubsubmessages/LowTrustUsers.hpp providers/twitch/pubsubmessages/Message.hpp providers/twitch/pubsubmessages/Unlisten.cpp providers/twitch/pubsubmessages/Unlisten.hpp diff --git a/src/controllers/commands/builtin/chatterino/Debugging.cpp b/src/controllers/commands/builtin/chatterino/Debugging.cpp index 9339bb3d..d1517dd3 100644 --- a/src/controllers/commands/builtin/chatterino/Debugging.cpp +++ b/src/controllers/commands/builtin/chatterino/Debugging.cpp @@ -10,7 +10,6 @@ #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" #include "messages/MessageElement.hpp" -#include "providers/twitch/PubSubActions.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Theme.hpp" @@ -149,27 +148,7 @@ QString debugTest(const CommandContext &ctx) const auto command = ctx.words.value(1); - if (command == "timeout-pubsub") - { - QJsonObject data; - data["created_by_user_id"] = ctx.twitchChannel->roomId(); - data["created_by"] = ctx.twitchChannel->getName(); - - BanAction action(data, ctx.twitchChannel->roomId()); - - action.source.id = ctx.twitchChannel->roomId(); - action.source.login = ctx.twitchChannel->getName(); - - action.target.id = "11148817"; - action.target.login = "pajlada"; - action.duration = 10; - - MessageBuilder msg(action, QDateTime::currentDateTime()); - msg->flags.set(MessageFlag::PubSub); - ctx.channel->addOrReplaceTimeout(msg.release(), - QDateTime::currentDateTime()); - } - else if (command == "timeout-irc") + if (command == "timeout-irc") { auto nowMillis = QDateTime::currentDateTime().toSecsSinceEpoch(); diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp index a13c888b..2a3aefbf 100644 --- a/src/messages/MessageBuilder.cpp +++ b/src/messages/MessageBuilder.cpp @@ -25,8 +25,6 @@ #include "providers/seventv/SeventvEmotes.hpp" #include "providers/twitch/api/Helix.hpp" #include "providers/twitch/ChannelPointReward.hpp" -#include "providers/twitch/PubSubActions.hpp" -#include "providers/twitch/pubsubmessages/AutoMod.hpp" #include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchBadge.hpp" #include "providers/twitch/TwitchBadges.hpp" @@ -376,15 +374,6 @@ bool doesWordContainATwitchEmote( return true; } -EmotePtr makeAutoModBadge() -{ - return std::make_shared(Emote{ - EmoteName{}, - ImageSet{Image::fromResourcePixmap(getResources().twitch.automod)}, - Tooltip{"AutoMod"}, - Url{"https://dashboard.twitch.tv/settings/moderation/automod"}}); -} - EmotePtr makeSharedChatBadge(const QString &sourceName, const QString &sourceProfileURL, const QString &sourceLogin) @@ -773,239 +762,6 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username, this->message().serverReceivedTime = time; } -MessageBuilder::MessageBuilder(const BanAction &action, const QDateTime &time, - uint32_t count) - : MessageBuilder() -{ - auto current = getApp()->getAccounts()->twitch.getCurrent(); - - this->emplace(); - this->message().flags.set(MessageFlag::System); - this->message().flags.set(MessageFlag::Timeout); - this->message().flags.set(MessageFlag::ModerationAction); - this->message().timeoutUser = action.target.login; - this->message().loginName = action.source.login; - this->message().count = count; - - QString text; - - if (action.target.id == current->getUserId()) - { - this->emplaceSystemTextAndUpdate("You", text) - ->setLink({Link::UserInfo, current->getUserName()}); - this->emplaceSystemTextAndUpdate("were", text); - if (action.isBan()) - { - this->appendOrEmplaceSystemTextAndUpdate("banned", text); - } - else - { - this->appendOrEmplaceSystemTextAndUpdate( - QString("timed out for %1").arg(formatTime(action.duration)), - text); - } - - if (!action.source.login.isEmpty()) - { - this->appendOrEmplaceSystemTextAndUpdate("by", text); - this->emplaceSystemTextAndUpdate( - action.source.login + (action.reason.isEmpty() ? "." : ":"), - text) - ->setLink({Link::UserInfo, action.source.login}); - } - - if (!action.reason.isEmpty()) - { - this->appendOrEmplaceSystemTextAndUpdate( - QString("\"%1\".").arg(action.reason), text); - } - } - else - { - if (action.isBan()) - { - this->emplaceSystemTextAndUpdate(action.source.login, text) - ->setLink({Link::UserInfo, action.source.login}); - this->emplaceSystemTextAndUpdate("banned", text); - if (action.reason.isEmpty()) - { - this->emplaceSystemTextAndUpdate(action.target.login + ".", - text) - ->setLink({Link::UserInfo, action.target.login}); - } - else - { - this->emplaceSystemTextAndUpdate(action.target.login + ":", - text) - ->setLink({Link::UserInfo, action.target.login}); - this->emplaceSystemTextAndUpdate( - QString("\"%1\".").arg(action.reason), text); - } - } - else - { - this->emplaceSystemTextAndUpdate(action.source.login, text) - ->setLink({Link::UserInfo, action.source.login}); - this->emplaceSystemTextAndUpdate("timed out", text); - this->emplaceSystemTextAndUpdate(action.target.login, text) - ->setLink({Link::UserInfo, action.target.login}); - if (action.reason.isEmpty()) - { - this->emplaceSystemTextAndUpdate( - QString("for %1.").arg(formatTime(action.duration)), text); - } - else - { - this->emplaceSystemTextAndUpdate( - QString("for %1: \"%2\".") - .arg(formatTime(action.duration)) - .arg(action.reason), - text); - } - - if (count > 1) - { - this->appendOrEmplaceSystemTextAndUpdate( - QString("(%1 times)").arg(count), text); - } - } - } - - this->message().messageText = text; - this->message().searchText = text; - - this->message().serverReceivedTime = time; -} - -MessageBuilder::MessageBuilder(const UnbanAction &action, const QDateTime &time) - : MessageBuilder() -{ - this->emplace(); - this->message().flags.set(MessageFlag::System); - this->message().flags.set(MessageFlag::Untimeout); - - this->message().timeoutUser = action.target.login; - - QString text; - - this->emplaceSystemTextAndUpdate(action.source.login, text) - ->setLink({Link::UserInfo, action.source.login}); - this->emplaceSystemTextAndUpdate( - action.wasBan() ? "unbanned" : "untimedout", text); - this->emplaceSystemTextAndUpdate(action.target.login + ".", text) - ->setLink({Link::UserInfo, action.target.login}); - - this->message().messageText = text; - this->message().searchText = text; - - this->message().serverReceivedTime = time; -} - -MessageBuilder::MessageBuilder(const WarnAction &action) - : MessageBuilder() -{ - this->emplace(); - this->message().flags.set(MessageFlag::System); - - QString text; - - // TODO: Use MentionElement here, once WarnAction includes username/displayname - this->emplaceSystemTextAndUpdate("A moderator", text) - ->setLink({Link::UserInfo, "id:" + action.source.id}); - this->emplaceSystemTextAndUpdate("warned", text); - this->emplaceSystemTextAndUpdate( - action.target.login + (action.reasons.isEmpty() ? "." : ":"), text) - ->setLink({Link::UserInfo, action.target.login}); - - if (!action.reasons.isEmpty()) - { - this->emplaceSystemTextAndUpdate(action.reasons.join(", "), text); - } - - this->message().messageText = text; - this->message().searchText = text; -} - -MessageBuilder::MessageBuilder(const RaidAction &action) - : MessageBuilder() -{ - this->emplace(); - this->message().flags.set(MessageFlag::System); - - QString text; - - this->emplaceSystemTextAndUpdate(action.source.login, text) - ->setLink({Link::UserInfo, "id:" + action.source.id}); - this->emplaceSystemTextAndUpdate("initiated a raid to", text); - this->emplaceSystemTextAndUpdate(action.target + ".", text) - ->setLink({Link::UserInfo, action.target}); - - this->message().messageText = text; - this->message().searchText = text; -} - -MessageBuilder::MessageBuilder(const UnraidAction &action) - : MessageBuilder() -{ - this->emplace(); - this->message().flags.set(MessageFlag::System); - - QString text; - - this->emplaceSystemTextAndUpdate(action.source.login, text) - ->setLink({Link::UserInfo, "id:" + action.source.id}); - this->emplaceSystemTextAndUpdate("canceled the raid.", text); - - this->message().messageText = text; - this->message().searchText = text; -} - -MessageBuilder::MessageBuilder(const AutomodUserAction &action) - : MessageBuilder() -{ - this->emplace(); - this->message().flags.set(MessageFlag::System); - - QString text; - switch (action.type) - { - case AutomodUserAction::AddPermitted: { - text = QString("%1 added \"%2\" as a permitted term on AutoMod.") - .arg(action.source.login, action.message); - } - break; - - case AutomodUserAction::AddBlocked: { - text = QString("%1 added \"%2\" as a blocked term on AutoMod.") - .arg(action.source.login, action.message); - } - break; - - case AutomodUserAction::RemovePermitted: { - text = QString("%1 removed \"%2\" as a permitted term on AutoMod.") - .arg(action.source.login, action.message); - } - break; - - case AutomodUserAction::RemoveBlocked: { - text = QString("%1 removed \"%2\" as a blocked term on AutoMod.") - .arg(action.source.login, action.message); - } - break; - - case AutomodUserAction::Properties: { - text = QString("%1 modified the AutoMod properties.") - .arg(action.source.login); - } - break; - } - this->message().messageText = text; - this->message().searchText = text; - - this->emplace(text, MessageElementFlag::Text, - MessageColor::System); -} - MessageBuilder::MessageBuilder(LiveUpdatesAddEmoteMessageTag /*unused*/, const QString &platform, const QString &actor, const std::vector &emoteNames) @@ -1531,50 +1287,6 @@ MessagePtr MessageBuilder::makeDeletionMessageFromIRC( return builder.release(); } -MessagePtr MessageBuilder::makeDeletionMessageFromPubSub( - const DeleteAction &action) -{ - MessageBuilder builder; - - builder.emplace(); - builder.message().flags.set(MessageFlag::System); - builder.message().flags.set(MessageFlag::DoNotTriggerNotification); - builder.message().flags.set(MessageFlag::ModerationAction); - - builder - .emplace(action.source.login, MessageElementFlag::Username, - MessageColor::System, FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, action.source.login}); - // TODO(mm2pl): If or when jumping to a single message gets implemented a link, - // add a link to the originalMessage - builder.emplace( - "deleted message from", MessageElementFlag::Text, MessageColor::System); - builder - .emplace(action.target.login, MessageElementFlag::Username, - MessageColor::System, FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, action.target.login}); - builder.emplace("saying:", MessageElementFlag::Text, - MessageColor::System); - if (action.messageText.length() > 50) - { - builder - .emplace(action.messageText.left(50) + "…", - MessageElementFlag::Text, MessageColor::Text) - ->setLink({Link::JumpToMessage, action.messageId}); - } - else - { - builder - .emplace(action.messageText, MessageElementFlag::Text, - MessageColor::Text) - ->setLink({Link::JumpToMessage, action.messageId}); - } - builder.message().timeoutUser = "msg:" + action.messageId; - builder.message().flags.set(MessageFlag::PubSub); - - return builder.release(); -} - MessagePtr MessageBuilder::makeListOfUsersMessage(QString prefix, QStringList users, Channel *channel, @@ -1729,368 +1441,6 @@ MessagePtr MessageBuilder::buildHypeChatMessage( return builder.release(); } -std::pair MessageBuilder::makeAutomodMessage( - const AutomodAction &action, const QString &channelName) -{ - MessageBuilder builder, builder2; - - if (action.reasonCode == PubSubAutoModQueueMessage::Reason::BlockedTerm) - { - builder.message().flags.set(MessageFlag::AutoModBlockedTerm); - builder2.message().flags.set(MessageFlag::AutoModBlockedTerm); - } - - // - // Builder for AutoMod message with explanation - builder.message().id = "automod_" + action.msgID; - builder.message().loginName = "automod"; - builder.message().channelName = channelName; - builder.message().flags.set(MessageFlag::PubSub); - builder.message().flags.set(MessageFlag::ModerationAction); - builder.message().flags.set(MessageFlag::AutoMod); - builder.message().flags.set(MessageFlag::AutoModOffendingMessageHeader); - - // AutoMod shield badge - builder.emplace(makeAutoModBadge(), - MessageElementFlag::BadgeChannelAuthority); - // AutoMod "username" - builder.emplace("AutoMod:", MessageElementFlag::Text, - AUTOMOD_USER_COLOR, FontStyle::ChatMediumBold); - // AutoMod header message - builder.emplace( - ("Held a message for reason: " + action.reason + - ". Allow will post it in chat. "), - MessageElementFlag::Text, MessageColor::Text); - // Allow link button - builder - .emplace("Allow", MessageElementFlag::Text, - MessageColor(QColor("green")), - FontStyle::ChatMediumBold) - ->setLink({Link::AutoModAllow, action.msgID}); - // Deny link button - builder - .emplace(" Deny", MessageElementFlag::Text, - MessageColor(QColor("red")), - FontStyle::ChatMediumBold) - ->setLink({Link::AutoModDeny, action.msgID}); - // ID of message caught by AutoMod - // builder.emplace(action.msgID, MessageElementFlag::Text, - // MessageColor::Text); - auto text1 = - QString("AutoMod: Held a message for reason: %1. Allow will post " - "it in chat. Allow Deny") - .arg(action.reason); - builder.message().messageText = text1; - builder.message().searchText = text1; - - auto message1 = builder.release(); - - // - // Builder for offender's message - builder2.message().channelName = channelName; - builder2 - .emplace("#" + channelName, - MessageElementFlag::ChannelName, - MessageColor::System) - ->setLink({Link::JumpToChannel, channelName}); - builder2.emplace(); - builder2.emplace(); - builder2.message().loginName = action.target.login; - builder2.message().flags.set(MessageFlag::PubSub); - builder2.message().flags.set(MessageFlag::ModerationAction); - builder2.message().flags.set(MessageFlag::AutoMod); - builder2.message().flags.set(MessageFlag::AutoModOffendingMessage); - - // sender username - builder2.emplace(action.target.displayName + ":", - action.target.login, MessageColor::Text, - action.target.color); - // sender's message caught by AutoMod - builder2.emplace(action.message, MessageElementFlag::Text, - MessageColor::Text); - auto text2 = - QString("%1: %2").arg(action.target.displayName, action.message); - builder2.message().messageText = text2; - builder2.message().searchText = text2; - - auto message2 = builder2.release(); - - // Normally highlights would be checked & triggered during the builder parse steps - // and when the message is added to the channel - // We do this a bit weird since the message comes in from PubSub and not the normal message route - auto [highlighted, highlightResult] = getApp()->getHighlights()->check( - {}, {}, action.target.login, action.message, message2->flags); - if (highlighted) - { - actuallyTriggerHighlights( - channelName, highlightResult.playSound, - highlightResult.customSoundUrl.value_or(QUrl{}), - highlightResult.alert); - } - - return std::make_pair(message1, message2); -} - -MessagePtr MessageBuilder::makeAutomodInfoMessage( - const AutomodInfoAction &action) -{ - auto builder = MessageBuilder(); - QString text("AutoMod: "); - - builder.emplace(); - builder.message().flags.set(MessageFlag::PubSub); - - // AutoMod shield badge - builder.emplace(makeAutoModBadge(), - MessageElementFlag::BadgeChannelAuthority); - // AutoMod "username" - builder.emplace("AutoMod:", MessageElementFlag::Text, - AUTOMOD_USER_COLOR, FontStyle::ChatMediumBold); - switch (action.type) - { - case AutomodInfoAction::OnHold: { - QString info("Hey! Your message is being checked " - "by mods and has not been sent."); - text += info; - builder.appendOrEmplaceText(info, MessageColor::Text); - } - break; - case AutomodInfoAction::Denied: { - QString info("Mods have removed your message."); - text += info; - builder.appendOrEmplaceText(info, MessageColor::Text); - } - break; - case AutomodInfoAction::Approved: { - QString info("Mods have accepted your message."); - text += info; - builder.appendOrEmplaceText(info, MessageColor::Text); - } - break; - } - - builder.message().flags.set(MessageFlag::AutoMod); - builder.message().messageText = text; - builder.message().searchText = text; - - auto message = builder.release(); - - return message; -} - -std::pair MessageBuilder::makeLowTrustUserMessage( - const PubSubLowTrustUsersMessage &action, const QString &channelName, - const TwitchChannel *twitchChannel) -{ - MessageBuilder builder, builder2; - - // Builder for low trust user message with explanation - builder.message().channelName = channelName; - builder.message().flags.set(MessageFlag::PubSub); - builder.message().flags.set(MessageFlag::LowTrustUsers); - - // AutoMod shield badge - builder.emplace(makeAutoModBadge(), - MessageElementFlag::BadgeChannelAuthority); - - // Suspicious user header message - QString prefix = "Suspicious User:"; - builder.emplace(prefix, MessageElementFlag::Text, - MessageColor(QColor("blue")), - FontStyle::ChatMediumBold); - - QString headerMessage; - if (action.treatment == PubSubLowTrustUsersMessage::Treatment::Restricted) - { - headerMessage = "Restricted"; - builder2.message().flags.set(MessageFlag::RestrictedMessage); - } - else - { - headerMessage = "Monitored"; - builder2.message().flags.set(MessageFlag::MonitoredMessage); - } - - if (action.restrictionTypes.has( - PubSubLowTrustUsersMessage::RestrictionType::ManuallyAdded)) - { - headerMessage += " by " + action.updatedByUserLogin; - } - - headerMessage += " at " + action.updatedAt; - - if (action.restrictionTypes.has( - PubSubLowTrustUsersMessage::RestrictionType::DetectedBanEvader)) - { - QString evader; - if (action.evasionEvaluation == - PubSubLowTrustUsersMessage::EvasionEvaluation::LikelyEvader) - { - evader = "likely"; - } - else - { - evader = "possible"; - } - - headerMessage += ". Detected as " + evader + " ban evader"; - } - - if (action.restrictionTypes.has( - PubSubLowTrustUsersMessage::RestrictionType::BannedInSharedChannel)) - { - headerMessage += ". Banned in " + - QString::number(action.sharedBanChannelIDs.size()) + - " shared channels"; - } - - builder.emplace(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("#" + channelName, - MessageElementFlag::ChannelName, - MessageColor::System) - ->setLink({Link::JumpToChannel, channelName}); - builder2.emplace(); - builder2.emplace(); - builder2.message().loginName = action.suspiciousUserLogin; - builder2.message().flags.set(MessageFlag::PubSub); - builder2.message().flags.set(MessageFlag::LowTrustUsers); - - // sender badges - appendBadges(&builder2, action.senderBadges, {}, twitchChannel); - - // sender username - builder2.emplace( - action.suspiciousUserDisplayName + ":", action.suspiciousUserLogin, - MessageColor::Text, action.suspiciousUserColor); - - // sender's message caught by AutoMod - for (const auto &fragment : action.fragments) - { - if (fragment.emoteID.isEmpty()) - { - builder2.emplace( - fragment.text, MessageElementFlag::Text, MessageColor::Text); - } - else - { - const auto emotePtr = - getApp()->getEmotes()->getTwitchEmotes()->getOrCreateEmote( - EmoteId{fragment.emoteID}, EmoteName{fragment.text}); - builder2.emplace( - emotePtr, MessageElementFlag::TwitchEmote, MessageColor::Text); - } - } - - auto text = - QString("%1: %2").arg(action.suspiciousUserDisplayName, action.text); - builder2.message().messageText = text; - builder2.message().searchText = text; - - auto message2 = builder2.release(); - - return std::make_pair(message1, message2); -} - -MessagePtr MessageBuilder::makeLowTrustUpdateMessage( - const PubSubLowTrustUsersMessage &action) -{ - /** - * Known issues: - * - Non-Twitch badges are not shown - * - Non-Twitch emotes are not shown - */ - - MessageBuilder builder; - builder.emplace(); - builder.message().flags.set(MessageFlag::System); - builder.message().flags.set(MessageFlag::PubSub); - builder.message().flags.set(MessageFlag::DoNotTriggerNotification); - - builder - .emplace(action.updatedByUserDisplayName, - MessageElementFlag::Username, - MessageColor::System, FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, action.updatedByUserLogin}); - - QString text; - assert(action.treatment != PubSubLowTrustUsersMessage::Treatment::INVALID); - switch (action.treatment) - { - case PubSubLowTrustUsersMessage::Treatment::NoTreatment: { - builder.emplace("removed", MessageElementFlag::Text, - MessageColor::System); - builder - .emplace(action.suspiciousUserDisplayName, - MessageElementFlag::Username, - MessageColor::System, - FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, action.suspiciousUserLogin}); - builder.emplace("from the suspicious user list.", - MessageElementFlag::Text, - MessageColor::System); - text = QString("%1 removed %2 from the suspicious user list.") - .arg(action.updatedByUserDisplayName, - action.suspiciousUserDisplayName); - } - break; - - case PubSubLowTrustUsersMessage::Treatment::ActiveMonitoring: { - builder.emplace("added", MessageElementFlag::Text, - MessageColor::System); - builder - .emplace(action.suspiciousUserDisplayName, - MessageElementFlag::Username, - MessageColor::System, - FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, action.suspiciousUserLogin}); - builder.emplace("as a monitored suspicious chatter.", - MessageElementFlag::Text, - MessageColor::System); - text = QString("%1 added %2 as a monitored suspicious chatter.") - .arg(action.updatedByUserDisplayName, - action.suspiciousUserDisplayName); - } - break; - - case PubSubLowTrustUsersMessage::Treatment::Restricted: { - builder.emplace("added", MessageElementFlag::Text, - MessageColor::System); - builder - .emplace(action.suspiciousUserDisplayName, - MessageElementFlag::Username, - MessageColor::System, - FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, action.suspiciousUserLogin}); - builder.emplace("as a restricted suspicious chatter.", - MessageElementFlag::Text, - MessageColor::System); - text = QString("%1 added %2 as a restricted suspicious chatter.") - .arg(action.updatedByUserDisplayName, - action.suspiciousUserDisplayName); - } - break; - - default: - qCDebug(chatterinoTwitch) << "Unexpected suspicious treatment: " - << action.treatmentString; - break; - } - - builder->messageText = text; - builder->searchText = text; - return builder.release(); -} - MessagePtrMut MessageBuilder::makeMissingScopesMessage( const QString &missingScopes) { diff --git a/src/messages/MessageBuilder.hpp b/src/messages/MessageBuilder.hpp index db848821..f4f6667c 100644 --- a/src/messages/MessageBuilder.hpp +++ b/src/messages/MessageBuilder.hpp @@ -4,29 +4,20 @@ #include "common/Outcome.hpp" #include "messages/MessageColor.hpp" #include "messages/MessageFlag.hpp" -#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp" #include #include #include #include +#include #include #include #include -#include #include namespace chatterino { -struct BanAction; -struct UnbanAction; -struct WarnAction; -struct RaidAction; -struct UnraidAction; -struct AutomodAction; -struct AutomodUserAction; -struct AutomodInfoAction; struct Message; using MessagePtr = std::shared_ptr; using MessagePtrMut = std::shared_ptr; @@ -43,7 +34,6 @@ class IgnorePhrase; struct HelixVip; using HelixModerator = HelixVip; struct ChannelPointReward; -struct DeleteAction; struct TwitchEmoteOccurrence; namespace linkparser { @@ -113,13 +103,6 @@ public: MessageBuilder(TimeoutMessageTag, const QString &username, const QString &durationInSeconds, bool multipleTimes, const QDateTime &time); - MessageBuilder(const BanAction &action, const QDateTime &time, - uint32_t count = 1); - MessageBuilder(const UnbanAction &action, const QDateTime &time); - MessageBuilder(const WarnAction &action); - MessageBuilder(const RaidAction &action); - MessageBuilder(const UnraidAction &action); - MessageBuilder(const AutomodUserAction &action); MessageBuilder(LiveUpdatesAddEmoteMessageTag, const QString &platform, const QString &actor, @@ -201,7 +184,6 @@ public: bool hostOn); static MessagePtr makeDeletionMessageFromIRC( const MessagePtr &originalMessage); - static MessagePtr makeDeletionMessageFromPubSub(const DeleteAction &action); static MessagePtr makeListOfUsersMessage(QString prefix, QStringList users, Channel *channel, MessageFlags extraFlags = {}); @@ -211,16 +193,6 @@ public: static MessagePtr buildHypeChatMessage(Communi::IrcPrivateMessage *message); - static std::pair makeAutomodMessage( - const AutomodAction &action, const QString &channelName); - static MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action); - - static std::pair makeLowTrustUserMessage( - const PubSubLowTrustUsersMessage &action, const QString &channelName, - const TwitchChannel *twitchChannel); - static MessagePtr makeLowTrustUpdateMessage( - const PubSubLowTrustUsersMessage &action); - /// @brief Builds a message out of an `ircMessage`. /// /// Building a message won't cause highlights to be triggered. They will diff --git a/src/providers/twitch/PubSubActions.cpp b/src/providers/twitch/PubSubActions.cpp deleted file mode 100644 index eb1e6ffb..00000000 --- a/src/providers/twitch/PubSubActions.cpp +++ /dev/null @@ -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 diff --git a/src/providers/twitch/PubSubActions.hpp b/src/providers/twitch/PubSubActions.hpp deleted file mode 100644 index 58e1a35e..00000000 --- a/src/providers/twitch/PubSubActions.hpp +++ /dev/null @@ -1,196 +0,0 @@ -#pragma once - -#include "providers/twitch/pubsubmessages/AutoMod.hpp" - -#include -#include -#include -#include -#include - -#include -#include - -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 diff --git a/src/providers/twitch/PubSubClient.cpp b/src/providers/twitch/PubSubClient.cpp index 514fd487..0e0860b4 100644 --- a/src/providers/twitch/PubSubClient.cpp +++ b/src/providers/twitch/PubSubClient.cpp @@ -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 -#include namespace chatterino { diff --git a/src/providers/twitch/PubSubManager.cpp b/src/providers/twitch/PubSubManager.cpp index 1df50b14..99d9d3a1 100644 --- a/src/providers/twitch/PubSubManager.cpp +++ b/src/providers/twitch/PubSubManager.cpp @@ -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 @@ -20,7 +14,6 @@ #include #include -#include #include #include @@ -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 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(); - 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(); @@ -1193,55 +559,6 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message) break; } } - else if (topic.startsWith("automod-queue.")) - { - auto oInnerMessage = message.toInner(); - 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(); - 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 diff --git a/src/providers/twitch/PubSubManager.hpp b/src/providers/twitch/PubSubManager.hpp index ddce5129..6fd086a6 100644 --- a/src/providers/twitch/PubSubManager.hpp +++ b/src/providers/twitch/PubSubManager.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -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 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 chatCleared; - Signal messageDeleted; - Signal modeChanged; - Signal moderationStateChanged; - - Signal raidStarted; - Signal raidCanceled; - - Signal userBanned; - Signal userUnbanned; - Signal userWarned; - - Signal suspiciousMessageReceived; - Signal suspiciousTreatmentUpdated; - - // Message caught by automod - // channelID - pajlada::Signals::Signal - autoModMessageCaught; - - // Message blocked by moderator - Signal autoModMessageBlocked; - - Signal automodUserMessage; - Signal automodInfoMessage; - } moderation; - struct { Signal 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 account); - void start(); void stop(); @@ -218,14 +130,6 @@ private: std::owner_less> clients; - std::unordered_map< - QString, std::function> - moderationActionHandlers; - - std::unordered_map< - QString, std::function> - 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 }; diff --git a/src/providers/twitch/PubSubMessages.hpp b/src/providers/twitch/PubSubMessages.hpp index 7828422b..4dad7764 100644 --- a/src/providers/twitch/PubSubMessages.hpp +++ b/src/providers/twitch/PubSubMessages.hpp @@ -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 diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 9b6a71ea..df2d8b1b 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -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", diff --git a/src/providers/twitch/TwitchIrcServer.cpp b/src/providers/twitch/TwitchIrcServer.cpp index 54c919e7..ad86ae0f 100644 --- a/src/providers/twitch/TwitchIrcServer.cpp +++ b/src/providers/twitch/TwitchIrcServer.cpp @@ -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 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(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(); diff --git a/src/providers/twitch/eventsub/Connection.cpp b/src/providers/twitch/eventsub/Connection.cpp index bea0d281..c1361b17 100644 --- a/src/providers/twitch/eventsub/Connection.cpp +++ b/src/providers/twitch/eventsub/Connection.cpp @@ -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" diff --git a/src/providers/twitch/eventsub/MessageBuilder.cpp b/src/providers/twitch/eventsub/MessageBuilder.cpp index 8aab04dc..984838c9 100644 --- a/src/providers/twitch/eventsub/MessageBuilder.cpp +++ b/src/providers/twitch/eventsub/MessageBuilder.cpp @@ -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" diff --git a/src/providers/twitch/eventsub/MessageHandlers.cpp b/src/providers/twitch/eventsub/MessageHandlers.cpp index 121f538e..915a9b4d 100644 --- a/src/providers/twitch/eventsub/MessageHandlers.cpp +++ b/src/providers/twitch/eventsub/MessageHandlers.cpp @@ -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" diff --git a/src/providers/twitch/pubsubmessages/AutoMod.cpp b/src/providers/twitch/pubsubmessages/AutoMod.cpp deleted file mode 100644 index 236b84a7..00000000 --- a/src/providers/twitch/pubsubmessages/AutoMod.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "providers/twitch/pubsubmessages/AutoMod.hpp" - -#include "util/QMagicEnum.hpp" - -#include - -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(this->typeString); - if (oType.has_value()) - { - this->type = oType.value(); - } - - this->reason = - qmagicenum::enumCast(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 diff --git a/src/providers/twitch/pubsubmessages/AutoMod.hpp b/src/providers/twitch/pubsubmessages/AutoMod.hpp deleted file mode 100644 index 4312d523..00000000 --- a/src/providers/twitch/pubsubmessages/AutoMod.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include - -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 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; - } -} diff --git a/src/providers/twitch/pubsubmessages/ChatModeratorAction.cpp b/src/providers/twitch/pubsubmessages/ChatModeratorAction.cpp deleted file mode 100644 index 2cc36ca9..00000000 --- a/src/providers/twitch/pubsubmessages/ChatModeratorAction.cpp +++ /dev/null @@ -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(this->typeString); - if (oType.has_value()) - { - this->type = oType.value(); - } -} - -} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/ChatModeratorAction.hpp b/src/providers/twitch/pubsubmessages/ChatModeratorAction.hpp deleted file mode 100644 index e04019cb..00000000 --- a/src/providers/twitch/pubsubmessages/ChatModeratorAction.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include -#include -#include - -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; - } -} diff --git a/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp b/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp deleted file mode 100644 index cac4e02f..00000000 --- a/src/providers/twitch/pubsubmessages/LowTrustUsers.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp" - -#include "util/QMagicEnum.hpp" - -#include -#include - -namespace chatterino { - -PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root) - : typeString(root.value("type").toString()) -{ - if (const auto oType = qmagicenum::enumCast(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(this->treatmentString); - oTreatment.has_value()) - { - this->treatment = oTreatment.value(); - } - - this->evasionEvaluationString = - data.value("ban_evasion_evaluation").toString(); - if (const auto oEvaluation = qmagicenum::enumCast( - this->evasionEvaluationString); - oEvaluation.has_value()) - { - this->evasionEvaluation = oEvaluation.value(); - } - - for (const auto &rType : data.value("types").toArray()) - { - if (const auto oRestriction = - qmagicenum::enumCast(rType.toString()); - oRestriction.has_value()) - { - this->restrictionTypes.set(oRestriction.value()); - } - } -} - -} // namespace chatterino diff --git a/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp b/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp deleted file mode 100644 index e2666281..00000000 --- a/src/providers/twitch/pubsubmessages/LowTrustUsers.hpp +++ /dev/null @@ -1,266 +0,0 @@ -#pragma once - -#include "providers/twitch/TwitchBadge.hpp" - -#include -#include -#include -#include -#include - -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 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 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 sharedBanChannelIDs; - - /** - * A list of badges of the user who sent the message. - * Only used for the UserMessage type. - */ - std::vector 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; - } -} diff --git a/tests/src/TwitchPubSubClient.cpp b/tests/src/TwitchPubSubClient.cpp index ab21e285..bd59d6fe 100644 --- a/tests/src/TwitchPubSubClient.cpp +++ b/tests/src/TwitchPubSubClient.cpp @@ -1,8 +1,6 @@ #include "mocks/BaseApplication.hpp" -#include "providers/twitch/PubSubActions.hpp" #include "providers/twitch/PubSubClient.hpp" #include "providers/twitch/PubSubManager.hpp" -#include "providers/twitch/pubsubmessages/AutoMod.hpp" #include "providers/twitch/TwitchAccount.hpp" #include "Test.hpp" @@ -20,11 +18,7 @@ using namespace std::chrono_literals; * Server randomly disconnects us, we should reconnect (COMPLETE) * Client listens to more than 50 topics, so it opens 2 connections (COMPLETE) * Server sends RECONNECT message to us, we should reconnect (INCOMPLETE, leaving for now since if we just ignore it and Twitch disconnects us we should already handle it properly) - * Listen that required authentication, but authentication is missing (COMPLETE) - * Listen that required authentication, but authentication is wrong (COMPLETE) - * Incoming AutoMod message * Incoming ChannelPoints message - * Incoming ChatModeratorAction message (COMPLETE) **/ #define RUN_PUBSUB_TESTS @@ -85,23 +79,18 @@ const QString TEST_SETTINGS = R"( class FTest : public PubSub { public: - explicit FTest(const char *path, std::chrono::seconds pingInterval, - QString token = "token") + explicit FTest(const char *path, std::chrono::seconds pingInterval) : PubSub(QString("wss://127.0.0.1:9050%1").arg(path), pingInterval) { - auto account = std::make_shared("testaccount_420", token, - "clientid", "123456"); - this->setAccount(account); } }; class MockApplication : public mock::BaseApplication { public: - MockApplication(const char *path, std::chrono::seconds pingInterval, - QString token = "token") + MockApplication(const char *path, std::chrono::seconds pingInterval) : mock::BaseApplication(TEST_SETTINGS) - , pubSub(path, pingInterval, token) + , pubSub(path, pingInterval) { } @@ -127,7 +116,7 @@ TEST(TwitchPubSubClient, ServerRespondsToPings) ASSERT_EQ(pubSub.diag.connectionsFailed, 0); ASSERT_EQ(pubSub.diag.messagesReceived, 0); - pubSub.listenToChannelModerationActions("123456"); + pubSub.listenToChannelPointRewards("123456"); std::this_thread::sleep_for(150ms); @@ -159,7 +148,7 @@ TEST(TwitchPubSubClient, ServerDoesntRespondToPings) auto &pubSub = a.pubSub; pubSub.start(); - pubSub.listenToChannelModerationActions("123456"); + pubSub.listenToChannelPointRewards("123456"); std::this_thread::sleep_for(750ms); @@ -198,7 +187,7 @@ TEST(TwitchPubSubClient, DisconnectedAfter1s) ASSERT_EQ(pubSub.diag.messagesReceived, 0); ASSERT_EQ(pubSub.diag.listenResponses, 0); - pubSub.listenToChannelModerationActions("123456"); + pubSub.listenToChannelPointRewards("123456"); std::this_thread::sleep_for(500ms); @@ -233,7 +222,7 @@ TEST(TwitchPubSubClient, ExceedTopicLimit) for (auto i = 0; i < PubSubClient::MAX_LISTENS; ++i) { - pubSub.listenToChannelModerationActions(QString("1%1").arg(i)); + pubSub.listenToChannelPointRewards(QString("1%1").arg(i)); } std::this_thread::sleep_for(50ms); @@ -244,7 +233,7 @@ TEST(TwitchPubSubClient, ExceedTopicLimit) for (auto i = 0; i < PubSubClient::MAX_LISTENS; ++i) { - pubSub.listenToChannelModerationActions(QString("2%1").arg(i)); + pubSub.listenToChannelPointRewards(QString("2%1").arg(i)); } std::this_thread::sleep_for(50ms); @@ -274,7 +263,7 @@ TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep) for (auto i = 0; i < PubSubClient::MAX_LISTENS * 2; ++i) { - pubSub.listenToChannelModerationActions("123456"); + pubSub.listenToChannelPointRewards("123456"); } std::this_thread::sleep_for(150ms); @@ -290,167 +279,6 @@ TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep) ASSERT_EQ(pubSub.diag.connectionsFailed, 0); } -TEST(TwitchPubSubClient, ModeratorActionsUserBanned) -{ - MockApplication a("/moderator-actions-user-banned", 1s); - auto &pubSub = a.pubSub; - - pubSub.start(); - - ReceivedMessage received; - - std::ignore = - pubSub.moderation.userBanned.connect([&received](const auto &action) { - received = action; - }); - - ASSERT_EQ(pubSub.diag.listenResponses, 0); - - pubSub.listenToChannelModerationActions("123456"); - - std::this_thread::sleep_for(50ms); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 0); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); - ASSERT_EQ(pubSub.diag.messagesReceived, 3); - ASSERT_EQ(pubSub.diag.listenResponses, 1); - - ASSERT_TRUE(received); - - ActionUser expectedTarget{"140114344", "1xelerate", "", QColor()}; - ActionUser expectedSource{"117691339", "mm2pl", "", QColor()}; - - ASSERT_EQ(received->reason, QString()); - ASSERT_EQ(received->duration, 0); - ASSERT_EQ(received->target, expectedTarget); - ASSERT_EQ(received->source, expectedSource); - - pubSub.stop(); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 1); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); -} - -TEST(TwitchPubSubClient, MissingToken) -{ - // The token that's required is "xD" - MockApplication a("/authentication-required", 1s, ""); - auto &pubSub = a.pubSub; - - pubSub.start(); - - pubSub.listenToChannelModerationActions("123456"); - - std::this_thread::sleep_for(150ms); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 0); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); - ASSERT_EQ(pubSub.diag.messagesReceived, 2); - ASSERT_EQ(pubSub.diag.listenResponses, 0); - ASSERT_EQ(pubSub.diag.failedListenResponses, 1); - - pubSub.stop(); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 1); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); -} - -TEST(TwitchPubSubClient, WrongToken) -{ - // The token that's required is "xD" - MockApplication a("/authentication-required", 1s); - auto &pubSub = a.pubSub; - - pubSub.start(); - - pubSub.listenToChannelModerationActions("123456"); - - std::this_thread::sleep_for(50ms); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 0); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); - ASSERT_EQ(pubSub.diag.messagesReceived, 2); - ASSERT_EQ(pubSub.diag.listenResponses, 0); - ASSERT_EQ(pubSub.diag.failedListenResponses, 1); - - pubSub.stop(); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 1); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); -} - -TEST(TwitchPubSubClient, CorrectToken) -{ - // The token that's required is "xD" - MockApplication a("/authentication-required", 1s, "xD"); - auto &pubSub = a.pubSub; - - pubSub.start(); - - pubSub.listenToChannelModerationActions("123456"); - - std::this_thread::sleep_for(50ms); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 0); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); - ASSERT_EQ(pubSub.diag.messagesReceived, 2); - ASSERT_EQ(pubSub.diag.listenResponses, 1); - ASSERT_EQ(pubSub.diag.failedListenResponses, 0); - - pubSub.stop(); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 1); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); -} - -TEST(TwitchPubSubClient, AutoModMessageHeld) -{ - MockApplication a("/automod-held", 1s); - auto &pubSub = a.pubSub; - - pubSub.start(); - - ReceivedMessage received; - ReceivedMessage channelID; - - std::ignore = pubSub.moderation.autoModMessageCaught.connect( - [&](const auto &msg, const QString &incomingChannelID) { - received = msg; - channelID = incomingChannelID; - }); - - pubSub.listenToAutomod("117166826"); - - std::this_thread::sleep_for(50ms); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 0); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); - ASSERT_EQ(pubSub.diag.messagesReceived, 3); - ASSERT_EQ(pubSub.diag.listenResponses, 1); - ASSERT_EQ(pubSub.diag.failedListenResponses, 0); - - ASSERT_TRUE(received); - ASSERT_TRUE(channelID); - - ASSERT_EQ(channelID, "117166826"); - ASSERT_EQ(received->messageText, "kurwa"); - - pubSub.stop(); - - ASSERT_EQ(pubSub.diag.connectionsOpened, 1); - ASSERT_EQ(pubSub.diag.connectionsClosed, 1); - ASSERT_EQ(pubSub.diag.connectionsFailed, 0); -} - } // namespace chatterino #endif