feat(eventsub): implement (shared-chat-)ban (#5985)

This commit is contained in:
nerix
2025-02-25 21:13:05 +01:00
committed by GitHub
parent aee5f1a54d
commit 044dc69aa6
18 changed files with 1060 additions and 331 deletions
+1 -1
View File
@@ -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)
+1 -7
View File
@@ -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,
)
)
+7 -1
View File
@@ -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"
)
@@ -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 -%}
};
}
+6
View File
@@ -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
@@ -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;
@@ -325,7 +325,7 @@ boost::json::result_for<Ban, boost::json::value>::type tag_invoke(
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto userID = boost::json::try_value_to<std::string>(*jvuserID);
auto userID = boost::json::try_value_to<String>(*jvuserID);
if (userID.has_error())
{
@@ -338,7 +338,7 @@ boost::json::result_for<Ban, boost::json::value>::type tag_invoke(
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto userLogin = boost::json::try_value_to<std::string>(*jvuserLogin);
auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);
if (userLogin.has_error())
{
@@ -351,7 +351,7 @@ boost::json::result_for<Ban, boost::json::value>::type tag_invoke(
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto userName = boost::json::try_value_to<std::string>(*jvuserName);
auto userName = boost::json::try_value_to<String>(*jvuserName);
if (userName.has_error())
{
@@ -364,7 +364,7 @@ boost::json::result_for<Ban, boost::json::value>::type tag_invoke(
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto reason = boost::json::try_value_to<std::string>(*jvreason);
auto reason = boost::json::try_value_to<String>(*jvreason);
if (reason.has_error())
{
@@ -381,9 +381,15 @@ boost::json::result_for<Ban, boost::json::value>::type tag_invoke(
boost::json::result_for<SharedChatBan, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<SharedChatBan> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return SharedChatBan{};
auto base = boost::json::try_value_to<Ban>(jvRoot);
if (base.has_error())
{
return base.error();
}
return SharedChatBan{std::move(*base)};
}
boost::json::result_for<Unban, boost::json::value>::type tag_invoke(
@@ -444,9 +450,15 @@ boost::json::result_for<Unban, boost::json::value>::type tag_invoke(
boost::json::result_for<SharedChatUnban, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<SharedChatUnban> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return SharedChatUnban{};
auto base = boost::json::try_value_to<Ban>(jvRoot);
if (base.has_error())
{
return base.error();
}
return SharedChatUnban{std::move(*base)};
}
boost::json::result_for<Timeout, boost::json::value>::type tag_invoke(
@@ -535,9 +547,15 @@ boost::json::result_for<Timeout, boost::json::value>::type tag_invoke(
boost::json::result_for<SharedChatTimeout, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<SharedChatTimeout> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return SharedChatTimeout{};
auto base = boost::json::try_value_to<Ban>(jvRoot);
if (base.has_error())
{
return base.error();
}
return SharedChatTimeout{std::move(*base)};
}
boost::json::result_for<Untimeout, boost::json::value>::type tag_invoke(
@@ -598,9 +616,15 @@ boost::json::result_for<Untimeout, boost::json::value>::type tag_invoke(
boost::json::result_for<SharedChatUntimeout, boost::json::value>::type
tag_invoke(boost::json::try_value_to_tag<SharedChatUntimeout> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return SharedChatUntimeout{};
auto base = boost::json::try_value_to<Ban>(jvRoot);
if (base.has_error())
{
return base.error();
}
return SharedChatUntimeout{std::move(*base)};
}
boost::json::result_for<Raid, boost::json::value>::type tag_invoke(
@@ -817,9 +841,15 @@ boost::json::result_for<Delete, boost::json::value>::type tag_invoke(
boost::json::result_for<SharedChatDelete, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<SharedChatDelete> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return SharedChatDelete{};
auto base = boost::json::try_value_to<Ban>(jvRoot);
if (base.has_error())
{
return base.error();
}
return SharedChatDelete{std::move(*base)};
}
boost::json::result_for<AutomodTerms, boost::json::value>::type tag_invoke(
@@ -898,30 +928,54 @@ boost::json::result_for<AutomodTerms, boost::json::value>::type tag_invoke(
boost::json::result_for<AddBlockedTerm, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<AddBlockedTerm> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return AddBlockedTerm{};
auto base = boost::json::try_value_to<AutomodTerms>(jvRoot);
if (base.has_error())
{
return base.error();
}
return AddBlockedTerm{std::move(*base)};
}
boost::json::result_for<AddPermittedTerm, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<AddPermittedTerm> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return AddPermittedTerm{};
auto base = boost::json::try_value_to<AutomodTerms>(jvRoot);
if (base.has_error())
{
return base.error();
}
return AddPermittedTerm{std::move(*base)};
}
boost::json::result_for<RemoveBlockedTerm, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<RemoveBlockedTerm> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return RemoveBlockedTerm{};
auto base = boost::json::try_value_to<AutomodTerms>(jvRoot);
if (base.has_error())
{
return base.error();
}
return RemoveBlockedTerm{std::move(*base)};
}
boost::json::result_for<RemovePermittedTerm, boost::json::value>::type
tag_invoke(boost::json::try_value_to_tag<RemovePermittedTerm> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return RemovePermittedTerm{};
auto base = boost::json::try_value_to<AutomodTerms>(jvRoot);
if (base.has_error())
{
return base.error();
}
return RemovePermittedTerm{std::move(*base)};
}
boost::json::result_for<UnbanRequest, boost::json::value>::type tag_invoke(
@@ -1013,16 +1067,28 @@ boost::json::result_for<UnbanRequest, boost::json::value>::type tag_invoke(
boost::json::result_for<ApproveUnbanRequest, boost::json::value>::type
tag_invoke(boost::json::try_value_to_tag<ApproveUnbanRequest> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return ApproveUnbanRequest{};
auto base = boost::json::try_value_to<UnbanRequest>(jvRoot);
if (base.has_error())
{
return base.error();
}
return ApproveUnbanRequest{std::move(*base)};
}
boost::json::result_for<DenyUnbanRequest, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<DenyUnbanRequest> /* tag */,
const boost::json::value & /* jvRoot */)
const boost::json::value &jvRoot)
{
return DenyUnbanRequest{};
auto base = boost::json::try_value_to<UnbanRequest>(jvRoot);
if (base.has_error())
{
return base.error();
}
return DenyUnbanRequest{std::move(*base)};
}
boost::json::result_for<Warn, boost::json::value>::type tag_invoke(
@@ -1519,19 +1585,70 @@ boost::json::result_for<Event, boost::json::value>::type tag_invoke(
}
else if (actionTag == AddBlockedTerm::TAG)
{
action.emplace<AddBlockedTerm>();
const auto *actionVal =
root.if_contains(detail::fieldFor<AddBlockedTerm>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionAddBlockedTerm =
boost::json::try_value_to<AddBlockedTerm>(*actionVal);
if (actionAddBlockedTerm.has_error())
{
return actionAddBlockedTerm.error();
}
action.emplace<AddBlockedTerm>(std::move(actionAddBlockedTerm.value()));
}
else if (actionTag == AddPermittedTerm::TAG)
{
action.emplace<AddPermittedTerm>();
const auto *actionVal =
root.if_contains(detail::fieldFor<AddPermittedTerm>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionAddPermittedTerm =
boost::json::try_value_to<AddPermittedTerm>(*actionVal);
if (actionAddPermittedTerm.has_error())
{
return actionAddPermittedTerm.error();
}
action.emplace<AddPermittedTerm>(
std::move(actionAddPermittedTerm.value()));
}
else if (actionTag == RemoveBlockedTerm::TAG)
{
action.emplace<RemoveBlockedTerm>();
const auto *actionVal =
root.if_contains(detail::fieldFor<RemoveBlockedTerm>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionRemoveBlockedTerm =
boost::json::try_value_to<RemoveBlockedTerm>(*actionVal);
if (actionRemoveBlockedTerm.has_error())
{
return actionRemoveBlockedTerm.error();
}
action.emplace<RemoveBlockedTerm>(
std::move(actionRemoveBlockedTerm.value()));
}
else if (actionTag == RemovePermittedTerm::TAG)
{
action.emplace<RemovePermittedTerm>();
const auto *actionVal =
root.if_contains(detail::fieldFor<RemovePermittedTerm>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionRemovePermittedTerm =
boost::json::try_value_to<RemovePermittedTerm>(*actionVal);
if (actionRemovePermittedTerm.has_error())
{
return actionRemovePermittedTerm.error();
}
action.emplace<RemovePermittedTerm>(
std::move(actionRemovePermittedTerm.value()));
}
else if (actionTag == Mod::TAG)
{
@@ -1563,11 +1680,37 @@ boost::json::result_for<Event, boost::json::value>::type tag_invoke(
}
else if (actionTag == ApproveUnbanRequest::TAG)
{
action.emplace<ApproveUnbanRequest>();
const auto *actionVal =
root.if_contains(detail::fieldFor<ApproveUnbanRequest>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionApproveUnbanRequest =
boost::json::try_value_to<ApproveUnbanRequest>(*actionVal);
if (actionApproveUnbanRequest.has_error())
{
return actionApproveUnbanRequest.error();
}
action.emplace<ApproveUnbanRequest>(
std::move(actionApproveUnbanRequest.value()));
}
else if (actionTag == DenyUnbanRequest::TAG)
{
action.emplace<DenyUnbanRequest>();
const auto *actionVal =
root.if_contains(detail::fieldFor<DenyUnbanRequest>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionDenyUnbanRequest =
boost::json::try_value_to<DenyUnbanRequest>(*actionVal);
if (actionDenyUnbanRequest.has_error())
{
return actionDenyUnbanRequest.error();
}
action.emplace<DenyUnbanRequest>(
std::move(actionDenyUnbanRequest.value()));
}
else if (actionTag == Warn::TAG)
{
@@ -1585,23 +1728,87 @@ boost::json::result_for<Event, boost::json::value>::type tag_invoke(
}
else if (actionTag == SharedChatBan::TAG)
{
action.emplace<SharedChatBan>();
const auto *actionVal =
root.if_contains(detail::fieldFor<SharedChatBan>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionSharedChatBan =
boost::json::try_value_to<SharedChatBan>(*actionVal);
if (actionSharedChatBan.has_error())
{
return actionSharedChatBan.error();
}
action.emplace<SharedChatBan>(std::move(actionSharedChatBan.value()));
}
else if (actionTag == SharedChatTimeout::TAG)
{
action.emplace<SharedChatTimeout>();
const auto *actionVal =
root.if_contains(detail::fieldFor<SharedChatTimeout>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionSharedChatTimeout =
boost::json::try_value_to<SharedChatTimeout>(*actionVal);
if (actionSharedChatTimeout.has_error())
{
return actionSharedChatTimeout.error();
}
action.emplace<SharedChatTimeout>(
std::move(actionSharedChatTimeout.value()));
}
else if (actionTag == SharedChatUnban::TAG)
{
action.emplace<SharedChatUnban>();
const auto *actionVal =
root.if_contains(detail::fieldFor<SharedChatUnban>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionSharedChatUnban =
boost::json::try_value_to<SharedChatUnban>(*actionVal);
if (actionSharedChatUnban.has_error())
{
return actionSharedChatUnban.error();
}
action.emplace<SharedChatUnban>(
std::move(actionSharedChatUnban.value()));
}
else if (actionTag == SharedChatUntimeout::TAG)
{
action.emplace<SharedChatUntimeout>();
const auto *actionVal =
root.if_contains(detail::fieldFor<SharedChatUntimeout>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionSharedChatUntimeout =
boost::json::try_value_to<SharedChatUntimeout>(*actionVal);
if (actionSharedChatUntimeout.has_error())
{
return actionSharedChatUntimeout.error();
}
action.emplace<SharedChatUntimeout>(
std::move(actionSharedChatUntimeout.value()));
}
else if (actionTag == SharedChatDelete::TAG)
{
action.emplace<SharedChatDelete>();
const auto *actionVal =
root.if_contains(detail::fieldFor<SharedChatDelete>());
if (!actionVal)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto actionSharedChatDelete =
boost::json::try_value_to<SharedChatDelete>(*actionVal);
if (actionSharedChatDelete.has_error())
{
return actionSharedChatDelete.error();
}
action.emplace<SharedChatDelete>(
std::move(actionSharedChatDelete.value()));
}
else
{
+2 -46
View File
@@ -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<std::chrono::seconds>(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(
@@ -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
@@ -43,4 +43,9 @@ void makeModerateMessage(
const lib::payload::channel_moderate::v2::Event &event,
const lib::payload::channel_moderate::v2::Warn &action);
/// <MODERATOR> banned <USER>[ in <CHANNEL>]: <REASON>
void makeModerateMessage(EventSubMessageBuilder &builder,
const lib::payload::channel_moderate::v2::Event &event,
const lib::payload::channel_moderate::v2::Ban &action);
} // namespace chatterino::eventsub
@@ -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
@@ -2,10 +2,17 @@
#include "twitch-eventsub-ws/payloads/channel-moderate-v2.hpp"
#include <memory>
class QDateTime;
namespace chatterino {
class TwitchChannel;
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
} // namespace chatterino
namespace chatterino::eventsub {
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
]
}