Compare commits

...

4 commits

Author SHA1 Message Date
hemirt 2d5b6e4c33
Merge e5e5a79645 into 18c4815ad7 2024-10-22 18:42:24 +02:00
iProdigy 18c4815ad7
feat: add shared chat badge (#5661) 2024-10-22 18:42:19 +02:00
pajlada 2ec8fa8723
refactor: remove unused ReplyContext.highlight (#5669) 2024-10-21 19:22:23 +02:00
pajlada 45d2c292d0
fix: subscribed threads not being marked as subscribed (#5668) 2024-10-21 13:19:08 +02:00
13 changed files with 176 additions and 49 deletions

View file

@ -7,7 +7,7 @@
- Major: Improve high-DPI support on Windows. (#4868, #5391, #5664, #5666)
- Major: Added transparent overlay window (default keybind: <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>N</kbd>). (#4746, #5643, #5659)
- Minor: Removed the Ctrl+Shift+L hotkey for toggling the "live only" tab visibility state. (#5530)
- Minor: Add support for Shared Chat messages. Shared chat messages can be filtered with the `flags.shared` filter variable, or with search using `is:shared`. Some messages like subscriptions are filtered on purpose to avoid confusion for the broadcaster. If you have both channels participating in Shared Chat open, only one of the message triggering your highlight will trigger. (#5606, #5625)
- Minor: Add support for Shared Chat messages. Shared chat messages can be filtered with the `flags.shared` filter variable, or with search using `is:shared`. Some messages like subscriptions are filtered on purpose to avoid confusion for the broadcaster. If you have both channels participating in Shared Chat open, only one of the message triggering your highlight will trigger. (#5606, #5625, #5661)
- Minor: Moved tab visibility control to a submenu, without any toggle actions. (#5530)
- Minor: Add option to customise Moderation buttons with images. (#5369)
- Minor: Colored usernames now update on the fly when changing the "Color @usernames" setting. (#5300)
@ -111,7 +111,7 @@
- Dev: Emojis now use flags instead of a set of strings for capabilities. (#5616)
- Dev: Move plugins to Sol2. (#5622)
- Dev: Refactored static `MessageBuilder` helpers to standalone functions. (#5652)
- Dev: Decoupled reply parsing from `MessageBuilder`. (#5660)
- Dev: Decoupled reply parsing from `MessageBuilder`. (#5660, #5668)
- Dev: Refactored IRC message building. (#5663)
## 2.5.1

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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