mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
feat: improve handling of shared chat messages (#5606)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
81d72db76b
commit
06d9a37709
|
@ -6,6 +6,7 @@
|
|||
- Major: Release plugins alpha. (#5288)
|
||||
- Major: Improve high-DPI support on Windows. (#4868, #5391)
|
||||
- Minor: Removed the Ctrl+Shift+L hotkey for toggling the "live only" tab visibility state. (#5530)
|
||||
- Minor: Add support for Shared Chat messages. Shared chat messages can be filtered with the `flags.shared` filter variable, or with search using `is:shared`. Some messages like subscriptions are filtered on purpose to avoid confusion for the broadcaster. If you have both channels participating in Shared Chat open, only one of the message triggering your highlight will trigger. (#5606)
|
||||
- Minor: Moved tab visibility control to a submenu, without any toggle actions. (#5530)
|
||||
- Minor: Add option to customise Moderation buttons with images. (#5369)
|
||||
- Minor: Colored usernames now update on the fly when changing the "Color @usernames" setting. (#5300)
|
||||
|
|
|
@ -35,6 +35,7 @@ const QMap<QString, Type> MESSAGE_TYPING_CONTEXT{
|
|||
{"flags.automod", Type::Bool},
|
||||
{"flags.restricted", Type::Bool},
|
||||
{"flags.monitored", Type::Bool},
|
||||
{"flags.shared", Type::Bool},
|
||||
{"message.content", Type::String},
|
||||
{"message.length", Type::Int},
|
||||
{"reward.title", Type::String},
|
||||
|
@ -78,6 +79,7 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
|
|||
* flags.automod
|
||||
* flags.restricted
|
||||
* flags.monitored
|
||||
* flags.shared
|
||||
*
|
||||
* message.content
|
||||
* message.length
|
||||
|
@ -141,6 +143,7 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
|
|||
{"flags.automod", m->flags.has(MessageFlag::AutoMod)},
|
||||
{"flags.restricted", m->flags.has(MessageFlag::RestrictedMessage)},
|
||||
{"flags.monitored", m->flags.has(MessageFlag::MonitoredMessage)},
|
||||
{"flags.shared", m->flags.has(MessageFlag::SharedMessage)},
|
||||
|
||||
{"message.content", m->messageText},
|
||||
{"message.length", m->messageText.length()},
|
||||
|
|
|
@ -43,6 +43,7 @@ const QMap<QString, QString> VALID_IDENTIFIERS_MAP{
|
|||
{"flags.automod", "automod message?"},
|
||||
{"flags.restricted", "restricted message?"},
|
||||
{"flags.monitored", "monitored message?"},
|
||||
{"flags.shared", "shared message?"},
|
||||
{"message.content", "message text"},
|
||||
{"message.length", "message length"},
|
||||
{"reward.title", "point reward title"},
|
||||
|
|
|
@ -2677,6 +2677,26 @@ void MessageBuilder::parseRoomID()
|
|||
{
|
||||
this->twitchChannel->setRoomId(this->roomID_);
|
||||
}
|
||||
|
||||
if (auto it = this->tags.find("source-room-id"); it != this->tags.end())
|
||||
{
|
||||
auto sourceRoom = it.value().toString();
|
||||
if (this->roomID_ != sourceRoom)
|
||||
{
|
||||
this->message().flags.set(MessageFlag::SharedMessage);
|
||||
|
||||
auto sourceChan =
|
||||
getApp()->getTwitch()->getChannelOrEmptyByID(sourceRoom);
|
||||
if (sourceChan)
|
||||
{
|
||||
this->sourceChannel =
|
||||
dynamic_cast<TwitchChannel *>(sourceChan.get());
|
||||
// avoid duplicate pings
|
||||
this->message().flags.set(
|
||||
MessageFlag::DoNotTriggerNotification);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2894,18 +2914,19 @@ void MessageBuilder::appendUsername()
|
|||
}
|
||||
}
|
||||
|
||||
Outcome MessageBuilder::tryAppendEmote(const EmoteName &name)
|
||||
const TwitchChannel *MessageBuilder::getSourceChannel() const
|
||||
{
|
||||
auto *app = getApp();
|
||||
if (this->sourceChannel != nullptr)
|
||||
{
|
||||
return this->sourceChannel;
|
||||
}
|
||||
|
||||
const auto *globalBttvEmotes = app->getBttvEmotes();
|
||||
const auto *globalFfzEmotes = app->getFfzEmotes();
|
||||
const auto *globalSeventvEmotes = app->getSeventvEmotes();
|
||||
|
||||
auto flags = MessageElementFlags();
|
||||
auto emote = std::optional<EmotePtr>{};
|
||||
bool zeroWidth = false;
|
||||
return this->twitchChannel;
|
||||
}
|
||||
|
||||
std::tuple<std::optional<EmotePtr>, MessageElementFlags, bool>
|
||||
MessageBuilder::parseEmote(const EmoteName &name) const
|
||||
{
|
||||
// Emote order:
|
||||
// - FrankerFaceZ Channel
|
||||
// - BetterTTV Channel
|
||||
|
@ -2913,36 +2934,93 @@ Outcome MessageBuilder::tryAppendEmote(const EmoteName &name)
|
|||
// - FrankerFaceZ Global
|
||||
// - BetterTTV Global
|
||||
// - 7TV Global
|
||||
if (this->twitchChannel && (emote = this->twitchChannel->ffzEmote(name)))
|
||||
|
||||
const auto *globalFfzEmotes = getApp()->getFfzEmotes();
|
||||
const auto *globalBttvEmotes = getApp()->getBttvEmotes();
|
||||
const auto *globalSeventvEmotes = getApp()->getSeventvEmotes();
|
||||
|
||||
const auto *sourceChannel = this->getSourceChannel();
|
||||
|
||||
std::optional<EmotePtr> emote{};
|
||||
|
||||
if (sourceChannel != nullptr)
|
||||
{
|
||||
flags = MessageElementFlag::FfzEmote;
|
||||
// Check for channel emotes
|
||||
|
||||
emote = sourceChannel->ffzEmote(name);
|
||||
if (emote)
|
||||
{
|
||||
return {
|
||||
emote,
|
||||
MessageElementFlag::FfzEmote,
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
emote = sourceChannel->bttvEmote(name);
|
||||
if (emote)
|
||||
{
|
||||
return {
|
||||
emote,
|
||||
MessageElementFlag::BttvEmote,
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
emote = sourceChannel->seventvEmote(name);
|
||||
if (emote)
|
||||
{
|
||||
return {
|
||||
emote,
|
||||
MessageElementFlag::SevenTVEmote,
|
||||
emote.value()->zeroWidth,
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (this->twitchChannel &&
|
||||
(emote = this->twitchChannel->bttvEmote(name)))
|
||||
|
||||
// Check for global emotes
|
||||
|
||||
emote = globalFfzEmotes->emote(name);
|
||||
if (emote)
|
||||
{
|
||||
flags = MessageElementFlag::BttvEmote;
|
||||
return {
|
||||
emote,
|
||||
MessageElementFlag::FfzEmote,
|
||||
false,
|
||||
};
|
||||
}
|
||||
else if (this->twitchChannel != nullptr &&
|
||||
(emote = this->twitchChannel->seventvEmote(name)))
|
||||
|
||||
emote = globalBttvEmotes->emote(name);
|
||||
if (emote)
|
||||
{
|
||||
flags = MessageElementFlag::SevenTVEmote;
|
||||
zeroWidth = emote.value()->zeroWidth;
|
||||
return {
|
||||
emote,
|
||||
MessageElementFlag::BttvEmote,
|
||||
zeroWidthEmotes.contains(name.string),
|
||||
};
|
||||
}
|
||||
else if ((emote = globalFfzEmotes->emote(name)))
|
||||
|
||||
emote = globalSeventvEmotes->globalEmote(name);
|
||||
if (emote)
|
||||
{
|
||||
flags = MessageElementFlag::FfzEmote;
|
||||
}
|
||||
else if ((emote = globalBttvEmotes->emote(name)))
|
||||
{
|
||||
flags = MessageElementFlag::BttvEmote;
|
||||
zeroWidth = zeroWidthEmotes.contains(name.string);
|
||||
}
|
||||
else if ((emote = globalSeventvEmotes->globalEmote(name)))
|
||||
{
|
||||
flags = MessageElementFlag::SevenTVEmote;
|
||||
zeroWidth = emote.value()->zeroWidth;
|
||||
return {
|
||||
emote,
|
||||
MessageElementFlag::SevenTVEmote,
|
||||
emote.value()->zeroWidth,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
{},
|
||||
{},
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
Outcome MessageBuilder::tryAppendEmote(const EmoteName &name)
|
||||
{
|
||||
const auto [emote, flags, zeroWidth] = this->parseEmote(name);
|
||||
|
||||
if (emote)
|
||||
{
|
||||
if (zeroWidth && getSettings()->enableZeroWidthEmotes &&
|
||||
|
@ -3128,7 +3206,8 @@ Outcome MessageBuilder::tryParseCheermote(const QString &string)
|
|||
return Failure;
|
||||
}
|
||||
|
||||
auto cheerOpt = this->twitchChannel->cheerEmote(string);
|
||||
const auto *chan = this->getSourceChannel();
|
||||
auto cheerOpt = chan->cheerEmote(string);
|
||||
|
||||
if (!cheerOpt)
|
||||
{
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
|
@ -164,7 +165,10 @@ public:
|
|||
|
||||
QString userName;
|
||||
|
||||
/// The Twitch Channel the message was received in
|
||||
TwitchChannel *twitchChannel = nullptr;
|
||||
/// The Twitch Channel the message was sent in, according to the Shared Chat feature
|
||||
TwitchChannel *sourceChannel = nullptr;
|
||||
|
||||
Message *operator->();
|
||||
Message &message();
|
||||
|
@ -278,6 +282,15 @@ protected:
|
|||
void appendChannelName();
|
||||
void appendUsername();
|
||||
|
||||
/// Return the Twitch Channel this message originated from
|
||||
///
|
||||
/// Useful to handle messages from the "Shared Chat" feature
|
||||
///
|
||||
/// Can return nullptr
|
||||
const TwitchChannel *getSourceChannel() const;
|
||||
|
||||
std::tuple<std::optional<EmotePtr>, MessageElementFlags, bool> parseEmote(
|
||||
const EmoteName &name) const;
|
||||
Outcome tryAppendEmote(const EmoteName &name);
|
||||
|
||||
void addWords(const QStringList &words,
|
||||
|
|
|
@ -50,6 +50,8 @@ enum class MessageFlag : std::int64_t {
|
|||
MonitoredMessage = (1LL << 35),
|
||||
/// The message is an ACTION message (/me)
|
||||
Action = (1LL << 36),
|
||||
/// The message is sent in a different source channel as part of a Shared Chat session
|
||||
SharedMessage = (1LL << 37),
|
||||
};
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
|
|
|
@ -58,6 +58,10 @@ MessageFlagsPredicate::MessageFlagsPredicate(const QString &flags, bool negate)
|
|||
{
|
||||
this->flags_.set(MessageFlag::MonitoredMessage);
|
||||
}
|
||||
else if (flag == "shared")
|
||||
{
|
||||
this->flags_.set(MessageFlag::SharedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -456,6 +456,27 @@ std::vector<MessagePtr> parseUserNoticeMessage(Channel *channel,
|
|||
auto parameters = message->parameters();
|
||||
|
||||
QString msgType = tags.value("msg-id").toString();
|
||||
bool mirrored = msgType == "sharedchatnotice";
|
||||
if (mirrored)
|
||||
{
|
||||
msgType = tags.value("source-msg-id").toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto rIt = tags.find("room-id");
|
||||
auto sIt = tags.find("source-room-id");
|
||||
if (rIt != tags.end() && sIt != tags.end())
|
||||
{
|
||||
mirrored = rIt.value().toString() != sIt.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (mirrored && msgType != "announcement")
|
||||
{
|
||||
// avoid confusing broadcasters with user payments to other channels
|
||||
return {};
|
||||
}
|
||||
|
||||
QString content;
|
||||
if (parameters.size() >= 2)
|
||||
{
|
||||
|
@ -483,6 +504,10 @@ std::vector<MessagePtr> parseUserNoticeMessage(Channel *channel,
|
|||
MessageBuilder builder(channel, message, args, content, false);
|
||||
builder->flags.set(MessageFlag::Subscription);
|
||||
builder->flags.unset(MessageFlag::Highlighted);
|
||||
if (mirrored)
|
||||
{
|
||||
builder->flags.set(MessageFlag::SharedMessage);
|
||||
}
|
||||
builtMessages.emplace_back(builder.build());
|
||||
}
|
||||
}
|
||||
|
@ -546,6 +571,10 @@ std::vector<MessagePtr> parseUserNoticeMessage(Channel *channel,
|
|||
calculateMessageTime(message).time());
|
||||
|
||||
b->flags.set(MessageFlag::Subscription);
|
||||
if (mirrored)
|
||||
{
|
||||
b->flags.set(MessageFlag::SharedMessage);
|
||||
}
|
||||
auto newMessage = b.release();
|
||||
builtMessages.emplace_back(newMessage);
|
||||
}
|
||||
|
@ -954,6 +983,27 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
|
|||
|
||||
auto target = parameters[0];
|
||||
QString msgType = tags.value("msg-id").toString();
|
||||
bool mirrored = msgType == "sharedchatnotice";
|
||||
if (mirrored)
|
||||
{
|
||||
msgType = tags.value("source-msg-id").toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto rIt = tags.find("room-id");
|
||||
auto sIt = tags.find("source-room-id");
|
||||
if (rIt != tags.end() && sIt != tags.end())
|
||||
{
|
||||
mirrored = rIt.value().toString() != sIt.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (mirrored && msgType != "announcement")
|
||||
{
|
||||
// avoid confusing broadcasters with user payments to other channels
|
||||
return;
|
||||
}
|
||||
|
||||
QString content;
|
||||
if (parameters.size() >= 2)
|
||||
{
|
||||
|
@ -1039,6 +1089,10 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
|
|||
calculateMessageTime(message).time());
|
||||
|
||||
b->flags.set(MessageFlag::Subscription);
|
||||
if (mirrored)
|
||||
{
|
||||
b->flags.set(MessageFlag::SharedMessage);
|
||||
}
|
||||
auto newMessage = b.release();
|
||||
|
||||
QString channelName;
|
||||
|
|
|
@ -1869,7 +1869,7 @@ std::optional<EmotePtr> TwitchChannel::ffzCustomVipBadge() const
|
|||
return this->ffzCustomVipBadge_.get();
|
||||
}
|
||||
|
||||
std::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string)
|
||||
std::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string) const
|
||||
{
|
||||
auto sets = this->cheerEmoteSets_.access();
|
||||
for (const auto &set : *sets)
|
||||
|
|
|
@ -197,7 +197,7 @@ public:
|
|||
std::vector<FfzBadges::Badge> ffzChannelBadges(const QString &userID) const;
|
||||
|
||||
// Cheers
|
||||
std::optional<CheerEmote> cheerEmote(const QString &string);
|
||||
std::optional<CheerEmote> cheerEmote(const QString &string) const;
|
||||
|
||||
// Replies
|
||||
/**
|
||||
|
|
|
@ -57,6 +57,8 @@ const QStringList &getSampleCheerMessages()
|
|||
R"(@badge-info=;badges=bits/1;bits=1;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=da47f91a-40d3-4209-ba1c-0219d8b8ecaf;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440720363;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Scoops1)",
|
||||
R"(@badge-info=;badges=bits/1;bits=10;color=#8A2BE2;display-name=EkimSky;emotes=;flags=;id=8adea5b4-7430-44ea-a666-5ebaceb69441;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567833047623;turbo=0;user-id=42132818;user-type= :ekimsky!ekimsky@ekimsky.tmi.twitch.tv PRIVMSG #pajlada :Hi Cheer10)",
|
||||
R"(@badge-info=;badges=bits-leader/2;bits=500;color=;display-name=godkiller76;emotes=;flags=;id=80e86bcc-d048-44f3-8073-9a1014568e0c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567753685704;turbo=0;user-id=258838478;user-type= :godkiller76!godkiller76@godkiller76.tmi.twitch.tv PRIVMSG #pajlada :Party100 Party100 Party100 Party100 Party100)",
|
||||
R"(@mod=0;flags=;badge-info=;source-badge-info=;color=#DAA520;user-id=612865661;subscriber=0;id=886028cc-9985-47b9-a273-8164c6d59a76;turbo=0;source-badges=staff/1,moderator/1,twitch-recap-2023/1;room-id=11148817;source-id=eefbae4a-d3a1-4307-8d15-fab0f03fd9b9;source-room-id=1025594235;emotes=;display-name=lahoooo;tmi-sent-ts=1727304317562;badges=staff/1,raging-wolf-helm/1;user-type=staff;bits=1 :lahoooo!lahoooo@lahoooo.tmi.twitch.tv PRIVMSG #pajlada Cheer1)",
|
||||
R"(@id=7bf90f3f-75de-4e89-ab3d-2fdfefd6bfb1;source-id=590821fd-4a5c-4dd8-b27e-9cea4ffb8d87;source-badges=staff/1,moderator/1,bits-leader/3;user-id=612865661;badges=staff/1,raging-wolf-helm/1;emotes=;source-badge-info=;badge-info=;mod=0;source-room-id=1025594235;user-type=staff;color=#DAA520;tmi-sent-ts=1727375798676;display-name=lahoooo;bits=1;flags=;turbo=0;room-id=11148817;subscriber=0 :lahoooo!lahoooo@lahoooo.tmi.twitch.tv PRIVMSG #pajlada shared9Cheer1)",
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
@ -116,6 +118,12 @@ const QStringList &getSampleMiscMessages()
|
|||
// mod announcement
|
||||
R"(@badge-info=subscriber/47;badges=broadcaster/1,subscriber/3012,twitchconAmsterdam2020/1;color=#FF0000;display-name=Supinic;emotes=;flags=;id=8c26e1ab-b50c-4d9d-bc11-3fd57a941d90;login=supinic;mod=0;msg-id=announcement;msg-param-color=PRIMARY;room-id=31400525;subscriber=1;system-msg=;tmi-sent-ts=1648762219962;user-id=31400525;user-type= :tmi.twitch.tv USERNOTICE #supinic :mm test lol)",
|
||||
|
||||
// mod announcement from another channel
|
||||
R"(@badge-info=;badges=staff/1,raging-wolf-helm/1;color=#DAA520;display-name=lahoooo;emotes=;flags=;id=01cd601f-bc3f-49d5-ab4b-136fa9d6ec22;login=lahoooo;mod=0;msg-id=sharedchatnotice;msg-param-color=PRIMARY;room-id=11148817;source-badge-info=;source-badges=staff/1,moderator/1,bits-leader/1;source-id=4083dadc-9f20-40f9-ba92-949ebf6bc294;source-msg-id=announcement;source-room-id=1025594235;subscriber=0;system-msg=;tmi-sent-ts=1726118378465;user-id=612865661;user-type=staff;vip=0 :tmi.twitch.tv USERNOTICE #pajlada :hi this is an announcement from 1)",
|
||||
|
||||
// shared chat message
|
||||
R"(@badge-info=;flags=;room-id=11148817;color=;client-nonce=0d1632f37b6baee51576859d5dbaf325;emotes=;subscriber=0;tmi-sent-ts=1727395701680;id=19ee1663-c14d-41cd-a4a2-30a4bb609c5a;turbo=1;badges=staff/1,turbo/1;source-badges=staff/1,moderator/1,bits-leader/1;source-badge-info=;display-name=creativewind;source-room-id=1025594235;source-id=b97eea45-f9dc-4f0c-8744-f8256c3ed950;user-type=staff;user-id=106940612;mod=0 :creativewind!creativewind@creativewind.tmi.twitch.tv PRIVMSG #pajlada :Guys can you please not share the chat. My mom bought me this new laptop and it gets really hot when the chat is being shared. Now my leg is starting to hurt because it is getting so hot. Please, if you don't want me to get burned, then dont share the chat.)",
|
||||
|
||||
// Hype Chat (Paid option for keeping a message in chat longer)
|
||||
// no level
|
||||
R"(@badge-info=subscriber/3;badges=subscriber/0,bits-charity/1;color=#0000FF;display-name=SnoopyTheBot;emotes=;first-msg=0;flags=;id=8779a9e5-cf1b-47b3-b9fe-67a5b1b605f6;mod=0;pinned-chat-paid-amount=500;pinned-chat-paid-canonical-amount=5;pinned-chat-paid-currency=USD;pinned-chat-paid-exponent=2;returning-chatter=0;room-id=36340781;subscriber=1;tmi-sent-ts=1664505974154;turbo=0;user-id=136881249;user-type= :snoopythebot!snoopythebot@snoopythebot.tmi.twitch.tv PRIVMSG #pajlada :-$5)",
|
||||
|
@ -139,6 +147,7 @@ const QStringList &getSampleEmoteTestMessages()
|
|||
R"(@badge-info=;badges=moderator/1,partner/1;color=#5B99FF;display-name=StreamElements;emotes=86:30-39/822112:73-79;flags=22-27:S.5;id=03c3eec9-afd1-4858-a2e0-fccbf6ad8d1a;mod=1;room-id=11148817;subscriber=0;tmi-sent-ts=1588638345928;turbo=0;user-id=100135110;user-type=mod :streamelements!streamelements@streamelements.tmi.twitch.tv PRIVMSG #pajlada :╔ACTION A LOJA AINDA NÃO ESTÁ PRONTA BibleThump , AGUARDE... NOVIDADES EM BREVE FortOne╔)",
|
||||
R"(@badge-info=subscriber/20;badges=moderator/1,subscriber/12;color=#19E6E6;display-name=randers;emotes=25:39-43;flags=;id=3ea97f01-abb2-4acf-bdb8-f52e79cd0324;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1588837097115;turbo=0;user-id=40286300;user-type=mod :randers!randers@randers.tmi.twitch.tv PRIVMSG #pajlada :Då kan du begära skadestånd och förtal Kappa)",
|
||||
R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,15-22;flags=;id=a3196c7e-be4c-4b49-9c5a-8b8302b50c2a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922213730;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm (no space))",
|
||||
R"(@client-nonce=9e118ff9b63fbbeea66520f37929685d;source-id=040d1673-826d-48d1-9728-badc945e4a5e;user-type=staff;user-id=612865661;turbo=0;id=3ece90ea-9aaa-4634-bf3a-0105185da505;tmi-sent-ts=1727304403389;color=#DAA520;source-room-id=1025594235;flags=;emotes=emotesv2_8811dd848a214cef8a77575476cc33f4:0-9;subscriber=0;mod=0;emote-only=1;badges=staff/1,raging-wolf-helm/1;room-id=11148817;display-name=lahoooo;badge-info=;source-badge-info=;source-badges=staff/1,moderator/1,bits-leader/3 :lahoooo!lahoooo@lahoooo.tmi.twitch.tv PRIVMSG #pajlada shared9Dog)",
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue