diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a58bff5..6f8f2e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ - Bugfix: Fixed the input font not immediately updating when zooming in/out. (#5960) - Bugfix: Fixed color input thinking blue is also red. (#5982) - Dev: Subscriptions to PubSub channel points redemption topics now use no auth token, making it continue to work during PubSub shutdown. (#5947) -- Dev: Add initial experimental EventSub support. (#5837, #5895, #5897, #5904, #5910, #5903, #5915, #5916, #5930, #5935, #5932, #5943, #5952, #5953, #5968, #5973, #5974, #5980, #5981, #5985, #5990, #5992, #5993, #5996, #5995, #6000, #6001, #6002, #6003) +- Dev: Add initial experimental EventSub support. (#5837, #5895, #5897, #5904, #5910, #5903, #5915, #5916, #5930, #5935, #5932, #5943, #5952, #5953, #5968, #5973, #5974, #5980, #5981, #5985, #5990, #5992, #5993, #5996, #5995, #6000, #6001, #6002, #6003, #6005) - Dev: Remove unneeded platform specifier for toasts. (#5914) - Dev: Highlight checks now use non-capturing groups for the boundaries. (#5784) - Dev: Removed unused PubSub whisper code. (#5898) diff --git a/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/payloads/automod-message-hold-v2.hpp b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/payloads/automod-message-hold-v2.hpp index 0bc87b26..98ed53a7 100644 --- a/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/payloads/automod-message-hold-v2.hpp +++ b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/payloads/automod-message-hold-v2.hpp @@ -12,16 +12,16 @@ namespace chatterino::eventsub::lib::payload::automod_message_hold::v2 { struct Event { // Broadcaster of the channel the message was sent in - std::string broadcasterUserID; - std::string broadcasterUserLogin; - std::string broadcasterUserName; + String broadcasterUserID; + String broadcasterUserLogin; + String broadcasterUserName; // User who sent the message - std::string userID; - std::string userLogin; - std::string userName; + String userID; + String userLogin; + String userName; - std::string messageID; + String messageID; chat::Message message; // TODO: use chrono? diff --git a/lib/twitch-eventsub-ws/src/generated/payloads/automod-message-hold-v2.cpp b/lib/twitch-eventsub-ws/src/generated/payloads/automod-message-hold-v2.cpp index e5fbf3a2..3eb4decc 100644 --- a/lib/twitch-eventsub-ws/src/generated/payloads/automod-message-hold-v2.cpp +++ b/lib/twitch-eventsub-ws/src/generated/payloads/automod-message-hold-v2.cpp @@ -25,7 +25,7 @@ boost::json::result_for::type tag_invoke( } auto broadcasterUserID = - boost::json::try_value_to(*jvbroadcasterUserID); + boost::json::try_value_to(*jvbroadcasterUserID); if (broadcasterUserID.has_error()) { @@ -40,7 +40,7 @@ boost::json::result_for::type tag_invoke( } auto broadcasterUserLogin = - boost::json::try_value_to(*jvbroadcasterUserLogin); + boost::json::try_value_to(*jvbroadcasterUserLogin); if (broadcasterUserLogin.has_error()) { @@ -55,7 +55,7 @@ boost::json::result_for::type tag_invoke( } auto broadcasterUserName = - boost::json::try_value_to(*jvbroadcasterUserName); + boost::json::try_value_to(*jvbroadcasterUserName); if (broadcasterUserName.has_error()) { @@ -68,7 +68,7 @@ boost::json::result_for::type tag_invoke( EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); } - auto userID = boost::json::try_value_to(*jvuserID); + auto userID = boost::json::try_value_to(*jvuserID); if (userID.has_error()) { @@ -81,7 +81,7 @@ boost::json::result_for::type tag_invoke( EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); } - auto userLogin = boost::json::try_value_to(*jvuserLogin); + auto userLogin = boost::json::try_value_to(*jvuserLogin); if (userLogin.has_error()) { @@ -94,7 +94,7 @@ boost::json::result_for::type tag_invoke( EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); } - auto userName = boost::json::try_value_to(*jvuserName); + auto userName = boost::json::try_value_to(*jvuserName); if (userName.has_error()) { @@ -107,7 +107,7 @@ boost::json::result_for::type tag_invoke( EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); } - auto messageID = boost::json::try_value_to(*jvmessageID); + auto messageID = boost::json::try_value_to(*jvmessageID); if (messageID.has_error()) { diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 8f731842..5d8230bd 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -1506,6 +1506,22 @@ void TwitchChannel::refreshPubSub() }, }, }); + this->eventSubAutomodMessageHoldHandle = + getApp()->getEventSub()->subscribe(eventsub::SubscriptionRequest{ + .subscriptionType = "automod.message.hold", + .subscriptionVersion = "2", + .conditions = + { + { + "broadcaster_user_id", + roomId, + }, + { + "moderator_user_id", + currentAccount->getUserId(), + }, + }, + }); } else { diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index dcbc5c86..1832b41b 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -509,6 +509,7 @@ private: std::vector bSignals_; eventsub::SubscriptionHandle eventSubChannelModerateHandle; + eventsub::SubscriptionHandle eventSubAutomodMessageHoldHandle; friend class TwitchIrcServer; friend class MessageBuilder; diff --git a/src/providers/twitch/eventsub/Connection.cpp b/src/providers/twitch/eventsub/Connection.cpp index 15921c8c..7193502a 100644 --- a/src/providers/twitch/eventsub/Connection.cpp +++ b/src/providers/twitch/eventsub/Connection.cpp @@ -3,6 +3,7 @@ #include "Application.hpp" #include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" +#include "controllers/highlights/HighlightController.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" #include "providers/twitch/eventsub/MessageBuilder.hpp" @@ -177,9 +178,57 @@ void Connection::onAutomodMessageHold( const lib::messages::Metadata &metadata, const lib::payload::automod_message_hold::v2::Payload &payload) { - (void)metadata; - qCDebug(LOG) << "On automod message hold for" - << payload.event.broadcasterUserLogin.c_str(); + auto *channel = dynamic_cast( + getApp() + ->getTwitch() + ->getChannelOrEmpty(payload.event.broadcasterUserLogin.qt()) + .get()); + if (!channel || channel->isEmpty()) + { + qCDebug(LOG) + << "Automod message hold for broadcaster we're not interested in" + << payload.event.broadcasterUserLogin.qt(); + return; + } + + auto time = chronoToQDateTime(metadata.messageTimestamp); + auto header = makeAutomodHoldMessageHeader(channel, time, payload.event); + auto body = makeAutomodHoldMessageBody(channel, time, payload.event); + + auto messageText = payload.event.message.text.qt(); + auto userLogin = payload.event.userLogin.qt(); + + runInGuiThread([channel, messageText, userLogin, header, body] { + auto [highlighted, highlightResult] = getApp()->getHighlights()->check( + {}, {}, userLogin, messageText, body->flags); + if (highlighted) + { + MessageBuilder::triggerHighlights( + channel, + { + .customSound = + highlightResult.customSoundUrl.value_or({}), + .playSound = highlightResult.playSound, + .windowAlert = highlightResult.alert, + }); + } + + channel->addMessage(header, MessageContext::Original); + channel->addMessage(body, MessageContext::Original); + + getApp()->getTwitch()->getAutomodChannel()->addMessage( + header, MessageContext::Original); + getApp()->getTwitch()->getAutomodChannel()->addMessage( + body, MessageContext::Original); + + if (getSettings()->showAutomodInMentions) + { + getApp()->getTwitch()->getMentionsChannel()->addMessage( + header, MessageContext::Original); + getApp()->getTwitch()->getMentionsChannel()->addMessage( + body, MessageContext::Original); + } + }); } void Connection::onAutomodMessageUpdate( const lib::messages::Metadata &metadata, diff --git a/src/providers/twitch/eventsub/MessageBuilder.cpp b/src/providers/twitch/eventsub/MessageBuilder.cpp index 540d31a7..7827cd94 100644 --- a/src/providers/twitch/eventsub/MessageBuilder.cpp +++ b/src/providers/twitch/eventsub/MessageBuilder.cpp @@ -1,8 +1,14 @@ #include "providers/twitch/eventsub/MessageBuilder.hpp" +#include "Application.hpp" #include "common/Literals.hpp" +#include "messages/Emote.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" +#include "singletons/Resources.hpp" +#include "singletons/Settings.hpp" +#include "singletons/StreamerMode.hpp" +#include "util/Helpers.hpp" namespace { @@ -33,6 +39,99 @@ void makeModeMessage(EventSubMessageBuilder &builder, builder.message().searchText = text; } +QString stringifyAutomodReason(const lib::automod::AutomodReason &reason, + QStringView /* message */) +{ + return reason.category.qt() % u" level " % QString::number(reason.level); +} + +QString stringifyAutomodReason(const lib::automod::BlockedTermReason &reason, + QStringView message) +{ + if (reason.termsFound.empty()) + { + return u"blocked term usage"_s; + } + + QString msg = [&] { + if (reason.termsFound.size() == 1) + { + return u"matches 1 blocked term"_s; + } + return u"matches %1 blocked terms"_s.arg(reason.termsFound.size()); + }(); + + if (getSettings()->streamerModeHideBlockedTermText && + getApp()->getStreamerMode()->isEnabled()) + { + return msg; + } + + for (size_t i = 0; i < reason.termsFound.size(); i++) + { + if (i == 0) + { + msg.append(u" \""); + } + else + { + msg.append(u"\", \""); + } + msg.append(codepointSlice(message, + reason.termsFound[i].boundary.startPos, + reason.termsFound[i].boundary.endPos + 1)); + } + msg.append(u'"'); + + return msg; +} + +// XXX: this is a duplicate from messages/MessageBuilder.cpp +EmotePtr makeAutoModBadge() +{ + return std::make_shared(Emote{ + .name = EmoteName{}, + .images = + ImageSet{Image::fromResourcePixmap(getResources().twitch.automod)}, + .tooltip = Tooltip{"AutoMod"}, + .homePage = + Url{"https://dashboard.twitch.tv/settings/moderation/automod"}, + }); +} + +QString localizedDisplayName( + const lib::payload::automod_message_hold::v2::Event &event) +{ + QString displayName = event.userName.qt(); + bool hasLocalizedName = + displayName.compare(event.userLogin.qt(), Qt::CaseInsensitive) != 0; + + switch (getSettings()->usernameDisplayMode.getValue()) + { + case UsernameDisplayMode::Username: { + if (hasLocalizedName) + { + displayName = event.userLogin.qt(); + } + break; + } + case UsernameDisplayMode::LocalizedName: { + break; + } + case UsernameDisplayMode::UsernameAndLocalizedName: { + if (hasLocalizedName) + { + displayName = + event.userLogin.qt() % '(' % event.userName.qt() % ')'; + } + break; + } + default: + break; + } + return displayName; +} + } // namespace namespace chatterino::eventsub { @@ -47,6 +146,12 @@ EventSubMessageBuilder::EventSubMessageBuilder(TwitchChannel *channel, this->message().serverReceivedTime = time; } +EventSubMessageBuilder::EventSubMessageBuilder(TwitchChannel *channel) + : channel(channel) +{ + this->message().flags.set(MessageFlag::EventSub); +} + EventSubMessageBuilder::~EventSubMessageBuilder() = default; void EventSubMessageBuilder::appendUser(const lib::String &userName, @@ -450,4 +555,95 @@ void makeModerateMessage( builder.message().searchText = text; } +MessagePtr makeAutomodHoldMessageHeader( + TwitchChannel *channel, const QDateTime &time, + const lib::payload::automod_message_hold::v2::Event &event) +{ + EventSubMessageBuilder builder(channel); + builder->serverReceivedTime = time; + builder->id = u"automod_" % event.messageID.qt(); + builder->loginName = u"automod"_s; + builder->channelName = event.broadcasterUserLogin.qt(); + builder->flags.set(MessageFlag::PubSub, MessageFlag::Timeout, + MessageFlag::AutoMod, + MessageFlag::AutoModOffendingMessageHeader); + builder->flags.set( + MessageFlag::AutoModBlockedTerm, + std::holds_alternative(event.reason)); + + // AutoMod shield badge + builder.emplace(makeAutoModBadge(), + MessageElementFlag::BadgeChannelAuthority); + // AutoMod "username" + builder.emplace("AutoMod:", MessageElementFlag::Text, + QColor(0, 0, 255), FontStyle::ChatMediumBold); + // AutoMod header message + auto reason = std::visit( + [&](const auto &r) { + return stringifyAutomodReason(r, event.message.text.qt()); + }, + event.reason); + builder.emplace(u"Held a message for reason: " % reason % + u". Allow will post it in chat. ", + MessageElementFlag::Text, MessageColor::Text); + // Allow link button + builder + .emplace("Allow", MessageElementFlag::Text, + MessageColor(QColor(0, 255, 0)), + FontStyle::ChatMediumBold) + ->setLink({Link::AutoModAllow, event.messageID.qt()}); + // Deny link button + builder + .emplace(" Deny", MessageElementFlag::Text, + MessageColor(QColor(255, 0, 0)), + FontStyle::ChatMediumBold) + ->setLink({Link::AutoModDeny, event.messageID.qt()}); + auto text = u"AutoMod: Held a message for reason: " % reason % + u". Allow will post " + "it in chat. Allow Deny"; + builder->messageText = text; + builder->searchText = text; + + return builder.release(); +} + +MessagePtr makeAutomodHoldMessageBody( + TwitchChannel *channel, const QDateTime &time, + const lib::payload::automod_message_hold::v2::Event &event) +{ + EventSubMessageBuilder builder(channel); + builder->serverReceivedTime = time; + builder->flags.set(MessageFlag::PubSub, MessageFlag::Timeout, + MessageFlag::AutoMod, + MessageFlag::AutoModOffendingMessage); + builder->flags.set( + MessageFlag::AutoModBlockedTerm, + std::holds_alternative(event.reason)); + + // Builder for offender's message + builder->channelName = event.broadcasterUserLogin.qt(); + builder + .emplace(u'#' + event.broadcasterUserLogin.qt(), + MessageElementFlag::ChannelName, + MessageColor::System) + ->setLink({Link::JumpToChannel, event.broadcasterUserLogin.qt()}); + builder.emplace(time.time()); + builder.emplace(); + builder->loginName = event.userLogin.qt(); + + auto displayName = localizedDisplayName(event); + // sender username + builder.emplace( + displayName + ':', event.userLogin.qt(), MessageColor::Text, + channel->getUserColor(event.userLogin.qt())); + // sender's message caught by AutoMod + builder.emplace(event.message.text.qt(), + MessageElementFlag::Text, MessageColor::Text); + auto text = displayName % u": " % event.message.text.qt(); + builder->messageText = text; + builder->searchText = text; + + return builder.release(); +} + } // namespace chatterino::eventsub diff --git a/src/providers/twitch/eventsub/MessageBuilder.hpp b/src/providers/twitch/eventsub/MessageBuilder.hpp index 57287108..5377b05c 100644 --- a/src/providers/twitch/eventsub/MessageBuilder.hpp +++ b/src/providers/twitch/eventsub/MessageBuilder.hpp @@ -2,6 +2,7 @@ #include "messages/MessageBuilder.hpp" #include "providers/twitch/TwitchChannel.hpp" +#include "twitch-eventsub-ws/payloads/automod-message-hold-v2.hpp" #include "twitch-eventsub-ws/payloads/channel-moderate-v2.hpp" #include @@ -20,7 +21,9 @@ namespace chatterino::eventsub { class EventSubMessageBuilder : public MessageBuilder { public: + // builds a system message and adds a timestamp element EventSubMessageBuilder(TwitchChannel *channel, const QDateTime &time); + EventSubMessageBuilder(TwitchChannel *channel); ~EventSubMessageBuilder(); EventSubMessageBuilder(const EventSubMessageBuilder &) = delete; @@ -145,4 +148,12 @@ void makeModerateMessage( const lib::payload::channel_moderate::v2::Event &event, const lib::payload::channel_moderate::v2::Unraid &action); +MessagePtr makeAutomodHoldMessageHeader( + TwitchChannel *channel, const QDateTime &time, + const lib::payload::automod_message_hold::v2::Event &event); + +MessagePtr makeAutomodHoldMessageBody( + TwitchChannel *channel, const QDateTime &time, + const lib::payload::automod_message_hold::v2::Event &event); + } // namespace chatterino::eventsub diff --git a/src/util/Helpers.cpp b/src/util/Helpers.cpp index 5e3780dd..e61b7036 100644 --- a/src/util/Helpers.cpp +++ b/src/util/Helpers.cpp @@ -333,4 +333,39 @@ QDateTime chronoToQDateTime(std::chrono::system_clock::time_point time) return dt; } +QStringView codepointSlice(QStringView str, qsizetype begin, qsizetype end) +{ + if (end <= begin || begin < 0) + { + return {}; + } + + qsizetype n = 0; + const QChar *pos = str.begin(); + const QChar *endPos = str.end(); + + const QChar *sliceBegin = nullptr; + while (n < end) + { + if (pos >= endPos) + { + return {}; + } + if (n == begin) + { + sliceBegin = pos; + } + + QChar cur = *pos++; + if (cur.isHighSurrogate() && pos < endPos && pos->isLowSurrogate()) + { + pos++; + } + n++; + } + assert(pos <= endPos); + + return {sliceBegin, pos}; +} + } // namespace chatterino diff --git a/src/util/Helpers.hpp b/src/util/Helpers.hpp index d079a7d7..91674eed 100644 --- a/src/util/Helpers.hpp +++ b/src/util/Helpers.hpp @@ -199,4 +199,13 @@ QLocale getSystemLocale(); /// Note: When running tests, this will always return a date-time in UTC. QDateTime chronoToQDateTime(std::chrono::system_clock::time_point time); +/// Slices a string based on codepoint indices. +/// +/// If the specified range is outside the string, an empty string view is +/// returned. +/// +/// @param begin Start index (inclusive, in codepoints) +/// @param end End index (exclusive, in codepoints) +QStringView codepointSlice(QStringView str, qsizetype begin, qsizetype end); + } // namespace chatterino diff --git a/tests/snapshots/EventSub/automod-message-hold/automod-category.json b/tests/snapshots/EventSub/automod-message-hold/automod-category.json new file mode 100644 index 00000000..42d302d6 --- /dev/null +++ b/tests/snapshots/EventSub/automod-message-hold/automod-category.json @@ -0,0 +1,257 @@ +{ + "input": { + "automod": { + "boundaries": [ + { + "end_pos": 2, + "start_pos": 0 + } + ], + "category": "swearing", + "level": 4 + }, + "blocked_term": null, + "broadcaster_user_id": "11148817", + "broadcaster_user_login": "pajlada", + "broadcaster_user_name": "pajlada", + "held_at": "2025-02-28T16:15:15.008668809Z", + "message": { + "fragments": [ + { + "cheermote": null, + "emote": null, + "text": "ass", + "type": "text" + } + ], + "text": "ass" + }, + "message_id": "19d067ff-89b5-4790-a720-97599894eb6b", + "reason": "automod", + "user_id": "129546453", + "user_login": "nerixyz", + "user_name": "nerixyz" + }, + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "emote": { + "homePage": "https://dashboard.twitch.tv/settings/moderation/automod", + "images": { + "1x": "" + }, + "name": "", + "tooltip": "AutoMod" + }, + "flags": "BadgeChannelAuthority", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "AutoMod", + "trailingSpace": true, + "type": "BadgeElement" + }, + { + "color": "#ff0000ff", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "AutoMod:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Held", + "a", + "message", + "for", + "reason:", + "swearing", + "level", + "4.", + "Allow", + "will", + "post", + "it", + "in", + "chat.", + "" + ] + }, + { + "color": "#ff00ff00", + "flags": "Text", + "link": { + "type": "AutoModAllow", + "value": "19d067ff-89b5-4790-a720-97599894eb6b" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Allow" + ] + }, + { + "color": "#ffff0000", + "flags": "Text", + "link": { + "type": "AutoModDeny", + "value": "19d067ff-89b5-4790-a720-97599894eb6b" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "", + "Deny" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessageHeader|EventSub", + "id": "automod_19d067ff-89b5-4790-a720-97599894eb6b", + "localizedName": "", + "loginName": "automod", + "messageText": "AutoMod: Held a message for reason: swearing level 4. Allow will post it in chat. Allow Deny", + "searchText": "AutoMod: Held a message for reason: swearing level 4. Allow will post it in chat. Allow Deny", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + }, + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "color": "System", + "flags": "ChannelName", + "link": { + "type": "JumpToChannel", + "value": "pajlada" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "#pajlada" + ] + }, + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "12:31" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "12:31:47", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "flags": "ModeratorTools", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "", + "trailingSpace": true, + "type": "TwitchModerationElement" + }, + { + "color": "Text", + "fallbackColor": "Text", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "Text", + "userLoginName": "nerixyz", + "words": [ + "nerixyz:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "ass" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessage|EventSub", + "id": "", + "localizedName": "", + "loginName": "nerixyz", + "messageText": "nerixyz: ass", + "searchText": "nerixyz: ass", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + } + ] +} diff --git a/tests/snapshots/EventSub/automod-message-hold/blocked-term-enclosed.json b/tests/snapshots/EventSub/automod-message-hold/blocked-term-enclosed.json new file mode 100644 index 00000000..035c75c6 --- /dev/null +++ b/tests/snapshots/EventSub/automod-message-hold/blocked-term-enclosed.json @@ -0,0 +1,271 @@ +{ + "input": { + "automod": null, + "blocked_term": { + "terms_found": [ + { + "boundary": { + "end_pos": 18, + "start_pos": 4 + }, + "owner_broadcaster_user_id": "489584266", + "owner_broadcaster_user_login": "uint128", + "owner_broadcaster_user_name": "uint128", + "term_id": "1a79a074-4aaf-4d84-ac87-eba0a65b9a26" + } + ] + }, + "broadcaster_user_id": "11148817", + "broadcaster_user_login": "pajlada", + "broadcaster_user_name": "pajlada", + "held_at": "2025-02-28T15:56:05.608943282Z", + "message": { + "fragments": [ + { + "cheermote": null, + "emote": null, + "text": "๐Ÿ˜‚ ๐Ÿ˜‚ ", + "type": "text" + }, + { + "cheermote": null, + "emote": null, + "text": "๐Ÿ‹โ€๐ŸŸฉblockedterm๐Ÿ˜‚", + "type": "text" + } + ], + "text": "๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ‹โ€๐ŸŸฉblockedterm๐Ÿ˜‚" + }, + "message_id": "0cbb6e48-1606-46c6-b1ac-c97626e1c5cb", + "reason": "blocked_term", + "user_id": "129546453", + "user_login": "nerixyz", + "user_name": "nerixyz" + }, + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "emote": { + "homePage": "https://dashboard.twitch.tv/settings/moderation/automod", + "images": { + "1x": "" + }, + "name": "", + "tooltip": "AutoMod" + }, + "flags": "BadgeChannelAuthority", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "AutoMod", + "trailingSpace": true, + "type": "BadgeElement" + }, + { + "color": "#ff0000ff", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "AutoMod:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Held", + "a", + "message", + "for", + "reason:", + "matches", + "1", + "blocked", + "term", + "\"๐Ÿ‹โ€๐ŸŸฉblockedterm๐Ÿ˜‚\".", + "Allow", + "will", + "post", + "it", + "in", + "chat.", + "" + ] + }, + { + "color": "#ff00ff00", + "flags": "Text", + "link": { + "type": "AutoModAllow", + "value": "0cbb6e48-1606-46c6-b1ac-c97626e1c5cb" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Allow" + ] + }, + { + "color": "#ffff0000", + "flags": "Text", + "link": { + "type": "AutoModDeny", + "value": "0cbb6e48-1606-46c6-b1ac-c97626e1c5cb" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "", + "Deny" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessageHeader|AutoModBlockedTerm|EventSub", + "id": "automod_0cbb6e48-1606-46c6-b1ac-c97626e1c5cb", + "localizedName": "", + "loginName": "automod", + "messageText": "AutoMod: Held a message for reason: matches 1 blocked term \"๐Ÿ‹โ€๐ŸŸฉblockedterm๐Ÿ˜‚\". Allow will post it in chat. Allow Deny", + "searchText": "AutoMod: Held a message for reason: matches 1 blocked term \"๐Ÿ‹โ€๐ŸŸฉblockedterm๐Ÿ˜‚\". Allow will post it in chat. Allow Deny", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + }, + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "color": "System", + "flags": "ChannelName", + "link": { + "type": "JumpToChannel", + "value": "pajlada" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "#pajlada" + ] + }, + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "12:31" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "12:31:47", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "flags": "ModeratorTools", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "", + "trailingSpace": true, + "type": "TwitchModerationElement" + }, + { + "color": "Text", + "fallbackColor": "Text", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "Text", + "userLoginName": "nerixyz", + "words": [ + "nerixyz:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "๐Ÿ˜‚", + "๐Ÿ˜‚", + "๐Ÿ‹โ€๐ŸŸฉblockedterm๐Ÿ˜‚" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessage|AutoModBlockedTerm|EventSub", + "id": "", + "localizedName": "", + "loginName": "nerixyz", + "messageText": "nerixyz: ๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ‹โ€๐ŸŸฉblockedterm๐Ÿ˜‚", + "searchText": "nerixyz: ๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ‹โ€๐ŸŸฉblockedterm๐Ÿ˜‚", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + } + ] +} diff --git a/tests/snapshots/EventSub/automod-message-hold/blocked-term.json b/tests/snapshots/EventSub/automod-message-hold/blocked-term.json new file mode 100644 index 00000000..56859de9 --- /dev/null +++ b/tests/snapshots/EventSub/automod-message-hold/blocked-term.json @@ -0,0 +1,263 @@ +{ + "input": { + "automod": null, + "blocked_term": { + "terms_found": [ + { + "boundary": { + "end_pos": 10, + "start_pos": 0 + }, + "owner_broadcaster_user_id": "489584266", + "owner_broadcaster_user_login": "uint128", + "owner_broadcaster_user_name": "uint128", + "term_id": "1a79a074-4aaf-4d84-ac87-eba0a65b9a26" + } + ] + }, + "broadcaster_user_id": "11148817", + "broadcaster_user_login": "pajlada", + "broadcaster_user_name": "pajlada", + "held_at": "2025-02-28T15:55:41.953497979Z", + "message": { + "fragments": [ + { + "cheermote": null, + "emote": null, + "text": "blockedterm", + "type": "text" + } + ], + "text": "blockedterm" + }, + "message_id": "c0fac418-25a9-41b8-adf3-5bd637fca1e1", + "reason": "blocked_term", + "user_id": "129546453", + "user_login": "nerixyz", + "user_name": "nerixyz" + }, + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "emote": { + "homePage": "https://dashboard.twitch.tv/settings/moderation/automod", + "images": { + "1x": "" + }, + "name": "", + "tooltip": "AutoMod" + }, + "flags": "BadgeChannelAuthority", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "AutoMod", + "trailingSpace": true, + "type": "BadgeElement" + }, + { + "color": "#ff0000ff", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "AutoMod:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Held", + "a", + "message", + "for", + "reason:", + "matches", + "1", + "blocked", + "term", + "\"blockedterm\".", + "Allow", + "will", + "post", + "it", + "in", + "chat.", + "" + ] + }, + { + "color": "#ff00ff00", + "flags": "Text", + "link": { + "type": "AutoModAllow", + "value": "c0fac418-25a9-41b8-adf3-5bd637fca1e1" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Allow" + ] + }, + { + "color": "#ffff0000", + "flags": "Text", + "link": { + "type": "AutoModDeny", + "value": "c0fac418-25a9-41b8-adf3-5bd637fca1e1" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "", + "Deny" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessageHeader|AutoModBlockedTerm|EventSub", + "id": "automod_c0fac418-25a9-41b8-adf3-5bd637fca1e1", + "localizedName": "", + "loginName": "automod", + "messageText": "AutoMod: Held a message for reason: matches 1 blocked term \"blockedterm\". Allow will post it in chat. Allow Deny", + "searchText": "AutoMod: Held a message for reason: matches 1 blocked term \"blockedterm\". Allow will post it in chat. Allow Deny", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + }, + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "color": "System", + "flags": "ChannelName", + "link": { + "type": "JumpToChannel", + "value": "pajlada" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "#pajlada" + ] + }, + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "12:31" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "12:31:47", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "flags": "ModeratorTools", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "", + "trailingSpace": true, + "type": "TwitchModerationElement" + }, + { + "color": "Text", + "fallbackColor": "Text", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "Text", + "userLoginName": "nerixyz", + "words": [ + "nerixyz:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "blockedterm" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessage|AutoModBlockedTerm|EventSub", + "id": "", + "localizedName": "", + "loginName": "nerixyz", + "messageText": "nerixyz: blockedterm", + "searchText": "nerixyz: blockedterm", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + } + ] +} diff --git a/tests/snapshots/EventSub/automod-message-hold/blocked-terms-4.json b/tests/snapshots/EventSub/automod-message-hold/blocked-terms-4.json new file mode 100644 index 00000000..f7d04ed3 --- /dev/null +++ b/tests/snapshots/EventSub/automod-message-hold/blocked-terms-4.json @@ -0,0 +1,321 @@ +{ + "input": { + "automod": null, + "blocked_term": { + "terms_found": [ + { + "boundary": { + "end_pos": 7, + "start_pos": 5 + }, + "owner_broadcaster_user_id": "489584266", + "owner_broadcaster_user_login": "uint128", + "owner_broadcaster_user_name": "uint128", + "term_id": "b48d9032-5033-4598-9650-da7a1e4b1f4c" + }, + { + "boundary": { + "end_pos": 19, + "start_pos": 9 + }, + "owner_broadcaster_user_id": "489584266", + "owner_broadcaster_user_login": "uint128", + "owner_broadcaster_user_name": "uint128", + "term_id": "1a79a074-4aaf-4d84-ac87-eba0a65b9a26" + }, + { + "boundary": { + "end_pos": 23, + "start_pos": 23 + }, + "owner_broadcaster_user_id": "489584266", + "owner_broadcaster_user_login": "uint128", + "owner_broadcaster_user_name": "uint128", + "term_id": "84b1f858-7170-4c33-be4c-97612dde8e10" + } + ] + }, + "broadcaster_user_id": "11148817", + "broadcaster_user_login": "pajlada", + "broadcaster_user_name": "pajlada", + "held_at": "2025-02-28T16:14:38.089369252Z", + "message": { + "fragments": [ + { + "cheermote": null, + "emote": null, + "text": "๐Ÿ˜‚ ๐Ÿ˜‚ ", + "type": "text" + }, + { + "cheermote": null, + "emote": null, + "text": "๐Ÿ‹โ€๐ŸŸฉ", + "type": "text" + }, + { + "cheermote": null, + "emote": null, + "text": " ", + "type": "text" + }, + { + "cheermote": null, + "emote": null, + "text": "blockedterm", + "type": "text" + }, + { + "cheermote": null, + "emote": null, + "text": " ๐Ÿ˜‚ ", + "type": "text" + }, + { + "cheermote": null, + "emote": null, + "text": "๐Ÿฉ", + "type": "text" + } + ], + "text": "๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ‹โ€๐ŸŸฉ blockedterm ๐Ÿ˜‚ ๐Ÿฉ" + }, + "message_id": "45a63ca1-1b0c-46ef-9075-d112f9a9f376", + "reason": "blocked_term", + "user_id": "129546453", + "user_login": "nerixyz", + "user_name": "nerixyz" + }, + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "emote": { + "homePage": "https://dashboard.twitch.tv/settings/moderation/automod", + "images": { + "1x": "" + }, + "name": "", + "tooltip": "AutoMod" + }, + "flags": "BadgeChannelAuthority", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "AutoMod", + "trailingSpace": true, + "type": "BadgeElement" + }, + { + "color": "#ff0000ff", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "AutoMod:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Held", + "a", + "message", + "for", + "reason:", + "matches", + "3", + "blocked", + "terms", + "\"๐Ÿ‹โ€๐ŸŸฉ\",", + "\"blockedterm\",", + "\"๐Ÿฉ\".", + "Allow", + "will", + "post", + "it", + "in", + "chat.", + "" + ] + }, + { + "color": "#ff00ff00", + "flags": "Text", + "link": { + "type": "AutoModAllow", + "value": "45a63ca1-1b0c-46ef-9075-d112f9a9f376" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Allow" + ] + }, + { + "color": "#ffff0000", + "flags": "Text", + "link": { + "type": "AutoModDeny", + "value": "45a63ca1-1b0c-46ef-9075-d112f9a9f376" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "", + "Deny" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessageHeader|AutoModBlockedTerm|EventSub", + "id": "automod_45a63ca1-1b0c-46ef-9075-d112f9a9f376", + "localizedName": "", + "loginName": "automod", + "messageText": "AutoMod: Held a message for reason: matches 3 blocked terms \"๐Ÿ‹โ€๐ŸŸฉ\", \"blockedterm\", \"๐Ÿฉ\". Allow will post it in chat. Allow Deny", + "searchText": "AutoMod: Held a message for reason: matches 3 blocked terms \"๐Ÿ‹โ€๐ŸŸฉ\", \"blockedterm\", \"๐Ÿฉ\". Allow will post it in chat. Allow Deny", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + }, + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "color": "System", + "flags": "ChannelName", + "link": { + "type": "JumpToChannel", + "value": "pajlada" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "#pajlada" + ] + }, + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "12:31" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "12:31:47", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "flags": "ModeratorTools", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "", + "trailingSpace": true, + "type": "TwitchModerationElement" + }, + { + "color": "Text", + "fallbackColor": "Text", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "Text", + "userLoginName": "nerixyz", + "words": [ + "nerixyz:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "๐Ÿ˜‚", + "", + "๐Ÿ˜‚", + "๐Ÿ‹โ€๐ŸŸฉ", + "blockedterm", + "๐Ÿ˜‚", + "๐Ÿฉ" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessage|AutoModBlockedTerm|EventSub", + "id": "", + "localizedName": "", + "loginName": "nerixyz", + "messageText": "nerixyz: ๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ‹โ€๐ŸŸฉ blockedterm ๐Ÿ˜‚ ๐Ÿฉ", + "searchText": "nerixyz: ๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ‹โ€๐ŸŸฉ blockedterm ๐Ÿ˜‚ ๐Ÿฉ", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + } + ] +} diff --git a/tests/snapshots/EventSub/automod-message-hold/localized-name.json b/tests/snapshots/EventSub/automod-message-hold/localized-name.json new file mode 100644 index 00000000..abe8a004 --- /dev/null +++ b/tests/snapshots/EventSub/automod-message-hold/localized-name.json @@ -0,0 +1,257 @@ +{ + "input": { + "automod": { + "boundaries": [ + { + "end_pos": 2, + "start_pos": 0 + } + ], + "category": "swearing", + "level": 4 + }, + "blocked_term": null, + "broadcaster_user_id": "11148817", + "broadcaster_user_login": "pajlada", + "broadcaster_user_name": "pajlada", + "held_at": "2025-02-28T16:15:15.008668809Z", + "message": { + "fragments": [ + { + "cheermote": null, + "emote": null, + "text": "ass", + "type": "text" + } + ], + "text": "ass" + }, + "message_id": "19d067ff-89b5-4790-a720-97599894eb6b", + "reason": "automod", + "user_id": "117166826", + "user_login": "testaccount_420", + "user_name": "ํ…Œ์ŠคํŠธ๊ณ„์ •420" + }, + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "emote": { + "homePage": "https://dashboard.twitch.tv/settings/moderation/automod", + "images": { + "1x": "" + }, + "name": "", + "tooltip": "AutoMod" + }, + "flags": "BadgeChannelAuthority", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "AutoMod", + "trailingSpace": true, + "type": "BadgeElement" + }, + { + "color": "#ff0000ff", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "AutoMod:" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Held", + "a", + "message", + "for", + "reason:", + "swearing", + "level", + "4.", + "Allow", + "will", + "post", + "it", + "in", + "chat.", + "" + ] + }, + { + "color": "#ff00ff00", + "flags": "Text", + "link": { + "type": "AutoModAllow", + "value": "19d067ff-89b5-4790-a720-97599894eb6b" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "Allow" + ] + }, + { + "color": "#ffff0000", + "flags": "Text", + "link": { + "type": "AutoModDeny", + "value": "19d067ff-89b5-4790-a720-97599894eb6b" + }, + "style": "ChatMediumBold", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "", + "Deny" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessageHeader|EventSub", + "id": "automod_19d067ff-89b5-4790-a720-97599894eb6b", + "localizedName": "", + "loginName": "automod", + "messageText": "AutoMod: Held a message for reason: swearing level 4. Allow will post it in chat. Allow Deny", + "searchText": "AutoMod: Held a message for reason: swearing level 4. Allow will post it in chat. Allow Deny", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + }, + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "pajlada", + "count": 1, + "displayName": "", + "elements": [ + { + "color": "System", + "flags": "ChannelName", + "link": { + "type": "JumpToChannel", + "value": "pajlada" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "#pajlada" + ] + }, + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "12:31" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "12:31:47", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "flags": "ModeratorTools", + "link": { + "type": "None", + "value": "" + }, + "tooltip": "", + "trailingSpace": true, + "type": "TwitchModerationElement" + }, + { + "color": "Text", + "fallbackColor": "Text", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "Text", + "userLoginName": "testaccount_420", + "words": [ + "testaccount_420(ํ…Œ์ŠคํŠธ๊ณ„์ •420):" + ] + }, + { + "color": "Text", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "ass" + ] + } + ], + "flags": "Timeout|PubSub|AutoMod|AutoModOffendingMessage|EventSub", + "id": "", + "localizedName": "", + "loginName": "testaccount_420", + "messageText": "testaccount_420(ํ…Œ์ŠคํŠธ๊ณ„์ •420): ass", + "searchText": "testaccount_420(ํ…Œ์ŠคํŠธ๊ณ„์ •420): ass", + "serverReceivedTime": "2024-05-14T12:31:47Z", + "timeoutUser": "", + "userID": "", + "usernameColor": "#ff000000" + } + ] +} diff --git a/tests/src/EventSubMessages.cpp b/tests/src/EventSubMessages.cpp index d31c425e..95fb524e 100644 --- a/tests/src/EventSubMessages.cpp +++ b/tests/src/EventSubMessages.cpp @@ -1,5 +1,6 @@ #include "common/Literals.hpp" #include "controllers/accounts/AccountController.hpp" +#include "controllers/highlights/HighlightController.hpp" #include "lib/Snapshot.hpp" #include "messages/Message.hpp" #include "mocks/BaseApplication.hpp" @@ -62,12 +63,34 @@ const std::map "cost": 0 })", }, + { + "automod-message-hold", + R"({ + "id": "a3122e32-6498-4847-8675-109b9b94f29c", + "status": "enabled", + "type": "automod.message.hold", + "version": "2", + "condition": { + "broadcaster_user_id": "489584266", + "moderator_user_id": "489584266" + }, + "transport": { + "method":"websocket", + "session_id":"AgoQ59RRLw0mS6S000QtK8f54BIGY2VsbC1j" + }, + "created_at": "2025-02-28T15:55:37.85489173Z", + "cost": 0 + })", + }, }; class MockApplication : public mock::BaseApplication { public: - MockApplication() = default; + MockApplication() + : highlights(this->settings, &this->accounts) + { + } ILogging *getChatLogger() override { @@ -84,9 +107,15 @@ public: return &this->accounts; } + HighlightController *getHighlights() override + { + return &this->highlights; + } + mock::EmptyLogging logging; mock::MockTwitchIrcServer twitch; AccountController accounts; + HighlightController highlights; }; std::shared_ptr makeMockTwitchChannel(const QString &name) diff --git a/tests/src/Helpers.cpp b/tests/src/Helpers.cpp index ab4b1e1a..1a127041 100644 --- a/tests/src/Helpers.cpp +++ b/tests/src/Helpers.cpp @@ -578,3 +578,38 @@ TEST(Helpers, chronoToQDateTime) ASSERT_EQ(qPointSinceEpoch.toString(Qt::ISODateWithMs), "2025-02-26T12:49:49.131Z"); } + +TEST(Helpers, codepointSlice) +{ + ASSERT_EQ(codepointSlice(u"", 0, 0), u""); + ASSERT_EQ(codepointSlice(u"", 0, 1), u""); + ASSERT_EQ(codepointSlice(u"", 1, 1), u""); + ASSERT_EQ(codepointSlice(u"", -1, 1), u""); + + ASSERT_EQ(codepointSlice(u"a", 0, 0), u""); + ASSERT_EQ(codepointSlice(u"a", 0, 1), u"a"); + ASSERT_EQ(codepointSlice(u"a", 0, 2), u""); + ASSERT_EQ(codepointSlice(u"a", -1, 1), u""); + + ASSERT_EQ(codepointSlice(u"abcd", 1, 3), u"bc"); + ASSERT_EQ(codepointSlice(u"abcd", 0, 3), u"abc"); + ASSERT_EQ(codepointSlice(u"abcd", 1, 4), u"bcd"); + ASSERT_EQ(codepointSlice(u"abcd", 0, 4), u"abcd"); + ASSERT_EQ(codepointSlice(u"abcd", 0, 5), u""); + ASSERT_EQ(codepointSlice(u"abcd", 5, 0), u""); + + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ๐Ÿฅš๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ", 1, 3), u"๐ŸŸ๐Ÿฅš"); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ๐Ÿฅš๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ", 0, 3), u"๐Ÿฉ๐ŸŸ๐Ÿฅš"); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ๐Ÿฅš๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ", 0, 9), + u"๐Ÿฉ๐ŸŸ๐Ÿฅš๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ"); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ๐Ÿฅš๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ", 3, 9), u"๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ"); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ๐Ÿฅš๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ", 3, 10), u""); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ๐Ÿฅš๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ", 3, 8), u"๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž"); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ๐Ÿฅš๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ", 3, 7), u"๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ"); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ๐Ÿฅš๐Ÿณ๐ŸŒฎ๐Ÿž๐ŸŒญ๐Ÿฅž๐Ÿณ", 3, 4), u"๐Ÿณ"); + + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ\xD83E\xDD5A", 0, 3), u"๐Ÿฉ๐ŸŸ๐Ÿฅš"); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ\xD83E\xDD5A", 0, 4), u""); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ\xD83E", 0, 3), u"๐Ÿฉ๐ŸŸ\xD83E"); + ASSERT_EQ(codepointSlice(u"๐Ÿฉ๐ŸŸ\xD83E๐Ÿฅš", 0, 4), u"๐Ÿฉ๐ŸŸ\xD83E๐Ÿฅš"); +}