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",