diff --git a/CHANGELOG.md b/CHANGELOG.md
index c95aa2df7bc..7497e46dc35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@
- Major: Improve high-DPI support on Windows. (#4868, #5391, #5664, #5666)
- Major: Added transparent overlay window (default keybind: CTRL + ALT + N). (#4746, #5643, #5659)
- Minor: Removed the Ctrl+Shift+L hotkey for toggling the "live only" tab visibility state. (#5530)
-- Minor: Add support for Shared Chat messages. Shared chat messages can be filtered with the `flags.shared` filter variable, or with search using `is:shared`. Some messages like subscriptions are filtered on purpose to avoid confusion for the broadcaster. If you have both channels participating in Shared Chat open, only one of the message triggering your highlight will trigger. (#5606, #5625)
+- Minor: Add support for Shared Chat messages. Shared chat messages can be filtered with the `flags.shared` filter variable, or with search using `is:shared`. Some messages like subscriptions are filtered on purpose to avoid confusion for the broadcaster. If you have both channels participating in Shared Chat open, only one of the message triggering your highlight will trigger. (#5606, #5625, #5661)
- Minor: Moved tab visibility control to a submenu, without any toggle actions. (#5530)
- Minor: Add option to customise Moderation buttons with images. (#5369)
- Minor: Colored usernames now update on the fly when changing the "Color @usernames" setting. (#5300)
diff --git a/mocks/include/mocks/BaseApplication.hpp b/mocks/include/mocks/BaseApplication.hpp
index 2ba9f949cc0..619203afce8 100644
--- a/mocks/include/mocks/BaseApplication.hpp
+++ b/mocks/include/mocks/BaseApplication.hpp
@@ -3,6 +3,7 @@
#include "common/Args.hpp"
#include "mocks/DisabledStreamerMode.hpp"
#include "mocks/EmptyApplication.hpp"
+#include "mocks/TwitchUsers.hpp"
#include "providers/bttv/BttvLiveUpdates.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Settings.hpp"
@@ -55,6 +56,11 @@ class BaseApplication : public EmptyApplication
return &this->fonts;
}
+ ITwitchUsers *getTwitchUsers() override
+ {
+ return &this->twitchUsers;
+ }
+
BttvLiveUpdates *getBttvLiveUpdates() override
{
return nullptr;
@@ -71,6 +77,7 @@ class BaseApplication : public EmptyApplication
DisabledStreamerMode streamerMode;
Theme theme;
Fonts fonts;
+ TwitchUsers twitchUsers;
};
} // namespace chatterino::mock
diff --git a/mocks/include/mocks/TwitchUsers.hpp b/mocks/include/mocks/TwitchUsers.hpp
new file mode 100644
index 00000000000..14f6bf7e65f
--- /dev/null
+++ b/mocks/include/mocks/TwitchUsers.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "providers/twitch/TwitchUser.hpp"
+#include "providers/twitch/TwitchUsers.hpp"
+
+namespace chatterino::mock {
+
+class TwitchUsers : public ITwitchUsers
+{
+public:
+ TwitchUsers() = default;
+
+ std::shared_ptr resolveID(const UserId &id)
+ {
+ TwitchUser u = {
+ .id = id.string,
+ .name = {},
+ .displayName = {},
+ };
+ return std::make_shared(u);
+ }
+};
+
+} // namespace chatterino::mock
diff --git a/resources/twitch/sharedChat.png b/resources/twitch/sharedChat.png
new file mode 100644
index 00000000000..f9a66b17c8f
Binary files /dev/null and b/resources/twitch/sharedChat.png differ
diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp
index 299a3486075..6f02472faa0 100644
--- a/src/messages/MessageBuilder.cpp
+++ b/src/messages/MessageBuilder.cpp
@@ -33,6 +33,7 @@
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchIrc.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
+#include "providers/twitch/TwitchUsers.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
@@ -381,6 +382,18 @@ EmotePtr makeAutoModBadge()
Url{"https://dashboard.twitch.tv/settings/moderation/automod"}});
}
+EmotePtr makeSharedChatBadge(const QString &sourceName)
+{
+ return std::make_shared(Emote{
+ .name = EmoteName{},
+ .images = ImageSet{Image::fromResourcePixmap(
+ getResources().twitch.sharedChat, 0.25)},
+ .tooltip = Tooltip{"Shared Message" +
+ (sourceName.isEmpty() ? "" : " from " + sourceName)},
+ .homePage = Url{"https://link.twitch.tv/SharedChatViewer"},
+ });
+}
+
std::tuple, MessageElementFlags, bool> parseEmote(
TwitchChannel *twitchChannel, const QString &userID, const EmoteName &name)
{
@@ -2764,6 +2777,28 @@ void MessageBuilder::appendTwitchBadges(const QVariantMap &tags,
return;
}
+ if (this->message().flags.has(MessageFlag::SharedMessage))
+ {
+ const QString sourceId = tags["source-room-id"].toString();
+ QString sourceName;
+ if (sourceId.isEmpty())
+ {
+ sourceName = "";
+ }
+ else if (twitchChannel->roomId() == sourceId)
+ {
+ sourceName = twitchChannel->getName();
+ }
+ else
+ {
+ sourceName =
+ getApp()->getTwitchUsers()->resolveID({sourceId})->displayName;
+ }
+
+ this->emplace(makeSharedChatBadge(sourceName),
+ MessageElementFlag::BadgeSharedChannel);
+ }
+
auto badgeInfos = parseBadgeInfoTag(tags);
auto badges = parseBadgeTag(tags);
appendBadges(this, badges, badgeInfos, twitchChannel);
diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp
index 00f7046ec77..96e7d3dbc62 100644
--- a/src/messages/MessageElement.hpp
+++ b/src/messages/MessageElement.hpp
@@ -66,6 +66,10 @@ enum class MessageElementFlag : int64_t {
BitsStatic = (1LL << 11),
BitsAnimated = (1LL << 12),
+ // Slot 0: Twitch
+ // - Shared Channel indicator badge
+ BadgeSharedChannel = (1LL << 37),
+
// Slot 1: Twitch
// - Staff badge
// - Admin badge
@@ -119,7 +123,7 @@ enum class MessageElementFlag : int64_t {
Badges = BadgeGlobalAuthority | BadgePredictions | BadgeChannelAuthority |
BadgeSubscription | BadgeVanity | BadgeChatterino | BadgeSevenTV |
- BadgeFfz,
+ BadgeFfz | BadgeSharedChannel,
ChannelName = (1LL << 20),
diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp
index f64eb47e77c..88d5ec56566 100644
--- a/src/singletons/WindowManager.cpp
+++ b/src/singletons/WindowManager.cpp
@@ -195,6 +195,7 @@ void WindowManager::updateWordTypeMask()
flags.set(settings->animateEmotes ? MEF::BitsAnimated : MEF::BitsStatic);
// badges
+ flags.set(MEF::BadgeSharedChannel);
flags.set(settings->showBadgesGlobalAuthority ? MEF::BadgeGlobalAuthority
: MEF::None);
flags.set(settings->showBadgesPredictions ? MEF::BadgePredictions
diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp
index 066085030d0..50f6d1acc4d 100644
--- a/src/widgets/helper/ChannelView.cpp
+++ b/src/widgets/helper/ChannelView.cpp
@@ -2408,6 +2408,11 @@ void ChannelView::handleMouseClick(QMouseEvent *event,
return;
}
+ if (link.value.startsWith("id:"))
+ {
+ return;
+ }
+
// Insert @username into split input
const bool commaMention =
getSettings()->mentionUsersWithComma;
diff --git a/tests/snapshots/IrcMessageHandler/shared-chat-announcement.json b/tests/snapshots/IrcMessageHandler/shared-chat-announcement.json
index 9c6fa2f6511..a6877c365fe 100644
--- a/tests/snapshots/IrcMessageHandler/shared-chat-announcement.json
+++ b/tests/snapshots/IrcMessageHandler/shared-chat-announcement.json
@@ -64,6 +64,24 @@
"trailingSpace": true,
"type": "TwitchModerationElement"
},
+ {
+ "emote": {
+ "homePage": "https://link.twitch.tv/SharedChatViewer",
+ "images": {
+ "1x": ""
+ },
+ "name": "",
+ "tooltip": "Shared Message"
+ },
+ "flags": "BadgeSharedChannel",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Shared Message",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
{
"emote": {
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
diff --git a/tests/snapshots/IrcMessageHandler/shared-chat-emotes.json b/tests/snapshots/IrcMessageHandler/shared-chat-emotes.json
index b7f9256d45c..ba6485c2122 100644
--- a/tests/snapshots/IrcMessageHandler/shared-chat-emotes.json
+++ b/tests/snapshots/IrcMessageHandler/shared-chat-emotes.json
@@ -64,6 +64,24 @@
"trailingSpace": true,
"type": "TwitchModerationElement"
},
+ {
+ "emote": {
+ "homePage": "https://link.twitch.tv/SharedChatViewer",
+ "images": {
+ "1x": ""
+ },
+ "name": "",
+ "tooltip": "Shared Message from twitchdev"
+ },
+ "flags": "BadgeSharedChannel",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Shared Message from twitchdev",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
{
"color": "#ffff0000",
"flags": "Username",
diff --git a/tests/snapshots/IrcMessageHandler/shared-chat-known.json b/tests/snapshots/IrcMessageHandler/shared-chat-known.json
index 6a13adcb416..c4a3f3e2fab 100644
--- a/tests/snapshots/IrcMessageHandler/shared-chat-known.json
+++ b/tests/snapshots/IrcMessageHandler/shared-chat-known.json
@@ -64,6 +64,24 @@
"trailingSpace": true,
"type": "TwitchModerationElement"
},
+ {
+ "emote": {
+ "homePage": "https://link.twitch.tv/SharedChatViewer",
+ "images": {
+ "1x": ""
+ },
+ "name": "",
+ "tooltip": "Shared Message from twitchdev"
+ },
+ "flags": "BadgeSharedChannel",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Shared Message from twitchdev",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
{
"emote": {
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
diff --git a/tests/snapshots/IrcMessageHandler/shared-chat-unknown.json b/tests/snapshots/IrcMessageHandler/shared-chat-unknown.json
index accbb76b0e0..6ea80b2ce7b 100644
--- a/tests/snapshots/IrcMessageHandler/shared-chat-unknown.json
+++ b/tests/snapshots/IrcMessageHandler/shared-chat-unknown.json
@@ -64,6 +64,24 @@
"trailingSpace": true,
"type": "TwitchModerationElement"
},
+ {
+ "emote": {
+ "homePage": "https://link.twitch.tv/SharedChatViewer",
+ "images": {
+ "1x": ""
+ },
+ "name": "",
+ "tooltip": "Shared Message"
+ },
+ "flags": "BadgeSharedChannel",
+ "link": {
+ "type": "None",
+ "value": ""
+ },
+ "tooltip": "Shared Message",
+ "trailingSpace": true,
+ "type": "BadgeElement"
+ },
{
"emote": {
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",