diff --git a/.prettierignore b/.prettierignore
index 89270b789..b763c2ddd 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -2,6 +2,7 @@
resources/*.json
benchmarks/resources/*.json
tests/resources/*.json
+tests/snapshots/**/*.json
# ...themes should be prettified for readability.
!resources/themes/*.json
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c71db4ef..5cef1eba4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -102,6 +102,7 @@
- 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 (SHIFT + right-click) is now more extensive. (#5600)
+- Dev: Added more tests for message building. (#5598)
- 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)
diff --git a/mocks/include/mocks/ChatterinoBadges.hpp b/mocks/include/mocks/ChatterinoBadges.hpp
index 9070a7d7e..2f8279bc5 100644
--- a/mocks/include/mocks/ChatterinoBadges.hpp
+++ b/mocks/include/mocks/ChatterinoBadges.hpp
@@ -2,6 +2,8 @@
#include "providers/chatterino/ChatterinoBadges.hpp"
+#include
+
namespace chatterino::mock {
class ChatterinoBadges : public IChatterinoBadges
@@ -9,9 +11,21 @@ class ChatterinoBadges : public IChatterinoBadges
public:
std::optional getBadge(const UserId &id) override
{
- (void)id;
+ auto it = this->users.find(id);
+ if (it != this->users.end())
+ {
+ return it->second;
+ }
return std::nullopt;
}
+
+ void setBadge(UserId id, EmotePtr emote)
+ {
+ this->users.emplace(std::move(id), std::move(emote));
+ }
+
+private:
+ std::unordered_map users;
};
} // namespace chatterino::mock
diff --git a/mocks/include/mocks/TwitchIrcServer.hpp b/mocks/include/mocks/TwitchIrcServer.hpp
index bbeba8ca4..d218192b3 100644
--- a/mocks/include/mocks/TwitchIrcServer.hpp
+++ b/mocks/include/mocks/TwitchIrcServer.hpp
@@ -7,8 +7,11 @@
#include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/eventapi/Message.hpp"
#include "providers/seventv/SeventvEmotes.hpp"
+#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
+#include
+
namespace chatterino::mock {
class MockTwitchIrcServer : public ITwitchIrcServer
@@ -67,7 +70,30 @@ public:
std::shared_ptr getChannelOrEmptyByID(
const QString &channelID) override
{
- return {};
+ // XXX: this is the same as in TwitchIrcServer::getChannelOrEmptyByID
+ for (const auto &[name, weakChannel] : this->mockChannels)
+ {
+ auto channel = weakChannel.lock();
+ if (!channel)
+ {
+ continue;
+ }
+
+ auto twitchChannel =
+ std::dynamic_pointer_cast(channel);
+ if (!twitchChannel)
+ {
+ continue;
+ }
+
+ if (twitchChannel->roomId() == channelID &&
+ twitchChannel->getName().count(':') < 2)
+ {
+ return channel;
+ }
+ }
+
+ return Channel::getEmpty();
}
void dropSeventvChannel(const QString &userID,
@@ -123,6 +149,8 @@ public:
ChannelPtr liveChannel;
ChannelPtr automodChannel;
QString lastUserThatWhisperedMe{"forsen"};
+
+ std::unordered_map> mockChannels;
};
} // namespace chatterino::mock
diff --git a/mocks/include/mocks/UserData.hpp b/mocks/include/mocks/UserData.hpp
index 62159a19f..bf53ea4ab 100644
--- a/mocks/include/mocks/UserData.hpp
+++ b/mocks/include/mocks/UserData.hpp
@@ -2,6 +2,8 @@
#include "controllers/userdata/UserDataController.hpp"
+#include
+
namespace chatterino::mock {
class UserDataController : public IUserDataController
@@ -13,6 +15,11 @@ public:
// If the user does not have any extra data, return none
std::optional getUser(const QString &userID) const override
{
+ auto it = this->userMap.find(userID);
+ if (it != this->userMap.end())
+ {
+ return it->second;
+ }
return std::nullopt;
}
@@ -20,8 +27,21 @@ public:
void setUserColor(const QString &userID,
const QString &colorString) override
{
- // do nothing
+ auto it = this->userMap.find(userID);
+ if (it != this->userMap.end())
+ {
+ it->second.color = QColor(colorString);
+ }
+ else
+ {
+ this->userMap.emplace(userID, UserData{
+ .color = QColor(colorString),
+ });
+ }
}
+
+private:
+ std::unordered_map userMap;
};
} // namespace chatterino::mock
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d24a0155e..5c71aff95 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -497,6 +497,8 @@ set(SOURCE_FILES
util/InitUpdateButton.hpp
util/IpcQueue.cpp
util/IpcQueue.hpp
+ util/IrcHelpers.cpp
+ util/IrcHelpers.hpp
util/LayoutHelper.cpp
util/LayoutHelper.hpp
util/LoadPixmap.cpp
@@ -969,6 +971,7 @@ target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
IRC_STATIC
IRC_NAMESPACE=Communi
$<$:_WIN32_WINNT=0x0A00> # Windows 10
+ $<$:CHATTERINO_WITH_TESTS>
)
if (USE_SYSTEM_QTKEYCHAIN)
diff --git a/src/common/ChatterinoSetting.hpp b/src/common/ChatterinoSetting.hpp
index 35d538b13..f2006e6d7 100644
--- a/src/common/ChatterinoSetting.hpp
+++ b/src/common/ChatterinoSetting.hpp
@@ -73,7 +73,6 @@ public:
_registerSetting(this->getData());
}
- template
EnumSetting &operator=(Enum newValue)
{
this->setValue(Underlying(newValue));
diff --git a/src/controllers/ignores/IgnorePhrase.cpp b/src/controllers/ignores/IgnorePhrase.cpp
index 9f81fbc6e..41a49ec58 100644
--- a/src/controllers/ignores/IgnorePhrase.cpp
+++ b/src/controllers/ignores/IgnorePhrase.cpp
@@ -95,11 +95,11 @@ bool IgnorePhrase::containsEmote() const
{
if (!this->emotesChecked_)
{
- const auto &accvec = getApp()->getAccounts()->twitch.accounts;
- for (const auto &acc : accvec)
+ auto accemotes =
+ getApp()->getAccounts()->twitch.getCurrent()->accessEmotes();
+ if (*accemotes)
{
- const auto &accemotes = *acc->accessEmotes();
- for (const auto &emote : *accemotes)
+ for (const auto &emote : **accemotes)
{
if (this->replace_.contains(emote.first.string,
Qt::CaseSensitive))
diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp
index 41048fb7e..3802497b0 100644
--- a/src/messages/MessageBuilder.cpp
+++ b/src/messages/MessageBuilder.cpp
@@ -49,6 +49,7 @@
#include
#include
#include
+#include
#include
#include
@@ -1820,10 +1821,12 @@ MessagePtr MessageBuilder::buildHypeChatMessage(
// actualAmount = amount * 10^(-exponent)
double actualAmount = std::pow(10.0, double(-exponent)) * double(amount);
- subtitle += QLocale::system().toCurrencyString(actualAmount, currency);
- MessageBuilder builder(systemMessage, parseTagString(subtitle),
- calculateMessageTime(message).time());
+ auto locale = getSystemLocale();
+ subtitle += locale.toCurrencyString(actualAmount, currency);
+
+ auto dt = calculateMessageTime(message);
+ MessageBuilder builder(systemMessage, parseTagString(subtitle), dt.time());
builder->flags.set(MessageFlag::ElevatedMessage);
return builder.release();
}
diff --git a/src/providers/ffz/FfzBadges.cpp b/src/providers/ffz/FfzBadges.cpp
index fb4358ab9..c300efa43 100644
--- a/src/providers/ffz/FfzBadges.cpp
+++ b/src/providers/ffz/FfzBadges.cpp
@@ -1,5 +1,6 @@
#include "providers/ffz/FfzBadges.hpp"
+#include "Application.hpp"
#include "common/network/NetworkRequest.hpp"
#include "common/network/NetworkResult.hpp"
#include "messages/Emote.hpp"
@@ -109,4 +110,30 @@ void FfzBadges::load()
.execute();
}
+void FfzBadges::registerBadge(int badgeID, Badge badge)
+{
+ assert(getApp()->isTest());
+
+ std::unique_lock lock(this->mutex_);
+
+ this->badges.emplace(badgeID, std::move(badge));
+}
+
+void FfzBadges::assignBadgeToUser(const UserId &userID, int badgeID)
+{
+ assert(getApp()->isTest());
+
+ std::unique_lock lock(this->mutex_);
+
+ auto it = this->userBadges.find(userID.string);
+ if (it != this->userBadges.end())
+ {
+ it->second.emplace(badgeID);
+ }
+ else
+ {
+ this->userBadges.emplace(userID.string, std::set{badgeID});
+ }
+}
+
} // namespace chatterino
diff --git a/src/providers/ffz/FfzBadges.hpp b/src/providers/ffz/FfzBadges.hpp
index 761111831..4219cd035 100644
--- a/src/providers/ffz/FfzBadges.hpp
+++ b/src/providers/ffz/FfzBadges.hpp
@@ -30,6 +30,9 @@ public:
std::vector getUserBadges(const UserId &id);
std::optional getBadge(int badgeID) const;
+ void registerBadge(int badgeID, Badge badge);
+ void assignBadgeToUser(const UserId &userID, int badgeID);
+
void load();
private:
diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp
index 7b093881f..8bac0d9fe 100644
--- a/src/providers/twitch/IrcMessageHandler.cpp
+++ b/src/providers/twitch/IrcMessageHandler.cpp
@@ -632,15 +632,6 @@ std::vector parsePrivMessage(Channel *channel,
builder.triggerHighlights();
}
- if (message->tags().contains(u"pinned-chat-paid-amount"_s))
- {
- auto ptr = MessageBuilder::buildHypeChatMessage(message);
- if (ptr)
- {
- builtMessages.emplace_back(std::move(ptr));
- }
- }
-
return builtMessages;
}
@@ -676,6 +667,11 @@ std::vector IrcMessageHandler::parseMessageWithReply(
QString content = privMsg->content();
int messageOffset = stripLeadingReplyMention(privMsg->tags(), content);
MessageParseArgs args;
+ auto tags = privMsg->tags();
+ if (const auto it = tags.find("custom-reward-id"); it != tags.end())
+ {
+ args.channelPointRewardId = it.value().toString();
+ }
MessageBuilder builder(channel, message, args, content,
privMsg->isAction());
builder.setMessageOffset(messageOffset);
@@ -688,6 +684,15 @@ std::vector IrcMessageHandler::parseMessageWithReply(
builder.triggerHighlights();
}
+ if (message->tags().contains(u"pinned-chat-paid-amount"_s))
+ {
+ auto ptr = MessageBuilder::buildHypeChatMessage(privMsg);
+ if (ptr)
+ {
+ builtMessages.emplace_back(std::move(ptr));
+ }
+ }
+
return builtMessages;
}
diff --git a/src/providers/twitch/TwitchAccount.cpp b/src/providers/twitch/TwitchAccount.cpp
index 5a66c7231..3dbbe7a10 100644
--- a/src/providers/twitch/TwitchAccount.cpp
+++ b/src/providers/twitch/TwitchAccount.cpp
@@ -174,6 +174,17 @@ void TwitchAccount::unblockUser(const QString &userId, const QObject *caller,
std::move(onFailure));
}
+void TwitchAccount::blockUserLocally(const QString &userID)
+{
+ assertInGuiThread();
+ assert(getApp()->isTest());
+
+ TwitchUser blockedUser;
+ blockedUser.id = userID;
+ this->ignores_.insert(blockedUser);
+ this->ignoresUserIds_.insert(blockedUser.id);
+}
+
const std::unordered_set &TwitchAccount::blocks() const
{
assertInGuiThread();
@@ -336,6 +347,12 @@ SharedAccessGuard> TwitchAccount::accessEmotes()
return this->emotes_.accessConst();
}
+void TwitchAccount::setEmotes(std::shared_ptr emotes)
+{
+ assert(getApp()->isTest());
+ *this->emotes_.access() = std::move(emotes);
+}
+
std::optional TwitchAccount::twitchEmote(const EmoteName &name) const
{
auto emotes = this->emotes_.accessConst();
diff --git a/src/providers/twitch/TwitchAccount.hpp b/src/providers/twitch/TwitchAccount.hpp
index f77d60df1..fe191a850 100644
--- a/src/providers/twitch/TwitchAccount.hpp
+++ b/src/providers/twitch/TwitchAccount.hpp
@@ -71,6 +71,8 @@ public:
std::function onSuccess,
std::function onFailure);
+ void blockUserLocally(const QString &userID);
+
[[nodiscard]] const std::unordered_set &blocks() const;
[[nodiscard]] const std::unordered_set &blockedUserIds() const;
@@ -83,16 +85,21 @@ public:
/// Returns true if the account has access to the given emote set
bool hasEmoteSet(const EmoteSetId &id) const;
- /// Return a map of emote sets the account has access to
+ /// Returns a map of emote sets the account has access to
///
/// Key being the emote set ID, and contents being information about the emote set
/// and the emotes contained in the emote set
SharedAccessGuard>
accessEmoteSets() const;
- /// Return a map of emotes the account has access to
+ /// Returns a map of emotes the account has access to
SharedAccessGuard> accessEmotes() const;
+ /// Sets the emotes this account has access to
+ ///
+ /// This should only be used in tests.
+ void setEmotes(std::shared_ptr emotes);
+
/// Return the emote by emote name if the account has access to the emote
std::optional twitchEmote(const EmoteName &name) const;
diff --git a/src/providers/twitch/TwitchBadges.hpp b/src/providers/twitch/TwitchBadges.hpp
index b9519a906..287e8bcae 100644
--- a/src/providers/twitch/TwitchBadges.hpp
+++ b/src/providers/twitch/TwitchBadges.hpp
@@ -45,10 +45,10 @@ public:
void loadTwitchBadges();
-private:
/// Loads the badges shipped with Chatterino (twitch-badges.json)
void loadLocalBadges();
+private:
void loaded();
void loadEmoteImage(const QString &name, const ImagePtr &image,
BadgeIconCallback &&callback);
diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp
index 48bc1ee19..93ac4519c 100644
--- a/src/providers/twitch/TwitchChannel.cpp
+++ b/src/providers/twitch/TwitchChannel.cpp
@@ -462,6 +462,14 @@ void TwitchChannel::addChannelPointReward(const ChannelPointReward &reward)
}
}
+void TwitchChannel::addKnownChannelPointReward(const ChannelPointReward &reward)
+{
+ assert(getApp()->isTest());
+
+ auto channelPointRewards = this->channelPointRewards_.access();
+ channelPointRewards->try_emplace(reward.id, reward);
+}
+
bool TwitchChannel::isChannelPointRewardKnown(const QString &rewardId)
{
const auto &pointRewards = this->channelPointRewards_.accessConst();
@@ -1560,7 +1568,7 @@ void TwitchChannel::refreshBadges()
getHelix()->getChannelBadges(
this->roomId(),
// successCallback
- [this, weak = weakOf(this)](auto channelBadges) {
+ [this, weak = weakOf(this)](const auto &channelBadges) {
auto shared = weak.lock();
if (!shared)
{
@@ -1568,31 +1576,7 @@ void TwitchChannel::refreshBadges()
return;
}
- auto badgeSets = this->badgeSets_.access();
-
- for (const auto &badgeSet : channelBadges.badgeSets)
- {
- const auto &setID = badgeSet.setID;
- for (const auto &version : badgeSet.versions)
- {
- auto emote = Emote{
- .name = EmoteName{},
- .images =
- ImageSet{
- Image::fromUrl(version.imageURL1x, 1,
- BASE_BADGE_SIZE),
- Image::fromUrl(version.imageURL2x, .5,
- BASE_BADGE_SIZE * 2),
- Image::fromUrl(version.imageURL4x, .25,
- BASE_BADGE_SIZE * 4),
- },
- .tooltip = Tooltip{version.title},
- .homePage = version.clickURL,
- };
- (*badgeSets)[setID][version.id] =
- std::make_shared(emote);
- }
- }
+ this->addTwitchBadgeSets(channelBadges);
},
// failureCallback
[this, weak = weakOf(this)](auto error, auto message) {
@@ -1623,6 +1607,33 @@ void TwitchChannel::refreshBadges()
});
}
+void TwitchChannel::addTwitchBadgeSets(const HelixChannelBadges &channelBadges)
+{
+ auto badgeSets = this->badgeSets_.access();
+
+ for (const auto &badgeSet : channelBadges.badgeSets)
+ {
+ const auto &setID = badgeSet.setID;
+ for (const auto &version : badgeSet.versions)
+ {
+ auto emote = Emote{
+ .name = EmoteName{},
+ .images =
+ ImageSet{
+ Image::fromUrl(version.imageURL1x, 1, BASE_BADGE_SIZE),
+ Image::fromUrl(version.imageURL2x, .5,
+ BASE_BADGE_SIZE * 2),
+ Image::fromUrl(version.imageURL4x, .25,
+ BASE_BADGE_SIZE * 4),
+ },
+ .tooltip = Tooltip{version.title},
+ .homePage = version.clickURL,
+ };
+ (*badgeSets)[setID][version.id] = std::make_shared(emote);
+ }
+ }
+}
+
void TwitchChannel::refreshCheerEmotes()
{
getHelix()->getCheermotes(
@@ -1635,74 +1646,75 @@ void TwitchChannel::refreshCheerEmotes()
return;
}
- std::vector emoteSets;
-
- for (const auto &set : cheermoteSets)
- {
- auto cheerEmoteSet = CheerEmoteSet();
- cheerEmoteSet.regex = QRegularExpression(
- "^" + set.prefix + "([1-9][0-9]*)$",
- QRegularExpression::CaseInsensitiveOption);
-
- for (const auto &tier : set.tiers)
- {
- CheerEmote cheerEmote;
-
- cheerEmote.color = QColor(tier.color);
- cheerEmote.minBits = tier.minBits;
- cheerEmote.regex = cheerEmoteSet.regex;
-
- // TODO(pajlada): We currently hardcode dark here :|
- // We will continue to do so for now since we haven't had to
- // solve that anywhere else
-
- // Combine the prefix (e.g. BibleThump) with the tier (1, 100 etc.)
- auto emoteTooltip =
- set.prefix + tier.id + "
Twitch Cheer Emote";
- auto makeImageSet = [](const HelixCheermoteImage &image) {
- return ImageSet{
- Image::fromUrl(image.imageURL1x, 1.0,
- BASE_BADGE_SIZE),
- Image::fromUrl(image.imageURL2x, 0.5,
- BASE_BADGE_SIZE * 2),
- Image::fromUrl(image.imageURL4x, 0.25,
- BASE_BADGE_SIZE * 4),
- };
- };
- cheerEmote.animatedEmote = std::make_shared(Emote{
- .name = EmoteName{"cheer emote"},
- .images = makeImageSet(tier.darkAnimated),
- .tooltip = Tooltip{emoteTooltip},
- .homePage = Url{},
- });
- cheerEmote.staticEmote = std::make_shared(Emote{
- .name = EmoteName{"cheer emote"},
- .images = makeImageSet(tier.darkStatic),
- .tooltip = Tooltip{emoteTooltip},
- .homePage = Url{},
- });
-
- cheerEmoteSet.cheerEmotes.emplace_back(
- std::move(cheerEmote));
- }
-
- // Sort cheermotes by cost
- std::sort(cheerEmoteSet.cheerEmotes.begin(),
- cheerEmoteSet.cheerEmotes.end(),
- [](const auto &lhs, const auto &rhs) {
- return lhs.minBits > rhs.minBits;
- });
-
- emoteSets.emplace_back(std::move(cheerEmoteSet));
- }
-
- *this->cheerEmoteSets_.access() = std::move(emoteSets);
+ this->setCheerEmoteSets(cheermoteSets);
},
[] {
// Failure
});
}
+void TwitchChannel::setCheerEmoteSets(
+ const std::vector &cheermoteSets)
+{
+ std::vector emoteSets;
+
+ for (const auto &set : cheermoteSets)
+ {
+ auto cheerEmoteSet = CheerEmoteSet();
+ cheerEmoteSet.regex =
+ QRegularExpression("^" + set.prefix + "([1-9][0-9]*)$",
+ QRegularExpression::CaseInsensitiveOption);
+
+ for (const auto &tier : set.tiers)
+ {
+ CheerEmote cheerEmote;
+
+ cheerEmote.color = QColor(tier.color);
+ cheerEmote.minBits = tier.minBits;
+ cheerEmote.regex = cheerEmoteSet.regex;
+
+ // TODO(pajlada): We currently hardcode dark here :|
+ // We will continue to do so for now since we haven't had to
+ // solve that anywhere else
+
+ // Combine the prefix (e.g. BibleThump) with the tier (1, 100 etc.)
+ auto emoteTooltip = set.prefix + tier.id + "
Twitch Cheer Emote";
+ auto makeImageSet = [](const HelixCheermoteImage &image) {
+ return ImageSet{
+ Image::fromUrl(image.imageURL1x, 1.0, BASE_BADGE_SIZE),
+ Image::fromUrl(image.imageURL2x, 0.5, BASE_BADGE_SIZE * 2),
+ Image::fromUrl(image.imageURL4x, 0.25, BASE_BADGE_SIZE * 4),
+ };
+ };
+ cheerEmote.animatedEmote = std::make_shared(Emote{
+ .name = EmoteName{u"cheer emote"_s},
+ .images = makeImageSet(tier.darkAnimated),
+ .tooltip = Tooltip{emoteTooltip},
+ .homePage = Url{},
+ });
+ cheerEmote.staticEmote = std::make_shared(Emote{
+ .name = EmoteName{u"cheer emote"_s},
+ .images = makeImageSet(tier.darkStatic),
+ .tooltip = Tooltip{emoteTooltip},
+ .homePage = Url{},
+ });
+
+ cheerEmoteSet.cheerEmotes.emplace_back(std::move(cheerEmote));
+ }
+
+ // Sort cheermotes by cost
+ std::sort(cheerEmoteSet.cheerEmotes.begin(),
+ cheerEmoteSet.cheerEmotes.end(),
+ [](const auto &lhs, const auto &rhs) {
+ return lhs.minBits > rhs.minBits;
+ });
+
+ emoteSets.emplace_back(std::move(cheerEmoteSet));
+ }
+
+ *this->cheerEmoteSets_.access() = std::move(emoteSets);
+}
+
void TwitchChannel::createClip()
{
if (!this->isLive())
@@ -1859,6 +1871,12 @@ std::vector TwitchChannel::ffzChannelBadges(
return badges;
}
+void TwitchChannel::setFfzChannelBadges(FfzChannelBadgeMap map)
+{
+ this->tgFfzChannelBadges_.guard();
+ this->ffzChannelBadges_ = std::move(map);
+}
+
std::optional TwitchChannel::ffzCustomModBadge() const
{
return this->ffzCustomModBadge_.get();
@@ -1869,6 +1887,16 @@ std::optional TwitchChannel::ffzCustomVipBadge() const
return this->ffzCustomVipBadge_.get();
}
+void TwitchChannel::setFfzCustomModBadge(std::optional badge)
+{
+ this->ffzCustomModBadge_.set(std::move(badge));
+}
+
+void TwitchChannel::setFfzCustomVipBadge(std::optional badge)
+{
+ this->ffzCustomVipBadge_.set(std::move(badge));
+}
+
std::optional TwitchChannel::cheerEmote(const QString &string) const
{
auto sets = this->cheerEmoteSets_.access();
diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp
index fd2edaf79..802da8112 100644
--- a/src/providers/twitch/TwitchChannel.hpp
+++ b/src/providers/twitch/TwitchChannel.hpp
@@ -25,6 +25,8 @@
#include
#include
+class TestMessageBuilderP;
+
namespace chatterino {
enum class HighlightState;
@@ -51,6 +53,9 @@ struct ChannelPointReward;
class MessageThread;
struct CheerEmoteSet;
struct HelixStream;
+struct HelixCheermoteSet;
+struct HelixGlobalBadges;
+using HelixChannelBadges = HelixGlobalBadges;
class TwitchIrcServer;
@@ -195,9 +200,15 @@ public:
* Returns a list of channel-specific FrankerFaceZ badges for the given user
*/
std::vector ffzChannelBadges(const QString &userID) const;
+ void setFfzChannelBadges(FfzChannelBadgeMap map);
+ void setFfzCustomModBadge(std::optional badge);
+ void setFfzCustomVipBadge(std::optional badge);
+
+ void addTwitchBadgeSets(const HelixChannelBadges &channelBadges);
// Cheers
std::optional cheerEmote(const QString &string) const;
+ void setCheerEmoteSets(const std::vector &cheermoteSets);
// Replies
/**
@@ -243,6 +254,10 @@ public:
* This will look at queued up partial messages, and if one is found it will add the queued up partial messages fully hydrated.
**/
void addChannelPointReward(const ChannelPointReward &reward);
+ /// Adds @a reward to the known rewards
+ ///
+ /// Unlike in #addChannelPointReward(), no message will be sent.
+ void addKnownChannelPointReward(const ChannelPointReward &reward);
bool isChannelPointRewardKnown(const QString &rewardId);
std::optional channelPointReward(
const QString &rewardId) const;
@@ -449,6 +464,7 @@ private:
friend class MessageBuilder;
friend class IrcMessageHandler;
friend class Commands_E2E_Test;
+ friend class ::TestMessageBuilderP;
};
} // namespace chatterino
diff --git a/src/util/Helpers.cpp b/src/util/Helpers.cpp
index e32c1c7ff..4df84739a 100644
--- a/src/util/Helpers.cpp
+++ b/src/util/Helpers.cpp
@@ -1,5 +1,6 @@
#include "util/Helpers.hpp"
+#include "Application.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include
@@ -301,4 +302,16 @@ QString unescapeZeroWidthJoiner(QString escaped)
return escaped;
}
+QLocale getSystemLocale()
+{
+#ifdef CHATTERINO_WITH_TESTS
+ if (getApp()->isTest())
+ {
+ return {QLocale::English};
+ }
+#endif
+
+ return QLocale::system();
+}
+
} // namespace chatterino
diff --git a/src/util/Helpers.hpp b/src/util/Helpers.hpp
index 340ad5fcd..5ea221f29 100644
--- a/src/util/Helpers.hpp
+++ b/src/util/Helpers.hpp
@@ -189,4 +189,6 @@ constexpr std::optional> makeConditionedOptional(bool condition,
/// a ZWJ. See also: https://github.com/Chatterino/chatterino2/issues/3384.
QString unescapeZeroWidthJoiner(QString escaped);
+QLocale getSystemLocale();
+
} // namespace chatterino
diff --git a/src/util/IrcHelpers.cpp b/src/util/IrcHelpers.cpp
new file mode 100644
index 000000000..5096594aa
--- /dev/null
+++ b/src/util/IrcHelpers.cpp
@@ -0,0 +1,72 @@
+#include "util/IrcHelpers.hpp"
+
+#include "Application.hpp"
+
+namespace {
+
+using namespace chatterino;
+
+QDateTime calculateMessageTimeBase(const Communi::IrcMessage *message)
+{
+ // Check if message is from recent-messages API
+ if (message->tags().contains("historical"))
+ {
+ bool customReceived = false;
+ auto ts =
+ message->tags().value("rm-received-ts").toLongLong(&customReceived);
+ if (!customReceived)
+ {
+ ts = message->tags().value("tmi-sent-ts").toLongLong();
+ }
+
+ return QDateTime::fromMSecsSinceEpoch(ts);
+ }
+
+ // If present, handle tmi-sent-ts tag and use it as timestamp
+ if (message->tags().contains("tmi-sent-ts"))
+ {
+ auto ts = message->tags().value("tmi-sent-ts").toLongLong();
+ return QDateTime::fromMSecsSinceEpoch(ts);
+ }
+
+ // Some IRC Servers might have server-time tag containing UTC date in ISO format, use it as timestamp
+ // See: https://ircv3.net/irc/#server-time
+ if (message->tags().contains("time"))
+ {
+ QString timedate = message->tags().value("time").toString();
+
+ auto date = QDateTime::fromString(timedate, Qt::ISODate);
+ date.setTimeZone(QTimeZone::utc());
+ return date.toLocalTime();
+ }
+
+ // Fallback to current time
+#ifdef CHATTERINO_WITH_TESTS
+ if (getApp()->isTest())
+ {
+ return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::utc());
+ }
+#endif
+
+ return QDateTime::currentDateTime();
+}
+
+} // namespace
+
+namespace chatterino {
+
+QDateTime calculateMessageTime(const Communi::IrcMessage *message)
+{
+ auto dt = calculateMessageTimeBase(message);
+
+#ifdef CHATTERINO_WITH_TESTS
+ if (getApp()->isTest())
+ {
+ return dt.toUTC();
+ }
+#endif
+
+ return dt;
+}
+
+} // namespace chatterino
diff --git a/src/util/IrcHelpers.hpp b/src/util/IrcHelpers.hpp
index 52691b350..f7941be08 100644
--- a/src/util/IrcHelpers.hpp
+++ b/src/util/IrcHelpers.hpp
@@ -59,43 +59,7 @@ inline QString parseTagString(const QString &input)
return output;
}
-inline QDateTime calculateMessageTime(const Communi::IrcMessage *message)
-{
- // Check if message is from recent-messages API
- if (message->tags().contains("historical"))
- {
- bool customReceived = false;
- auto ts =
- message->tags().value("rm-received-ts").toLongLong(&customReceived);
- if (!customReceived)
- {
- ts = message->tags().value("tmi-sent-ts").toLongLong();
- }
-
- return QDateTime::fromMSecsSinceEpoch(ts);
- }
-
- // If present, handle tmi-sent-ts tag and use it as timestamp
- if (message->tags().contains("tmi-sent-ts"))
- {
- auto ts = message->tags().value("tmi-sent-ts").toLongLong();
- return QDateTime::fromMSecsSinceEpoch(ts);
- }
-
- // Some IRC Servers might have server-time tag containing UTC date in ISO format, use it as timestamp
- // See: https://ircv3.net/irc/#server-time
- if (message->tags().contains("time"))
- {
- QString timedate = message->tags().value("time").toString();
-
- auto date = QDateTime::fromString(timedate, Qt::ISODate);
- date.setTimeZone(QTimeZone::utc());
- return date.toLocalTime();
- }
-
- // Fallback to current time
- return QDateTime::currentDateTime();
-}
+QDateTime calculateMessageTime(const Communi::IrcMessage *message);
// "foo/bar/baz,tri/hard" can be a valid badge-info tag
// In that case, valid map content should be 'split by slash' only once:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 41f90b1ff..36e7e31d8 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -48,6 +48,8 @@ set(test_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/FlagsEnum.cpp
${CMAKE_CURRENT_LIST_DIR}/src/MessageLayoutContainer.cpp
${CMAKE_CURRENT_LIST_DIR}/src/CancellationToken.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/src/lib/Snapshot.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/src/lib/Snapshot.hpp
# Add your new file above this line!
)
@@ -59,6 +61,8 @@ target_link_libraries(${PROJECT_NAME} PRIVATE chatterino-mocks)
target_link_libraries(${PROJECT_NAME} PRIVATE gtest gmock)
+target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/src")
+
set_target_properties(${PROJECT_NAME}
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
diff --git a/tests/snapshots/MessageBuilder/IRC/action.json b/tests/snapshots/MessageBuilder/IRC/action.json
new file mode 100644
index 000000000..f70f3ed47
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/action.json
@@ -0,0 +1,191 @@
+{
+ "input": "@badge-info=subscriber/80;badges=broadcaster/1,subscriber/3072,partner/1;color=#CC44FF;display-name=pajlada;emote-only=1;emotes=25:0-4;first-msg=0;flags=;id=90ef1e46-8baa-4bf2-9c54-272f39d6fa11;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1662206235860;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada :\u0001ACTION Kappa\u0001",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "80"
+ },
+ "badges": [
+ "broadcaster",
+ "subscriber",
+ "partner"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "pajlada",
+ "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": [
+ "11:57"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "11:57:15",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/3"
+ },
+ "name": "",
+ "tooltip": "Broadcaster"
+ },
+ "flags": "BadgeChannelAuthority",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Broadcaster",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/tb-1",
+ "2x": "https://chatterino.com/tb-2",
+ "3x": "https://chatterino.com/tb-3"
+ },
+ "name": "",
+ "tooltip": "Subscriber"
+ },
+ "flags": "BadgeSubscription",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Subscriber (Tier 3, 80 months)",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://blog.twitch.tv/2017/04/24/the-verified-badge-is-here-13381bc05735",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/3"
+ },
+ "name": "",
+ "tooltip": "Verified"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Verified",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffcc44ff",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "pajlada"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "pajlada"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "#ffcc44ff",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "90ef1e46-8baa-4bf2-9c54-272f39d6fa11"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|Action",
+ "id": "90ef1e46-8baa-4bf2-9c54-272f39d6fa11",
+ "localizedName": "",
+ "loginName": "pajlada",
+ "messageText": "Kappa",
+ "searchText": "pajlada pajlada: Kappa ",
+ "serverReceivedTime": "2022-09-03T11:57:15Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffcc44ff"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/all-usernames.json b/tests/snapshots/MessageBuilder/IRC/all-usernames.json
new file mode 100644
index 000000000..d74b64bd4
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/all-usernames.json
@@ -0,0 +1,315 @@
+{
+ "input": "@tmi-sent-ts=1726689518974;subscriber=1;id=438b85cc-fa67-4c03-bc38-c4ec2527822c;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@TwitchDev TwitchDev UserColor @UserColor UserColor! UserColorKappa UserChatter usercolor UserColor2 @UserColor2 ?!",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "19:58"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "19:58:38",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "MentionElement",
+ "userColor": "Text",
+ "userLoginName": "TwitchDev",
+ "words": [
+ "@TwitchDev"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "TwitchDev"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "MentionElement",
+ "userColor": "#ff010203",
+ "userLoginName": "UserColor",
+ "words": [
+ "UserColor"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "MentionElement",
+ "userColor": "#ff010203",
+ "userLoginName": "UserColor",
+ "words": [
+ "@UserColor"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": false,
+ "type": "MentionElement",
+ "userColor": "#ff010203",
+ "userLoginName": "UserColor",
+ "words": [
+ "UserColor"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "!"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "UserColorKappa"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "MentionElement",
+ "userColor": "Text",
+ "userLoginName": "UserChatter",
+ "words": [
+ "UserChatter"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "MentionElement",
+ "userColor": "#ff010203",
+ "userLoginName": "usercolor",
+ "words": [
+ "usercolor"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "UserColor2"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "MentionElement",
+ "userColor": "#ff050607",
+ "userLoginName": "UserColor2",
+ "words": [
+ "@UserColor2"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "?!"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "438b85cc-fa67-4c03-bc38-c4ec2527822c"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "438b85cc-fa67-4c03-bc38-c4ec2527822c",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "@TwitchDev TwitchDev UserColor @UserColor UserColor! UserColorKappa UserChatter usercolor UserColor2 @UserColor2 ?!",
+ "searchText": "nerixyz nerixyz: @TwitchDev TwitchDev UserColor @UserColor UserColor! UserColorKappa UserChatter usercolor UserColor2 @UserColor2 ?! ",
+ "serverReceivedTime": "2024-09-18T19:58:38Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "appearance": {
+ "messages": {
+ "findAllUsernames": true
+ }
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/bad-emotes.json b/tests/snapshots/MessageBuilder/IRC/bad-emotes.json
new file mode 100644
index 000000000..33314c1e8
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/bad-emotes.json
@@ -0,0 +1,159 @@
+{
+ "input": "@badge-info=;badges=no_audio/1;color=#DAA520;display-name=Mm2PL;emote-only=1;emotes=25:04;first-msg=0;flags=;id=7be87072-bf24-4fa3-b3df-0ea6fa5f1474;mod=0;returning-chatter=0;room-id=11148817;subscriber=0;tmi-sent-ts=1662201102276;turbo=0;user-id=117691339;user-type= :mm2pl!mm2pl@mm2pl.tmi.twitch.tv PRIVMSG #pajlada :Kappa",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "no_audio"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "Mm2PL",
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:42",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/3"
+ },
+ "name": "",
+ "tooltip": "Watching without audio"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Watching without audio",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffdaa521",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "Mm2PL"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Mm2PL:"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/Kappa",
+ "images": {
+ "1x": "https://chatterino.com/Kappa.png"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa Tooltip"
+ },
+ "flags": "BttvEmoteImage|BttvEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "7be87072-bf24-4fa3-b3df-0ea6fa5f1474"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "7be87072-bf24-4fa3-b3df-0ea6fa5f1474",
+ "localizedName": "",
+ "loginName": "mm2pl",
+ "messageText": "Kappa",
+ "searchText": "mm2pl mm2pl: Kappa ",
+ "serverReceivedTime": "2022-09-03T10:31:42Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/bad-emotes2.json b/tests/snapshots/MessageBuilder/IRC/bad-emotes2.json
new file mode 100644
index 000000000..4455bf818
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/bad-emotes2.json
@@ -0,0 +1,159 @@
+{
+ "input": "@badge-info=;badges=no_audio/1;color=#DAA520;display-name=Mm2PL;emote-only=1;emotes=25:4-0;first-msg=0;flags=;id=7be87072-bf24-4fa3-b3df-0ea6fa5f1474;mod=0;returning-chatter=0;room-id=11148817;subscriber=0;tmi-sent-ts=1662201102276;turbo=0;user-id=117691339;user-type= :mm2pl!mm2pl@mm2pl.tmi.twitch.tv PRIVMSG #pajlada :Kappa",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "no_audio"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "Mm2PL",
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:42",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/3"
+ },
+ "name": "",
+ "tooltip": "Watching without audio"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Watching without audio",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffdaa521",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "Mm2PL"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Mm2PL:"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/Kappa",
+ "images": {
+ "1x": "https://chatterino.com/Kappa.png"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa Tooltip"
+ },
+ "flags": "BttvEmoteImage|BttvEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "7be87072-bf24-4fa3-b3df-0ea6fa5f1474"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "7be87072-bf24-4fa3-b3df-0ea6fa5f1474",
+ "localizedName": "",
+ "loginName": "mm2pl",
+ "messageText": "Kappa",
+ "searchText": "mm2pl mm2pl: Kappa ",
+ "serverReceivedTime": "2022-09-03T10:31:42Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/bad-emotes3.json b/tests/snapshots/MessageBuilder/IRC/bad-emotes3.json
new file mode 100644
index 000000000..aaefb0fcf
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/bad-emotes3.json
@@ -0,0 +1,135 @@
+{
+ "input": "@tmi-sent-ts=1662201102276;emotes=84608:0-15 :test!test@test.tmi.twitch.tv PRIVMSG #pajlada :foo bar",
+ "output": [
+ {
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:42",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ff999999",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "test:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "foo"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "bar"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": ""
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "",
+ "localizedName": "",
+ "loginName": "test",
+ "messageText": "foo bar",
+ "searchText": "test test: foo bar ",
+ "serverReceivedTime": "2022-09-03T10:31:42Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/bad-emotes4.json b/tests/snapshots/MessageBuilder/IRC/bad-emotes4.json
new file mode 100644
index 000000000..e70385c6f
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/bad-emotes4.json
@@ -0,0 +1,135 @@
+{
+ "input": "@tmi-sent-ts=1662201102276;emotes=84608:9-10 :test!test@test.tmi.twitch.tv PRIVMSG #pajlada :foo bar",
+ "output": [
+ {
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:42",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ff999999",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "test:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "foo"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "bar"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": ""
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "",
+ "localizedName": "",
+ "loginName": "test",
+ "messageText": "foo bar",
+ "searchText": "test test: foo bar ",
+ "serverReceivedTime": "2022-09-03T10:31:42Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/badges-invalid.json b/tests/snapshots/MessageBuilder/IRC/badges-invalid.json
new file mode 100644
index 000000000..3680b1980
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/badges-invalid.json
@@ -0,0 +1,228 @@
+{
+ "input": "@tmi-sent-ts=1726764056444;subscriber=1;id=546c42a6-21d0-4f3d-a6a0-c77f78d7b131;room-id=11148817;user-id=123456;display-name=badgy;badges=subscriber24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :badgy!badgy@badgy.tmi.twitch.tv PRIVMSG #pajlada :badge",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "badgy",
+ "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": [
+ "16:40"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:40:56",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/Chatterino.png"
+ },
+ "name": "",
+ "tooltip": "Chatterino badge"
+ },
+ "flags": "BadgeChatterino",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Chatterino badge",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#0c090a0b",
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/FFZ1.png"
+ },
+ "name": "",
+ "tooltip": "FFZ1 badge"
+ },
+ "flags": "BadgeFfz",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "FFZ1 badge",
+ "trailingSpace": true,
+ "type": "FfzBadgeElement"
+ },
+ {
+ "color": "#100d0e0f",
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/FFZ2.png"
+ },
+ "name": "",
+ "tooltip": "FFZ2 badge"
+ },
+ "flags": "BadgeFfz",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "FFZ2 badge",
+ "trailingSpace": true,
+ "type": "FfzBadgeElement"
+ },
+ {
+ "color": "#14111213",
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/FFZ2.png"
+ },
+ "name": "",
+ "tooltip": "FFZ2 badge"
+ },
+ "flags": "BadgeFfz",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "FFZ2 badge",
+ "trailingSpace": true,
+ "type": "FfzBadgeElement"
+ },
+ {
+ "color": "#18151617",
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/FFZ2.png"
+ },
+ "name": "",
+ "tooltip": "FFZ2 badge"
+ },
+ "flags": "BadgeFfz",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "FFZ2 badge",
+ "trailingSpace": true,
+ "type": "FfzBadgeElement"
+ },
+ {
+ "emote": {
+ "id": "1",
+ "images": {
+ "1x": "https://chatterino.com/7tv//1x"
+ },
+ "name": "",
+ "tooltip": "7TV badge"
+ },
+ "flags": "BadgeSevenTV",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "7TV badge",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "badgy"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "badgy:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "badge"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "546c42a6-21d0-4f3d-a6a0-c77f78d7b131"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "546c42a6-21d0-4f3d-a6a0-c77f78d7b131",
+ "localizedName": "",
+ "loginName": "badgy",
+ "messageText": "badge",
+ "searchText": "badgy badgy: badge ",
+ "serverReceivedTime": "2024-09-19T16:40:56Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/badges.json b/tests/snapshots/MessageBuilder/IRC/badges.json
new file mode 100644
index 000000000..313cf600c
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/badges.json
@@ -0,0 +1,229 @@
+{
+ "input": "@tmi-sent-ts=1726764056444;subscriber=1;id=546c42a6-21d0-4f3d-a6a0-c77f78d7b131;room-id=11148817;user-id=123456;display-name=badgy;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :badgy!badgy@badgy.tmi.twitch.tv PRIVMSG #pajlada :badge",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "badgy",
+ "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": [
+ "16:40"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:40:56",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/Chatterino.png"
+ },
+ "name": "",
+ "tooltip": "Chatterino badge"
+ },
+ "flags": "BadgeChatterino",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Chatterino badge",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#0c090a0b",
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/FFZ1.png"
+ },
+ "name": "",
+ "tooltip": "FFZ1 badge"
+ },
+ "flags": "BadgeFfz",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "FFZ1 badge",
+ "trailingSpace": true,
+ "type": "FfzBadgeElement"
+ },
+ {
+ "color": "#100d0e0f",
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/FFZ2.png"
+ },
+ "name": "",
+ "tooltip": "FFZ2 badge"
+ },
+ "flags": "BadgeFfz",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "FFZ2 badge",
+ "trailingSpace": true,
+ "type": "FfzBadgeElement"
+ },
+ {
+ "color": "#14111213",
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/FFZ2.png"
+ },
+ "name": "",
+ "tooltip": "FFZ2 badge"
+ },
+ "flags": "BadgeFfz",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "FFZ2 badge",
+ "trailingSpace": true,
+ "type": "FfzBadgeElement"
+ },
+ {
+ "color": "#18151617",
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/FFZ2.png"
+ },
+ "name": "",
+ "tooltip": "FFZ2 badge"
+ },
+ "flags": "BadgeFfz",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "FFZ2 badge",
+ "trailingSpace": true,
+ "type": "FfzBadgeElement"
+ },
+ {
+ "emote": {
+ "id": "1",
+ "images": {
+ "1x": "https://chatterino.com/7tv//1x"
+ },
+ "name": "",
+ "tooltip": "7TV badge"
+ },
+ "flags": "BadgeSevenTV",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "7TV badge",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "badgy"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "badgy:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "badge"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "546c42a6-21d0-4f3d-a6a0-c77f78d7b131"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "546c42a6-21d0-4f3d-a6a0-c77f78d7b131",
+ "localizedName": "",
+ "loginName": "badgy",
+ "messageText": "badge",
+ "searchText": "badgy badgy: badge ",
+ "serverReceivedTime": "2024-09-19T16:40:56Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/blocked-user.json b/tests/snapshots/MessageBuilder/IRC/blocked-user.json
new file mode 100644
index 000000000..ee23a860f
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/blocked-user.json
@@ -0,0 +1,5 @@
+{
+ "input": "@tmi-sent-ts=1726692855226;subscriber=1;id=ff82c584-5d20-459f-b2d5-dcacbb693559;room-id=11148817;user-id=12345;display-name=blocked;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :blocked!blocked@blocked.tmi.twitch.tv PRIVMSG #pajlada :a",
+ "output": [
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/cheer1.json b/tests/snapshots/MessageBuilder/IRC/cheer1.json
new file mode 100644
index 000000000..d85bf8844
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/cheer1.json
@@ -0,0 +1,596 @@
+{
+ "input": "@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=fd6c5507-3a4e-4d24-8f6e-fadf07f520d3;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824273752;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 a Cheer1 Cheer1 Cheer1 Cheer1 b c",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "bits-leader"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "slyckity",
+ "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": [
+ "2:44"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "02:44:33",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://bits.twitch.tv",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/f1d2aab6-b647-47af-965b-84909cf303aa/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/f1d2aab6-b647-47af-965b-84909cf303aa/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/f1d2aab6-b647-47af-965b-84909cf303aa/3"
+ },
+ "name": "",
+ "tooltip": "Bits Leader 3"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Bits Leader 3",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff0000ff",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "slyckity"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "slyckity:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.png",
+ "2x": "https://chatterino.com/bits/2.png",
+ "3x": "https://chatterino.com/bits/4.png"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsStatic",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.gif",
+ "2x": "https://chatterino.com/bits/2.gif",
+ "3x": "https://chatterino.com/bits/4.gif"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsAnimated",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "#ff979797",
+ "flags": "BitsAmount",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.png",
+ "2x": "https://chatterino.com/bits/2.png",
+ "3x": "https://chatterino.com/bits/4.png"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsStatic",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.gif",
+ "2x": "https://chatterino.com/bits/2.gif",
+ "3x": "https://chatterino.com/bits/4.gif"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsAnimated",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "#ff979797",
+ "flags": "BitsAmount",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.png",
+ "2x": "https://chatterino.com/bits/2.png",
+ "3x": "https://chatterino.com/bits/4.png"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsStatic",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.gif",
+ "2x": "https://chatterino.com/bits/2.gif",
+ "3x": "https://chatterino.com/bits/4.gif"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsAnimated",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "#ff979797",
+ "flags": "BitsAmount",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.png",
+ "2x": "https://chatterino.com/bits/2.png",
+ "3x": "https://chatterino.com/bits/4.png"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsStatic",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.gif",
+ "2x": "https://chatterino.com/bits/2.gif",
+ "3x": "https://chatterino.com/bits/4.gif"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsAnimated",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "#ff979797",
+ "flags": "BitsAmount",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.png",
+ "2x": "https://chatterino.com/bits/2.png",
+ "3x": "https://chatterino.com/bits/4.png"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsStatic",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.gif",
+ "2x": "https://chatterino.com/bits/2.gif",
+ "3x": "https://chatterino.com/bits/4.gif"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsAnimated",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "#ff979797",
+ "flags": "BitsAmount",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "b"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "c"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "fd6c5507-3a4e-4d24-8f6e-fadf07f520d3"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|CheerMessage",
+ "id": "fd6c5507-3a4e-4d24-8f6e-fadf07f520d3",
+ "localizedName": "",
+ "loginName": "slyckity",
+ "messageText": "Cheer1 a Cheer1 Cheer1 Cheer1 Cheer1 b c",
+ "searchText": "slyckity slyckity: Cheer1 a Cheer1 Cheer1 Cheer1 Cheer1 b c ",
+ "serverReceivedTime": "2019-09-07T02:44:33Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff0000ff"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/cheer2.json b/tests/snapshots/MessageBuilder/IRC/cheer2.json
new file mode 100644
index 000000000..779645fbc
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/cheer2.json
@@ -0,0 +1,261 @@
+{
+ "input": "@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=fd6c5507-3a4e-4d24-8f6e-fadf07f520d3;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824273752;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 a Cheer1 Cheer1 Cheer1 Cheer1 b c",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "bits-leader"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "slyckity",
+ "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": [
+ "2:44"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "02:44:33",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://bits.twitch.tv",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/f1d2aab6-b647-47af-965b-84909cf303aa/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/f1d2aab6-b647-47af-965b-84909cf303aa/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/f1d2aab6-b647-47af-965b-84909cf303aa/3"
+ },
+ "name": "",
+ "tooltip": "Bits Leader 3"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Bits Leader 3",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff0000ff",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "slyckity"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "slyckity:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.png",
+ "2x": "https://chatterino.com/bits/2.png",
+ "3x": "https://chatterino.com/bits/4.png"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsStatic",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.gif",
+ "2x": "https://chatterino.com/bits/2.gif",
+ "3x": "https://chatterino.com/bits/4.gif"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsAnimated",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "#ff979797",
+ "flags": "BitsAmount",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "5"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "b"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "c"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "fd6c5507-3a4e-4d24-8f6e-fadf07f520d3"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|CheerMessage",
+ "id": "fd6c5507-3a4e-4d24-8f6e-fadf07f520d3",
+ "localizedName": "",
+ "loginName": "slyckity",
+ "messageText": "Cheer1 a Cheer1 Cheer1 Cheer1 Cheer1 b c",
+ "searchText": "slyckity slyckity: Cheer1 a Cheer1 Cheer1 Cheer1 Cheer1 b c ",
+ "serverReceivedTime": "2019-09-07T02:44:33Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff0000ff"
+ }
+ ],
+ "settings": {
+ "emotes": {
+ "stackBits": true
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/cheer3.json b/tests/snapshots/MessageBuilder/IRC/cheer3.json
new file mode 100644
index 000000000..33b474a4a
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/cheer3.json
@@ -0,0 +1,211 @@
+{
+ "input": "@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=fd6c5507-3a4e-4d24-8f6e-fadf07f520d3;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824273752;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer100",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "bits-leader"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "slyckity",
+ "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": [
+ "2:44"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "02:44:33",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://bits.twitch.tv",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/f1d2aab6-b647-47af-965b-84909cf303aa/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/f1d2aab6-b647-47af-965b-84909cf303aa/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/f1d2aab6-b647-47af-965b-84909cf303aa/3"
+ },
+ "name": "",
+ "tooltip": "Bits Leader 3"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Bits Leader 3",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff0000ff",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "slyckity"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "slyckity:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.png",
+ "2x": "https://chatterino.com/bits/2.png",
+ "3x": "https://chatterino.com/bits/4.png"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsStatic",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/bits/1.gif",
+ "2x": "https://chatterino.com/bits/2.gif",
+ "3x": "https://chatterino.com/bits/4.gif"
+ },
+ "name": "cheer emote",
+ "tooltip": "Cheer1
Twitch Cheer Emote"
+ },
+ "flags": "BitsAnimated",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "cheer",
+ "emote"
+ ]
+ },
+ "tooltip": "Cheer1
Twitch Cheer Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "#ff979797",
+ "flags": "BitsAmount",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "5"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "fd6c5507-3a4e-4d24-8f6e-fadf07f520d3"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|CheerMessage",
+ "id": "fd6c5507-3a4e-4d24-8f6e-fadf07f520d3",
+ "localizedName": "",
+ "loginName": "slyckity",
+ "messageText": "Cheer100",
+ "searchText": "slyckity slyckity: Cheer100 ",
+ "serverReceivedTime": "2019-09-07T02:44:33Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff0000ff"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/cheer4.json b/tests/snapshots/MessageBuilder/IRC/cheer4.json
new file mode 100644
index 000000000..24d5b9ba3
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/cheer4.json
@@ -0,0 +1,141 @@
+{
+ "input": "@badge-info=;badges=bits/1;bits=10;color=#00FF7F;display-name=EXDE_HUN;emotes=;flags=;id=60d8835b-23fa-418c-96ca-5874e5d5e8ba;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566654664248;turbo=0;user-id=129793695;user-type= :exde_hun!exde_hun@exde_hun.tmi.twitch.tv PRIVMSG #pajlada :PogChamp10",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "bits"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "EXDE_HUN",
+ "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": [
+ "13:51"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "13:51:04",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://bits.twitch.tv",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/73b5c3fb-24f9-4a82-a852-2f475b59411c/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/73b5c3fb-24f9-4a82-a852-2f475b59411c/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/73b5c3fb-24f9-4a82-a852-2f475b59411c/3"
+ },
+ "name": "",
+ "tooltip": "cheer 1"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Twitch cheer 1",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff00ff7f",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "EXDE_HUN"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "EXDE_HUN:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "PogChamp10"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "60d8835b-23fa-418c-96ca-5874e5d5e8ba"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|CheerMessage",
+ "id": "60d8835b-23fa-418c-96ca-5874e5d5e8ba",
+ "localizedName": "",
+ "loginName": "exde_hun",
+ "messageText": "PogChamp10",
+ "searchText": "exde_hun exde_hun: PogChamp10 ",
+ "serverReceivedTime": "2019-08-24T13:51:04Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff00ff7f"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/custom-mod.json b/tests/snapshots/MessageBuilder/IRC/custom-mod.json
new file mode 100644
index 000000000..825b0918f
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/custom-mod.json
@@ -0,0 +1,133 @@
+{
+ "input": "@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=;flags=;id=97c28382-e8d2-45a0-bb5d-2305fc4ef139;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922036771;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :foo",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "34"
+ },
+ "badges": [
+ "moderator",
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "testaccount_420",
+ "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": [
+ "10:47"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:47:16",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/ffz-mod1x.png"
+ },
+ "name": "",
+ "tooltip": "Moderator"
+ },
+ "flags": "BadgeChannelAuthority",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Moderator",
+ "trailingSpace": true,
+ "type": "ModBadgeElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "testaccount_420"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "testaccount_420(테스트계정420):"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "foo"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "97c28382-e8d2-45a0-bb5d-2305fc4ef139"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "97c28382-e8d2-45a0-bb5d-2305fc4ef139",
+ "localizedName": "테스트계정420",
+ "loginName": "testaccount_420",
+ "messageText": "foo",
+ "searchText": "testaccount_420(테스트계정420) 테스트계정420 testaccount_420: foo ",
+ "serverReceivedTime": "2020-05-31T10:47:16Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "params": {
+ "ffzCustomModBadge": true
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/custom-vip.json b/tests/snapshots/MessageBuilder/IRC/custom-vip.json
new file mode 100644
index 000000000..d66cd81b8
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/custom-vip.json
@@ -0,0 +1,143 @@
+{
+ "input": "@tmi-sent-ts=1726920321214;subscriber=1;vip=1;id=97bb0dfb-a35f-446d-8634-7522d8ef73ed;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=vip/1,subscriber/48;badge-info=subscriber/64;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :a",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "64"
+ },
+ "badges": [
+ "vip",
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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:05"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "12:05:21",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/ffz-vip1x.png"
+ },
+ "name": "",
+ "tooltip": "VIP"
+ },
+ "flags": "BadgeChannelAuthority",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "VIP",
+ "trailingSpace": true,
+ "type": "VipBadgeElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "97bb0dfb-a35f-446d-8634-7522d8ef73ed"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "97bb0dfb-a35f-446d-8634-7522d8ef73ed",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "a",
+ "searchText": "nerixyz nerixyz: a ",
+ "serverReceivedTime": "2024-09-21T12:05:21Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "params": {
+ "ffzCustomVipBadge": true
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/emote-emoji.json b/tests/snapshots/MessageBuilder/IRC/emote-emoji.json
new file mode 100644
index 000000000..70700cc33
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/emote-emoji.json
@@ -0,0 +1,257 @@
+{
+ "input": "@badge-info=subscriber/80;badges=broadcaster/1,subscriber/3072,partner/1;color=#CC44FF;display-name=pajlada;emotes=25:0-4,8-12;first-msg=0;flags=;id=44f85d39-b5fb-475d-8555-f4244f2f7e82;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1662204423418;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada :Kappa 😂 Kappa",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "80"
+ },
+ "badges": [
+ "broadcaster",
+ "subscriber",
+ "partner"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "pajlada",
+ "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": [
+ "11:27"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "11:27:03",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/3"
+ },
+ "name": "",
+ "tooltip": "Broadcaster"
+ },
+ "flags": "BadgeChannelAuthority",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Broadcaster",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://chatterino.com/tb-1",
+ "2x": "https://chatterino.com/tb-2",
+ "3x": "https://chatterino.com/tb-3"
+ },
+ "name": "",
+ "tooltip": "Subscriber"
+ },
+ "flags": "BadgeSubscription",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Subscriber (Tier 3, 80 months)",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://blog.twitch.tv/2017/04/24/the-verified-badge-is-here-13381bc05735",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/3"
+ },
+ "name": "",
+ "tooltip": "Verified"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Verified",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffcc44ff",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "pajlada"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "pajlada:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://pajbot.com/static/emoji-v2/img/twitter/64/1f602.png"
+ },
+ "name": "😂",
+ "tooltip": ":joy:
Emoji"
+ },
+ "flags": "EmojiImage|EmojiText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "😂"
+ ]
+ },
+ "tooltip": ":joy:
Emoji",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "44f85d39-b5fb-475d-8555-f4244f2f7e82"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "44f85d39-b5fb-475d-8555-f4244f2f7e82",
+ "localizedName": "",
+ "loginName": "pajlada",
+ "messageText": "Kappa 😂 Kappa",
+ "searchText": "pajlada pajlada: Kappa 😂 Kappa ",
+ "serverReceivedTime": "2022-09-03T11:27:03Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffcc44ff"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/emote.json b/tests/snapshots/MessageBuilder/IRC/emote.json
new file mode 100644
index 000000000..5a9ec3fd2
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/emote.json
@@ -0,0 +1,159 @@
+{
+ "input": "@badge-info=;badges=no_audio/1;color=#DAA520;display-name=Mm2PL;emote-only=1;emotes=1902:0-4;first-msg=0;flags=;id=9b1c3cb9-7817-47ea-add1-f9d4a9b4f846;mod=0;returning-chatter=0;room-id=11148817;subscriber=0;tmi-sent-ts=1662201095690;turbo=0;user-id=117691339;user-type= :mm2pl!mm2pl@mm2pl.tmi.twitch.tv PRIVMSG #pajlada :Keepo",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "no_audio"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "Mm2PL",
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:35",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/3"
+ },
+ "name": "",
+ "tooltip": "Watching without audio"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Watching without audio",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffdaa521",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "Mm2PL"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Mm2PL:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/3.0"
+ },
+ "name": "Keepo",
+ "tooltip": "Keepo
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Keepo"
+ ]
+ },
+ "tooltip": "Keepo
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "9b1c3cb9-7817-47ea-add1-f9d4a9b4f846"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "9b1c3cb9-7817-47ea-add1-f9d4a9b4f846",
+ "localizedName": "",
+ "loginName": "mm2pl",
+ "messageText": "Keepo",
+ "searchText": "mm2pl mm2pl: Keepo ",
+ "serverReceivedTime": "2022-09-03T10:31:35Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/emotes.json b/tests/snapshots/MessageBuilder/IRC/emotes.json
new file mode 100644
index 000000000..19d6bd7bf
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/emotes.json
@@ -0,0 +1,227 @@
+{
+ "input": "@badge-info=;badges=no_audio/1;color=#DAA520;display-name=Mm2PL;emote-only=1;emotes=25:0-4/1902:6-10/305954156:12-19;first-msg=0;flags=;id=7be87072-bf24-4fa3-b3df-0ea6fa5f1474;mod=0;returning-chatter=0;room-id=11148817;subscriber=0;tmi-sent-ts=1662201102276;turbo=0;user-id=117691339;user-type= :mm2pl!mm2pl@mm2pl.tmi.twitch.tv PRIVMSG #pajlada :Kappa Keepo PogChamp",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "no_audio"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "Mm2PL",
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:42",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/3"
+ },
+ "name": "",
+ "tooltip": "Watching without audio"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Watching without audio",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffdaa521",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "Mm2PL"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Mm2PL:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/3.0"
+ },
+ "name": "Keepo",
+ "tooltip": "Keepo
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Keepo"
+ ]
+ },
+ "tooltip": "Keepo
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/3.0"
+ },
+ "name": "PogChamp",
+ "tooltip": "PogChamp
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "PogChamp"
+ ]
+ },
+ "tooltip": "PogChamp
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "7be87072-bf24-4fa3-b3df-0ea6fa5f1474"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "7be87072-bf24-4fa3-b3df-0ea6fa5f1474",
+ "localizedName": "",
+ "loginName": "mm2pl",
+ "messageText": "Kappa Keepo PogChamp",
+ "searchText": "mm2pl mm2pl: Kappa Keepo PogChamp ",
+ "serverReceivedTime": "2022-09-03T10:31:42Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/emotes2.json b/tests/snapshots/MessageBuilder/IRC/emotes2.json
new file mode 100644
index 000000000..1fc89dbb6
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/emotes2.json
@@ -0,0 +1,485 @@
+{
+ "input": "@tmi-sent-ts=1726868573676;subscriber=1;id=23c8b81c-7c73-44e4-84f1-7d37e99714e4;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=25:10-14/305954156:50-57/1902:69-73 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :BTTVEmote Kappa 7TVEmote 7TVEmote0w 7TVEmote0w 😂😂 PogChamp 7TVGlobal Keepo FFZEmote FFZGlobal",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "21:42"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "21:42:53",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/BTTVEmote",
+ "images": {
+ "1x": "https://chatterino.com/BTTVEmote.png"
+ },
+ "name": "BTTVEmote",
+ "tooltip": "BTTVEmote Tooltip"
+ },
+ "flags": "BttvEmoteImage|BttvEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "BTTVEmote"
+ ]
+ },
+ "tooltip": "BTTVEmote Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emotes": [
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/7TVEmote",
+ "id": "1",
+ "images": {
+ "1x": "https://chatterino.com/7TVEmote.png"
+ },
+ "name": "7TVEmote",
+ "tooltip": "7TVEmote Tooltip"
+ },
+ "flags": "SevenTVEmoteImage|SevenTVEmoteText"
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "baseName": "ZeroWidth",
+ "homePage": "https://chatterino.com/7TVEmote0w",
+ "id": "2",
+ "images": {
+ "1x": "https://chatterino.com/7TVEmote0w.png"
+ },
+ "name": "7TVEmote0w",
+ "tooltip": "7TVEmote0w Tooltip",
+ "zeroWidth": true
+ },
+ "flags": "SevenTVEmoteImage|SevenTVEmoteText"
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "baseName": "ZeroWidth",
+ "homePage": "https://chatterino.com/7TVEmote0w",
+ "id": "2",
+ "images": {
+ "1x": "https://chatterino.com/7TVEmote0w.png"
+ },
+ "name": "7TVEmote0w",
+ "tooltip": "7TVEmote0w Tooltip",
+ "zeroWidth": true
+ },
+ "flags": "SevenTVEmoteImage|SevenTVEmoteText"
+ }
+ ],
+ "flags": "SevenTVEmoteImage|SevenTVEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "7TVEmote",
+ "7TVEmote0w",
+ "7TVEmote0w"
+ ]
+ },
+ "textElementColor": "Text",
+ "tooltip": "7TVEmote 7TVEmote0w 7TVEmote0w",
+ "tooltips": [
+ ],
+ "trailingSpace": true,
+ "type": "LayeredEmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://pajbot.com/static/emoji-v2/img/twitter/64/1f602.png"
+ },
+ "name": "😂",
+ "tooltip": ":joy:
Emoji"
+ },
+ "flags": "EmojiImage|EmojiText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "😂"
+ ]
+ },
+ "tooltip": ":joy:
Emoji",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://pajbot.com/static/emoji-v2/img/twitter/64/1f602.png"
+ },
+ "name": "😂",
+ "tooltip": ":joy:
Emoji"
+ },
+ "flags": "EmojiImage|EmojiText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "😂"
+ ]
+ },
+ "tooltip": ":joy:
Emoji",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/3.0"
+ },
+ "name": "PogChamp",
+ "tooltip": "PogChamp
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "PogChamp"
+ ]
+ },
+ "tooltip": "PogChamp
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/7TVGlobal",
+ "id": "G1",
+ "images": {
+ "1x": "https://chatterino.com/7TVGlobal.png"
+ },
+ "name": "7TVGlobal",
+ "tooltip": "7TVGlobal Tooltip"
+ },
+ "flags": "SevenTVEmoteImage|SevenTVEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "7TVGlobal"
+ ]
+ },
+ "tooltip": "7TVGlobal Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/3.0"
+ },
+ "name": "Keepo",
+ "tooltip": "Keepo
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Keepo"
+ ]
+ },
+ "tooltip": "Keepo
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/FFZEmote",
+ "images": {
+ "1x": "https://chatterino.com/FFZEmote.png"
+ },
+ "name": "FFZEmote",
+ "tooltip": "FFZEmote Tooltip"
+ },
+ "flags": "FfzEmoteImage|FfzEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "FFZEmote"
+ ]
+ },
+ "tooltip": "FFZEmote Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/FFZGlobal",
+ "images": {
+ "1x": "https://chatterino.com/FFZGlobal.png"
+ },
+ "name": "FFZGlobal",
+ "tooltip": "FFZGlobal Tooltip"
+ },
+ "flags": "FfzEmoteImage|FfzEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "FFZGlobal"
+ ]
+ },
+ "tooltip": "FFZGlobal Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "23c8b81c-7c73-44e4-84f1-7d37e99714e4"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "23c8b81c-7c73-44e4-84f1-7d37e99714e4",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "BTTVEmote Kappa 7TVEmote 7TVEmote0w 7TVEmote0w 😂😂 PogChamp 7TVGlobal Keepo FFZEmote FFZGlobal",
+ "searchText": "nerixyz nerixyz: BTTVEmote Kappa 7TVEmote 7TVEmote0w 7TVEmote0w 😂😂 PogChamp 7TVGlobal Keepo FFZEmote FFZGlobal ",
+ "serverReceivedTime": "2024-09-20T21:42:53Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/emotes3.json b/tests/snapshots/MessageBuilder/IRC/emotes3.json
new file mode 100644
index 000000000..9249074b9
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/emotes3.json
@@ -0,0 +1,284 @@
+{
+ "input": "@tmi-sent-ts=1726761013541;subscriber=1;id=aea562cc-1adf-47de-b656-0aaa245f1030;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=25:1-5/25:9-13 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :😂Kappa😂 &Kappa a b",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "15:50"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "15:50:13",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://pajbot.com/static/emoji-v2/img/twitter/64/1f602.png"
+ },
+ "name": "😂",
+ "tooltip": ":joy:
Emoji"
+ },
+ "flags": "EmojiImage|EmojiText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "😂"
+ ]
+ },
+ "tooltip": ":joy:
Emoji",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": false,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://pajbot.com/static/emoji-v2/img/twitter/64/1f602.png"
+ },
+ "name": "😂",
+ "tooltip": ":joy:
Emoji"
+ },
+ "flags": "EmojiImage|EmojiText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "😂"
+ ]
+ },
+ "tooltip": ":joy:
Emoji",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "&"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "b"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "aea562cc-1adf-47de-b656-0aaa245f1030"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "aea562cc-1adf-47de-b656-0aaa245f1030",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "😂Kappa😂 &Kappa a b",
+ "searchText": "nerixyz nerixyz: 😂Kappa😂 &Kappa a b ",
+ "serverReceivedTime": "2024-09-19T15:50:13Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/emotes4.json b/tests/snapshots/MessageBuilder/IRC/emotes4.json
new file mode 100644
index 000000000..bf50ec23c
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/emotes4.json
@@ -0,0 +1,169 @@
+{
+ "input": "@tmi-sent-ts=1662201102276;emotes=84608:0-0 :test!test@test.tmi.twitch.tv PRIVMSG #pajlada :foo bar",
+ "output": [
+ {
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:42",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ff999999",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "test:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/84608/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/84608/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/84608/default/dark/3.0"
+ },
+ "name": "f",
+ "tooltip": "f
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "f"
+ ]
+ },
+ "tooltip": "f
Twitch Emote",
+ "trailingSpace": false,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "oo"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "bar"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": ""
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "",
+ "localizedName": "",
+ "loginName": "test",
+ "messageText": "foo bar",
+ "searchText": "test test: foo bar ",
+ "serverReceivedTime": "2022-09-03T10:31:42Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/emotes5.json b/tests/snapshots/MessageBuilder/IRC/emotes5.json
new file mode 100644
index 000000000..3b2e01b74
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/emotes5.json
@@ -0,0 +1,169 @@
+{
+ "input": "@tmi-sent-ts=1662201102276;emotes=84609:0-1 :test!test@test.tmi.twitch.tv PRIVMSG #pajlada :foo bar",
+ "output": [
+ {
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:42",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ff999999",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "test:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/84609/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/84609/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/84609/default/dark/3.0"
+ },
+ "name": "fo",
+ "tooltip": "fo
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "fo"
+ ]
+ },
+ "tooltip": "fo
Twitch Emote",
+ "trailingSpace": false,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "o"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "bar"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": ""
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "",
+ "localizedName": "",
+ "loginName": "test",
+ "messageText": "foo bar",
+ "searchText": "test test: foo bar ",
+ "serverReceivedTime": "2022-09-03T10:31:42Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/first-msg.json b/tests/snapshots/MessageBuilder/IRC/first-msg.json
new file mode 100644
index 000000000..ada382d14
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/first-msg.json
@@ -0,0 +1,161 @@
+{
+ "input": "@badge-info=subscriber/17;badges=subscriber/12,no_audio/1;color=#EBA2C0;display-name=jammehcow;emote-only=1;emotes=25:0-4;first-msg=1;flags=;id=9c2dd916-5a6d-4c1f-9fe7-a081b62a9c6b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1662201093248;turbo=0;user-id=82674227;user-type= :jammehcow!jammehcow@jammehcow.tmi.twitch.tv PRIVMSG #pajlada :Kappa",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "17"
+ },
+ "badges": [
+ "subscriber",
+ "no_audio"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "jammehcow",
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:33",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/3"
+ },
+ "name": "",
+ "tooltip": "Watching without audio"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Watching without audio",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffeba2c0",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "jammehcow"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "jammehcow:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "9c2dd916-5a6d-4c1f-9fe7-a081b62a9c6b"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|FirstMessage",
+ "id": "9c2dd916-5a6d-4c1f-9fe7-a081b62a9c6b",
+ "localizedName": "",
+ "loginName": "jammehcow",
+ "messageText": "Kappa",
+ "searchText": "jammehcow jammehcow: Kappa ",
+ "serverReceivedTime": "2022-09-03T10:31:33Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffeba2c0"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/highlight1.json b/tests/snapshots/MessageBuilder/IRC/highlight1.json
new file mode 100644
index 000000000..870c15a04
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/highlight1.json
@@ -0,0 +1,152 @@
+{
+ "input": "@tmi-sent-ts=1726865239492;subscriber=1;id=1bf2d49a-8b88-4116-81dd-5098f11726eb;room-id=11148817;user-id=129546453;badges=;color=#FF0000;flags=;user-type=;emotes= :ignoreduser!ignoreduser@ignoreduser.tmi.twitch.tv PRIVMSG #pajlada :my-highlight",
+ "output": [
+ {
+ "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": [
+ "20:47"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:47:19",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "ignoreduser:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "my-highlight"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "1bf2d49a-8b88-4116-81dd-5098f11726eb"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "1bf2d49a-8b88-4116-81dd-5098f11726eb",
+ "localizedName": "",
+ "loginName": "ignoreduser",
+ "messageText": "my-highlight",
+ "searchText": "ignoreduser ignoreduser: my-highlight ",
+ "serverReceivedTime": "2024-09-20T20:47:19Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "highlighting": {
+ "blacklist": [
+ {
+ "pattern": "ignoreduser",
+ "regex": false
+ }
+ ],
+ "highlights": [
+ {
+ "alert": false,
+ "case": false,
+ "color": "#7f7f3f49",
+ "pattern": "my-highlight",
+ "regex": false,
+ "showInMentions": true,
+ "sound": true,
+ "soundUrl": ""
+ },
+ {
+ "alert": false,
+ "case": false,
+ "color": "#48ae812f",
+ "pattern": "no-mention-highlight",
+ "regex": false,
+ "showInMentions": false,
+ "sound": true,
+ "soundUrl": ""
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/highlight2.json b/tests/snapshots/MessageBuilder/IRC/highlight2.json
new file mode 100644
index 000000000..aff7ba1d4
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/highlight2.json
@@ -0,0 +1,179 @@
+{
+ "input": "@tmi-sent-ts=1726866916736;subscriber=1;id=7b5d2152-8eec-41ce-83eb-999ce5c21d28;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :something my-highlight *",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "21:15"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "21:15:16",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "something"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "my-highlight"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "*"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "7b5d2152-8eec-41ce-83eb-999ce5c21d28"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Highlighted|Collapsed|ShowInMentions",
+ "highlightColor": "#7f7f3f49",
+ "id": "7b5d2152-8eec-41ce-83eb-999ce5c21d28",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "something my-highlight *",
+ "searchText": "nerixyz nerixyz: something my-highlight * ",
+ "serverReceivedTime": "2024-09-20T21:15:16Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "highlighting": {
+ "highlights": [
+ {
+ "alert": false,
+ "case": false,
+ "color": "#7f7f3f49",
+ "pattern": "my-highlight",
+ "regex": false,
+ "showInMentions": true,
+ "sound": true,
+ "soundUrl": ""
+ },
+ {
+ "alert": false,
+ "case": false,
+ "color": "#48ae812f",
+ "pattern": "no-mention-highlight",
+ "regex": false,
+ "showInMentions": false,
+ "sound": true,
+ "soundUrl": ""
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/highlight3.json b/tests/snapshots/MessageBuilder/IRC/highlight3.json
new file mode 100644
index 000000000..f60774fc9
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/highlight3.json
@@ -0,0 +1,164 @@
+{
+ "input": "@tmi-sent-ts=1726866927483;subscriber=1;id=f72f5a7a-522d-407c-ac16-a0b413632fa9;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :foo no-mention-highlight",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "21:15"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "21:15:27",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "foo"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "no-mention-highlight"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "f72f5a7a-522d-407c-ac16-a0b413632fa9"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Highlighted|Collapsed",
+ "highlightColor": "#48ae812f",
+ "id": "f72f5a7a-522d-407c-ac16-a0b413632fa9",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "foo no-mention-highlight",
+ "searchText": "nerixyz nerixyz: foo no-mention-highlight ",
+ "serverReceivedTime": "2024-09-20T21:15:27Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "highlighting": {
+ "highlights": [
+ {
+ "alert": false,
+ "case": false,
+ "color": "#7f7f3f49",
+ "pattern": "my-highlight",
+ "regex": false,
+ "showInMentions": true,
+ "sound": true,
+ "soundUrl": ""
+ },
+ {
+ "alert": false,
+ "case": false,
+ "color": "#48ae812f",
+ "pattern": "no-mention-highlight",
+ "regex": false,
+ "showInMentions": false,
+ "sound": true,
+ "soundUrl": ""
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/hype-chat-invalid.json b/tests/snapshots/MessageBuilder/IRC/hype-chat-invalid.json
new file mode 100644
index 000000000..414077792
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/hype-chat-invalid.json
@@ -0,0 +1,162 @@
+{
+ "input": "@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=5-00;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",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "3"
+ },
+ "badges": [
+ "subscriber",
+ "bits-charity"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "SnoopyTheBot",
+ "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": [
+ "2:46"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "02:46:14",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/5d9f2208-5dd8-11e7-8513-2ff4adfae661/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/5d9f2208-5dd8-11e7-8513-2ff4adfae661/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/5d9f2208-5dd8-11e7-8513-2ff4adfae661/3"
+ },
+ "name": "",
+ "tooltip": "Subscriber"
+ },
+ "flags": "BadgeSubscription",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Subscriber (3 months)",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://link.twitch.tv/blizzardofbits",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/a539dc18-ae19-49b0-98c4-8391a594332b/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/a539dc18-ae19-49b0-98c4-8391a594332b/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/a539dc18-ae19-49b0-98c4-8391a594332b/3"
+ },
+ "name": "",
+ "tooltip": "Direct Relief - Charity 2018"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Direct Relief - Charity 2018",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff0000ff",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "SnoopyTheBot"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "SnoopyTheBot:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "-$5"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "8779a9e5-cf1b-47b3-b9fe-67a5b1b605f6"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|ElevatedMessage",
+ "id": "8779a9e5-cf1b-47b3-b9fe-67a5b1b605f6",
+ "localizedName": "",
+ "loginName": "snoopythebot",
+ "messageText": "-$5",
+ "searchText": "snoopythebot snoopythebot: -$5 ",
+ "serverReceivedTime": "2022-09-30T02:46:14Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff0000ff"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/hype-chat0.json b/tests/snapshots/MessageBuilder/IRC/hype-chat0.json
new file mode 100644
index 000000000..f22bd0e0f
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/hype-chat0.json
@@ -0,0 +1,254 @@
+{
+ "input": "@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",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "3"
+ },
+ "badges": [
+ "subscriber",
+ "bits-charity"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "SnoopyTheBot",
+ "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": [
+ "2:46"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "02:46:14",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/5d9f2208-5dd8-11e7-8513-2ff4adfae661/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/5d9f2208-5dd8-11e7-8513-2ff4adfae661/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/5d9f2208-5dd8-11e7-8513-2ff4adfae661/3"
+ },
+ "name": "",
+ "tooltip": "Subscriber"
+ },
+ "flags": "BadgeSubscription",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Subscriber (3 months)",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://link.twitch.tv/blizzardofbits",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/a539dc18-ae19-49b0-98c4-8391a594332b/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/a539dc18-ae19-49b0-98c4-8391a594332b/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/a539dc18-ae19-49b0-98c4-8391a594332b/3"
+ },
+ "name": "",
+ "tooltip": "Direct Relief - Charity 2018"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Direct Relief - Charity 2018",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff0000ff",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "SnoopyTheBot"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "SnoopyTheBot:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "-$5"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "8779a9e5-cf1b-47b3-b9fe-67a5b1b605f6"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|ElevatedMessage",
+ "id": "8779a9e5-cf1b-47b3-b9fe-67a5b1b605f6",
+ "localizedName": "",
+ "loginName": "snoopythebot",
+ "messageText": "-$5",
+ "searchText": "snoopythebot snoopythebot: -$5 ",
+ "serverReceivedTime": "2022-09-30T02:46:14Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff0000ff"
+ },
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ ],
+ "channelName": "",
+ "count": 1,
+ "displayName": "",
+ "elements": [
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "2:46"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "02:46:14",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "color": "System",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Hype"
+ ]
+ },
+ {
+ "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": [
+ "USD5.00"
+ ]
+ }
+ ],
+ "flags": "System|DoNotTriggerNotification|ElevatedMessage",
+ "id": "",
+ "localizedName": "",
+ "loginName": "",
+ "messageText": "Hype Chat USD5.00",
+ "searchText": "Hype Chat USD5.00",
+ "serverReceivedTime": "",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/hype-chat1.json b/tests/snapshots/MessageBuilder/IRC/hype-chat1.json
new file mode 100644
index 000000000..2d709c606
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/hype-chat1.json
@@ -0,0 +1,406 @@
+{
+ "input": "@pinned-chat-paid-level=ONE;mod=0;flags=;pinned-chat-paid-amount=1400;pinned-chat-paid-exponent=2;tmi-sent-ts=1687970631828;subscriber=1;user-type=;color=#9DA364;emotes=;badges=predictions/blue-1,subscriber/60,twitchconAmsterdam2020/1;pinned-chat-paid-canonical-amount=1400;turbo=0;user-id=26753388;id=e6681ba0-cdc6-4482-93a3-515b74361e8b;room-id=36340781;first-msg=0;returning-chatter=0;pinned-chat-paid-currency=NOK;pinned-chat-paid-is-system-message=0;badge-info=predictions/Day\\s53/53\\sforsenSmug,subscriber/67;display-name=matrHS :matrhs!matrhs@matrhs.tmi.twitch.tv PRIVMSG #pajlada :Title: Beating the record. but who is recordingLOL",
+ "output": [
+ {
+ "badgeInfos": {
+ "predictions": "Day\\s53/53\\sforsenSmug",
+ "subscriber": "67"
+ },
+ "badges": [
+ "predictions",
+ "subscriber",
+ "twitchconAmsterdam2020"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "matrHS",
+ "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": [
+ "16:43"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:43:51",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/e33d8b46-f63b-4e67-996d-4a7dcec0ad33/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/e33d8b46-f63b-4e67-996d-4a7dcec0ad33/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/e33d8b46-f63b-4e67-996d-4a7dcec0ad33/3"
+ },
+ "name": "",
+ "tooltip": "Predicted Blue (1)"
+ },
+ "flags": "BadgePredictions",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Predicted Day 53/53 forsenSmug",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://www.twitchcon.com/amsterdam/?utm_source=twitch-chat&utm_medium=badge&utm_campaign=tcamsterdam20",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/ed917c9a-1a45-4340-9c64-ca8be4348c51/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/ed917c9a-1a45-4340-9c64-ca8be4348c51/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/ed917c9a-1a45-4340-9c64-ca8be4348c51/3"
+ },
+ "name": "",
+ "tooltip": "TwitchCon 2020 - Amsterdam"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "TwitchCon 2020 - Amsterdam",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff9da364",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "matrHS"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "matrHS:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Title:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Beating"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "the"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "record."
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "but"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "who"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "is"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "recordingLOL"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "e6681ba0-cdc6-4482-93a3-515b74361e8b"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|ElevatedMessage",
+ "id": "e6681ba0-cdc6-4482-93a3-515b74361e8b",
+ "localizedName": "",
+ "loginName": "matrhs",
+ "messageText": "Title: Beating the record. but who is recordingLOL",
+ "searchText": "matrhs matrhs: Title: Beating the record. but who is recordingLOL ",
+ "serverReceivedTime": "2023-06-28T16:43:51Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff9da364"
+ },
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ ],
+ "channelName": "",
+ "count": 1,
+ "displayName": "",
+ "elements": [
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "16:43"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:43:51",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "color": "System",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Level"
+ ]
+ },
+ {
+ "color": "System",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "color": "System",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Hype"
+ ]
+ },
+ {
+ "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": [
+ "(30s)"
+ ]
+ },
+ {
+ "color": "System",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "NOK14.00"
+ ]
+ }
+ ],
+ "flags": "System|DoNotTriggerNotification|ElevatedMessage",
+ "id": "",
+ "localizedName": "",
+ "loginName": "",
+ "messageText": "Level 1 Hype Chat (30s) NOK14.00",
+ "searchText": "Level 1 Hype Chat (30s) NOK14.00",
+ "serverReceivedTime": "",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/hype-chat2.json b/tests/snapshots/MessageBuilder/IRC/hype-chat2.json
new file mode 100644
index 000000000..20d8b872d
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/hype-chat2.json
@@ -0,0 +1,404 @@
+{
+ "input": "@room-id=36340781;tmi-sent-ts=1687970634371;flags=;id=39a80a3d-c16e-420f-9bbb-faba4976a3bb;badges=subscriber/6,premium/1;emotes=;display-name=rickharrisoncoc;pinned-chat-paid-level=TWO;turbo=0;pinned-chat-paid-amount=500;pinned-chat-paid-is-system-message=0;color=#FF69B4;subscriber=1;user-type=;first-msg=0;pinned-chat-paid-currency=USD;pinned-chat-paid-canonical-amount=500;user-id=518404689;badge-info=subscriber/10;pinned-chat-paid-exponent=2;returning-chatter=0;mod=0 :rickharrisoncoc!rickharrisoncoc@rickharrisoncoc.tmi.twitch.tv PRIVMSG #pajlada :forsen please read my super chat. Please.",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "10"
+ },
+ "badges": [
+ "subscriber",
+ "premium"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "rickharrisoncoc",
+ "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": [
+ "16:43"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:43:54",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/ed51a614-2c44-4a60-80b6-62908436b43a/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/ed51a614-2c44-4a60-80b6-62908436b43a/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/ed51a614-2c44-4a60-80b6-62908436b43a/3"
+ },
+ "name": "",
+ "tooltip": "6-Month Subscriber"
+ },
+ "flags": "BadgeSubscription",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "6-Month Subscriber (10 months)",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://gaming.amazon.com",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/bbbe0db0-a598-423e-86d0-f9fb98ca1933/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/bbbe0db0-a598-423e-86d0-f9fb98ca1933/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/bbbe0db0-a598-423e-86d0-f9fb98ca1933/3"
+ },
+ "name": "",
+ "tooltip": "Prime Gaming"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Prime Gaming",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffff69b4",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "rickharrisoncoc"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "rickharrisoncoc:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "forsen"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "please"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "read"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "my"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "super"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "chat."
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Please."
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "39a80a3d-c16e-420f-9bbb-faba4976a3bb"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|ElevatedMessage",
+ "id": "39a80a3d-c16e-420f-9bbb-faba4976a3bb",
+ "localizedName": "",
+ "loginName": "rickharrisoncoc",
+ "messageText": "forsen please read my super chat. Please.",
+ "searchText": "rickharrisoncoc rickharrisoncoc: forsen please read my super chat. Please. ",
+ "serverReceivedTime": "2023-06-28T16:43:54Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff69b4"
+ },
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ ],
+ "channelName": "",
+ "count": 1,
+ "displayName": "",
+ "elements": [
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "16:43"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:43:54",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "color": "System",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Level"
+ ]
+ },
+ {
+ "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": [
+ "Hype"
+ ]
+ },
+ {
+ "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": [
+ "(2m"
+ ]
+ },
+ {
+ "color": "System",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "30s)"
+ ]
+ },
+ {
+ "color": "System",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "USD5.00"
+ ]
+ }
+ ],
+ "flags": "System|DoNotTriggerNotification|ElevatedMessage",
+ "id": "",
+ "localizedName": "",
+ "loginName": "",
+ "messageText": "Level 2 Hype Chat (2m 30s) USD5.00",
+ "searchText": "Level 2 Hype Chat (2m 30s) USD5.00",
+ "serverReceivedTime": "",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/ignore-block1.json b/tests/snapshots/MessageBuilder/IRC/ignore-block1.json
new file mode 100644
index 000000000..94467cb05
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/ignore-block1.json
@@ -0,0 +1,163 @@
+{
+ "input": "@tmi-sent-ts=1726692539140;subscriber=1;id=c34a372b-d59e-4a36-9ad8-d964098526e4;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :block!",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "20:48"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:48:59",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "block!"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "c34a372b-d59e-4a36-9ad8-d964098526e4"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "c34a372b-d59e-4a36-9ad8-d964098526e4",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "block!",
+ "searchText": "nerixyz nerixyz: block! ",
+ "serverReceivedTime": "2024-09-18T20:48:59Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "ignore": {
+ "phrases": [
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "ignore",
+ "regex": false,
+ "replaceWith": "replace"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "BLOCK",
+ "regex": false,
+ "replaceWith": "?"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "block!{2,}",
+ "regex": true,
+ "replaceWith": "?"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "",
+ "regex": false,
+ "replaceWith": "empty"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "(",
+ "regex": true,
+ "replaceWith": "invalid"
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/ignore-block2.json b/tests/snapshots/MessageBuilder/IRC/ignore-block2.json
new file mode 100644
index 000000000..119db3bbf
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/ignore-block2.json
@@ -0,0 +1,32 @@
+{
+ "input": "@tmi-sent-ts=1726692555236;subscriber=1;id=38a32590-81c6-460b-a49d-fcf2bec0f788;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :somethingblock!!something",
+ "output": [
+ ],
+ "settings": {
+ "ignore": {
+ "phrases": [
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "ignore",
+ "regex": false,
+ "replaceWith": "replace"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "block!{2,}",
+ "regex": true,
+ "replaceWith": "?"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "",
+ "regex": false,
+ "replaceWith": "empty"
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/ignore-infinite.json b/tests/snapshots/MessageBuilder/IRC/ignore-infinite.json
new file mode 100644
index 000000000..28030cafa
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/ignore-infinite.json
@@ -0,0 +1,225 @@
+{
+ "input": "@tmi-sent-ts=1726865562691;subscriber=1;id=08e1573a-c09a-4052-8cf6-dd9fc5edf35b;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :this is an infinite-loop",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "20:52"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:52:42",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Too"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "many"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "replacements"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "-"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "check"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "your"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "ignores!"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "08e1573a-c09a-4052-8cf6-dd9fc5edf35b"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "08e1573a-c09a-4052-8cf6-dd9fc5edf35b",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "Too many replacements - check your ignores!",
+ "searchText": "nerixyz nerixyz: Too many replacements - check your ignores! ",
+ "serverReceivedTime": "2024-09-20T20:52:42Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "ignore": {
+ "phrases": [
+ {
+ "caseSensitive": true,
+ "isBlock": false,
+ "pattern": "(?<=infinite-loop)$",
+ "regex": true,
+ "replaceWith": "infinite-loop"
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/ignore-replace.json b/tests/snapshots/MessageBuilder/IRC/ignore-replace.json
new file mode 100644
index 000000000..23f92df75
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/ignore-replace.json
@@ -0,0 +1,540 @@
+{
+ "input": "@tmi-sent-ts=1726925714864;subscriber=1;id=2199102c-31ae-49b1-9d2c-a33bb3a02021;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=305954156:0-7/25:16-20,92-96/1902:31-35 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :PogChamp ignore Kappa &fooo123 Keepo &boo1 &baa1 &bi1 &biii1 &biiiiiiiiii420 &foo123&fo2 &[ Kappa ]& summon-emote",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "13:35"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "13:35:14",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/3.0"
+ },
+ "name": "PogChamp",
+ "tooltip": "PogChamp
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "PogChamp"
+ ]
+ },
+ "tooltip": "PogChamp
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "replace"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "&baz1[ooo+123]"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/3.0"
+ },
+ "name": "Keepo",
+ "tooltip": "Keepo
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Keepo"
+ ]
+ },
+ "tooltip": "Keepo
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "&baz2[1+\\2]"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "&baz3[1+\\42]"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "&bi1"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "&biii1"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "&baz4[i+420+i]"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "&baz1[oo+123]&baz1[o+2]"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "{"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "}"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "woah->"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/MyCoolTwitchEmote",
+ "id": "5678",
+ "images": {
+ "1x": "https://chatterino.com/MyCoolTwitchEmote.png"
+ },
+ "name": "MyCoolTwitchEmote",
+ "tooltip": "MyCoolTwitchEmote Tooltip"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "MyCoolTwitchEmote"
+ ]
+ },
+ "tooltip": "MyCoolTwitchEmote Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "2199102c-31ae-49b1-9d2c-a33bb3a02021"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "2199102c-31ae-49b1-9d2c-a33bb3a02021",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "PogChamp replace Kappa &baz1[ooo+123] Keepo &baz2[1+\\2] &baz3[1+\\42] &bi1 &biii1 &baz4[i+420+i] &baz1[oo+123]&baz1[o+2] { Kappa } woah-> MyCoolTwitchEmote",
+ "searchText": "nerixyz nerixyz: PogChamp replace Kappa &baz1[ooo+123] Keepo &baz2[1+\\2] &baz3[1+\\42] &bi1 &biii1 &baz4[i+420+i] &baz1[oo+123]&baz1[o+2] { Kappa } woah-> MyCoolTwitchEmote ",
+ "serverReceivedTime": "2024-09-21T13:35:14Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "ignore": {
+ "phrases": [
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "ignore",
+ "regex": false,
+ "replaceWith": "replace"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": false,
+ "pattern": "CaseSensitive",
+ "regex": false,
+ "replaceWith": "casesensitivE"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": false,
+ "pattern": "summon-emote",
+ "regex": false,
+ "replaceWith": "woah-> MyCoolTwitchEmote"
+ },
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "&f(o+)(\\d+)",
+ "regex": true,
+ "replaceWith": "&baz1[\\1+\\2]"
+ },
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "&b(?:o+)(\\d+)",
+ "regex": true,
+ "replaceWith": "&baz2[\\1+\\2]"
+ },
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "&b(?:a+)(\\d+)",
+ "regex": true,
+ "replaceWith": "&baz3[\\1+\\42]"
+ },
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "&b(i)(i)(i)(i)(i)(i)(i)(i)(i)(i)(\\d+)",
+ "regex": true,
+ "replaceWith": "&baz4[\\10+\\11+\\1]"
+ },
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "&\\[ (\\w+) \\]&",
+ "regex": true,
+ "replaceWith": "{ \\1 }"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "BLOCK",
+ "regex": false,
+ "replaceWith": "?"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "block!{2,}",
+ "regex": true,
+ "replaceWith": "?"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": false,
+ "pattern": "",
+ "regex": false,
+ "replaceWith": "empty"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": false,
+ "pattern": "(",
+ "regex": true,
+ "replaceWith": "invalid"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": false,
+ "pattern": "(?<=infinite-loop)$",
+ "regex": true,
+ "replaceWith": "infinite-loop"
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/justinfan.json b/tests/snapshots/MessageBuilder/IRC/justinfan.json
new file mode 100644
index 000000000..0ed4190ad
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/justinfan.json
@@ -0,0 +1,122 @@
+{
+ "input": "@tmi-sent-ts=1726918643421;subscriber=1;id=e5a690e7-74c8-41cb-977f-55b903f6be23;room-id=11148817;user-id=64537;display-name=justinfan64537;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :justinfan64537!justinfan64537@justinfan64537.tmi.twitch.tv PRIVMSG #pajlada :a",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "justinfan64537",
+ "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": [
+ "11:37"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "11:37:23",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "justinfan64537"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "justinfan64537:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "e5a690e7-74c8-41cb-977f-55b903f6be23"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "e5a690e7-74c8-41cb-977f-55b903f6be23",
+ "localizedName": "",
+ "loginName": "justinfan64537",
+ "messageText": "a",
+ "searchText": "justinfan64537 justinfan64537: a ",
+ "serverReceivedTime": "2024-09-21T11:37:23Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/links.json b/tests/snapshots/MessageBuilder/IRC/links.json
new file mode 100644
index 000000000..7a3325d2c
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/links.json
@@ -0,0 +1,239 @@
+{
+ "input": "@tmi-sent-ts=1726678491958;subscriber=1;id=84b6f44a-c8eb-4abe-8a78-cf736642f695;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :https://chatterino.com (chatterino.com chatterino.com) (chatterino.com)",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "16:54"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:54:51",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Link",
+ "flags": "Text",
+ "link": "https://chatterino.com",
+ "lowercase": [
+ "https://chatterino.com"
+ ],
+ "original": [
+ "https://chatterino.com"
+ ],
+ "style": "ChatMedium",
+ "tooltip": "https://chatterino.com",
+ "trailingSpace": true,
+ "type": "LinkElement",
+ "words": [
+ ""
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": false,
+ "type": "TextElement",
+ "words": [
+ "("
+ ]
+ },
+ {
+ "color": "Link",
+ "flags": "Text",
+ "link": "http://chatterino.com",
+ "lowercase": [
+ "chatterino.com"
+ ],
+ "original": [
+ "chatterino.com"
+ ],
+ "style": "ChatMedium",
+ "tooltip": "chatterino.com",
+ "trailingSpace": true,
+ "type": "LinkElement",
+ "words": [
+ ""
+ ]
+ },
+ {
+ "color": "Link",
+ "flags": "Text",
+ "link": "http://chatterino.com",
+ "lowercase": [
+ "chatterino.com"
+ ],
+ "original": [
+ "chatterino.com"
+ ],
+ "style": "ChatMedium",
+ "tooltip": "chatterino.com",
+ "trailingSpace": false,
+ "type": "LinkElement",
+ "words": [
+ ""
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ ")"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": false,
+ "type": "TextElement",
+ "words": [
+ "("
+ ]
+ },
+ {
+ "color": "Link",
+ "flags": "Text",
+ "link": "http://chatterino.com",
+ "lowercase": [
+ "chatterino.com"
+ ],
+ "original": [
+ "chatterino.com"
+ ],
+ "style": "ChatMedium",
+ "tooltip": "chatterino.com",
+ "trailingSpace": false,
+ "type": "LinkElement",
+ "words": [
+ ""
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ ")"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "84b6f44a-c8eb-4abe-8a78-cf736642f695"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "84b6f44a-c8eb-4abe-8a78-cf736642f695",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "https://chatterino.com (chatterino.com chatterino.com) (chatterino.com)",
+ "searchText": "nerixyz nerixyz: https://chatterino.com (chatterino.com chatterino.com) (chatterino.com) ",
+ "serverReceivedTime": "2024-09-18T16:54:51Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/mentions.json b/tests/snapshots/MessageBuilder/IRC/mentions.json
new file mode 100644
index 000000000..4c52bc389
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/mentions.json
@@ -0,0 +1,173 @@
+{
+ "input": "@tmi-sent-ts=1726678643417;subscriber=1;id=49f33e31-101f-4b03-bfc4-4b8252d0c09c;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@~ @TwitchDev @UserColor!",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "16:57"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:57:23",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "@~"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "MentionElement",
+ "userColor": "Text",
+ "userLoginName": "TwitchDev",
+ "words": [
+ "@TwitchDev"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": false,
+ "type": "MentionElement",
+ "userColor": "#ff010203",
+ "userLoginName": "UserColor",
+ "words": [
+ "@UserColor"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "!"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "49f33e31-101f-4b03-bfc4-4b8252d0c09c"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "49f33e31-101f-4b03-bfc4-4b8252d0c09c",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "@~ @TwitchDev @UserColor!",
+ "searchText": "nerixyz nerixyz: @~ @TwitchDev @UserColor! ",
+ "serverReceivedTime": "2024-09-18T16:57:23Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/mod.json b/tests/snapshots/MessageBuilder/IRC/mod.json
new file mode 100644
index 000000000..f0c632c67
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/mod.json
@@ -0,0 +1,215 @@
+{
+ "input": "@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,16-23;flags=;id=97c28382-e8d2-45a0-bb5d-2305fc4ef139;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922036771;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm, Kreygasm",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "34"
+ },
+ "badges": [
+ "moderator",
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "testaccount_420",
+ "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": [
+ "10:47"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:47:16",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/3267646d-33f0-4b17-b3df-f923a41db1d0/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/3267646d-33f0-4b17-b3df-f923a41db1d0/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/3267646d-33f0-4b17-b3df-f923a41db1d0/3"
+ },
+ "name": "",
+ "tooltip": "Moderator"
+ },
+ "flags": "BadgeChannelAuthority",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Moderator",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "testaccount_420"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "testaccount_420(테스트계정420):"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "-tags"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/41/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/41/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/41/default/dark/3.0"
+ },
+ "name": "Kreygasm",
+ "tooltip": "Kreygasm
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kreygasm"
+ ]
+ },
+ "tooltip": "Kreygasm
Twitch Emote",
+ "trailingSpace": false,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ ","
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/41/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/41/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/41/default/dark/3.0"
+ },
+ "name": "Kreygasm",
+ "tooltip": "Kreygasm
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kreygasm"
+ ]
+ },
+ "tooltip": "Kreygasm
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "97c28382-e8d2-45a0-bb5d-2305fc4ef139"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "97c28382-e8d2-45a0-bb5d-2305fc4ef139",
+ "localizedName": "테스트계정420",
+ "loginName": "testaccount_420",
+ "messageText": "-tags Kreygasm, Kreygasm",
+ "searchText": "testaccount_420(테스트계정420) 테스트계정420 testaccount_420: -tags Kreygasm, Kreygasm ",
+ "serverReceivedTime": "2020-05-31T10:47:16Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/nickname.json b/tests/snapshots/MessageBuilder/IRC/nickname.json
new file mode 100644
index 000000000..8535e03dc
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/nickname.json
@@ -0,0 +1,132 @@
+{
+ "input": "@tmi-sent-ts=1726915984645;subscriber=1;id=a964d705-0b72-4867-aab7-bc9a14945742;room-id=11148817;user-id=12345678;display-name=NickName;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nickname!nickname@nickname.tmi.twitch.tv PRIVMSG #pajlada :nickname",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "NickName",
+ "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": [
+ "10:53"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:53:04",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "NickName"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "replacement:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nickname"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "a964d705-0b72-4867-aab7-bc9a14945742"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "a964d705-0b72-4867-aab7-bc9a14945742",
+ "localizedName": "",
+ "loginName": "nickname",
+ "messageText": "nickname",
+ "searchText": "replacement nickname: nickname ",
+ "serverReceivedTime": "2024-09-21T10:53:04Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "nicknames": [
+ {
+ "isCaseSensitive": false,
+ "isRegex": false,
+ "name": "nickname",
+ "replace": "replacement"
+ }
+ ]
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/no-nick.json b/tests/snapshots/MessageBuilder/IRC/no-nick.json
new file mode 100644
index 000000000..db59fbd29
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/no-nick.json
@@ -0,0 +1,122 @@
+{
+ "input": "@tmi-sent-ts=1726865239492;subscriber=1;id=1bf2d49a-8b88-4116-81dd-5098f11726eb;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :no",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "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": [
+ "20:47"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:47:19",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "(nerixyz):"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "no"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "1bf2d49a-8b88-4116-81dd-5098f11726eb"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "1bf2d49a-8b88-4116-81dd-5098f11726eb",
+ "localizedName": "nerixyz",
+ "loginName": "",
+ "messageText": "no",
+ "searchText": "(nerixyz) nerixyz : no ",
+ "serverReceivedTime": "2024-09-20T20:47:19Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/no-tags.json b/tests/snapshots/MessageBuilder/IRC/no-tags.json
new file mode 100644
index 000000000..103148bc5
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/no-tags.json
@@ -0,0 +1,139 @@
+{
+ "input": "@tmi-sent-ts=1726764056444 :jammehcow!jammehcow@jammehcow.tmi.twitch.tv PRIVMSG #pajlada :Kappa",
+ "output": [
+ {
+ "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": [
+ "16:40"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:40:56",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ff999999",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "jammehcow:"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/Kappa",
+ "images": {
+ "1x": "https://chatterino.com/Kappa.png"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa Tooltip"
+ },
+ "flags": "BttvEmoteImage|BttvEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": ""
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "",
+ "localizedName": "",
+ "loginName": "jammehcow",
+ "messageText": "Kappa",
+ "searchText": "jammehcow jammehcow: Kappa ",
+ "serverReceivedTime": "2024-09-19T16:40:56Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff000000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/redeemed-highlight.json b/tests/snapshots/MessageBuilder/IRC/redeemed-highlight.json
new file mode 100644
index 000000000..e81342e9e
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/redeemed-highlight.json
@@ -0,0 +1,190 @@
+{
+ "input": "@tmi-sent-ts=1726662616942;subscriber=1;id=c5a927a2-1f68-41f7-9137-809a37e58e61;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=1:21-22;msg-id=highlighted-message :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :BTTVGlobal highlight :)",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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:30"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "12:30:16",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/BTTVGlobal",
+ "images": {
+ "1x": "https://chatterino.com/BTTVGlobal.png"
+ },
+ "name": "BTTVGlobal",
+ "tooltip": "BTTVGlobal Tooltip"
+ },
+ "flags": "BttvEmoteImage|BttvEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "BTTVGlobal"
+ ]
+ },
+ "tooltip": "BTTVGlobal Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "highlight"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/1/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/1/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/1/default/dark/3.0"
+ },
+ "name": ":)",
+ "tooltip": ":)
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ ":)"
+ ]
+ },
+ "tooltip": ":)
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "c5a927a2-1f68-41f7-9137-809a37e58e61"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|RedeemedHighlight",
+ "id": "c5a927a2-1f68-41f7-9137-809a37e58e61",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "BTTVGlobal highlight :)",
+ "searchText": "nerixyz nerixyz: BTTVGlobal highlight :) ",
+ "serverReceivedTime": "2024-09-18T12:30:16Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reply-action.json b/tests/snapshots/MessageBuilder/IRC/reply-action.json
new file mode 100644
index 000000000..6f75671c6
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reply-action.json
@@ -0,0 +1,191 @@
+{
+ "input": "@tmi-sent-ts=1726691189926;subscriber=1;id=9d74021f-375a-44f1-80e4-0dd58e64396a;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-thread-parent-display-name=nerixyz;reply-parent-display-name=nerixyz;reply-thread-parent-msg-id=c6ff10fb-7eed-4326-b7ff-bf2b77b9b021;reply-thread-parent-user-login=nerixyz;reply-parent-user-login=nerixyz;reply-thread-parent-user-id=129546453;reply-parent-msg-body=a;reply-parent-msg-id=c6ff10fb-7eed-4326-b7ff-bf2b77b9b021;reply-parent-user-id=129546453 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz b",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "color": "System",
+ "flags": "ChannelName",
+ "link": {
+ "type": "JumpToChannel",
+ "value": "pajlada"
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "#pajlada"
+ ]
+ },
+ {
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ReplyCurveElement"
+ },
+ {
+ "color": "System",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "c6ff10fb-7eed-4326-b7ff-bf2b77b9b021"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Replying",
+ "to"
+ ]
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "@nerixyz"
+ ]
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Text|RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "c6ff10fb-7eed-4326-b7ff-bf2b77b9b021"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "SingleLineTextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "20:26"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:26:29",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "b"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ViewThread",
+ "value": "c6ff10fb-7eed-4326-b7ff-bf2b77b9b021"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|ReplyMessage",
+ "id": "9d74021f-375a-44f1-80e4-0dd58e64396a",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "b",
+ "replyParent": "c6ff10fb-7eed-4326-b7ff-bf2b77b9b021",
+ "replyThread": {
+ "replies": [
+ "9d74021f-375a-44f1-80e4-0dd58e64396a"
+ ],
+ "rootId": "c6ff10fb-7eed-4326-b7ff-bf2b77b9b021",
+ "subscription": "None"
+ },
+ "searchText": "nerixyz nerixyz: b ",
+ "serverReceivedTime": "2024-09-18T20:26:29Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "params": {
+ "prevMessages": [
+ "@tmi-sent-ts=1726691182314;subscriber=1;id=c6ff10fb-7eed-4326-b7ff-bf2b77b9b021;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :\u0001ACTION a\u0001"
+ ]
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reply-block.json b/tests/snapshots/MessageBuilder/IRC/reply-block.json
new file mode 100644
index 000000000..600828ae5
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reply-block.json
@@ -0,0 +1,217 @@
+{
+ "input": "@tmi-sent-ts=1726692474917;subscriber=1;id=289ffa22-d29a-4300-88be-1734b0a33b2a;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-parent-msg-body=BLOCK;reply-thread-parent-user-login=nerixyz;reply-thread-parent-user-id=129546453;reply-parent-user-login=nerixyz;reply-thread-parent-display-name=nerixyz;reply-parent-msg-id=4d2af478-c471-4b3a-8c6f-568a54d2fe7a;reply-thread-parent-msg-id=4d2af478-c471-4b3a-8c6f-568a54d2fe7a;reply-parent-display-name=nerixyz;reply-parent-user-id=129546453 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz reply",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "color": "System",
+ "flags": "ChannelName",
+ "link": {
+ "type": "JumpToChannel",
+ "value": "pajlada"
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "#pajlada"
+ ]
+ },
+ {
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ReplyCurveElement"
+ },
+ {
+ "color": "System",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Replying",
+ "to"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "@nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text|RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "SingleLineTextElement",
+ "words": [
+ "BLOCK"
+ ]
+ },
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "20:47"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:47:54",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "reply"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "289ffa22-d29a-4300-88be-1734b0a33b2a"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "289ffa22-d29a-4300-88be-1734b0a33b2a",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "reply",
+ "searchText": "nerixyz nerixyz: reply ",
+ "serverReceivedTime": "2024-09-18T20:47:54Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "params": {
+ "prevMessages": [
+ "@tmi-sent-ts=1726692469188;subscriber=1;id=4d2af478-c471-4b3a-8c6f-568a54d2fe7a;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :BLOCK"
+ ]
+ },
+ "settings": {
+ "ignore": {
+ "phrases": [
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "ignore",
+ "regex": false,
+ "replaceWith": "replace"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": false,
+ "pattern": "CaseSensitive",
+ "regex": false,
+ "replaceWith": "casesensitivE"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "BLOCK",
+ "regex": false,
+ "replaceWith": "?"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "block!{2,}",
+ "regex": true,
+ "replaceWith": "replace"
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reply-blocked-user.json b/tests/snapshots/MessageBuilder/IRC/reply-blocked-user.json
new file mode 100644
index 000000000..acd299367
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reply-blocked-user.json
@@ -0,0 +1,187 @@
+{
+ "input": "@tmi-sent-ts=1726694327762;subscriber=1;id=e5b84adf-b62d-47ff-8534-2cc57e24a5fb;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-parent-msg-body=a;reply-thread-parent-user-login=blocked;reply-thread-parent-user-id=12345;reply-parent-user-login=blocked;reply-thread-parent-display-name=blocked;reply-parent-msg-id=ff82c584-5d20-459f-b2d5-dcacbb693559;reply-thread-parent-msg-id=ff82c584-5d20-459f-b2d5-dcacbb693559;reply-parent-display-name=blocked;reply-parent-user-id=12345 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz reply",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "color": "System",
+ "flags": "ChannelName",
+ "link": {
+ "type": "JumpToChannel",
+ "value": "pajlada"
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "#pajlada"
+ ]
+ },
+ {
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ReplyCurveElement"
+ },
+ {
+ "color": "System",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Replying",
+ "to"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text|RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "SingleLineTextElement",
+ "words": [
+ "[Blocked",
+ "user]"
+ ]
+ },
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "21:18"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "21:18:47",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "fallbackColor": "Text",
+ "flags": "Text|Mention",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "MentionElement",
+ "userColor": "#ffff0000",
+ "userLoginName": "nerixyz",
+ "words": [
+ "@nerixyz"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "reply"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "e5b84adf-b62d-47ff-8534-2cc57e24a5fb"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "e5b84adf-b62d-47ff-8534-2cc57e24a5fb",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "@nerixyz reply",
+ "searchText": "nerixyz nerixyz: @nerixyz reply ",
+ "serverReceivedTime": "2024-09-18T21:18:47Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "params": {
+ "prevMessages": [
+ "@tmi-sent-ts=1726692855226;subscriber=1;id=ff82c584-5d20-459f-b2d5-dcacbb693559;room-id=11148817;user-id=12345;display-name=blocked;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :blocked!blocked@blocked.tmi.twitch.tv PRIVMSG #pajlada :a"
+ ]
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reply-child.json b/tests/snapshots/MessageBuilder/IRC/reply-child.json
new file mode 100644
index 000000000..8862f7738
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reply-child.json
@@ -0,0 +1,192 @@
+{
+ "input": "@tmi-sent-ts=1726690731517;subscriber=1;id=997cb8fa-4d24-411b-a5cd-433e515a5b72;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-parent-display-name=nerixyz;reply-parent-user-login=nerixyz;reply-thread-parent-user-id=129546453;reply-parent-msg-body=@nerixyz\\sb;reply-parent-user-id=129546453;reply-thread-parent-display-name=nerixyz;reply-thread-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-thread-parent-user-login=nerixyz;reply-parent-msg-id=474f19ab-a1b0-410a-877a-5b0e2ae8be6d :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz c",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "color": "System",
+ "flags": "ChannelName",
+ "link": {
+ "type": "JumpToChannel",
+ "value": "pajlada"
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "#pajlada"
+ ]
+ },
+ {
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ReplyCurveElement"
+ },
+ {
+ "color": "System",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "72d76cb2-f34d-4a93-8005-61bf244456ee"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Replying",
+ "to"
+ ]
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "@nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text|RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "72d76cb2-f34d-4a93-8005-61bf244456ee"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "SingleLineTextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "20:18"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:18:51",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "c"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ViewThread",
+ "value": "72d76cb2-f34d-4a93-8005-61bf244456ee"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|ReplyMessage",
+ "id": "997cb8fa-4d24-411b-a5cd-433e515a5b72",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "c",
+ "replyThread": {
+ "replies": [
+ "474f19ab-a1b0-410a-877a-5b0e2ae8be6d",
+ "997cb8fa-4d24-411b-a5cd-433e515a5b72"
+ ],
+ "rootId": "72d76cb2-f34d-4a93-8005-61bf244456ee",
+ "subscription": "None"
+ },
+ "searchText": "nerixyz nerixyz: c ",
+ "serverReceivedTime": "2024-09-18T20:18:51Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "params": {
+ "prevMessages": [
+ "@tmi-sent-ts=1726690688294;subscriber=1;id=72d76cb2-f34d-4a93-8005-61bf244456ee;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :a",
+ "@tmi-sent-ts=1726690691139;subscriber=1;id=474f19ab-a1b0-410a-877a-5b0e2ae8be6d;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-parent-user-login=nerixyz;reply-thread-parent-user-login=nerixyz;reply-parent-msg-body=a;reply-parent-user-id=129546453;reply-thread-parent-user-id=129546453;reply-thread-parent-display-name=nerixyz;reply-thread-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-parent-display-name=nerixyz :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz b"
+ ]
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reply-ignore.json b/tests/snapshots/MessageBuilder/IRC/reply-ignore.json
new file mode 100644
index 000000000..f3d280877
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reply-ignore.json
@@ -0,0 +1,225 @@
+{
+ "input": "@tmi-sent-ts=1726692492710;subscriber=1;id=89c8f200-ae37-4279-909d-5ff4f9ed10a0;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-parent-user-id=129546453;reply-parent-msg-body=ignore;reply-parent-display-name=nerixyz;reply-thread-parent-user-login=nerixyz;reply-parent-user-login=nerixyz;reply-thread-parent-display-name=nerixyz;reply-thread-parent-msg-id=68a68ef7-0ee3-4584-8937-6d20ff0a7a8a;reply-thread-parent-user-id=129546453;reply-parent-msg-id=68a68ef7-0ee3-4584-8937-6d20ff0a7a8a :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz CaseSensitive",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "color": "System",
+ "flags": "ChannelName",
+ "link": {
+ "type": "JumpToChannel",
+ "value": "pajlada"
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "#pajlada"
+ ]
+ },
+ {
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ReplyCurveElement"
+ },
+ {
+ "color": "System",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "68a68ef7-0ee3-4584-8937-6d20ff0a7a8a"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Replying",
+ "to"
+ ]
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "@nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text|RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "68a68ef7-0ee3-4584-8937-6d20ff0a7a8a"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "SingleLineTextElement",
+ "words": [
+ "replace"
+ ]
+ },
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "20:48"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:48:12",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "casesensitivE"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ViewThread",
+ "value": "68a68ef7-0ee3-4584-8937-6d20ff0a7a8a"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|ReplyMessage",
+ "id": "89c8f200-ae37-4279-909d-5ff4f9ed10a0",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "casesensitivE",
+ "replyParent": "68a68ef7-0ee3-4584-8937-6d20ff0a7a8a",
+ "replyThread": {
+ "replies": [
+ "89c8f200-ae37-4279-909d-5ff4f9ed10a0"
+ ],
+ "rootId": "68a68ef7-0ee3-4584-8937-6d20ff0a7a8a",
+ "subscription": "None"
+ },
+ "searchText": "nerixyz nerixyz: casesensitivE ",
+ "serverReceivedTime": "2024-09-18T20:48:12Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "params": {
+ "prevMessages": [
+ "@tmi-sent-ts=1726692478291;subscriber=1;id=68a68ef7-0ee3-4584-8937-6d20ff0a7a8a;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :ignore"
+ ]
+ },
+ "settings": {
+ "ignore": {
+ "phrases": [
+ {
+ "caseSensitive": false,
+ "isBlock": false,
+ "pattern": "ignore",
+ "regex": false,
+ "replaceWith": "replace"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": false,
+ "pattern": "CaseSensitive",
+ "regex": false,
+ "replaceWith": "casesensitivE"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "BLOCK",
+ "regex": false,
+ "replaceWith": "?"
+ },
+ {
+ "caseSensitive": true,
+ "isBlock": true,
+ "pattern": "block!{2,}",
+ "regex": true,
+ "replaceWith": "replace"
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reply-no-prev.json b/tests/snapshots/MessageBuilder/IRC/reply-no-prev.json
new file mode 100644
index 000000000..ca767517e
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reply-no-prev.json
@@ -0,0 +1,178 @@
+{
+ "input": "@tmi-sent-ts=1726690691139;subscriber=1;id=474f19ab-a1b0-410a-877a-5b0e2ae8be6d;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-parent-user-login=nerixyz;reply-thread-parent-user-login=nerixyz;reply-parent-msg-body=a;reply-parent-user-id=129546453;reply-thread-parent-user-id=129546453;reply-thread-parent-display-name=nerixyz;reply-thread-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-parent-display-name=nerixyz :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz b",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "color": "System",
+ "flags": "ChannelName",
+ "link": {
+ "type": "JumpToChannel",
+ "value": "pajlada"
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "#pajlada"
+ ]
+ },
+ {
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ReplyCurveElement"
+ },
+ {
+ "color": "System",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Replying",
+ "to"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "@nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text|RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "SingleLineTextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "20:18"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:18:11",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "b"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "474f19ab-a1b0-410a-877a-5b0e2ae8be6d"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "474f19ab-a1b0-410a-877a-5b0e2ae8be6d",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "b",
+ "searchText": "nerixyz nerixyz: b ",
+ "serverReceivedTime": "2024-09-18T20:18:11Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reply-root.json b/tests/snapshots/MessageBuilder/IRC/reply-root.json
new file mode 100644
index 000000000..2e138c3d6
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reply-root.json
@@ -0,0 +1,195 @@
+{
+ "input": "@tmi-sent-ts=1726690741480;subscriber=1;id=f0b45994-3e92-4c37-a35f-062072163d9d;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-parent-user-login=nerixyz;reply-thread-parent-display-name=nerixyz;reply-parent-display-name=nerixyz;reply-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-thread-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-thread-parent-user-id=129546453;reply-thread-parent-user-login=nerixyz;reply-parent-msg-body=a;reply-parent-user-id=129546453 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz d",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "color": "System",
+ "flags": "ChannelName",
+ "link": {
+ "type": "JumpToChannel",
+ "value": "pajlada"
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "#pajlada"
+ ]
+ },
+ {
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ReplyCurveElement"
+ },
+ {
+ "color": "System",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "72d76cb2-f34d-4a93-8005-61bf244456ee"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Replying",
+ "to"
+ ]
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "@nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text|RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "72d76cb2-f34d-4a93-8005-61bf244456ee"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "SingleLineTextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "20:19"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:19:01",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "d"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ViewThread",
+ "value": "72d76cb2-f34d-4a93-8005-61bf244456ee"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|ReplyMessage",
+ "id": "f0b45994-3e92-4c37-a35f-062072163d9d",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "d",
+ "replyParent": "72d76cb2-f34d-4a93-8005-61bf244456ee",
+ "replyThread": {
+ "replies": [
+ "474f19ab-a1b0-410a-877a-5b0e2ae8be6d",
+ "997cb8fa-4d24-411b-a5cd-433e515a5b72",
+ "f0b45994-3e92-4c37-a35f-062072163d9d"
+ ],
+ "rootId": "72d76cb2-f34d-4a93-8005-61bf244456ee",
+ "subscription": "None"
+ },
+ "searchText": "nerixyz nerixyz: d ",
+ "serverReceivedTime": "2024-09-18T20:19:01Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "params": {
+ "prevMessages": [
+ "@tmi-sent-ts=1726690688294;subscriber=1;id=72d76cb2-f34d-4a93-8005-61bf244456ee;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :a",
+ "@tmi-sent-ts=1726690691139;subscriber=1;id=474f19ab-a1b0-410a-877a-5b0e2ae8be6d;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-parent-user-login=nerixyz;reply-thread-parent-user-login=nerixyz;reply-parent-msg-body=a;reply-parent-user-id=129546453;reply-thread-parent-user-id=129546453;reply-thread-parent-display-name=nerixyz;reply-thread-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-parent-display-name=nerixyz :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz b",
+ "@tmi-sent-ts=1726690731517;subscriber=1;id=997cb8fa-4d24-411b-a5cd-433e515a5b72;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;reply-parent-display-name=nerixyz;reply-parent-user-login=nerixyz;reply-thread-parent-user-id=129546453;reply-parent-msg-body=@nerixyz\\sb;reply-parent-user-id=129546453;reply-thread-parent-display-name=nerixyz;reply-thread-parent-msg-id=72d76cb2-f34d-4a93-8005-61bf244456ee;reply-thread-parent-user-login=nerixyz;reply-parent-msg-id=474f19ab-a1b0-410a-877a-5b0e2ae8be6d :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz c"
+ ]
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reply-single.json b/tests/snapshots/MessageBuilder/IRC/reply-single.json
new file mode 100644
index 000000000..2fbcf0964
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reply-single.json
@@ -0,0 +1,310 @@
+{
+ "input": "@tmi-sent-ts=1726690593888;subscriber=1;id=5e5f526a-5d60-4d81-800b-3b81b8a34c2c;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=1902:11-15;reply-parent-display-name=nerixyz;reply-thread-parent-msg-id=d95d04a6-5c6a-476a-8dbd-7c6d3b3c277f;reply-thread-parent-user-login=nerixyz;reply-thread-parent-display-name=nerixyz;reply-parent-msg-body=a\\sKappa\\sBTTVEmote\\s😂\\sb;reply-parent-user-login=nerixyz;reply-parent-user-id=129546453;reply-thread-parent-user-id=129546453;reply-parent-msg-id=d95d04a6-5c6a-476a-8dbd-7c6d3b3c277f :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :@nerixyz c Keepo FFZEmote 😭 d",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "color": "System",
+ "flags": "ChannelName",
+ "link": {
+ "type": "JumpToChannel",
+ "value": "pajlada"
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "#pajlada"
+ ]
+ },
+ {
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ReplyCurveElement"
+ },
+ {
+ "color": "System",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "d95d04a6-5c6a-476a-8dbd-7c6d3b3c277f"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Replying",
+ "to"
+ ]
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "RepliedMessage",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "@nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text|RepliedMessage",
+ "link": {
+ "type": "ViewThread",
+ "value": "d95d04a6-5c6a-476a-8dbd-7c6d3b3c277f"
+ },
+ "style": "ChatMediumSmall",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "SingleLineTextElement",
+ "words": [
+ "a",
+ "Kappa",
+ "BTTVEmote",
+ "😂",
+ "b"
+ ]
+ },
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "20:16"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "20:16:33",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "c"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/1902/default/dark/3.0"
+ },
+ "name": "Keepo",
+ "tooltip": "Keepo
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Keepo"
+ ]
+ },
+ "tooltip": "Keepo
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/FFZEmote",
+ "images": {
+ "1x": "https://chatterino.com/FFZEmote.png"
+ },
+ "name": "FFZEmote",
+ "tooltip": "FFZEmote Tooltip"
+ },
+ "flags": "FfzEmoteImage|FfzEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "FFZEmote"
+ ]
+ },
+ "tooltip": "FFZEmote Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://pajbot.com/static/emoji-v2/img/twitter/64/1f62d.png"
+ },
+ "name": "😭",
+ "tooltip": ":sob:
Emoji"
+ },
+ "flags": "EmojiImage|EmojiText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "😭"
+ ]
+ },
+ "tooltip": ":sob:
Emoji",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "d"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ViewThread",
+ "value": "d95d04a6-5c6a-476a-8dbd-7c6d3b3c277f"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|ReplyMessage",
+ "id": "5e5f526a-5d60-4d81-800b-3b81b8a34c2c",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "c Keepo FFZEmote 😭 d",
+ "replyParent": "d95d04a6-5c6a-476a-8dbd-7c6d3b3c277f",
+ "replyThread": {
+ "replies": [
+ "5e5f526a-5d60-4d81-800b-3b81b8a34c2c"
+ ],
+ "rootId": "d95d04a6-5c6a-476a-8dbd-7c6d3b3c277f",
+ "subscription": "None"
+ },
+ "searchText": "nerixyz nerixyz: c Keepo FFZEmote 😭 d ",
+ "serverReceivedTime": "2024-09-18T20:16:33Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "params": {
+ "prevMessages": [
+ "@tmi-sent-ts=1726690560784;subscriber=1;id=d95d04a6-5c6a-476a-8dbd-7c6d3b3c277f;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=25:2-6 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :a Kappa BTTVEmote 😂 b"
+ ]
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reward-bits.json b/tests/snapshots/MessageBuilder/IRC/reward-bits.json
new file mode 100644
index 000000000..8ad2e447d
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reward-bits.json
@@ -0,0 +1,305 @@
+{
+ "input": "@tmi-sent-ts=1726662938032;subscriber=1;id=87af876f-5591-4a63-924f-46f465ecd3c4;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;custom-reward-id=CELEBRATION :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :reward 1",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "0:00"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "00:00:00",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "UserInfo",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ ""
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "redeemed"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/42/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/42/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/42/default/dark/3.0"
+ },
+ "name": "MyBitsEmote",
+ "tooltip": "MyBitsEmote
Twitch Emote"
+ },
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "MyBitsEmote"
+ ]
+ },
+ "tooltip": "MyBitsEmote
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "On-Screen",
+ "Celebration"
+ ]
+ },
+ {
+ "flags": "TwitchEmoteImage|ChannelPointReward",
+ "image": "https://chatterino.com/reward1x.png",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ScalingImageElement"
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "bits"
+ ]
+ },
+ {
+ "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:35"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "12:35:38",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "reward"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "87af876f-5591-4a63-924f-46f465ecd3c4"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|RedeemedChannelPointReward",
+ "id": "87af876f-5591-4a63-924f-46f465ecd3c4",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "reward 1",
+ "reward": {
+ "channelId": "11148817",
+ "cost": 1,
+ "emoteId": "42",
+ "emoteName": "MyBitsEmote",
+ "id": "CELEBRATION",
+ "image": {
+ "1x": "https://chatterino.com/reward1x.png",
+ "2x": "https://chatterino.com/reward2x.png",
+ "3x": "https://chatterino.com/reward4x.png"
+ },
+ "isBits": true,
+ "isUserInputRequired": false,
+ "title": "On-Screen Celebration",
+ "user": {
+ "displayName": "",
+ "id": "",
+ "login": ""
+ }
+ },
+ "searchText": "nerixyz nerixyz: reward 1 redeemed On-Screen Celebration 1",
+ "serverReceivedTime": "2024-09-18T12:35:38Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reward-blocked-user.json b/tests/snapshots/MessageBuilder/IRC/reward-blocked-user.json
new file mode 100644
index 000000000..5aa2fb015
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reward-blocked-user.json
@@ -0,0 +1,5 @@
+{
+ "input": "@tmi-sent-ts=1728127030336;subscriber=1;id=d09d1034-4f73-422c-b239-29d463a47973;room-id=11148817;user-id=12345;display-name=blocked;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=25:11-15;custom-reward-id=47cc9d27-f771-47d9-8fb9-893b8524ccb3 :blocked!blocked@blocked.tmi.twitch.tv PRIVMSG #pajlada :test TESTS Kappa",
+ "output": [
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reward-empty.json b/tests/snapshots/MessageBuilder/IRC/reward-empty.json
new file mode 100644
index 000000000..b9a9f92b4
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reward-empty.json
@@ -0,0 +1,240 @@
+{
+ "input": "@tmi-sent-ts=1726677572248;subscriber=1;id=2932e250-1f49-4587-8783-7a13d9f99f6b;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;custom-reward-id=dc8d1dac-256e-42b9-b7ba-40b32e5294e2 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :empty",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "0:00"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "00:00:00",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "UserInfo",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ ""
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "redeemed"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "test"
+ ]
+ },
+ {
+ "flags": "TwitchEmoteImage|ChannelPointReward",
+ "image": "https://chatterino.com/reward1x.png",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ScalingImageElement"
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "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": [
+ "16:39"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "16:39:32",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "empty"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "2932e250-1f49-4587-8783-7a13d9f99f6b"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|RedeemedChannelPointReward",
+ "id": "2932e250-1f49-4587-8783-7a13d9f99f6b",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "empty",
+ "reward": {
+ "channelId": "11148817",
+ "cost": 1,
+ "emoteId": "",
+ "emoteName": "",
+ "id": "dc8d1dac-256e-42b9-b7ba-40b32e5294e2",
+ "image": {
+ "1x": "https://chatterino.com/reward1x.png",
+ "2x": "https://chatterino.com/reward2x.png",
+ "3x": "https://chatterino.com/reward4x.png"
+ },
+ "isBits": false,
+ "isUserInputRequired": false,
+ "title": "test",
+ "user": {
+ "displayName": "",
+ "id": "",
+ "login": ""
+ }
+ },
+ "searchText": "nerixyz nerixyz: empty redeemed test 1",
+ "serverReceivedTime": "2024-09-18T16:39:32Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reward-known.json b/tests/snapshots/MessageBuilder/IRC/reward-known.json
new file mode 100644
index 000000000..1be46d8cf
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reward-known.json
@@ -0,0 +1,251 @@
+{
+ "input": "@tmi-sent-ts=1726662938032;subscriber=1;id=87af876f-5591-4a63-924f-46f465ecd3c4;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;custom-reward-id=31a2344e-0fce-4229-9453-fb2e8b6dd02c :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :reward 1",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "elements": [
+ {
+ "element": {
+ "color": "System",
+ "flags": "Timestamp",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "0:00"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "00:00:00",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Redeemed"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "my",
+ "reward"
+ ]
+ },
+ {
+ "flags": "TwitchEmoteImage|ChannelPointReward",
+ "image": "https://chatterino.com/reward1x.png",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "ScalingImageElement"
+ },
+ {
+ "color": "Text",
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "flags": "ChannelPointReward",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "LinebreakElement"
+ },
+ {
+ "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:35"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "12:35:38",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "reward"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "1"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "87af876f-5591-4a63-924f-46f465ecd3c4"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|RedeemedChannelPointReward",
+ "id": "87af876f-5591-4a63-924f-46f465ecd3c4",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "reward 1",
+ "reward": {
+ "channelId": "11148817",
+ "cost": 1,
+ "emoteId": "",
+ "emoteName": "",
+ "id": "31a2344e-0fce-4229-9453-fb2e8b6dd02c",
+ "image": {
+ "1x": "https://chatterino.com/reward1x.png",
+ "2x": "https://chatterino.com/reward2x.png",
+ "3x": "https://chatterino.com/reward4x.png"
+ },
+ "isBits": false,
+ "isUserInputRequired": true,
+ "title": "my reward",
+ "user": {
+ "displayName": "",
+ "id": "",
+ "login": ""
+ }
+ },
+ "searchText": "nerixyz nerixyz: reward 1 Redeemed my reward 1",
+ "serverReceivedTime": "2024-09-18T12:35:38Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/reward-unknown.json b/tests/snapshots/MessageBuilder/IRC/reward-unknown.json
new file mode 100644
index 000000000..d614dd1fa
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/reward-unknown.json
@@ -0,0 +1,137 @@
+{
+ "input": "@tmi-sent-ts=1726662961590;subscriber=1;id=4d409401-f692-4dd1-9295-f21350f86860;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=;custom-reward-id=3a02deb3-626e-4d8e-adde-226c90bbbb84 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :reward 2",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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:36"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "12:36:01",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "reward"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "2"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "4d409401-f692-4dd1-9295-f21350f86860"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "4d409401-f692-4dd1-9295-f21350f86860",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "reward 2",
+ "searchText": "nerixyz nerixyz: reward 2 ",
+ "serverReceivedTime": "2024-09-18T12:36:01Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/rm-deleted.json b/tests/snapshots/MessageBuilder/IRC/rm-deleted.json
new file mode 100644
index 000000000..343dee792
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/rm-deleted.json
@@ -0,0 +1,273 @@
+{
+ "input": "@id=3dc39240-0798-4103-8c3c-51e1a9a567a3;first-msg=0;historical=1;rm-received-ts=1726600627161;color=#FF0000;badges=subscriber/24;turbo=0;room-id=11148817;flags=;mod=0;returning-chatter=0;rm-deleted=1;emotes=25:9-13;tmi-sent-ts=1726600626982;subscriber=1;user-id=129546453;display-name=nerixyz;badge-info=subscriber/27;user-type= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :7TVEmote Kappa my7TVEmote hi 7TVEmote❤",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "19:17"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "19:17:07",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/7TVEmote",
+ "id": "1",
+ "images": {
+ "1x": "https://chatterino.com/7TVEmote.png"
+ },
+ "name": "7TVEmote",
+ "tooltip": "7TVEmote Tooltip"
+ },
+ "flags": "SevenTVEmoteImage|SevenTVEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "7TVEmote"
+ ]
+ },
+ "tooltip": "7TVEmote Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "my7TVEmote"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "hi"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/7TVEmote",
+ "id": "1",
+ "images": {
+ "1x": "https://chatterino.com/7TVEmote.png"
+ },
+ "name": "7TVEmote",
+ "tooltip": "7TVEmote Tooltip"
+ },
+ "flags": "SevenTVEmoteImage|SevenTVEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "7TVEmote"
+ ]
+ },
+ "tooltip": "7TVEmote Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://pajbot.com/static/emoji-v2/img/twitter/64/2764-fe0f.png"
+ },
+ "name": "❤️",
+ "tooltip": ":heart:
Emoji"
+ },
+ "flags": "EmojiImage|EmojiText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "❤️"
+ ]
+ },
+ "tooltip": ":heart:
Emoji",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "3dc39240-0798-4103-8c3c-51e1a9a567a3"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Disabled|Collapsed",
+ "id": "3dc39240-0798-4103-8c3c-51e1a9a567a3",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "7TVEmote Kappa my7TVEmote hi 7TVEmote❤",
+ "searchText": "nerixyz nerixyz: 7TVEmote Kappa my7TVEmote hi 7TVEmote❤ ",
+ "serverReceivedTime": "2024-09-17T19:17:07Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/shared-chat-emotes.json b/tests/snapshots/MessageBuilder/IRC/shared-chat-emotes.json
new file mode 100644
index 000000000..b7f9256d4
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/shared-chat-emotes.json
@@ -0,0 +1,392 @@
+{
+ "input": "@tmi-sent-ts=1728311235904;subscriber=1;id=5b6f9721-80fd-4036-951e-1ced9c591b32;room-id=11148817;user-id=129546453;display-name=nerixyz;source-badge-info=;source-room-id=141981764;source-id=d7be8e12-187c-4f96-9d96-d87a17aa9ce9;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=;emotes=25:0-4 :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :Kappa BTTVEmote BTTVGlobal 7TVEmote 7TVGlobal FFZEmote FFZGlobal BTTVTwitchDev 7TVTwitchDev FFZTwitchDev",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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": [
+ "14:27"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "14:27:15",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "BTTVEmote"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/BTTVGlobal",
+ "images": {
+ "1x": "https://chatterino.com/BTTVGlobal.png"
+ },
+ "name": "BTTVGlobal",
+ "tooltip": "BTTVGlobal Tooltip"
+ },
+ "flags": "BttvEmoteImage|BttvEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "BTTVGlobal"
+ ]
+ },
+ "tooltip": "BTTVGlobal Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "7TVEmote"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/7TVGlobal",
+ "id": "G1",
+ "images": {
+ "1x": "https://chatterino.com/7TVGlobal.png"
+ },
+ "name": "7TVGlobal",
+ "tooltip": "7TVGlobal Tooltip"
+ },
+ "flags": "SevenTVEmoteImage|SevenTVEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "7TVGlobal"
+ ]
+ },
+ "tooltip": "7TVGlobal Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "FFZEmote"
+ ]
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/FFZGlobal",
+ "images": {
+ "1x": "https://chatterino.com/FFZGlobal.png"
+ },
+ "name": "FFZGlobal",
+ "tooltip": "FFZGlobal Tooltip"
+ },
+ "flags": "FfzEmoteImage|FfzEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "FFZGlobal"
+ ]
+ },
+ "tooltip": "FFZGlobal Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/BTTVTwitchDev",
+ "images": {
+ "1x": "https://chatterino.com/BTTVTwitchDev.png"
+ },
+ "name": "BTTVTwitchDev",
+ "tooltip": "BTTVTwitchDev Tooltip"
+ },
+ "flags": "BttvEmoteImage|BttvEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "BTTVTwitchDev"
+ ]
+ },
+ "tooltip": "BTTVTwitchDev Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/7TVTwitchDev",
+ "id": "t5",
+ "images": {
+ "1x": "https://chatterino.com/7TVTwitchDev.png"
+ },
+ "name": "7TVTwitchDev",
+ "tooltip": "7TVTwitchDev Tooltip"
+ },
+ "flags": "SevenTVEmoteImage|SevenTVEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "7TVTwitchDev"
+ ]
+ },
+ "tooltip": "7TVTwitchDev Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "emote": {
+ "author": "Chatterino",
+ "homePage": "https://chatterino.com/FFZTwitchDev",
+ "images": {
+ "1x": "https://chatterino.com/FFZTwitchDev.png"
+ },
+ "name": "FFZTwitchDev",
+ "tooltip": "FFZTwitchDev Tooltip"
+ },
+ "flags": "FfzEmoteImage|FfzEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "FFZTwitchDev"
+ ]
+ },
+ "tooltip": "FFZTwitchDev Tooltip",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "5b6f9721-80fd-4036-951e-1ced9c591b32"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "DoNotTriggerNotification|Collapsed|SharedMessage",
+ "id": "5b6f9721-80fd-4036-951e-1ced9c591b32",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "Kappa BTTVEmote BTTVGlobal 7TVEmote 7TVGlobal FFZEmote FFZGlobal BTTVTwitchDev 7TVTwitchDev FFZTwitchDev",
+ "searchText": "nerixyz nerixyz: Kappa BTTVEmote BTTVGlobal 7TVEmote 7TVGlobal FFZEmote FFZGlobal BTTVTwitchDev 7TVTwitchDev FFZTwitchDev ",
+ "serverReceivedTime": "2024-10-07T14:27:15Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/shared-chat-known.json b/tests/snapshots/MessageBuilder/IRC/shared-chat-known.json
new file mode 100644
index 000000000..6a13adcb4
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/shared-chat-known.json
@@ -0,0 +1,161 @@
+{
+ "input": "@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=141981764;source-id=b97eea45-f9dc-4f0c-8744-f8256c3ed950;user-type=staff;user-id=106940612;mod=0 :creativewind!creativewind@creativewind.tmi.twitch.tv PRIVMSG #pajlada :Guy",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "staff",
+ "turbo"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "creativewind",
+ "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": [
+ "0:08"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "00:08:21",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/3"
+ },
+ "name": "",
+ "tooltip": "Staff"
+ },
+ "flags": "BadgeGlobalAuthority",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Staff",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/3"
+ },
+ "name": "",
+ "tooltip": "Turbo"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Turbo",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff00ff00",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "creativewind"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "creativewind:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Guy"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "19ee1663-c14d-41cd-a4a2-30a4bb609c5a"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "DoNotTriggerNotification|Collapsed|SharedMessage",
+ "id": "19ee1663-c14d-41cd-a4a2-30a4bb609c5a",
+ "localizedName": "",
+ "loginName": "creativewind",
+ "messageText": "Guy",
+ "searchText": "creativewind creativewind: Guy ",
+ "serverReceivedTime": "2024-09-27T00:08:21Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff00ff00"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/shared-chat-same-channel.json b/tests/snapshots/MessageBuilder/IRC/shared-chat-same-channel.json
new file mode 100644
index 000000000..5ab3daad0
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/shared-chat-same-channel.json
@@ -0,0 +1,161 @@
+{
+ "input": "@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=11148817;source-id=19ee1663-c14d-41cd-a4a2-30a4bb609c5a;user-type=staff;user-id=106940612;mod=0 :creativewind!creativewind@creativewind.tmi.twitch.tv PRIVMSG #pajlada :Guys",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "staff",
+ "turbo"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "creativewind",
+ "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": [
+ "0:08"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "00:08:21",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/3"
+ },
+ "name": "",
+ "tooltip": "Staff"
+ },
+ "flags": "BadgeGlobalAuthority",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Staff",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/3"
+ },
+ "name": "",
+ "tooltip": "Turbo"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Turbo",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff00ff00",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "creativewind"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "creativewind:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Guys"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "19ee1663-c14d-41cd-a4a2-30a4bb609c5a"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "19ee1663-c14d-41cd-a4a2-30a4bb609c5a",
+ "localizedName": "",
+ "loginName": "creativewind",
+ "messageText": "Guys",
+ "searchText": "creativewind creativewind: Guys ",
+ "serverReceivedTime": "2024-09-27T00:08:21Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff00ff00"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/shared-chat-unknown.json b/tests/snapshots/MessageBuilder/IRC/shared-chat-unknown.json
new file mode 100644
index 000000000..accbb76b0
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/shared-chat-unknown.json
@@ -0,0 +1,161 @@
+{
+ "input": "@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",
+ "output": [
+ {
+ "badgeInfos": {
+ },
+ "badges": [
+ "staff",
+ "turbo"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "creativewind",
+ "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": [
+ "0:08"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "00:08:21",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/3"
+ },
+ "name": "",
+ "tooltip": "Staff"
+ },
+ "flags": "BadgeGlobalAuthority",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Staff",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/3"
+ },
+ "name": "",
+ "tooltip": "Turbo"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Turbo",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ff00ff00",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "creativewind"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "creativewind:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Guys"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "19ee1663-c14d-41cd-a4a2-30a4bb609c5a"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed|SharedMessage",
+ "id": "19ee1663-c14d-41cd-a4a2-30a4bb609c5a",
+ "localizedName": "",
+ "loginName": "creativewind",
+ "messageText": "Guys",
+ "searchText": "creativewind creativewind: Guys ",
+ "serverReceivedTime": "2024-09-27T00:08:21Z",
+ "timeoutUser": "",
+ "usernameColor": "#ff00ff00"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/simple.json b/tests/snapshots/MessageBuilder/IRC/simple.json
new file mode 100644
index 000000000..1ce363b9a
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/simple.json
@@ -0,0 +1,161 @@
+{
+ "input": "@badge-info=subscriber/17;badges=subscriber/12,no_audio/1;color=#EBA2C0;display-name=jammehcow;emote-only=1;emotes=25:0-4;first-msg=0;flags=;id=9c2dd916-5a6d-4c1f-9fe7-a081b62a9c6b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1662201093248;turbo=0;user-id=82674227;user-type= :jammehcow!jammehcow@jammehcow.tmi.twitch.tv PRIVMSG #pajlada :Kappa",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "17"
+ },
+ "badges": [
+ "subscriber",
+ "no_audio"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "jammehcow",
+ "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": [
+ "10:31"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:31:33",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/aef2cd08-f29b-45a1-8c12-d44d7fd5e6f0/3"
+ },
+ "name": "",
+ "tooltip": "Watching without audio"
+ },
+ "flags": "BadgeVanity",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Watching without audio",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffeba2c0",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "jammehcow"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "jammehcow:"
+ ]
+ },
+ {
+ "emote": {
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0",
+ "2x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/2.0",
+ "3x": "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/3.0"
+ },
+ "name": "Kappa",
+ "tooltip": "Kappa
Twitch Emote"
+ },
+ "flags": "TwitchEmoteImage|TwitchEmoteText",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "text": {
+ "color": "Text",
+ "flags": "Misc",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "Kappa"
+ ]
+ },
+ "tooltip": "Kappa
Twitch Emote",
+ "trailingSpace": true,
+ "type": "EmoteElement"
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "9c2dd916-5a6d-4c1f-9fe7-a081b62a9c6b"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "9c2dd916-5a6d-4c1f-9fe7-a081b62a9c6b",
+ "localizedName": "",
+ "loginName": "jammehcow",
+ "messageText": "Kappa",
+ "searchText": "jammehcow jammehcow: Kappa ",
+ "serverReceivedTime": "2022-09-03T10:31:33Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffeba2c0"
+ }
+ ]
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/username-localized.json b/tests/snapshots/MessageBuilder/IRC/username-localized.json
new file mode 100644
index 000000000..8c78475d3
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/username-localized.json
@@ -0,0 +1,119 @@
+{
+ "input": "@tmi-sent-ts=1726916057144;subscriber=1;id=3ad26770-7299-4261-ab9b-24f410944517;room-id=11148817;user-id=117166826;display-name=display-name=테스트계정420;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=mod;emotes= :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :username",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "testaccount_420",
+ "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": [
+ "10:54"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:54:17",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "testaccount_420"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "display-name=테스트계정420:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "username"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "3ad26770-7299-4261-ab9b-24f410944517"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "3ad26770-7299-4261-ab9b-24f410944517",
+ "localizedName": "display-name=테스트계정420",
+ "loginName": "testaccount_420",
+ "messageText": "username",
+ "searchText": "display-name=테스트계정420 display-name=테스트계정420 testaccount_420: username ",
+ "serverReceivedTime": "2024-09-21T10:54:17Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "appearance": {
+ "messages": {
+ "usernameDisplayMode": 2
+ }
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/username-localized2.json b/tests/snapshots/MessageBuilder/IRC/username-localized2.json
new file mode 100644
index 000000000..66cbe408d
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/username-localized2.json
@@ -0,0 +1,119 @@
+{
+ "input": "@tmi-sent-ts=1726916057144;subscriber=1;id=3ad26770-7299-4261-ab9b-24f410944517;room-id=11148817;user-id=117166826;display-name=testaccount_420;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=mod;emotes= :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :username",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "testaccount_420",
+ "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": [
+ "10:54"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:54:17",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "testaccount_420"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "testaccount_420:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "username"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "3ad26770-7299-4261-ab9b-24f410944517"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "3ad26770-7299-4261-ab9b-24f410944517",
+ "localizedName": "",
+ "loginName": "testaccount_420",
+ "messageText": "username",
+ "searchText": "testaccount_420 testaccount_420: username ",
+ "serverReceivedTime": "2024-09-21T10:54:17Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "appearance": {
+ "messages": {
+ "usernameDisplayMode": 2
+ }
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/username.json b/tests/snapshots/MessageBuilder/IRC/username.json
new file mode 100644
index 000000000..9076136a8
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/username.json
@@ -0,0 +1,119 @@
+{
+ "input": "@tmi-sent-ts=1726916057144;subscriber=1;id=3ad26770-7299-4261-ab9b-24f410944517;room-id=11148817;user-id=117166826;display-name=display-name=테스트계정420;badges=subscriber/24;badge-info=subscriber/27;color=#FF0000;flags=;user-type=mod;emotes= :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :username",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "27"
+ },
+ "badges": [
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "testaccount_420",
+ "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": [
+ "10:54"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "10:54:17",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "testaccount_420"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "testaccount_420:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "username"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "3ad26770-7299-4261-ab9b-24f410944517"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "3ad26770-7299-4261-ab9b-24f410944517",
+ "localizedName": "display-name=테스트계정420",
+ "loginName": "testaccount_420",
+ "messageText": "username",
+ "searchText": "testaccount_420 display-name=테스트계정420 testaccount_420: username ",
+ "serverReceivedTime": "2024-09-21T10:54:17Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ],
+ "settings": {
+ "appearance": {
+ "messages": {
+ "usernameDisplayMode": 1
+ }
+ }
+ }
+}
diff --git a/tests/snapshots/MessageBuilder/IRC/vip.json b/tests/snapshots/MessageBuilder/IRC/vip.json
new file mode 100644
index 000000000..e7cac5680
--- /dev/null
+++ b/tests/snapshots/MessageBuilder/IRC/vip.json
@@ -0,0 +1,143 @@
+{
+ "input": "@tmi-sent-ts=1726920321214;subscriber=1;vip=1;id=97bb0dfb-a35f-446d-8634-7522d8ef73ed;room-id=11148817;user-id=129546453;display-name=nerixyz;badges=vip/1,subscriber/48;badge-info=subscriber/64;color=#FF0000;flags=;user-type=;emotes= :nerixyz!nerixyz@nerixyz.tmi.twitch.tv PRIVMSG #pajlada :a",
+ "output": [
+ {
+ "badgeInfos": {
+ "subscriber": "64"
+ },
+ "badges": [
+ "vip",
+ "subscriber"
+ ],
+ "channelName": "pajlada",
+ "count": 1,
+ "displayName": "nerixyz",
+ "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:05"
+ ]
+ },
+ "flags": "Timestamp",
+ "format": "",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "time": "12:05:21",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TimestampElement"
+ },
+ {
+ "flags": "ModeratorTools",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TwitchModerationElement"
+ },
+ {
+ "emote": {
+ "homePage": "https://help.twitch.tv/customer/en/portal/articles/659115-twitch-chat-badges-guide",
+ "images": {
+ "1x": "https://static-cdn.jtvnw.net/badges/v1/b817aba4-fad8-49e2-b88a-7cc744dfa6ec/1",
+ "2x": "https://static-cdn.jtvnw.net/badges/v1/b817aba4-fad8-49e2-b88a-7cc744dfa6ec/2",
+ "3x": "https://static-cdn.jtvnw.net/badges/v1/b817aba4-fad8-49e2-b88a-7cc744dfa6ec/3"
+ },
+ "name": "",
+ "tooltip": "VIP"
+ },
+ "flags": "BadgeChannelAuthority",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "VIP",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
+ {
+ "color": "#ffff0000",
+ "flags": "Username",
+ "link": {
+ "type": "UserInfo",
+ "value": "nerixyz"
+ },
+ "style": "ChatMediumBold",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "nerixyz:"
+ ]
+ },
+ {
+ "color": "Text",
+ "flags": "Text",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "style": "ChatMedium",
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "TextElement",
+ "words": [
+ "a"
+ ]
+ },
+ {
+ "background": "#ffa0a0a4",
+ "flags": "ReplyButton",
+ "link": {
+ "type": "ReplyToMessage",
+ "value": "97bb0dfb-a35f-446d-8634-7522d8ef73ed"
+ },
+ "padding": 2,
+ "tooltip": "",
+ "trailingSpace": true,
+ "type": "CircularImageElement",
+ "url": ""
+ }
+ ],
+ "flags": "Collapsed",
+ "id": "97bb0dfb-a35f-446d-8634-7522d8ef73ed",
+ "localizedName": "",
+ "loginName": "nerixyz",
+ "messageText": "a",
+ "searchText": "nerixyz nerixyz: a ",
+ "serverReceivedTime": "2024-09-21T12:05:21Z",
+ "timeoutUser": "",
+ "usernameColor": "#ffff0000"
+ }
+ ]
+}
diff --git a/tests/src/MessageBuilder.cpp b/tests/src/MessageBuilder.cpp
index a51a93b62..b76d78018 100644
--- a/tests/src/MessageBuilder.cpp
+++ b/tests/src/MessageBuilder.cpp
@@ -1,34 +1,63 @@
#include "messages/MessageBuilder.hpp"
+#include "common/Literals.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "controllers/ignores/IgnorePhrase.hpp"
+#include "controllers/sound/NullBackend.hpp"
+#include "lib/Snapshot.hpp"
+#include "messages/Emote.hpp"
+#include "messages/Message.hpp"
#include "mocks/BaseApplication.hpp"
-#include "mocks/Channel.hpp"
#include "mocks/ChatterinoBadges.hpp"
#include "mocks/DisabledStreamerMode.hpp"
#include "mocks/Emotes.hpp"
+#include "mocks/LinkResolver.hpp"
#include "mocks/Logging.hpp"
#include "mocks/TwitchIrcServer.hpp"
#include "mocks/UserData.hpp"
#include "providers/ffz/FfzBadges.hpp"
#include "providers/seventv/SeventvBadges.hpp"
+#include "providers/twitch/api/Helix.hpp"
+#include "providers/twitch/ChannelPointReward.hpp"
+#include "providers/twitch/IrcMessageHandler.hpp"
+#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchBadge.hpp"
+#include "providers/twitch/TwitchBadges.hpp"
+#include "providers/twitch/TwitchChannel.hpp"
+#include "singletons/Emotes.hpp"
#include "Test.hpp"
#include "util/IrcHelpers.hpp"
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
#include
#include
#include
using namespace chatterino;
-using chatterino::mock::MockChannel;
+using namespace literals;
namespace {
+/// Controls whether snapshots will be updated (true) or verified (false)
+///
+/// In CI, all snapshots must be verified, thus the integrity tests checks for
+/// this constant.
+///
+/// When adding a test, start with `{ "input": "..." }` and set this to `true`
+/// to generate an initial snapshot. Make sure to verify the output!
+constexpr bool UPDATE_SNAPSHOTS = false;
+
+const QString IRC_CATEGORY = u"MessageBuilder/IRC"_s;
+
class MockApplication : public mock::BaseApplication
{
public:
@@ -37,6 +66,12 @@ public:
{
}
+ MockApplication(const QString &settingsData)
+ : mock::BaseApplication(settingsData)
+ , highlights(this->settings, &this->accounts)
+ {
+ }
+
IEmotes *getEmotes() override
{
return &this->emotes;
@@ -97,6 +132,21 @@ public:
return &this->logging;
}
+ TwitchBadges *getTwitchBadges() override
+ {
+ return &this->twitchBadges;
+ }
+
+ ILinkResolver *getLinkResolver() override
+ {
+ return &this->linkResolver;
+ }
+
+ ISoundController *getSound() override
+ {
+ return &this->sound;
+ }
+
mock::EmptyLogging logging;
AccountController accounts;
mock::Emotes emotes;
@@ -109,8 +159,292 @@ public:
BttvEmotes bttvEmotes;
FfzEmotes ffzEmotes;
SeventvEmotes seventvEmotes;
+ TwitchBadges twitchBadges;
+ mock::EmptyLinkResolver linkResolver;
+ NullBackend sound;
};
+std::pair makeEmote(Emote &&emote)
+{
+ auto ptr = std::make_shared(std::move(emote));
+ ptr->homePage = {u"https://chatterino.com/" % ptr->name.string};
+ ptr->tooltip = {ptr->name.string % u" Tooltip"_s};
+ ptr->author = {u"Chatterino"_s};
+ ptr->images = {
+ Url{u"https://chatterino.com/" % ptr->name.string % u".png"}};
+ return {ptr->name, ptr};
+}
+
+using EmoteMapPtr = std::shared_ptr;
+
+EmoteMapPtr makeEmotes(auto &&...emotes)
+{
+ auto map = std::make_shared();
+ ((map->emplace(makeEmote(std::forward(emotes)))), ...);
+ return map;
+}
+
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers")
+
+struct MockEmotes {
+ EmoteMapPtr seventv;
+ EmoteMapPtr bttv;
+ EmoteMapPtr ffz;
+ EmoteMapPtr twitchAccount;
+
+ static MockEmotes channel()
+ {
+ return {
+ .seventv = makeEmotes(
+ Emote{
+ .name = {u"7TVEmote"_s},
+ .id = {u"1"_s},
+ },
+ Emote{
+ .name = {u"7TVEmote0w"_s},
+ .zeroWidth = true,
+ .id = {u"2"_s},
+ .baseName = EmoteName{u"ZeroWidth"_s},
+ },
+ Emote{
+ .name = {u"PogChamp"_s},
+ .id = {u"3"_s},
+ }),
+ .bttv = makeEmotes(
+ Emote{
+ .name = {u"BTTVEmote"_s},
+ },
+ Emote{
+ .name = {u"Kappa"_s},
+ }),
+ .ffz = makeEmotes(
+ Emote{
+ .name = {u"FFZEmote"_s},
+ },
+ Emote{
+ .name = {u"Keepo"_s},
+ }),
+ };
+ }
+
+ static MockEmotes twitchdev()
+ {
+ return {
+ .seventv = makeEmotes(Emote{
+ .name = {u"7TVTwitchDev"_s},
+ .id = {u"t5"_s},
+ }),
+ .bttv = makeEmotes(Emote{
+ .name = {u"BTTVTwitchDev"_s},
+ }),
+ .ffz = makeEmotes(Emote{
+ .name = {u"FFZTwitchDev"_s},
+ }),
+ };
+ }
+
+ static MockEmotes global()
+ {
+ return {
+ .seventv = makeEmotes(Emote{
+ .name = {u"7TVGlobal"_s},
+ .id = {u"G1"_s},
+ }),
+ .bttv = makeEmotes(Emote{
+ .name = {u"BTTVGlobal"_s},
+ }),
+ .ffz = makeEmotes(Emote{
+ .name = {u"FFZGlobal"_s},
+ }),
+ .twitchAccount = makeEmotes(Emote{
+ .name = {u"MyCoolTwitchEmote"_s},
+ .id = {u"5678"_s},
+ }),
+ };
+ }
+};
+
+const QByteArray CHEERMOTE_JSON{R"({
+ "prefix": "Cheer",
+ "tiers": [
+ {
+ "min_bits": 1,
+ "id": "1",
+ "color": "#979797",
+ "images": {
+ "dark": {
+ "animated": {
+ "1": "https://chatterino.com/bits/1.gif",
+ "2": "https://chatterino.com/bits/2.gif",
+ "4": "https://chatterino.com/bits/4.gif"
+ },
+ "static": {
+ "1": "https://chatterino.com/bits/1.png",
+ "2": "https://chatterino.com/bits/2.png",
+ "4": "https://chatterino.com/bits/4.png"
+ }
+ }
+ },
+ "can_cheer": true,
+ "show_in_bits_card": true
+ },
+ {
+ "min_bits": 100,
+ "id": "100",
+ "color": "#9c3ee8",
+ "images": {
+ "dark": {
+ "animated": {
+ "1": "https://chatterino.com/bits/1.gif",
+ "2": "https://chatterino.com/bits/2.gif",
+ "4": "https://chatterino.com/bits/4.gif"
+ },
+ "static": {
+ "1": "https://chatterino.com/bits/1.png",
+ "2": "https://chatterino.com/bits/2.png",
+ "4": "https://chatterino.com/bits/4.png"
+ }
+ }
+ },
+ "can_cheer": true,
+ "show_in_bits_card": true
+ }
+ ],
+ "type": "global_first_party",
+ "order": 1,
+ "last_updated": "2018-05-22T00:06:04Z",
+ "is_charitable": false
+})"_ba};
+
+const QByteArray LOCAL_BADGE_JSON{R"({
+ "data": [
+ {
+ "set_id": "subscriber",
+ "versions": [
+ {
+ "click_url": null,
+ "description": "Subscriber",
+ "id": "3072",
+ "image_url_1x": "https://chatterino.com/tb-1",
+ "image_url_2x": "https://chatterino.com/tb-2",
+ "image_url_4x": "https://chatterino.com/tb-3",
+ "title": "Subscriber"
+ }
+ ]
+ }
+ ]
+})"_ba};
+
+const QByteArray SETTINGS_DEFAULT{"{}"_ba};
+
+std::shared_ptr makeMockTwitchChannel(
+ const QString &name, const testlib::Snapshot &snapshot)
+{
+ auto chan = std::make_shared(name);
+ auto mocks = MockEmotes::channel();
+ chan->setSeventvEmotes(std::move(mocks.seventv));
+ chan->setBttvEmotes(std::move(mocks.bttv));
+ chan->setFfzEmotes(std::move(mocks.ffz));
+
+ QJsonObject defaultImage{
+ {u"url_1x"_s, u"https://chatterino.com/reward1x.png"_s},
+ {u"url_2x"_s, u"https://chatterino.com/reward2x.png"_s},
+ {u"url_4x"_s, u"https://chatterino.com/reward4x.png"_s},
+ };
+ chan->addKnownChannelPointReward({{
+ {u"channel_id"_s, u"11148817"_s},
+ {u"id"_s, u"unused"_s},
+ {u"reward"_s,
+ {{
+ {u"channel_id"_s, u"11148817"_s},
+ {u"cost"_s, 1},
+ {u"id"_s, u"31a2344e-0fce-4229-9453-fb2e8b6dd02c"_s},
+ {u"is_user_input_required"_s, true},
+ {u"title"_s, u"my reward"_s},
+ {u"image"_s, defaultImage},
+ }}},
+ }});
+ chan->addKnownChannelPointReward({{
+ {u"channel_id"_s, u"11148817"_s},
+ {u"id"_s, u"unused"_s},
+ {u"reward"_s,
+ {{
+ {u"channel_id"_s, u"11148817"_s},
+ {u"cost"_s, 1},
+ {u"id"_s, u"dc8d1dac-256e-42b9-b7ba-40b32e5294e2"_s},
+ {u"is_user_input_required"_s, false},
+ {u"title"_s, u"test"_s},
+ {u"image"_s, defaultImage},
+ }}},
+ }});
+ chan->addKnownChannelPointReward({{
+ {u"channel_id"_s, u"11148817"_s},
+ {u"id"_s, u"unused"_s},
+ {u"reward"_s,
+ {{
+ {u"channel_id"_s, u"11148817"_s},
+ {u"cost"_s, 1},
+ {u"default_bits_cost"_s, 2},
+ {u"bits_cost"_s, 0},
+ {u"pricing_type"_s, u"BITS"_s},
+ {u"reward_type"_s, u"CELEBRATION"_s},
+ {u"is_user_input_required"_s, false},
+ {u"title"_s, u"BitReward"_s},
+ {u"image"_s, defaultImage},
+ }}},
+ {u"redemption_metadata"_s,
+ QJsonObject{
+ {u"celebration_emote_metadata"_s,
+ QJsonObject{
+ {u"emote"_s,
+ {{
+ {u"id"_s, u"42"_s},
+ {u"token"_s, u"MyBitsEmote"_s},
+ }}},
+ }},
+ }},
+ }});
+
+ chan->setUserColor("UserColor", {1, 2, 3, 4});
+ chan->setUserColor("UserColor2", {5, 6, 7, 8});
+ chan->addRecentChatter("UserChatter");
+ chan->addRecentChatter("UserColor");
+
+ chan->setCheerEmoteSets({
+ HelixCheermoteSet{QJsonDocument::fromJson(CHEERMOTE_JSON).object()},
+ });
+
+ chan->setFfzChannelBadges({{u"123456"_s, {3, 4}}});
+
+ chan->addTwitchBadgeSets(HelixChannelBadges{
+ QJsonDocument::fromJson(LOCAL_BADGE_JSON).object(),
+ });
+
+ if (snapshot.param("ffzCustomVipBadge").toBool())
+ {
+ chan->setFfzCustomVipBadge(std::make_shared(Emote{
+ .name = {},
+ .images = {Url{"https://chatterino.com/ffz-vip1x.png"}},
+ .tooltip = {"VIP"},
+ .homePage = {},
+ }));
+ }
+ if (snapshot.param("ffzCustomModBadge").toBool())
+ {
+ chan->setFfzCustomModBadge(std::make_shared(Emote{
+ .name = {},
+ .images = {Url{"https://chatterino.com/ffz-mod1x.png"}},
+ .tooltip = {"Moderator"},
+ .homePage = {},
+ }));
+ }
+
+ return chan;
+}
+
+QT_WARNING_POP
+
} // namespace
TEST(MessageBuilder, CommaSeparatedListTagParsing)
@@ -408,7 +742,7 @@ TEST_F(TestMessageBuilder, ParseTwitchEmotes)
for (const auto &test : testCases)
{
- auto *privmsg = static_cast(
+ auto *privmsg = dynamic_cast(
Communi::IrcPrivateMessage::fromData(test.input, nullptr));
QString originalMessage = privmsg->content();
@@ -423,73 +757,6 @@ TEST_F(TestMessageBuilder, ParseTwitchEmotes)
}
}
-TEST_F(TestMessageBuilder, ParseMessage)
-{
- MockChannel channel("pajlada");
-
- struct TestCase {
- QByteArray input;
- };
-
- std::vector testCases{
- {
- // action /me message
- R"(@badge-info=subscriber/80;badges=broadcaster/1,subscriber/3072,partner/1;color=#CC44FF;display-name=pajlada;emote-only=1;emotes=25:0-4;first-msg=0;flags=;id=90ef1e46-8baa-4bf2-9c54-272f39d6fa11;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1662206235860;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada :ACTION Kappa)",
- },
- {
- R"(@badge-info=subscriber/17;badges=subscriber/12,no_audio/1;color=#EBA2C0;display-name=jammehcow;emote-only=1;emotes=25:0-4;first-msg=0;flags=;id=9c2dd916-5a6d-4c1f-9fe7-a081b62a9c6b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1662201093248;turbo=0;user-id=82674227;user-type= :jammehcow!jammehcow@jammehcow.tmi.twitch.tv PRIVMSG #pajlada :Kappa)",
- },
- {
- R"(@badge-info=;badges=no_audio/1;color=#DAA520;display-name=Mm2PL;emote-only=1;emotes=1902:0-4;first-msg=0;flags=;id=9b1c3cb9-7817-47ea-add1-f9d4a9b4f846;mod=0;returning-chatter=0;room-id=11148817;subscriber=0;tmi-sent-ts=1662201095690;turbo=0;user-id=117691339;user-type= :mm2pl!mm2pl@mm2pl.tmi.twitch.tv PRIVMSG #pajlada :Keepo)",
- },
- {
- R"(@badge-info=;badges=no_audio/1;color=#DAA520;display-name=Mm2PL;emote-only=1;emotes=25:0-4/1902:6-10/305954156:12-19;first-msg=0;flags=;id=7be87072-bf24-4fa3-b3df-0ea6fa5f1474;mod=0;returning-chatter=0;room-id=11148817;subscriber=0;tmi-sent-ts=1662201102276;turbo=0;user-id=117691339;user-type= :mm2pl!mm2pl@mm2pl.tmi.twitch.tv PRIVMSG #pajlada :Kappa Keepo PogChamp)",
- },
- {
- R"(@badge-info=subscriber/80;badges=broadcaster/1,subscriber/3072,partner/1;color=#CC44FF;display-name=pajlada;emote-only=1;emotes=25:0-4,6-10;first-msg=0;flags=;id=f7516287-e5d1-43ca-974e-fe0cff84400b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1662204375009;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada :Kappa Kappa)",
- },
- {
- R"(@badge-info=subscriber/80;badges=broadcaster/1,subscriber/3072,partner/1;color=#CC44FF;display-name=pajlada;emotes=25:0-4,8-12;first-msg=0;flags=;id=44f85d39-b5fb-475d-8555-f4244f2f7e82;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1662204423418;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada :Kappa 😂 Kappa)",
- },
- {
- // start out of range
- R"(@emotes=84608:9-10 :test!test@test.tmi.twitch.tv PRIVMSG #pajlada :foo bar)",
- },
- {
- // one character emote
- R"(@emotes=84608:0-0 :test!test@test.tmi.twitch.tv PRIVMSG #pajlada :foo bar)",
- },
- {
- // two character emote
- R"(@emotes=84609:0-1 :test!test@test.tmi.twitch.tv PRIVMSG #pajlada :foo bar)",
- },
- {
- // end out of range
- R"(@emotes=84608:0-15 :test!test@test.tmi.twitch.tv PRIVMSG #pajlada :foo bar)",
- },
- {
- // range bad (end character before start)
- R"(@emotes=84608:15-2 :test!test@test.tmi.twitch.tv PRIVMSG #pajlada :foo bar)",
- },
- };
-
- for (const auto &test : testCases)
- {
- auto *privmsg = dynamic_cast(
- Communi::IrcPrivateMessage::fromData(test.input, nullptr));
- EXPECT_NE(privmsg, nullptr);
-
- QString originalMessage = privmsg->content();
-
- MessageBuilder builder(&channel, privmsg, MessageParseArgs{});
-
- auto msg = builder.build();
- EXPECT_NE(msg.get(), nullptr);
-
- delete privmsg;
- }
-}
-
TEST_F(TestMessageBuilder, IgnoresReplace)
{
struct TestCase {
@@ -627,3 +894,172 @@ TEST_F(TestMessageBuilder, IgnoresReplace)
<< "' and output '" << message << "'";
}
}
+
+class TestMessageBuilderP : public ::testing::TestWithParam
+{
+public:
+ void SetUp() override
+ {
+ auto param = TestMessageBuilderP::GetParam();
+ this->snapshot = testlib::Snapshot::read(IRC_CATEGORY, param);
+
+ this->mockApplication =
+ std::make_unique(QString::fromUtf8(
+ this->snapshot->mergedSettings(SETTINGS_DEFAULT)));
+ auto mocks = MockEmotes::global();
+ this->mockApplication->seventvEmotes.setGlobalEmotes(mocks.seventv);
+ this->mockApplication->bttvEmotes.setEmotes(mocks.bttv);
+ this->mockApplication->ffzEmotes.setEmotes(mocks.ffz);
+ this->mockApplication->getAccounts()->twitch.getCurrent()->setEmotes(
+ mocks.twitchAccount);
+ this->mockApplication->getUserData()->setUserColor(u"117691339"_s,
+ u"#DAA521"_s);
+
+ this->mockApplication->getAccounts()
+ ->twitch.getCurrent()
+ ->blockUserLocally(u"12345"_s);
+
+ auto makeBadge = [](QStringView platform) {
+ return std::make_shared(Emote{
+ .name = {},
+ .images = {Url{u"https://chatterino.com/" % platform %
+ u".png"}},
+ .tooltip = {platform % u" badge"},
+ .homePage = {},
+ .zeroWidth = false,
+ .id = {},
+ .author = {},
+ .baseName = {},
+ });
+ };
+
+ // Chatterino
+ this->mockApplication->chatterinoBadges.setBadge(
+ {u"123456"_s}, makeBadge(u"Chatterino"));
+
+ // FFZ
+ this->mockApplication->ffzBadges.registerBadge(
+ 1, {.emote = makeBadge(u"FFZ1"), .color = {9, 10, 11, 12}});
+ this->mockApplication->ffzBadges.registerBadge(
+ 2, {.emote = makeBadge(u"FFZ2"), .color = {13, 14, 15, 16}});
+ this->mockApplication->ffzBadges.registerBadge(
+ 3, {.emote = makeBadge(u"FFZ2"), .color = {17, 18, 19, 20}});
+ this->mockApplication->ffzBadges.registerBadge(
+ 4, {.emote = makeBadge(u"FFZ2"), .color = {21, 22, 23, 24}});
+ this->mockApplication->getFfzBadges()->assignBadgeToUser({u"123456"_s},
+ 1);
+ this->mockApplication->getFfzBadges()->assignBadgeToUser({u"123456"_s},
+ 2);
+
+ // 7TV
+ this->mockApplication->getSeventvBadges()->registerBadge({
+ {u"id"_s, u"1"_s},
+ {u"tooltip"_s, u"7TV badge"_s},
+ {
+ u"host"_s,
+ {{
+ {u"url"_s, u"//chatterino.com/7tv/"_s},
+ {u"files"_s,
+ QJsonArray{
+ {{
+ {u"name"_s, u"1x"_s},
+ {u"format"_s, u"WEBP"_s},
+ {u"width"_s, 16},
+ }},
+ }},
+ }},
+ },
+ });
+ this->mockApplication->getSeventvBadges()->assignBadgeToUser(
+ u"1"_s, {u"123456"_s});
+
+ // Twitch
+ this->mockApplication->getTwitchBadges()->loadLocalBadges();
+
+ this->twitchdevChannel = std::make_shared("twitchdev");
+ this->twitchdevChannel->setRoomId("141981764");
+
+ auto tdMocks = MockEmotes::twitchdev();
+ this->twitchdevChannel->setSeventvEmotes(std::move(tdMocks.seventv));
+ this->twitchdevChannel->setBttvEmotes(std::move(tdMocks.bttv));
+ this->twitchdevChannel->setFfzEmotes(std::move(tdMocks.ffz));
+
+ this->mockApplication->twitch.mockChannels.emplace(
+ "twitchdev", this->twitchdevChannel);
+ }
+
+ void TearDown() override
+ {
+ this->twitchdevChannel.reset();
+ this->mockApplication.reset();
+ this->snapshot.reset();
+ }
+
+ std::shared_ptr twitchdevChannel;
+ std::unique_ptr mockApplication;
+ std::unique_ptr snapshot;
+};
+
+/// This tests the process of parsing IRC messages and emitting `MessagePtr`s.
+///
+/// Even though it's in the message builder category, this uses
+/// `IrcMesssageHandler` to ensure the correct (or: "real") arguments to build
+/// messages.
+///
+/// Tests are contained in `tests/snapshots/MessageBuilder/IRC`. Fixtures
+/// consist of an object with the keys `input`, `output`, `settings` (optional),
+/// and `params` (optional).
+///
+/// `UPDATE_SNAPSHOTS` (top) controls whether the `output` will be generated or
+/// checked.
+///
+/// `params` is an optional object with the following keys:
+/// - `prevMessages`: An array of past messages (used for replies)
+/// - `findAllUsernames`: A boolean controlling the equally named setting
+/// (default: false)
+TEST_P(TestMessageBuilderP, Run)
+{
+ auto channel = makeMockTwitchChannel(u"pajlada"_s, *snapshot);
+
+ std::vector prevMessages;
+
+ for (auto prevInput : snapshot->param("prevMessages").toArray())
+ {
+ auto *ircMessage = Communi::IrcMessage::fromData(
+ prevInput.toString().toUtf8(), nullptr);
+ ASSERT_NE(ircMessage, nullptr);
+ auto builtMessages = IrcMessageHandler::parseMessageWithReply(
+ channel.get(), ircMessage, prevMessages);
+ for (const auto &builtMessage : builtMessages)
+ {
+ prevMessages.emplace_back(builtMessage);
+ }
+ delete ircMessage;
+ }
+
+ auto *ircMessage =
+ Communi::IrcMessage::fromData(snapshot->inputUtf8(), nullptr);
+ ASSERT_NE(ircMessage, nullptr);
+
+ auto builtMessages = IrcMessageHandler::parseMessageWithReply(
+ channel.get(), ircMessage, prevMessages);
+
+ QJsonArray got;
+ for (const auto &msg : builtMessages)
+ {
+ got.append(msg->toJson());
+ }
+
+ delete ircMessage;
+
+ ASSERT_TRUE(snapshot->run(got, UPDATE_SNAPSHOTS));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ IrcMessage, TestMessageBuilderP,
+ testing::ValuesIn(testlib::Snapshot::discover(IRC_CATEGORY)));
+
+TEST(TestMessageBuilderP, Integrity)
+{
+ ASSERT_FALSE(UPDATE_SNAPSHOTS); // make sure fixtures are actually tested
+}
diff --git a/tests/src/lib/Snapshot.cpp b/tests/src/lib/Snapshot.cpp
new file mode 100644
index 000000000..fb4749102
--- /dev/null
+++ b/tests/src/lib/Snapshot.cpp
@@ -0,0 +1,238 @@
+#include "lib/Snapshot.hpp"
+
+#include "common/Literals.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace {
+
+using namespace chatterino::literals;
+
+bool compareJson(const QJsonValue &expected, const QJsonValue &got,
+ const QString &context)
+{
+ if (expected == got)
+ {
+ return true;
+ }
+ if (expected.type() != got.type())
+ {
+ qWarning() << context
+ << "- mismatching type - expected:" << expected.type()
+ << "got:" << got.type();
+ return false;
+ }
+ switch (expected.type())
+ {
+ case QJsonValue::Array: {
+ auto expArr = expected.toArray();
+ auto gotArr = got.toArray();
+ if (expArr.size() != gotArr.size())
+ {
+ qWarning() << context << "- Mismatching array size - expected:"
+ << expArr.size() << "got:" << gotArr.size();
+ return false;
+ }
+ for (QJsonArray::size_type i = 0; i < expArr.size(); i++)
+ {
+ if (!compareJson(expArr[i], gotArr[i],
+ context % '[' % QString::number(i) % ']'))
+ {
+ return false;
+ }
+ }
+ }
+ break; // unreachable
+ case QJsonValue::Object: {
+ auto expObj = expected.toObject();
+ auto gotObj = got.toObject();
+ if (expObj.size() != gotObj.size())
+ {
+ qWarning() << context << "- Mismatching object size - expected:"
+ << expObj.size() << "got:" << gotObj.size();
+ return false;
+ }
+
+ for (auto it = expObj.constBegin(); it != expObj.constEnd(); it++)
+ {
+ if (!gotObj.contains(it.key()))
+ {
+ qWarning() << context << "- Object doesn't contain key"
+ << it.key();
+ return false;
+ }
+ if (!compareJson(it.value(), gotObj[it.key()],
+ context % '.' % it.key()))
+ {
+ return false;
+ }
+ }
+ }
+ break;
+ case QJsonValue::Null:
+ case QJsonValue::Bool:
+ case QJsonValue::Double:
+ case QJsonValue::String:
+ case QJsonValue::Undefined:
+ break;
+ }
+
+ qWarning() << context << "- expected:" << expected << "got:" << got;
+ return false;
+}
+
+void mergeJson(QJsonObject &base, const QJsonObject &additional)
+{
+ for (auto it = additional.begin(); it != additional.end(); it++)
+ {
+ auto ref = base[it.key()];
+
+ if (ref.isArray())
+ {
+ // there's no way of pushing to the array without detaching first
+ auto arr = ref.toArray();
+ if (!it->isArray())
+ {
+ throw std::runtime_error("Mismatched types");
+ }
+
+ // append all additional values
+ auto addArr = it->toArray();
+ for (auto v : addArr)
+ {
+ arr.append(v);
+ }
+ ref = arr;
+ continue;
+ }
+
+ if (ref.isObject())
+ {
+ // same here, detach first and overwrite
+ auto obj = ref.toObject();
+ if (!it->isObject())
+ {
+ throw std::runtime_error("Mismatched types");
+ }
+ mergeJson(obj, it->toObject());
+ ref = obj;
+ continue;
+ }
+
+ ref = it.value(); // overwrite for simple types/non-existent keys
+ }
+}
+
+QDir baseDir(const QString &category)
+{
+ QDir snapshotDir(QStringLiteral(__FILE__));
+ snapshotDir.cd("../../../snapshots/");
+ snapshotDir.cd(category);
+ return snapshotDir;
+}
+
+QString filePath(const QString &category, const QString &name)
+{
+ return baseDir(category).filePath(name);
+}
+
+} // namespace
+
+namespace chatterino::testlib {
+
+std::unique_ptr Snapshot::read(QString category, QString name)
+{
+ if (!name.endsWith(u".json"))
+ {
+ name.append(u".json");
+ }
+
+ QFile file(filePath(category, name));
+ if (!file.open(QFile::ReadOnly))
+ {
+ throw std::runtime_error("Failed to open file");
+ }
+ auto content = file.readAll();
+ file.close();
+ const auto doc = QJsonDocument::fromJson(content).object();
+
+ return std::unique_ptr(
+ new Snapshot(std::move(category), std::move(name), doc));
+}
+
+QStringList Snapshot::discover(const QString &category)
+{
+ auto files =
+ baseDir(category).entryList(QDir::NoDotAndDotDot | QDir::Files);
+ for (auto &file : files)
+ {
+ file.remove(".json");
+ }
+ return files;
+}
+
+bool Snapshot::run(const QJsonValue &got, bool updateSnapshots) const
+{
+ if (updateSnapshots)
+ {
+ this->write(got);
+ return true;
+ }
+
+ return compareJson(this->output_, got, QStringLiteral("output"));
+}
+
+Snapshot::Snapshot(QString category, QString name, const QJsonObject &root)
+ : category_(std::move(category))
+ , name_(std::move(name))
+ , input_(root["input"_L1])
+ , params_(root["params"_L1].toObject())
+ , settings_(root["settings"_L1].toObject())
+ , output_(root["output"_L1])
+{
+}
+
+void Snapshot::write(const QJsonValue &got) const
+{
+ QFile file(filePath(this->category_, this->name_));
+ if (!file.open(QFile::WriteOnly))
+ {
+ throw std::runtime_error("Failed to open file");
+ }
+
+ QJsonObject obj{
+ {"input"_L1, this->input_},
+ {"output"_L1, got},
+ };
+ if (!this->params_.isEmpty())
+ {
+ obj.insert("params"_L1, this->params_);
+ }
+ if (!this->settings_.isEmpty())
+ {
+ obj.insert("settings"_L1, this->settings_);
+ }
+
+ file.write(QJsonDocument{obj}.toJson());
+ file.close();
+}
+
+QByteArray Snapshot::mergedSettings(const QByteArray &base) const
+{
+ auto baseDoc = QJsonDocument::fromJson(base);
+ if (!baseDoc.isObject())
+ {
+ throw std::runtime_error("Invalid base settings");
+ }
+ auto baseObj = baseDoc.object();
+ mergeJson(baseObj, this->settings_);
+
+ baseDoc.setObject(baseObj);
+ return baseDoc.toJson(QJsonDocument::Compact);
+}
+
+} // namespace chatterino::testlib
diff --git a/tests/src/lib/Snapshot.hpp b/tests/src/lib/Snapshot.hpp
new file mode 100644
index 000000000..39f663eaf
--- /dev/null
+++ b/tests/src/lib/Snapshot.hpp
@@ -0,0 +1,135 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include
+
+namespace chatterino::testlib {
+
+/// @brief JSON based snapshot/approval tests
+///
+/// Snapshot tests record the output of some computation based on some @a input.
+/// Additionally, users can provide @a params. There isn't any rule on what goes
+/// into @a input vs. @a params - a rule of thumb is to put everything that's
+/// not directly an input to the target function into @a params (like settings).
+/// Similarly, settings can be specified in "settings". These can be merged with
+/// existing settings (the base) in mergedSettings().
+///
+/// Snapshots are stored in `tests/snapshots/{category}/{name}.json`.
+/// `category` can consist of multiple directories (e.g. `foo/bar`).
+///
+/// Note that when using CTest, added snapshots are only discovered when
+/// reloading the tests.
+///
+/// @par A minimal example
+///
+/// ```cpp
+/// #include "lib/Snapshot.hpp"
+/// #include "Test.hpp"
+///
+/// #include
+/// #include
+///
+/// namespace testlib = chatterino::testlib;
+///
+/// constexpr bool UPDATE_SNAPSHOTS = false;
+///
+/// class ExampleTest : public ::testing::TestWithParam {};
+///
+/// TEST_P(ExampleTest, Run) {
+/// auto fixture = testlib::Snapshot::read("category", GetParam());
+/// auto output = functionToTest(fixture.input()); // or input{String,Utf8}
+/// // if snapshots are supposed to be updated, this will write the output
+/// ASSERT_TRUE(fixture.run(output, UPDATE_SNAPSHOTS));
+/// }
+///
+/// INSTANTIATE_TEST_SUITE_P(ExampleInstance, ExampleTest,
+/// testing::ValuesIn(testlib::Snapshot::discover("category")));
+///
+/// // verify that all snapshots are included
+/// TEST(ExampleTest, Integrity) {
+/// ASSERT_FALSE(UPDATE_SNAPSHOTS); // make sure fixtures are actually tested
+/// }
+/// ```
+class Snapshot
+{
+public:
+ Snapshot(const Snapshot &) = delete;
+ Snapshot &operator=(const Snapshot &) = delete;
+
+ Snapshot(Snapshot &&) = default;
+ Snapshot &operator=(Snapshot &&) = default;
+ ~Snapshot() = default;
+
+ /// Read a snapshot
+ static std::unique_ptr read(QString category, QString name);
+
+ /// Finds all tests in @a category
+ static QStringList discover(const QString &category);
+
+ /// @brief Runs the snapshot test
+ ///
+ /// If @a updateSnapshots is `false`, this checks that @a got matches the
+ /// expected output (#output()).
+ /// If @a updateSnapshots is `true`, this sets @a got as the expected
+ /// output of this snapshot.
+ bool run(const QJsonValue &got, bool updateSnapshots) const;
+
+ QString name() const
+ {
+ return this->name_;
+ }
+
+ QString category() const
+ {
+ return this->category_;
+ }
+
+ QJsonValue input() const
+ {
+ return this->input_;
+ }
+
+ QString inputString() const
+ {
+ return this->input_.toString();
+ }
+
+ QByteArray inputUtf8() const
+ {
+ return this->input_.toString().toUtf8();
+ }
+
+ QJsonValue param(QLatin1String name) const
+ {
+ return this->params_[name];
+ }
+ QJsonValue param(const char *name) const
+ {
+ return this->param(QLatin1String{name});
+ }
+
+ QByteArray mergedSettings(const QByteArray &base) const;
+
+ QJsonValue output() const
+ {
+ return this->output_;
+ }
+
+private:
+ Snapshot(QString category, QString name, const QJsonObject &root);
+
+ void write(const QJsonValue &got) const;
+
+ QString category_;
+ QString name_;
+ QJsonValue input_;
+ QJsonObject params_;
+ QJsonObject settings_;
+ QJsonValue output_;
+};
+
+} // namespace chatterino::testlib