From 044dc69aa653cd6a1b74de239eb8be4b3e1e81d6 Mon Sep 17 00:00:00 2001 From: nerix Date: Tue, 25 Feb 2025 21:13:05 +0100 Subject: [PATCH] feat(eventsub): implement (shared-chat-)ban (#5985) --- CHANGELOG.md | 2 +- lib/twitch-eventsub-ws/ast/lib/member.py | 8 +- lib/twitch-eventsub-ws/ast/lib/struct.py | 8 +- .../lib/templates/struct-implementation.tmpl | 13 +- lib/twitch-eventsub-ws/ast/lib/walker.py | 6 + .../payloads/channel-moderate-v2.hpp | 14 +- .../payloads/channel-moderate-v2.cpp | 281 +++++++++++++++--- src/providers/twitch/eventsub/Connection.cpp | 48 +-- .../twitch/eventsub/MessageBuilder.cpp | 33 ++ .../twitch/eventsub/MessageBuilder.hpp | 5 + .../twitch/eventsub/MessageHandlers.cpp | 16 + .../twitch/eventsub/MessageHandlers.hpp | 7 + tests/snapshots/EventSub/channel-ban/ban.json | 112 ------- .../EventSub/channel-ban/timeout.json | 115 ------- .../EventSub/channel-moderate/ban-reason.json | 172 +++++++++++ .../EventSub/channel-moderate/ban.json | 156 ++++++++++ .../channel-moderate/shared-ban-reason.json | 206 +++++++++++++ .../EventSub/channel-moderate/shared-ban.json | 189 ++++++++++++ 18 files changed, 1060 insertions(+), 331 deletions(-) create mode 100644 tests/snapshots/EventSub/channel-moderate/ban-reason.json create mode 100644 tests/snapshots/EventSub/channel-moderate/ban.json create mode 100644 tests/snapshots/EventSub/channel-moderate/shared-ban-reason.json create mode 100644 tests/snapshots/EventSub/channel-moderate/shared-ban.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 25cd90ee..d12084cd 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) +- 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) - 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/ast/lib/member.py b/lib/twitch-eventsub-ws/ast/lib/member.py index 52f9b5ed..04ffa9b1 100644 --- a/lib/twitch-eventsub-ws/ast/lib/member.py +++ b/lib/twitch-eventsub-ws/ast/lib/member.py @@ -55,12 +55,6 @@ def _is_trivially_copyable(type: clang.cindex.Type) -> bool: return _is_chrono_like_type(type) -def _has_no_fields(type: clang.cindex.Type) -> bool: - for _ in type.get_fields(): - return False - return True - - @dataclass class VariantType: name: str @@ -179,7 +173,7 @@ class Member: VariantType( name=name, trivial=_is_trivially_copyable(inner), - empty=_has_no_fields(inner), + empty=inner.get_size() <= 1, ) ) diff --git a/lib/twitch-eventsub-ws/ast/lib/struct.py b/lib/twitch-eventsub-ws/ast/lib/struct.py index ba6ec397..416d63d4 100644 --- a/lib/twitch-eventsub-ws/ast/lib/struct.py +++ b/lib/twitch-eventsub-ws/ast/lib/struct.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import List +from typing import List, Optional import logging @@ -20,6 +20,7 @@ class Struct: self.comment_commands = CommentCommands() self.inner_root: str = "" self.namespace = namespace + self.base: Optional[str] = None @property def full_name(self) -> str: @@ -48,3 +49,8 @@ class Struct: def apply_comment_commands(self, comment_commands: CommentCommands) -> None: self.inner_root = comment_commands.inner_root + + def validate(self) -> None: + assert not (self.base and self.members), ( + f"Unsupported: Struct {self.name} has both a base class as well as additional members" + ) diff --git a/lib/twitch-eventsub-ws/ast/lib/templates/struct-implementation.tmpl b/lib/twitch-eventsub-ws/ast/lib/templates/struct-implementation.tmpl index c32931c2..08943a7d 100644 --- a/lib/twitch-eventsub-ws/ast/lib/templates/struct-implementation.tmpl +++ b/lib/twitch-eventsub-ws/ast/lib/templates/struct-implementation.tmpl @@ -1,5 +1,5 @@ boost::json::result_for<{{struct.full_name}}, boost::json::value>::type tag_invoke( - boost::json::try_value_to_tag<{{struct.full_name}}> /* tag */, const boost::json::value &{%- if struct.members|length -%}jvRoot{%- else -%}/* jvRoot */{%- endif -%}) + boost::json::try_value_to_tag<{{struct.full_name}}> /* tag */, const boost::json::value &{%- if struct.members|length or struct.base -%}jvRoot{%- else -%}/* jvRoot */{%- endif -%}) { {% if struct.inner_root %} if (!jvRoot.is_object()) @@ -24,6 +24,12 @@ boost::json::result_for<{{struct.full_name}}, boost::json::value>::type tag_invo EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject); } const auto &root = jvRoot.get_object(); + {% elif struct.base %} + auto base = boost::json::try_value_to<{{struct.base}}>(jvRoot); + if (base.has_error()) + { + return base.error(); + } {% endif %} {% for field in struct.members %} @@ -56,6 +62,9 @@ boost::json::result_for<{{struct.full_name}}, boost::json::value>::type tag_invo {%- elif field.member_type == MemberType.VARIANT -%} {% include 'initializer-variant.tmpl' indent content %} {%- endif -%} -{% endfor %} +{% endfor -%} +{%- if struct.base %} + std::move(*base) +{% endif -%} }; } diff --git a/lib/twitch-eventsub-ws/ast/lib/walker.py b/lib/twitch-eventsub-ws/ast/lib/walker.py index 05195b55..48762384 100644 --- a/lib/twitch-eventsub-ws/ast/lib/walker.py +++ b/lib/twitch-eventsub-ws/ast/lib/walker.py @@ -36,6 +36,7 @@ class Walker: for child in node.get_children(): self.handle_node(child, new_struct, None) + new_struct.validate() self.structs.append(new_struct) return True @@ -83,6 +84,11 @@ class Walker: self.namespace = self.namespace[:-1] return True + case CursorKind.CXX_BASE_SPECIFIER: + assert struct + struct.base = node.type.spelling + return False + case CursorKind.FUNCTION_DECL: # Ignore function declarations pass diff --git a/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/payloads/channel-moderate-v2.hpp b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/payloads/channel-moderate-v2.hpp index c64df994..3c4cb1a1 100644 --- a/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/payloads/channel-moderate-v2.hpp +++ b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/payloads/channel-moderate-v2.hpp @@ -161,13 +161,11 @@ struct Unmod { struct Ban { static constexpr std::string_view TAG = "ban"; - static constexpr std::string_view FIELD = "ban"; - std::string userID; - std::string userLogin; - std::string userName; - // TODO: Verify that we handle null here - std::string reason; + String userID; + String userLogin; + String userName; + String reason; }; struct SharedChatBan : public Ban { static constexpr std::string_view TAG = "shared_chat_ban"; @@ -179,7 +177,6 @@ struct SharedChatBan : public Ban { struct Unban { static constexpr std::string_view TAG = "unban"; - static constexpr std::string_view FIELD = "unban"; std::string userID; std::string userLogin; @@ -199,7 +196,6 @@ struct SharedChatUnban : public Ban { struct Timeout { static constexpr std::string_view TAG = "timeout"; - static constexpr std::string_view FIELD = "timeout"; std::string userID; std::string userLogin; @@ -219,7 +215,6 @@ struct SharedChatTimeout : public Ban { struct Untimeout { static constexpr std::string_view TAG = "untimeout"; - static constexpr std::string_view FIELD = "untimeout"; std::string userID; std::string userLogin; @@ -261,7 +256,6 @@ struct Unraid { struct Delete { static constexpr std::string_view TAG = "delete"; - static constexpr std::string_view FIELD = "delete"; std::string userID; std::string userLogin; diff --git a/lib/twitch-eventsub-ws/src/generated/payloads/channel-moderate-v2.cpp b/lib/twitch-eventsub-ws/src/generated/payloads/channel-moderate-v2.cpp index a60e3477..ba7ddf0c 100644 --- a/lib/twitch-eventsub-ws/src/generated/payloads/channel-moderate-v2.cpp +++ b/lib/twitch-eventsub-ws/src/generated/payloads/channel-moderate-v2.cpp @@ -325,7 +325,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()) { @@ -338,7 +338,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()) { @@ -351,7 +351,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()) { @@ -364,7 +364,7 @@ boost::json::result_for::type tag_invoke( EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); } - auto reason = boost::json::try_value_to(*jvreason); + auto reason = boost::json::try_value_to(*jvreason); if (reason.has_error()) { @@ -381,9 +381,15 @@ boost::json::result_for::type tag_invoke( boost::json::result_for::type tag_invoke( boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return SharedChatBan{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return SharedChatBan{std::move(*base)}; } boost::json::result_for::type tag_invoke( @@ -444,9 +450,15 @@ boost::json::result_for::type tag_invoke( boost::json::result_for::type tag_invoke( boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return SharedChatUnban{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return SharedChatUnban{std::move(*base)}; } boost::json::result_for::type tag_invoke( @@ -535,9 +547,15 @@ boost::json::result_for::type tag_invoke( boost::json::result_for::type tag_invoke( boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return SharedChatTimeout{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return SharedChatTimeout{std::move(*base)}; } boost::json::result_for::type tag_invoke( @@ -598,9 +616,15 @@ boost::json::result_for::type tag_invoke( boost::json::result_for::type tag_invoke(boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return SharedChatUntimeout{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return SharedChatUntimeout{std::move(*base)}; } boost::json::result_for::type tag_invoke( @@ -817,9 +841,15 @@ boost::json::result_for::type tag_invoke( boost::json::result_for::type tag_invoke( boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return SharedChatDelete{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return SharedChatDelete{std::move(*base)}; } boost::json::result_for::type tag_invoke( @@ -898,30 +928,54 @@ boost::json::result_for::type tag_invoke( boost::json::result_for::type tag_invoke( boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return AddBlockedTerm{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return AddBlockedTerm{std::move(*base)}; } boost::json::result_for::type tag_invoke( boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return AddPermittedTerm{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return AddPermittedTerm{std::move(*base)}; } boost::json::result_for::type tag_invoke( boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return RemoveBlockedTerm{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return RemoveBlockedTerm{std::move(*base)}; } boost::json::result_for::type tag_invoke(boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return RemovePermittedTerm{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return RemovePermittedTerm{std::move(*base)}; } boost::json::result_for::type tag_invoke( @@ -1013,16 +1067,28 @@ boost::json::result_for::type tag_invoke( boost::json::result_for::type tag_invoke(boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return ApproveUnbanRequest{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return ApproveUnbanRequest{std::move(*base)}; } boost::json::result_for::type tag_invoke( boost::json::try_value_to_tag /* tag */, - const boost::json::value & /* jvRoot */) + const boost::json::value &jvRoot) { - return DenyUnbanRequest{}; + auto base = boost::json::try_value_to(jvRoot); + if (base.has_error()) + { + return base.error(); + } + + return DenyUnbanRequest{std::move(*base)}; } boost::json::result_for::type tag_invoke( @@ -1519,19 +1585,70 @@ boost::json::result_for::type tag_invoke( } else if (actionTag == AddBlockedTerm::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionAddBlockedTerm = + boost::json::try_value_to(*actionVal); + if (actionAddBlockedTerm.has_error()) + { + return actionAddBlockedTerm.error(); + } + action.emplace(std::move(actionAddBlockedTerm.value())); } else if (actionTag == AddPermittedTerm::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionAddPermittedTerm = + boost::json::try_value_to(*actionVal); + if (actionAddPermittedTerm.has_error()) + { + return actionAddPermittedTerm.error(); + } + action.emplace( + std::move(actionAddPermittedTerm.value())); } else if (actionTag == RemoveBlockedTerm::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionRemoveBlockedTerm = + boost::json::try_value_to(*actionVal); + if (actionRemoveBlockedTerm.has_error()) + { + return actionRemoveBlockedTerm.error(); + } + action.emplace( + std::move(actionRemoveBlockedTerm.value())); } else if (actionTag == RemovePermittedTerm::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionRemovePermittedTerm = + boost::json::try_value_to(*actionVal); + if (actionRemovePermittedTerm.has_error()) + { + return actionRemovePermittedTerm.error(); + } + action.emplace( + std::move(actionRemovePermittedTerm.value())); } else if (actionTag == Mod::TAG) { @@ -1563,11 +1680,37 @@ boost::json::result_for::type tag_invoke( } else if (actionTag == ApproveUnbanRequest::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionApproveUnbanRequest = + boost::json::try_value_to(*actionVal); + if (actionApproveUnbanRequest.has_error()) + { + return actionApproveUnbanRequest.error(); + } + action.emplace( + std::move(actionApproveUnbanRequest.value())); } else if (actionTag == DenyUnbanRequest::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionDenyUnbanRequest = + boost::json::try_value_to(*actionVal); + if (actionDenyUnbanRequest.has_error()) + { + return actionDenyUnbanRequest.error(); + } + action.emplace( + std::move(actionDenyUnbanRequest.value())); } else if (actionTag == Warn::TAG) { @@ -1585,23 +1728,87 @@ boost::json::result_for::type tag_invoke( } else if (actionTag == SharedChatBan::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionSharedChatBan = + boost::json::try_value_to(*actionVal); + if (actionSharedChatBan.has_error()) + { + return actionSharedChatBan.error(); + } + action.emplace(std::move(actionSharedChatBan.value())); } else if (actionTag == SharedChatTimeout::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionSharedChatTimeout = + boost::json::try_value_to(*actionVal); + if (actionSharedChatTimeout.has_error()) + { + return actionSharedChatTimeout.error(); + } + action.emplace( + std::move(actionSharedChatTimeout.value())); } else if (actionTag == SharedChatUnban::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionSharedChatUnban = + boost::json::try_value_to(*actionVal); + if (actionSharedChatUnban.has_error()) + { + return actionSharedChatUnban.error(); + } + action.emplace( + std::move(actionSharedChatUnban.value())); } else if (actionTag == SharedChatUntimeout::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionSharedChatUntimeout = + boost::json::try_value_to(*actionVal); + if (actionSharedChatUntimeout.has_error()) + { + return actionSharedChatUntimeout.error(); + } + action.emplace( + std::move(actionSharedChatUntimeout.value())); } else if (actionTag == SharedChatDelete::TAG) { - action.emplace(); + const auto *actionVal = + root.if_contains(detail::fieldFor()); + if (!actionVal) + { + EVENTSUB_BAIL_HERE(error::Kind::FieldMissing); + } + auto actionSharedChatDelete = + boost::json::try_value_to(*actionVal); + if (actionSharedChatDelete.has_error()) + { + return actionSharedChatDelete.error(); + } + action.emplace( + std::move(actionSharedChatDelete.value())); } else { diff --git a/src/providers/twitch/eventsub/Connection.cpp b/src/providers/twitch/eventsub/Connection.cpp index 213eb93b..1b2ae43c 100644 --- a/src/providers/twitch/eventsub/Connection.cpp +++ b/src/providers/twitch/eventsub/Connection.cpp @@ -73,52 +73,8 @@ void Connection::onChannelBan( const lib::payload::channel_ban::v1::Payload &payload) { (void)metadata; - - auto roomID = QString::fromStdString(payload.event.broadcasterUserID); - - BanAction action{}; - - if (!getApp()->isTest()) - { - action.timestamp = std::chrono::steady_clock::now(); - } - action.roomID = roomID; - action.source = ActionUser{ - .id = QString::fromStdString(payload.event.moderatorUserID), - .login = QString::fromStdString(payload.event.moderatorUserLogin), - .displayName = QString::fromStdString(payload.event.moderatorUserName), - }; - action.target = ActionUser{ - .id = QString::fromStdString(payload.event.userID), - .login = QString::fromStdString(payload.event.userLogin), - .displayName = QString::fromStdString(payload.event.userName), - }; - action.reason = QString::fromStdString(payload.event.reason); - if (payload.event.isPermanent) - { - action.duration = 0; - } - else - { - auto timeoutDuration = payload.event.timeoutDuration(); - auto timeoutDurationInSeconds = - std::chrono::duration_cast(timeoutDuration) - .count(); - action.duration = timeoutDurationInSeconds; - } - - auto chan = getApp()->getTwitch()->getChannelOrEmptyByID(roomID); - - runInGuiThread([action{std::move(action)}, chan{std::move(chan)}] { - auto time = QDateTime::currentDateTime(); - if (getApp()->isTest()) - { - time = QDateTime::fromSecsSinceEpoch(0).toUTC(); - } - MessageBuilder msg(action, time); - msg->flags.set(MessageFlag::PubSub); - chan->addOrReplaceTimeout(msg.release(), QDateTime::currentDateTime()); - }); + qCDebug(LOG) << "On channel ban event for channel" + << payload.event.broadcasterUserLogin.c_str(); } void Connection::onStreamOnline( diff --git a/src/providers/twitch/eventsub/MessageBuilder.cpp b/src/providers/twitch/eventsub/MessageBuilder.cpp index b89787f4..941c4464 100644 --- a/src/providers/twitch/eventsub/MessageBuilder.cpp +++ b/src/providers/twitch/eventsub/MessageBuilder.cpp @@ -107,4 +107,37 @@ void makeModerateMessage(EventSubMessageBuilder &builder, builder.message().searchText = text; } +void makeModerateMessage(EventSubMessageBuilder &builder, + const lib::payload::channel_moderate::v2::Event &event, + const lib::payload::channel_moderate::v2::Ban &action) +{ + QString text; + bool isShared = event.isFromSharedChat(); + + builder.appendUser(event.moderatorUserName, event.moderatorUserLogin, text); + builder.emplaceSystemTextAndUpdate("banned", text); + builder.appendUser(action.userName, action.userLogin, text, isShared); + + if (isShared) + { + builder.emplaceSystemTextAndUpdate("in", text); + builder.appendUser(*event.sourceBroadcasterUserName, + *event.sourceBroadcasterUserLogin, text, false); + } + + if (action.reason.view().empty()) + { + builder.emplaceSystemTextAndUpdate(".", text); + } + else + { + builder.emplaceSystemTextAndUpdate(":", text); + builder.emplaceSystemTextAndUpdate(action.reason.qt(), text); + } + + builder->messageText = text; + builder->searchText = text; + builder->timeoutUser = action.userLogin.qt(); +} + } // namespace chatterino::eventsub diff --git a/src/providers/twitch/eventsub/MessageBuilder.hpp b/src/providers/twitch/eventsub/MessageBuilder.hpp index 664c7d25..28683ca0 100644 --- a/src/providers/twitch/eventsub/MessageBuilder.hpp +++ b/src/providers/twitch/eventsub/MessageBuilder.hpp @@ -43,4 +43,9 @@ void makeModerateMessage( const lib::payload::channel_moderate::v2::Event &event, const lib::payload::channel_moderate::v2::Warn &action); +/// banned [ in ]: +void makeModerateMessage(EventSubMessageBuilder &builder, + const lib::payload::channel_moderate::v2::Event &event, + const lib::payload::channel_moderate::v2::Ban &action); + } // namespace chatterino::eventsub diff --git a/src/providers/twitch/eventsub/MessageHandlers.cpp b/src/providers/twitch/eventsub/MessageHandlers.cpp index 2f27c5d0..ef8caa14 100644 --- a/src/providers/twitch/eventsub/MessageHandlers.cpp +++ b/src/providers/twitch/eventsub/MessageHandlers.cpp @@ -1,6 +1,7 @@ #include "providers/twitch/eventsub/MessageHandlers.hpp" #include "Application.hpp" +#include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "singletons/Settings.hpp" @@ -26,4 +27,19 @@ void handleModerateMessage( }); } +void addModerateMessage( + TwitchChannel *channel, MessagePtr message, const QDateTime &time, + const lib::payload::channel_moderate::v2::Ban & /*action*/) +{ + runInGuiThread([channel, msg{std::move(message)}, time] { + channel->addOrReplaceTimeout(msg, time); + 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(); + } + }); +} + } // namespace chatterino::eventsub diff --git a/src/providers/twitch/eventsub/MessageHandlers.hpp b/src/providers/twitch/eventsub/MessageHandlers.hpp index c28dc56b..6850f7e6 100644 --- a/src/providers/twitch/eventsub/MessageHandlers.hpp +++ b/src/providers/twitch/eventsub/MessageHandlers.hpp @@ -2,10 +2,17 @@ #include "twitch-eventsub-ws/payloads/channel-moderate-v2.hpp" +#include + class QDateTime; namespace chatterino { + class TwitchChannel; + +struct Message; +using MessagePtr = std::shared_ptr; + } // namespace chatterino namespace chatterino::eventsub { diff --git a/tests/snapshots/EventSub/channel-ban/ban.json b/tests/snapshots/EventSub/channel-ban/ban.json index 5c8051cf..ba81912a 100644 --- a/tests/snapshots/EventSub/channel-ban/ban.json +++ b/tests/snapshots/EventSub/channel-ban/ban.json @@ -15,117 +15,5 @@ "user_name": "testFromUser" }, "output": [ - { - "badgeInfos": { - }, - "badges": [ - ], - "channelName": "", - "count": 1, - "displayName": "", - "elements": [ - { - "element": { - "color": "System", - "flags": "Timestamp", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "0:00" - ] - }, - "flags": "Timestamp", - "format": "", - "link": { - "type": "None", - "value": "" - }, - "time": "00:00:00", - "tooltip": "", - "trailingSpace": true, - "type": "TimestampElement" - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "UserInfo", - "value": "CLIModerator" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "CLIModerator" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "banned" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "UserInfo", - "value": "testFromUser" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "testFromUser:" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "\"This", - "is", - "a", - "test", - "event\"." - ] - } - ], - "flags": "System|Timeout|PubSub", - "id": "", - "localizedName": "", - "loginName": "CLIModerator", - "messageText": "CLIModerator banned testFromUser: \"This is a test event\". ", - "searchText": "CLIModerator banned testFromUser: \"This is a test event\". ", - "serverReceivedTime": "1970-01-01T00:00:00Z", - "timeoutUser": "testFromUser", - "userID": "", - "usernameColor": "#ff000000" - } ] } diff --git a/tests/snapshots/EventSub/channel-ban/timeout.json b/tests/snapshots/EventSub/channel-ban/timeout.json index 437d2596..9b7d36f9 100644 --- a/tests/snapshots/EventSub/channel-ban/timeout.json +++ b/tests/snapshots/EventSub/channel-ban/timeout.json @@ -15,120 +15,5 @@ "user_name": "testFromUser" }, "output": [ - { - "badgeInfos": { - }, - "badges": [ - ], - "channelName": "", - "count": 1, - "displayName": "", - "elements": [ - { - "element": { - "color": "System", - "flags": "Timestamp", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "0:00" - ] - }, - "flags": "Timestamp", - "format": "", - "link": { - "type": "None", - "value": "" - }, - "time": "00:00:00", - "tooltip": "", - "trailingSpace": true, - "type": "TimestampElement" - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "UserInfo", - "value": "CLIModerator" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "CLIModerator" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "timed", - "out" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "UserInfo", - "value": "testFromUser" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "testFromUser" - ] - }, - { - "color": "System", - "flags": "Text", - "link": { - "type": "None", - "value": "" - }, - "style": "ChatMedium", - "tooltip": "", - "trailingSpace": true, - "type": "TextElement", - "words": [ - "for", - "10m:", - "\"This", - "is", - "a", - "test", - "event\"." - ] - } - ], - "flags": "System|Timeout|PubSub", - "id": "", - "localizedName": "", - "loginName": "CLIModerator", - "messageText": "CLIModerator timed out testFromUser for 10m: \"This is a test event\". ", - "searchText": "CLIModerator timed out testFromUser for 10m: \"This is a test event\". ", - "serverReceivedTime": "1970-01-01T00:00:00Z", - "timeoutUser": "testFromUser", - "userID": "", - "usernameColor": "#ff000000" - } ] } diff --git a/tests/snapshots/EventSub/channel-moderate/ban-reason.json b/tests/snapshots/EventSub/channel-moderate/ban-reason.json new file mode 100644 index 00000000..afdb8aaa --- /dev/null +++ b/tests/snapshots/EventSub/channel-moderate/ban-reason.json @@ -0,0 +1,172 @@ +{ + "input": { + "action": "ban", + "automod_terms": null, + "ban": { + "reason": "my reason", + "user_id": "489584266", + "user_login": "uint128", + "user_name": "uint128" + }, + "broadcaster_user_id": "11148817", + "broadcaster_user_login": "pajlada", + "broadcaster_user_name": "pajlada", + "delete": null, + "followers": null, + "mod": null, + "moderator_user_id": "129546453", + "moderator_user_login": "nerixyz", + "moderator_user_name": "nerixyz", + "raid": null, + "shared_chat_ban": null, + "shared_chat_delete": null, + "shared_chat_timeout": null, + "shared_chat_unban": null, + "shared_chat_untimeout": null, + "slow": null, + "source_broadcaster_user_id": null, + "source_broadcaster_user_login": null, + "source_broadcaster_user_name": null, + "timeout": null, + "unban": null, + "unban_request": null, + "unmod": null, + "unraid": null, + "untimeout": null, + "unvip": null, + "vip": null, + "warn": null + }, + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "", + "count": 1, + "displayName": "", + "elements": [ + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "0:00" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "00:00:00", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "nerixyz", + "words": [ + "nerixyz" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "banned" + ] + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": false, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "uint128", + "words": [ + "uint128" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + ":" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "my", + "reason" + ] + } + ], + "flags": "System|Timeout", + "id": "", + "localizedName": "", + "loginName": "nerixyz", + "messageText": "nerixyz banned uint128: my reason ", + "searchText": "nerixyz banned uint128: my reason ", + "serverReceivedTime": "1970-01-01T00:00:00Z", + "timeoutUser": "uint128", + "userID": "", + "usernameColor": "#ff000000" + } + ] +} diff --git a/tests/snapshots/EventSub/channel-moderate/ban.json b/tests/snapshots/EventSub/channel-moderate/ban.json new file mode 100644 index 00000000..0298e650 --- /dev/null +++ b/tests/snapshots/EventSub/channel-moderate/ban.json @@ -0,0 +1,156 @@ +{ + "input": { + "action": "ban", + "automod_terms": null, + "ban": { + "reason": "", + "user_id": "489584266", + "user_login": "uint128", + "user_name": "uint128" + }, + "broadcaster_user_id": "11148817", + "broadcaster_user_login": "pajlada", + "broadcaster_user_name": "pajlada", + "delete": null, + "followers": null, + "mod": null, + "moderator_user_id": "129546453", + "moderator_user_login": "nerixyz", + "moderator_user_name": "nerixyz", + "raid": null, + "shared_chat_ban": null, + "shared_chat_delete": null, + "shared_chat_timeout": null, + "shared_chat_unban": null, + "shared_chat_untimeout": null, + "slow": null, + "source_broadcaster_user_id": null, + "source_broadcaster_user_login": null, + "source_broadcaster_user_name": null, + "timeout": null, + "unban": null, + "unban_request": null, + "unmod": null, + "unraid": null, + "untimeout": null, + "unvip": null, + "vip": null, + "warn": null + }, + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "", + "count": 1, + "displayName": "", + "elements": [ + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "0:00" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "00:00:00", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "nerixyz", + "words": [ + "nerixyz" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "banned" + ] + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": false, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "uint128", + "words": [ + "uint128" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "." + ] + } + ], + "flags": "System|Timeout", + "id": "", + "localizedName": "", + "loginName": "nerixyz", + "messageText": "nerixyz banned uint128. ", + "searchText": "nerixyz banned uint128. ", + "serverReceivedTime": "1970-01-01T00:00:00Z", + "timeoutUser": "uint128", + "userID": "", + "usernameColor": "#ff000000" + } + ] +} diff --git a/tests/snapshots/EventSub/channel-moderate/shared-ban-reason.json b/tests/snapshots/EventSub/channel-moderate/shared-ban-reason.json new file mode 100644 index 00000000..908c6b32 --- /dev/null +++ b/tests/snapshots/EventSub/channel-moderate/shared-ban-reason.json @@ -0,0 +1,206 @@ +{ + "input": { + "action": "shared_chat_ban", + "automod_terms": null, + "ban": null, + "broadcaster_user_id": "11148817", + "broadcaster_user_login": "pajlada", + "broadcaster_user_name": "pajlada", + "delete": null, + "followers": null, + "mod": null, + "moderator_user_id": "129546453", + "moderator_user_login": "nerixyz", + "moderator_user_name": "nerixyz", + "raid": null, + "shared_chat_ban": { + "reason": "a reason πŸ˜‚", + "user_id": "141981764", + "user_login": "twitchdev", + "user_name": "TwitchDev" + }, + "shared_chat_delete": null, + "shared_chat_timeout": null, + "shared_chat_unban": null, + "shared_chat_untimeout": null, + "slow": null, + "source_broadcaster_user_id": "117166826", + "source_broadcaster_user_login": "testaccount_420", + "source_broadcaster_user_name": "ν…ŒμŠ€νŠΈκ³„μ •420", + "timeout": null, + "unban": null, + "unban_request": null, + "unmod": null, + "unraid": null, + "untimeout": null, + "unvip": null, + "vip": null, + "warn": null + }, + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "", + "count": 1, + "displayName": "", + "elements": [ + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "0:00" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "00:00:00", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "nerixyz", + "words": [ + "nerixyz" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "banned" + ] + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "twitchdev", + "words": [ + "TwitchDev" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "in" + ] + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": false, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "testaccount_420", + "words": [ + "ν…ŒμŠ€νŠΈκ³„μ •420" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + ":" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "a", + "reason", + "πŸ˜‚" + ] + } + ], + "flags": "System|Timeout", + "id": "", + "localizedName": "", + "loginName": "nerixyz", + "messageText": "nerixyz banned twitchdev in testaccount_420: a reason πŸ˜‚ ", + "searchText": "nerixyz banned twitchdev in testaccount_420: a reason πŸ˜‚ ", + "serverReceivedTime": "1970-01-01T00:00:00Z", + "timeoutUser": "twitchdev", + "userID": "", + "usernameColor": "#ff000000" + } + ] +} diff --git a/tests/snapshots/EventSub/channel-moderate/shared-ban.json b/tests/snapshots/EventSub/channel-moderate/shared-ban.json new file mode 100644 index 00000000..43334455 --- /dev/null +++ b/tests/snapshots/EventSub/channel-moderate/shared-ban.json @@ -0,0 +1,189 @@ +{ + "input": { + "action": "shared_chat_ban", + "automod_terms": null, + "ban": null, + "broadcaster_user_id": "11148817", + "broadcaster_user_login": "pajlada", + "broadcaster_user_name": "pajlada", + "delete": null, + "followers": null, + "mod": null, + "moderator_user_id": "129546453", + "moderator_user_login": "nerixyz", + "moderator_user_name": "nerixyz", + "raid": null, + "shared_chat_ban": { + "reason": "", + "user_id": "141981764", + "user_login": "twitchdev", + "user_name": "TwitchDev" + }, + "shared_chat_delete": null, + "shared_chat_timeout": null, + "shared_chat_unban": null, + "shared_chat_untimeout": null, + "slow": null, + "source_broadcaster_user_id": "117166826", + "source_broadcaster_user_login": "testaccount_420", + "source_broadcaster_user_name": "ν…ŒμŠ€νŠΈκ³„μ •420", + "timeout": null, + "unban": null, + "unban_request": null, + "unmod": null, + "unraid": null, + "untimeout": null, + "unvip": null, + "vip": null, + "warn": null + }, + "output": [ + { + "badgeInfos": { + }, + "badges": [ + ], + "channelName": "", + "count": 1, + "displayName": "", + "elements": [ + { + "element": { + "color": "System", + "flags": "Timestamp", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "0:00" + ] + }, + "flags": "Timestamp", + "format": "", + "link": { + "type": "None", + "value": "" + }, + "time": "00:00:00", + "tooltip": "", + "trailingSpace": true, + "type": "TimestampElement" + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "nerixyz", + "words": [ + "nerixyz" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "banned" + ] + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "twitchdev", + "words": [ + "TwitchDev" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "in" + ] + }, + { + "color": "Text", + "fallbackColor": "System", + "flags": "Text|Mention", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": false, + "type": "MentionElement", + "userColor": "System", + "userLoginName": "testaccount_420", + "words": [ + "ν…ŒμŠ€νŠΈκ³„μ •420" + ] + }, + { + "color": "System", + "flags": "Text", + "link": { + "type": "None", + "value": "" + }, + "style": "ChatMedium", + "tooltip": "", + "trailingSpace": true, + "type": "TextElement", + "words": [ + "." + ] + } + ], + "flags": "System|Timeout", + "id": "", + "localizedName": "", + "loginName": "nerixyz", + "messageText": "nerixyz banned twitchdev in testaccount_420. ", + "searchText": "nerixyz banned twitchdev in testaccount_420. ", + "serverReceivedTime": "1970-01-01T00:00:00Z", + "timeoutUser": "twitchdev", + "userID": "", + "usernameColor": "#ff000000" + } + ] +}