feat(eventsub): implement automod message hold (#6005)
This commit is contained in:
+1
-1
@@ -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)
|
||||
|
||||
+7
-7
@@ -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?
|
||||
|
||||
@@ -25,7 +25,7 @@ boost::json::result_for<Event, boost::json::value>::type tag_invoke(
|
||||
}
|
||||
|
||||
auto broadcasterUserID =
|
||||
boost::json::try_value_to<std::string>(*jvbroadcasterUserID);
|
||||
boost::json::try_value_to<String>(*jvbroadcasterUserID);
|
||||
|
||||
if (broadcasterUserID.has_error())
|
||||
{
|
||||
@@ -40,7 +40,7 @@ boost::json::result_for<Event, boost::json::value>::type tag_invoke(
|
||||
}
|
||||
|
||||
auto broadcasterUserLogin =
|
||||
boost::json::try_value_to<std::string>(*jvbroadcasterUserLogin);
|
||||
boost::json::try_value_to<String>(*jvbroadcasterUserLogin);
|
||||
|
||||
if (broadcasterUserLogin.has_error())
|
||||
{
|
||||
@@ -55,7 +55,7 @@ boost::json::result_for<Event, boost::json::value>::type tag_invoke(
|
||||
}
|
||||
|
||||
auto broadcasterUserName =
|
||||
boost::json::try_value_to<std::string>(*jvbroadcasterUserName);
|
||||
boost::json::try_value_to<String>(*jvbroadcasterUserName);
|
||||
|
||||
if (broadcasterUserName.has_error())
|
||||
{
|
||||
@@ -68,7 +68,7 @@ boost::json::result_for<Event, 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())
|
||||
{
|
||||
@@ -81,7 +81,7 @@ boost::json::result_for<Event, 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())
|
||||
{
|
||||
@@ -94,7 +94,7 @@ boost::json::result_for<Event, 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())
|
||||
{
|
||||
@@ -107,7 +107,7 @@ boost::json::result_for<Event, boost::json::value>::type tag_invoke(
|
||||
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
|
||||
}
|
||||
|
||||
auto messageID = boost::json::try_value_to<std::string>(*jvmessageID);
|
||||
auto messageID = boost::json::try_value_to<String>(*jvmessageID);
|
||||
|
||||
if (messageID.has_error())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -509,6 +509,7 @@ private:
|
||||
std::vector<boost::signals2::scoped_connection> bSignals_;
|
||||
|
||||
eventsub::SubscriptionHandle eventSubChannelModerateHandle;
|
||||
eventsub::SubscriptionHandle eventSubAutomodMessageHoldHandle;
|
||||
|
||||
friend class TwitchIrcServer;
|
||||
friend class MessageBuilder;
|
||||
|
||||
@@ -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<TwitchChannel *>(
|
||||
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<QUrl>({}),
|
||||
.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,
|
||||
|
||||
@@ -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>(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<lib::automod::BlockedTermReason>(event.reason));
|
||||
|
||||
// AutoMod shield badge
|
||||
builder.emplace<BadgeElement>(makeAutoModBadge(),
|
||||
MessageElementFlag::BadgeChannelAuthority);
|
||||
// AutoMod "username"
|
||||
builder.emplace<TextElement>("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<TextElement>(u"Held a message for reason: " % reason %
|
||||
u". Allow will post it in chat. ",
|
||||
MessageElementFlag::Text, MessageColor::Text);
|
||||
// Allow link button
|
||||
builder
|
||||
.emplace<TextElement>("Allow", MessageElementFlag::Text,
|
||||
MessageColor(QColor(0, 255, 0)),
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::AutoModAllow, event.messageID.qt()});
|
||||
// Deny link button
|
||||
builder
|
||||
.emplace<TextElement>(" 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<lib::automod::BlockedTermReason>(event.reason));
|
||||
|
||||
// Builder for offender's message
|
||||
builder->channelName = event.broadcasterUserLogin.qt();
|
||||
builder
|
||||
.emplace<TextElement>(u'#' + event.broadcasterUserLogin.qt(),
|
||||
MessageElementFlag::ChannelName,
|
||||
MessageColor::System)
|
||||
->setLink({Link::JumpToChannel, event.broadcasterUserLogin.qt()});
|
||||
builder.emplace<TimestampElement>(time.time());
|
||||
builder.emplace<TwitchModerationElement>();
|
||||
builder->loginName = event.userLogin.qt();
|
||||
|
||||
auto displayName = localizedDisplayName(event);
|
||||
// sender username
|
||||
builder.emplace<MentionElement>(
|
||||
displayName + ':', event.userLogin.qt(), MessageColor::Text,
|
||||
channel->getUserColor(event.userLogin.qt()));
|
||||
// sender's message caught by AutoMod
|
||||
builder.emplace<TextElement>(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
|
||||
|
||||
@@ -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 <QDateTime>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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<QString, std::string_view, QCompareCaseInsensitive>
|
||||
"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<TwitchChannel> makeMockTwitchChannel(const QString &name)
|
||||
|
||||
@@ -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🥚");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user