Compare commits

...

9 commits

26 changed files with 777 additions and 68 deletions

View file

@ -7,7 +7,7 @@
- Major: Improve high-DPI support on Windows. (#4868, #5391, #5664, #5666)
- Major: Added transparent overlay window (default keybind: <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>N</kbd>). (#4746, #5643, #5659)
- 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, #5625)
- 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, #5625, #5661)
- 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)
@ -55,6 +55,7 @@
- Bugfix: Fixed event emotes not showing up in autocomplete and popups. (#5239, #5580, #5582, #5632)
- Bugfix: Fixed tab visibility being controllable in the emote popup. (#5530)
- Bugfix: Fixed account switch not being saved if no other settings were changed. (#5558)
- Bugfix: Fixed 7TV badges being inadvertently animated. (#5674)
- Bugfix: Fixed some tooltips not being readable. (#5578)
- Bugfix: Fixed log files being locked longer than needed. (#5592)
- Bugfix: Fixed global badges not showing in anonymous mode. (#5599)
@ -105,13 +106,13 @@
- Dev: Added more tests for input completion. (#5604)
- Dev: Refactored legacy Unicode zero-width-joiner replacement. (#5594)
- Dev: The JSON output when copying a message (<kbd>SHIFT</kbd> + right-click) is now more extensive. (#5600)
- Dev: Added more tests for message building. (#5598, #5654, #5656)
- Dev: Added more tests for message building. (#5598, #5654, #5656, #5671)
- Dev: Twitch messages are now sent using Twitch's Helix API instead of IRC by default. (#5607)
- Dev: `GIFTimer` is no longer initialized in tests. (#5608)
- Dev: Emojis now use flags instead of a set of strings for capabilities. (#5616)
- Dev: Move plugins to Sol2. (#5622)
- Dev: Refactored static `MessageBuilder` helpers to standalone functions. (#5652)
- Dev: Decoupled reply parsing from `MessageBuilder`. (#5660)
- Dev: Decoupled reply parsing from `MessageBuilder`. (#5660, #5668)
- Dev: Refactored IRC message building. (#5663)
## 2.5.1

@ -1 +1 @@
Subproject commit 88ee08eb3c3f3627ca54b90dafd1d63a6d4da96b
Subproject commit 5b5caad7cd57d5ba3ca796bf1521b131d73ca405

@ -1 +1 @@
Subproject commit c58874c1aa5d0619df2c975bcb87433941b46920
Subproject commit 4a0a1e599377cdcdc91b0fbbefc312936b48730c

View file

@ -3,6 +3,7 @@
#include "common/Args.hpp"
#include "mocks/DisabledStreamerMode.hpp"
#include "mocks/EmptyApplication.hpp"
#include "mocks/TwitchUsers.hpp"
#include "providers/bttv/BttvLiveUpdates.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Settings.hpp"
@ -55,6 +56,11 @@ public:
return &this->fonts;
}
ITwitchUsers *getTwitchUsers() override
{
return &this->twitchUsers;
}
BttvLiveUpdates *getBttvLiveUpdates() override
{
return nullptr;
@ -71,6 +77,7 @@ public:
DisabledStreamerMode streamerMode;
Theme theme;
Fonts fonts;
TwitchUsers twitchUsers;
};
} // namespace chatterino::mock

View file

@ -0,0 +1,24 @@
#pragma once
#include "providers/twitch/TwitchUser.hpp"
#include "providers/twitch/TwitchUsers.hpp"
namespace chatterino::mock {
class TwitchUsers : public ITwitchUsers
{
public:
TwitchUsers() = default;
std::shared_ptr<TwitchUser> resolveID(const UserId &id)
{
TwitchUser u = {
.id = id.string,
.name = {},
.displayName = {},
};
return std::make_shared<TwitchUser>(u);
}
};
} // namespace chatterino::mock

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -32,6 +32,7 @@
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchIrc.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
#include "providers/twitch/TwitchUsers.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
@ -380,6 +381,18 @@ EmotePtr makeAutoModBadge()
Url{"https://dashboard.twitch.tv/settings/moderation/automod"}});
}
EmotePtr makeSharedChatBadge(const QString &sourceName)
{
return std::make_shared<Emote>(Emote{
.name = EmoteName{},
.images = ImageSet{Image::fromResourcePixmap(
getResources().twitch.sharedChat, 0.25)},
.tooltip = Tooltip{"Shared Message" +
(sourceName.isEmpty() ? "" : " from " + sourceName)},
.homePage = Url{"https://link.twitch.tv/SharedChatViewer"},
});
}
std::tuple<std::optional<EmotePtr>, MessageElementFlags, bool> parseEmote(
TwitchChannel *twitchChannel, const EmoteName &name)
{
@ -2382,6 +2395,11 @@ void MessageBuilder::parseThread(const QString &messageContent,
this->message().replyParent = parent;
thread->addToThread(std::weak_ptr{this->message_});
if (thread->subscribed())
{
this->message().flags.set(MessageFlag::SubscribedThread);
}
// enable reply flag
this->message().flags.set(MessageFlag::ReplyMessage);
@ -2746,6 +2764,28 @@ void MessageBuilder::appendTwitchBadges(const QVariantMap &tags,
return;
}
if (this->message().flags.has(MessageFlag::SharedMessage))
{
const QString sourceId = tags["source-room-id"].toString();
QString sourceName;
if (sourceId.isEmpty())
{
sourceName = "";
}
else if (twitchChannel->roomId() == sourceId)
{
sourceName = twitchChannel->getName();
}
else
{
sourceName =
getApp()->getTwitchUsers()->resolveID({sourceId})->displayName;
}
this->emplace<BadgeElement>(makeSharedChatBadge(sourceName),
MessageElementFlag::BadgeSharedChannel);
}
auto badgeInfos = parseBadgeInfoTag(tags);
auto badges = parseBadgeTag(tags);
appendBadges(this, badges, badgeInfos, twitchChannel);

View file

@ -66,6 +66,10 @@ enum class MessageElementFlag : int64_t {
BitsStatic = (1LL << 11),
BitsAnimated = (1LL << 12),
// Slot 0: Twitch
// - Shared Channel indicator badge
BadgeSharedChannel = (1LL << 37),
// Slot 1: Twitch
// - Staff badge
// - Admin badge
@ -119,7 +123,7 @@ enum class MessageElementFlag : int64_t {
Badges = BadgeGlobalAuthority | BadgePredictions | BadgeChannelAuthority |
BadgeSubscription | BadgeVanity | BadgeChatterino | BadgeSevenTV |
BadgeFfz,
BadgeFfz | BadgeSharedChannel,
ChannelName = (1LL << 20),

View file

@ -59,7 +59,7 @@ void SeventvBadges::registerBadge(const QJsonObject &badgeJson)
auto emote = Emote{
.name = EmoteName{},
.images = SeventvEmotes::createImageSet(badgeJson),
.images = SeventvEmotes::createImageSet(badgeJson, true),
.tooltip = Tooltip{badgeJson["tooltip"].toString()},
.homePage = Url{},
.id = EmoteId{badgeID},

View file

@ -106,12 +106,18 @@ CreateEmoteResult createEmote(const QJsonObject &activeEmote,
? createAliasedTooltip(emoteName.string, baseEmoteName.string,
author.string, isGlobal)
: createTooltip(emoteName.string, author.string, isGlobal);
auto imageSet = SeventvEmotes::createImageSet(emoteData);
auto imageSet = SeventvEmotes::createImageSet(emoteData, false);
auto emote =
Emote({emoteName, imageSet, tooltip,
Url{EMOTE_LINK_FORMAT.arg(emoteId.string)}, zeroWidth, emoteId,
author, makeConditionedOptional(aliasedName, baseEmoteName)});
auto emote = Emote({
emoteName,
imageSet,
tooltip,
Url{EMOTE_LINK_FORMAT.arg(emoteId.string)},
zeroWidth,
emoteId,
author,
makeConditionedOptional(aliasedName, baseEmoteName),
});
return {emote, emoteId, emoteName, !emote.images.getImage1()->isEmpty()};
}
@ -427,7 +433,8 @@ void SeventvEmotes::getEmoteSet(
});
}
ImageSet SeventvEmotes::createImageSet(const QJsonObject &emoteData)
ImageSet SeventvEmotes::createImageSet(const QJsonObject &emoteData,
bool useStatic)
{
auto host = emoteData["host"].toObject();
// "//cdn.7tv[...]"
@ -463,9 +470,21 @@ ImageSet SeventvEmotes::createImageSet(const QJsonObject &emoteData)
baseWidth = width;
}
auto image = Image::fromUrl(
{QString("https:%1/%2").arg(baseUrl, file["name"].toString())},
scale, {static_cast<int>(width), file["height"].toInt(16)});
auto name = [&] {
if (useStatic)
{
auto staticName = file["static_name"].toString();
if (!staticName.isEmpty())
{
return staticName;
}
}
return file["name"].toString();
}();
auto image =
Image::fromUrl({QString("https:%1/%2").arg(baseUrl, name)}, scale,
{static_cast<int>(width), file["height"].toInt(16)});
sizes.at(nextSize) = image;
nextSize++;

View file

@ -153,8 +153,10 @@ public:
* Creates an image set from a 7TV emote or badge.
*
* @param emoteData { host: { files: [], url } }
* @param useStatic use static version if possible
*/
static ImageSet createImageSet(const QJsonObject &emoteData);
static ImageSet createImageSet(const QJsonObject &emoteData,
bool useStatic);
private:
Atomic<std::shared_ptr<const EmoteMap>> global_;

View file

@ -123,47 +123,34 @@ int stripLeadingReplyMention(const QVariantMap &tags, QString &content)
return 0;
}
[[nodiscard]] bool shouldHighlightReplyThread(
const QVariantMap &tags, const QString &senderLogin,
std::shared_ptr<MessageThread> &thread, bool isNew)
void checkThreadSubscription(const QVariantMap &tags,
const QString &senderLogin,
std::shared_ptr<MessageThread> &thread)
{
const auto &currentLogin =
getApp()->getAccounts()->twitch.getCurrent()->getUserName();
if (thread->subscribed())
if (thread->subscribed() || thread->unsubscribed())
{
return true;
}
if (thread->unsubscribed())
{
return false;
return;
}
if (getSettings()->autoSubToParticipatedThreads)
{
if (isNew)
{
if (const auto it = tags.find("reply-parent-user-login");
it != tags.end())
{
auto name = it.value().toString();
if (name == currentLogin)
{
thread->markSubscribed();
return true; // already marked as participated
}
}
}
const auto &currentLogin =
getApp()->getAccounts()->twitch.getCurrent()->getUserName();
if (senderLogin == currentLogin)
{
thread->markSubscribed();
// don't set the highlight here
}
else if (const auto it = tags.find("reply-parent-user-login");
it != tags.end())
{
auto name = it.value().toString();
if (name == currentLogin)
{
thread->markSubscribed();
}
}
}
return false;
}
ChannelPtr channelOrEmptyByTarget(const QString &target,
@ -243,7 +230,6 @@ QMap<QString, QString> parseBadges(const QString &badgesString)
struct ReplyContext {
std::shared_ptr<MessageThread> thread;
MessagePtr parent;
bool highlight = false;
};
[[nodiscard]] ReplyContext getReplyContext(
@ -265,8 +251,7 @@ struct ReplyContext {
if (owned)
{
// Thread already exists (has a reply)
ctx.highlight = shouldHighlightReplyThread(
tags, message->nick(), owned, false);
checkThreadSubscription(tags, message->nick(), owned);
ctx.thread = owned;
rootThread = owned;
}
@ -301,8 +286,7 @@ struct ReplyContext {
{
std::shared_ptr<MessageThread> newThread =
std::make_shared<MessageThread>(foundMessage);
ctx.highlight = shouldHighlightReplyThread(
tags, message->nick(), newThread, true);
checkThreadSubscription(tags, message->nick(), newThread);
ctx.thread = newThread;
rootThread = newThread;
@ -724,10 +708,6 @@ std::vector<MessagePtr> IrcMessageHandler::parseMessageWithReply(
if (built)
{
if (replyCtx.highlight)
{
built->flags.set(MessageFlag::SubscribedThread);
}
builtMessages.emplace_back(built);
MessageBuilder::triggerHighlights(channel, alert);
}
@ -1552,8 +1532,7 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *message,
{
// Thread already exists (has a reply)
auto thread = threadIt->second.lock();
replyCtx.highlight = shouldHighlightReplyThread(
tags, message->nick(), thread, false);
checkThreadSubscription(tags, message->nick(), thread);
replyCtx.thread = thread;
rootThread = thread;
}
@ -1565,8 +1544,7 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *message,
{
// Found root reply message
auto newThread = std::make_shared<MessageThread>(root);
replyCtx.highlight = shouldHighlightReplyThread(
tags, message->nick(), newThread, true);
checkThreadSubscription(tags, message->nick(), newThread);
replyCtx.thread = newThread;
rootThread = newThread;
@ -1621,10 +1599,6 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *message,
msg->flags.set(MessageFlag::Subscription);
msg->flags.unset(MessageFlag::Highlighted);
}
if (replyCtx.highlight)
{
msg->flags.set(MessageFlag::SubscribedThread);
}
IrcMessageHandler::setSimilarityFlags(msg, chan);

View file

@ -195,6 +195,7 @@ void WindowManager::updateWordTypeMask()
flags.set(settings->animateEmotes ? MEF::BitsAnimated : MEF::BitsStatic);
// badges
flags.set(MEF::BadgeSharedChannel);
flags.set(settings->showBadgesGlobalAuthority ? MEF::BadgeGlobalAuthority
: MEF::None);
flags.set(settings->showBadgesPredictions ? MEF::BadgePredictions

View file

@ -2414,6 +2414,11 @@ void ChannelView::handleMouseClick(QMouseEvent *event,
return;
}
if (link.value.startsWith("id:"))
{
return;
}
// Insert @username into split input
const bool commaMention =
getSettings()->mentionUsersWithComma;

View file

@ -180,7 +180,7 @@ public:
LimitedQueueSnapshot<MessageLayoutPtr> &getMessagesSnapshot();
// Returns true if message should be included
bool shouldIncludeMessage(const MessagePtr &m) const;
bool shouldIncludeMessage(const MessagePtr &message) const;
void queueLayout();
void invalidateBuffers();
@ -216,6 +216,11 @@ public:
Scrollbar *scrollbar();
using ChannelViewID = std::size_t;
///
/// \brief Get the ID of this ChannelView
///
/// The ID is made of the underlying channel's name
/// combined with the filter set IDs
ChannelViewID getID() const;
pajlada::Signals::Signal<QMouseEvent *> mouseDown;

View file

@ -597,11 +597,10 @@ bool NotebookTab::shouldMessageHighlight(const ChannelView &channelViewSource,
const auto &visibleSplits = visibleSplitContainer->getSplits();
for (const auto &visibleSplit : visibleSplits)
{
if (channelViewSource.underlyingChannel() ==
visibleSplit->getChannel() &&
if (channelViewSource.getID() ==
visibleSplit->getChannelView().getID() &&
visibleSplit->getChannelView().shouldIncludeMessage(message) &&
channelViewSource.shouldIncludeMessage(message) &&
channelViewSource.getFilterIds().empty())
channelViewSource.shouldIncludeMessage(message))
{
return false;
}

View file

@ -126,6 +126,11 @@ private:
const MessagePtr &message) const;
struct HighlightSources {
// Source of the update to the highlight status is the split
// in which a message that causes the update appears.
// This message can be just a plain message causing the
// HighlightState::NewMessage state, or a message that pings the user
// causing the HighlightState::Highlighted state
std::unordered_set<ChannelView::ChannelViewID> newMessageSource;
std::unordered_set<ChannelView::ChannelViewID> highlightedSource;

View file

@ -0,0 +1,157 @@
{
"input": "@room-id=11148817;rm-received-ts=1729627607652;tmi-sent-ts=1729627607545;historical=1 :tmi.twitch.tv CLEARCHAT #pajlada",
"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": [
"20:06"
]
},
"flags": "Timestamp",
"format": "",
"link": {
"type": "None",
"value": ""
},
"time": "20:06:47",
"tooltip": "",
"trailingSpace": true,
"type": "TimestampElement"
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"Chat"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"has"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"been"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"cleared"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"by"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"a"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"moderator."
]
}
],
"flags": "System|DoNotTriggerNotification",
"id": "",
"localizedName": "",
"loginName": "",
"messageText": "Chat has been cleared by a moderator.",
"searchText": "Chat has been cleared by a moderator.",
"serverReceivedTime": "",
"timeoutUser": "",
"usernameColor": "#ff000000"
}
]
}

View file

@ -0,0 +1,157 @@
{
"input": "@historical=1;rm-received-ts=1729627965650;msg-id=emote_only_on :tmi.twitch.tv NOTICE #pajlada :This room is now in emote-only mode.",
"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": [
"20:12"
]
},
"flags": "Timestamp",
"format": "",
"link": {
"type": "None",
"value": ""
},
"time": "20:12:45",
"tooltip": "",
"trailingSpace": true,
"type": "TimestampElement"
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"This"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"room"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"is"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"now"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"in"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"emote-only"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"mode."
]
}
],
"flags": "System|DoNotTriggerNotification",
"id": "",
"localizedName": "",
"loginName": "",
"messageText": "This room is now in emote-only mode.",
"searchText": "This room is now in emote-only mode.",
"serverReceivedTime": "",
"timeoutUser": "",
"usernameColor": "#ff000000"
}
]
}

View file

@ -0,0 +1,145 @@
{
"input": "@badges=subscriber/24;login=nerixyz;msg-param-displayName=nerixyz;user-type=;tmi-sent-ts=1729626466361;system-msg=2\\sraiders\\sfrom\\snerixyz\\shave\\sjoined!;room-id=11148817;user-id=129546453;display-name=nerixyz;subscriber=1;historical=1;rm-received-ts=1729626466492;msg-id=raid;vip=0;id=7299b7bc-61ce-423c-85ce-8d651b56cce4;msg-param-login=nerixyz;color=#FF0000;mod=0;msg-param-viewerCount=2;flags=;msg-param-profileImageURL=https://static-cdn.jtvnw.net/jtv_user_pictures/e065218b-49df-459d-afd3-c6557870f551-profile_image-%s.png;emotes=;badge-info=subscriber/28 :tmi.twitch.tv USERNOTICE #pajlada",
"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": [
"19:47"
]
},
"flags": "Timestamp",
"format": "",
"link": {
"type": "None",
"value": ""
},
"time": "19:47:46",
"tooltip": "",
"trailingSpace": true,
"type": "TimestampElement"
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"2"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"raiders"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"from"
]
},
{
"color": "Text",
"fallbackColor": "System",
"flags": "Text|Mention",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "MentionElement",
"userColor": "#ffff0000",
"userLoginName": "nerixyz",
"words": [
"nerixyz"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"have"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"joined!"
]
}
],
"flags": "System|DoNotTriggerNotification|Subscription",
"id": "",
"localizedName": "",
"loginName": "",
"messageText": "2 raiders from nerixyz have joined!",
"searchText": "2 raiders from nerixyz have joined!",
"serverReceivedTime": "",
"timeoutUser": "",
"usernameColor": "#ff000000"
}
]
}

View file

@ -64,6 +64,24 @@
"trailingSpace": true,
"type": "TwitchModerationElement"
},
{
"emote": {
"homePage": "https://link.twitch.tv/SharedChatViewer",
"images": {
"1x": ""
},
"name": "",
"tooltip": "Shared Message"
},
"flags": "BadgeSharedChannel",
"link": {
"type": "None",
"value": ""
},
"tooltip": "Shared Message",
"trailingSpace": true,
"type": "BadgeElement"
},
{
"emote": {
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",

View file

@ -64,6 +64,24 @@
"trailingSpace": true,
"type": "TwitchModerationElement"
},
{
"emote": {
"homePage": "https://link.twitch.tv/SharedChatViewer",
"images": {
"1x": ""
},
"name": "",
"tooltip": "Shared Message from twitchdev"
},
"flags": "BadgeSharedChannel",
"link": {
"type": "None",
"value": ""
},
"tooltip": "Shared Message from twitchdev",
"trailingSpace": true,
"type": "BadgeElement"
},
{
"color": "#ffff0000",
"flags": "Username",

View file

@ -64,6 +64,24 @@
"trailingSpace": true,
"type": "TwitchModerationElement"
},
{
"emote": {
"homePage": "https://link.twitch.tv/SharedChatViewer",
"images": {
"1x": ""
},
"name": "",
"tooltip": "Shared Message from twitchdev"
},
"flags": "BadgeSharedChannel",
"link": {
"type": "None",
"value": ""
},
"tooltip": "Shared Message from twitchdev",
"trailingSpace": true,
"type": "BadgeElement"
},
{
"emote": {
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",

View file

@ -0,0 +1,5 @@
{
"input": "@color=#FF0000;emotes=;subscriber=0;msg-id=sharedchatnotice;historical=1;msg-param-profileImageURL=https://static-cdn.jtvnw.net/jtv_user_pictures/e065218b-49df-459d-afd3-c6557870f551-profile_image-%s.png;tmi-sent-ts=1729627237027;rm-received-ts=1729627237138;msg-param-displayName=nerixyz;id=c585cb3e-cb4f-4a48-a251-b568d217587e;display-name=nerixyz;badges=;user-id=129546453;source-id=d86cdfb2-e138-48e2-985f-5b8efb765ba4;source-room-id=955766119;room-id=11148817;user-type=;msg-param-login=nerixyz;flags=;source-badge-info=;mod=0;vip=0;system-msg=2\\sraiders\\sfrom\\snerixyz\\shave\\sjoined!;login=nerixyz;msg-param-viewerCount=2;source-badges=;source-msg-id=raid;badge-info= :tmi.twitch.tv USERNOTICE #pajlada",
"output": [
]
}

View file

@ -64,6 +64,24 @@
"trailingSpace": true,
"type": "TwitchModerationElement"
},
{
"emote": {
"homePage": "https://link.twitch.tv/SharedChatViewer",
"images": {
"1x": ""
},
"name": "",
"tooltip": "Shared Message"
},
"flags": "BadgeSharedChannel",
"link": {
"type": "None",
"value": ""
},
"tooltip": "Shared Message",
"trailingSpace": true,
"type": "BadgeElement"
},
{
"emote": {
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",

View file

@ -0,0 +1,87 @@
{
"input": "@tmi-sent-ts=1729628658012;rm-received-ts=1729628658106;historical=1;ban-duration=1;room-id=11148817;target-user-id=129546453 :tmi.twitch.tv CLEARCHAT #pajlada nerixyz",
"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": [
"20:24"
]
},
"flags": "Timestamp",
"format": "",
"link": {
"type": "None",
"value": ""
},
"time": "20:24:18",
"tooltip": "",
"trailingSpace": true,
"type": "TimestampElement"
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "UserInfo",
"value": "nerixyz"
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"nerixyz"
]
},
{
"color": "System",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"has",
"been",
"timed",
"out",
"for",
"1s."
]
}
],
"flags": "System|Timeout|DoNotTriggerNotification",
"id": "",
"localizedName": "",
"loginName": "",
"messageText": "nerixyz has been timed out for 1s. ",
"searchText": "nerixyz has been timed out for 1s. ",
"serverReceivedTime": "",
"timeoutUser": "nerixyz",
"usernameColor": "#ff000000"
}
]
}