Compare commits

...

12 commits

Author SHA1 Message Date
iProdigy
731418dbb8
Merge 64ccef94d1 into 867e3f3ab0 2024-10-21 00:57:41 +02:00
nerix
867e3f3ab0
fix: only invalidate buffers for chat windows (#5666) 2024-10-21 00:57:37 +02:00
iProdigy
64ccef94d1 Merge branch 'main' into feature/shared-chat-badge
# Conflicts:
#	src/messages/MessageBuilder.cpp
#	src/messages/MessageBuilder.hpp
2024-10-20 13:50:22 -07:00
nerix
e35fabfabe
refactor: irc message builder (#5663) 2024-10-20 10:40:48 +00:00
iProdigy
6836105408 chore: crush png file 2024-10-19 19:14:40 -07:00
iProdigy
ee80dd5d27
Merge branch 'master' into feature/shared-chat-badge 2024-10-20 01:21:49 +00:00
iProdigy
5638346d7c chore: update shared chat icon 2024-10-19 17:27:09 -07:00
iProdigy
c64523f23c chore: fix snapshot tests 2024-10-19 16:12:25 -07:00
iProdigy
cde4144e30
Merge branch 'master' into feature/shared-chat-badge 2024-10-19 11:11:12 +00:00
iProdigy
c4faca50e0 chore: update mocks 2024-10-19 04:06:20 -07:00
iProdigy
7d0c473828
chore: update changelog 2024-10-19 03:48:56 -07:00
iProdigy
f4036b269b feat: add shared chat badge 2024-10-19 03:40:06 -07:00
28 changed files with 907 additions and 815 deletions

View file

@ -4,10 +4,10 @@
- Major: Add option to show pronouns in user card. (#5442, #5583)
- Major: Release plugins alpha. (#5288)
- Major: Improve high-DPI support on Windows. (#4868, #5391, #5664)
- 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,6 +111,7 @@
- Dev: Move plugins to Sol2. (#5622)
- Dev: Refactored static `MessageBuilder` helpers to standalone functions. (#5652)
- Dev: Decoupled reply parsing from `MessageBuilder`. (#5660)
- Dev: Refactored IRC message building. (#5663)
## 2.5.1

View file

@ -5,7 +5,6 @@ set(benchmark_SOURCES
resources/bench.qrc
src/Emojis.cpp
src/Highlights.cpp
src/FormatTime.cpp
src/Helpers.cpp
src/LimitedQueue.cpp

View file

@ -1,102 +0,0 @@
#include "Application.hpp"
#include "common/Channel.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "controllers/highlights/HighlightPhrase.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "mocks/BaseApplication.hpp"
#include "mocks/UserData.hpp"
#include "util/Helpers.hpp"
#include <benchmark/benchmark.h>
#include <QDebug>
#include <QString>
#include <QTemporaryDir>
using namespace chatterino;
class BenchmarkMessageBuilder : public MessageBuilder
{
public:
explicit BenchmarkMessageBuilder(
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
const MessageParseArgs &_args)
: MessageBuilder(_channel, _ircMessage, _args)
{
}
virtual MessagePtr build()
{
// PARSE
this->parse();
this->usernameColor_ = getRandomColor(this->ircMessage->nick());
// words
// this->addWords(this->originalMessage_.split(' '));
this->message().messageText = this->originalMessage_;
this->message().searchText = this->message().localizedName + " " +
this->userName + ": " +
this->originalMessage_;
return nullptr;
}
void bench()
{
this->parseHighlights();
}
};
class MockApplication : public mock::BaseApplication
{
public:
MockApplication()
: highlights(this->settings, &this->accounts)
{
}
AccountController *getAccounts() override
{
return &this->accounts;
}
HighlightController *getHighlights() override
{
return &this->highlights;
}
IUserDataController *getUserData() override
{
return &this->userData;
}
AccountController accounts;
HighlightController highlights;
mock::UserDataController userData;
};
static void BM_HighlightTest(benchmark::State &state)
{
MockApplication mockApplication;
std::string message =
R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,15-22;flags=;id=a3196c7e-be4c-4b49-9c5a-8b8302b50c2a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922213730;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm (no space))";
auto ircMessage = Communi::IrcMessage::fromData(message.c_str(), nullptr);
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(ircMessage);
assert(privMsg != nullptr);
MessageParseArgs args;
auto emptyChannel = Channel::getEmpty();
for (auto _ : state)
{
state.PauseTiming();
BenchmarkMessageBuilder b(emptyChannel.get(), privMsg, args);
b.build();
state.ResumeTiming();
b.bench();
}
}
BENCHMARK(BM_HighlightTest);

View file

@ -71,6 +71,17 @@ public:
}
}
void remove(const key_t &key)
{
auto it = _cache_items_map.find(key);
if (it == _cache_items_map.end())
{
throw std::range_error("There is no such key in cache");
}
_cache_items_list.erase(it->second);
_cache_items_map.erase(it);
}
const value_t &get(const key_t &key)
{
auto it = _cache_items_map.find(key);

View file

@ -26,6 +26,7 @@ public:
, mentionsChannel(std::shared_ptr<Channel>(new MockChannel("forsen3")))
, liveChannel(std::shared_ptr<Channel>(new MockChannel("forsen")))
, automodChannel(std::shared_ptr<Channel>(new MockChannel("forsen2")))
, channelNamesById_(1)
{
}
@ -49,6 +50,18 @@ public:
return {};
}
std::optional<QString> getOrPopulateChannelCache(
const QString &channelId) override
{
if (channelId == "11148817")
return "pajlada";
if (channelId == "141981764")
return "twitchdev";
if (channelId == "1025594235")
return "shared_chat_test_01";
return {};
}
void addFakeMessage(const QString &data) override
{
}
@ -148,6 +161,7 @@ public:
ChannelPtr mentionsChannel;
ChannelPtr liveChannel;
ChannelPtr automodChannel;
UniqueAccess<cache::lru_cache<QString, QString>> channelNamesById_;
QString lastUserThatWhisperedMe{"forsen"};
std::unordered_map<QString, std::weak_ptr<Channel>> mockChannels;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -22,6 +22,7 @@ class ScrollbarHighlight;
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
using MessagePtrMut = std::shared_ptr<Message>;
struct Message {
Message();
~Message();

File diff suppressed because it is too large Load diff

View file

@ -14,8 +14,6 @@
#include <ctime>
#include <memory>
#include <optional>
#include <tuple>
#include <unordered_map>
#include <utility>
@ -31,6 +29,7 @@ struct AutomodUserAction;
struct AutomodInfoAction;
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
using MessagePtrMut = std::shared_ptr<Message>;
class MessageElement;
class TextElement;
@ -68,6 +67,7 @@ struct LiveUpdatesUpdateEmoteSetMessageTag {
struct ImageUploaderResultTag {
};
// NOLINTBEGIN(readability-identifier-naming)
const SystemMessageTag systemMessage{};
const RaidEntryMessageTag raidEntryMessage{};
const TimeoutMessageTag timeoutMessage{};
@ -79,6 +79,7 @@ const LiveUpdatesUpdateEmoteSetMessageTag liveUpdatesUpdateEmoteSetMessage{};
// This signifies that you want to construct a message containing the result of
// a successful image upload.
const ImageUploaderResultTag imageUploaderResultMessage{};
// NOLINTEND(readability-identifier-naming)
MessagePtr makeSystemMessage(const QString &text);
MessagePtr makeSystemMessage(const QString &text, const QTime &time);
@ -90,26 +91,22 @@ struct MessageParseArgs {
bool trimSubscriberUsername = false;
bool isStaffOrBroadcaster = false;
bool isSubscriptionMessage = false;
bool allowIgnore = true;
bool isAction = false;
QString channelPointRewardId = "";
};
struct HighlightAlert {
QUrl customSound;
bool playSound = false;
bool windowAlert = false;
};
class MessageBuilder
{
public:
/// Build a message without a base IRC message.
MessageBuilder();
/// Build a message based on an incoming IRC PRIVMSG
explicit MessageBuilder(Channel *_channel,
const Communi::IrcPrivateMessage *_ircMessage,
const MessageParseArgs &_args);
/// Build a message based on an incoming IRC message (e.g. notice)
explicit MessageBuilder(Channel *_channel,
const Communi::IrcMessage *_ircMessage,
const MessageParseArgs &_args, QString content,
bool isAction);
MessageBuilder(SystemMessageTag, const QString &text,
const QTime &time = QTime::currentTime());
MessageBuilder(RaidEntryMessageTag, const QString &text,
@ -157,17 +154,10 @@ public:
~MessageBuilder() = default;
QString userName;
/// The Twitch Channel the message was received in
TwitchChannel *twitchChannel = nullptr;
/// The Twitch Channel the message was sent in, according to the Shared Chat feature
TwitchChannel *sourceChannel = nullptr;
Message *operator->();
Message &message();
MessagePtr release();
std::weak_ptr<Message> weakOf();
MessagePtrMut release();
std::weak_ptr<const Message> weakOf();
void append(std::unique_ptr<MessageElement> element);
void addLink(const linkparser::Parsed &parsedLink, const QString &source);
@ -184,14 +174,8 @@ public:
return pointer;
}
[[nodiscard]] bool isIgnored() const;
bool isIgnoredReply() const;
void triggerHighlights();
MessagePtr build();
void setThread(std::shared_ptr<MessageThread> thread);
void setParent(MessagePtr parent);
void setMessageOffset(int offset);
static void triggerHighlights(const Channel *channel,
const HighlightAlert &alert);
void appendChannelPointRewardMessage(const ChannelPointReward &reward,
bool isMod, bool isBroadcaster);
@ -231,96 +215,124 @@ public:
static MessagePtr makeLowTrustUpdateMessage(
const PubSubLowTrustUsersMessage &action);
protected:
void addTextOrEmoji(EmotePtr emote);
void addTextOrEmoji(const QString &string_);
/// @brief Builds a message out of an `ircMessage`.
///
/// Building a message won't cause highlights to be triggered. They will
/// only be parsed. To trigger highlights (play sound etc.), use
/// triggerHighlights().
///
/// @param channel The channel this message was sent to. Must not be
/// `nullptr`.
/// @param ircMessage The original message. This can be any message
/// (PRIVMSG, USERNOTICE, etc.). Its content is not
/// accessed through this parameter but through `content`,
/// as the content might be inside a tag (e.g. gifts in a
/// USERNOTICE).
/// @param args Arguments from parsing a chat message.
/// @param content The message text. This isn't always the entire text. In
/// replies, the leading mention can be cut off.
/// See `messageOffset`.
/// @param messageOffset Starting offset to be used on index-based
/// operations on `content` such as parsing emotes.
/// For example:
/// ircMessage = "@hi there"
/// content = "there"
/// messageOffset_ = 4
/// The index 6 would resolve to 6 - 4 = 2 => 'e'
/// @param thread The reply thread this message is part of. If there's no
/// thread, this is an empty `shared_ptr`.
/// @param parent The direct parent this message is replying to. This does
/// not need to be the `thread`s root. If this message isn't
/// replying to anything, this is an empty `shared_ptr`.
///
/// @returns The built message and a highlight result. If the message is
/// ignored (e.g. from a blocked user), then the returned pointer
/// will be en empty `shared_ptr`.
static std::pair<MessagePtrMut, HighlightAlert> makeIrcMessage(
Channel *channel, const Communi::IrcMessage *ircMessage,
const MessageParseArgs &args, QString content,
QString::size_type messageOffset,
const std::shared_ptr<MessageThread> &thread = {},
const MessagePtr &parent = {});
private:
struct TextState {
TwitchChannel *twitchChannel = nullptr;
bool hasBits = false;
bool bitsStacked = false;
int bitsLeft = 0;
};
void addEmoji(const EmotePtr &emote);
void addTextOrEmote(TextState &state, QString string);
Outcome tryAppendCheermote(TextState &state, const QString &string);
Outcome tryAppendEmote(TwitchChannel *twitchChannel, const EmoteName &name);
bool isEmpty() const;
MessageElement &back();
std::unique_ptr<MessageElement> releaseBack();
MessageColor textColor_ = MessageColor::Text;
// Helper method that emplaces some text stylized as system text
// and then appends that text to the QString parameter "toUpdate".
// Returns the TextElement that was emplaced.
TextElement *emplaceSystemTextAndUpdate(const QString &text,
QString &toUpdate);
std::shared_ptr<Message> message_;
void parse();
void parseUsernameColor();
void parseUsername();
void parseMessageID();
void parseRoomID();
void parseUsernameColor(const QVariantMap &tags, const QString &userID);
void parseUsername(const Communi::IrcMessage *ircMessage,
TwitchChannel *twitchChannel,
bool trimSubscriberUsername);
void parseMessageID(const QVariantMap &tags);
/// Parses the room-ID this message was received in
///
/// @returns The room-ID
static QString parseRoomID(const QVariantMap &tags,
TwitchChannel *twitchChannel);
/// Parses the shared-chat information from this message.
///
/// @param tags The tags of the received message
/// @param twitchChannel The channel this message was received in
/// @returns The source channel - the channel this message originated from.
/// If there's no channel currently open, @a twitchChannel is
/// returned.
TwitchChannel *parseSharedChatInfo(const QVariantMap &tags,
TwitchChannel *twitchChannel);
// Parse & build thread information into the message
// Will read information from thread_ or from IRC tags
void parseThread();
void parseThread(const QString &messageContent, const QVariantMap &tags,
const Channel *channel,
const std::shared_ptr<MessageThread> &thread,
const MessagePtr &parent);
// parseHighlights only updates the visual state of the message, but leaves the playing of alerts and sounds to the triggerHighlights function
void parseHighlights();
void appendChannelName();
void appendUsername();
HighlightAlert parseHighlights(const QVariantMap &tags,
const QString &originalMessage,
const MessageParseArgs &args);
/// Return the Twitch Channel this message originated from
///
/// Useful to handle messages from the "Shared Chat" feature
///
/// Can return nullptr
const TwitchChannel *getSourceChannel() const;
std::tuple<std::optional<EmotePtr>, MessageElementFlags, bool> parseEmote(
const EmoteName &name) const;
Outcome tryAppendEmote(const EmoteName &name);
void appendChannelName(const Channel *channel);
void appendUsername(const QVariantMap &tags, const MessageParseArgs &args);
void addWords(const QStringList &words,
const std::vector<TwitchEmoteOccurrence> &twitchEmotes);
const std::vector<TwitchEmoteOccurrence> &twitchEmotes,
TextState &state);
void appendTwitchBadges();
void appendChatterinoBadges();
void appendFfzBadges();
void appendSeventvBadges();
Outcome tryParseCheermote(const QString &string);
void appendTwitchBadges(const QVariantMap &tags,
TwitchChannel *twitchChannel);
void appendChatterinoBadges(const QString &userID);
void appendFfzBadges(TwitchChannel *twitchChannel, const QString &userID);
void appendSeventvBadges(const QString &userID);
bool shouldAddModerationElements() const;
[[nodiscard]] static bool isIgnored(const QString &originalMessage,
const QString &userID,
const Channel *channel);
QString roomID_;
bool hasBits_ = false;
QString bits;
int bitsLeft{};
bool bitsStacked = false;
bool historicalMessage_ = false;
std::shared_ptr<MessageThread> thread_;
MessagePtr parent_;
/**
* Starting offset to be used on index-based operations on `originalMessage_`.
*
* For example:
* originalMessage_ = "there"
* messageOffset_ = 4
* (the irc message is "hey there")
*
* then the index 6 would resolve to 6 - 4 = 2 => 'e'
*/
int messageOffset_ = 0;
QString userId_;
bool senderIsBroadcaster{};
Channel *channel = nullptr;
const Communi::IrcMessage *ircMessage;
MessageParseArgs args;
const QVariantMap tags;
QString originalMessage_;
const bool action_{};
std::shared_ptr<Message> message_;
MessageColor textColor_ = MessageColor::Text;
QColor usernameColor_ = {153, 153, 153};
bool highlightAlert_ = false;
bool highlightSound_ = false;
std::optional<QUrl> highlightSoundCustomUrl_{};
};
} // namespace chatterino

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

@ -508,15 +508,20 @@ std::vector<MessagePtr> parseUserNoticeMessage(Channel *channel,
{
MessageParseArgs args;
args.trimSubscriberUsername = true;
args.allowIgnore = false;
MessageBuilder builder(channel, message, args, content, false);
builder->flags.set(MessageFlag::Subscription);
builder->flags.unset(MessageFlag::Highlighted);
if (mirrored)
auto [built, highlight] = MessageBuilder::makeIrcMessage(
channel, message, args, content, 0);
if (built)
{
builder->flags.set(MessageFlag::SharedMessage);
built->flags.set(MessageFlag::Subscription);
built->flags.unset(MessageFlag::Highlighted);
if (mirrored)
{
built->flags.set(MessageFlag::SharedMessage);
}
builtMessages.emplace_back(std::move(built));
}
builtMessages.emplace_back(builder.build());
}
}
@ -661,12 +666,13 @@ std::vector<MessagePtr> parsePrivMessage(Channel *channel,
std::vector<MessagePtr> builtMessages;
MessageParseArgs args;
MessageBuilder builder(channel, message, args, message->content(),
message->isAction());
if (!builder.isIgnored())
args.isAction = message->isAction();
auto [built, alert] = MessageBuilder::makeIrcMessage(channel, message, args,
message->content(), 0);
if (built)
{
builtMessages.emplace_back(builder.build());
builder.triggerHighlights();
builtMessages.emplace_back(std::move(built));
MessageBuilder::triggerHighlights(channel, alert);
}
return builtMessages;
@ -709,22 +715,21 @@ std::vector<MessagePtr> IrcMessageHandler::parseMessageWithReply(
{
args.channelPointRewardId = it.value().toString();
}
MessageBuilder builder(channel, message, args, content,
privMsg->isAction());
builder.setMessageOffset(messageOffset);
args.isAction = privMsg->isAction();
auto replyCtx = getReplyContext(tc, message, otherLoaded);
builder.setThread(std::move(replyCtx.thread));
builder.setParent(std::move(replyCtx.parent));
if (replyCtx.highlight)
{
builder.message().flags.set(MessageFlag::SubscribedThread);
}
auto [built, alert] = MessageBuilder::makeIrcMessage(
channel, message, args, content, messageOffset, replyCtx.thread,
replyCtx.parent);
if (!builder.isIgnored())
if (built)
{
builtMessages.emplace_back(builder.build());
builder.triggerHighlights();
if (replyCtx.highlight)
{
built->flags.set(MessageFlag::SubscribedThread);
}
builtMessages.emplace_back(built);
MessageBuilder::triggerHighlights(channel, alert);
}
if (message->tags().contains(u"pinned-chat-paid-amount"_s))
@ -1016,20 +1021,18 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *ircMessage)
auto *c = getApp()->getTwitch()->getWhispersChannel().get();
MessageBuilder builder(c, ircMessage, args,
unescapeZeroWidthJoiner(ircMessage->parameter(1)),
false);
if (builder.isIgnored())
auto [message, alert] = MessageBuilder::makeIrcMessage(
c, ircMessage, args, unescapeZeroWidthJoiner(ircMessage->parameter(1)),
0);
if (!message)
{
return;
}
builder->flags.set(MessageFlag::Whisper);
MessagePtr message = builder.build();
builder.triggerHighlights();
message->flags.set(MessageFlag::Whisper);
MessageBuilder::triggerHighlights(c, alert);
getApp()->getTwitch()->setLastUserThatWhisperedMe(builder.userName);
getApp()->getTwitch()->setLastUserThatWhisperedMe(message->loginName);
if (message->flags.has(MessageFlag::ShowInMentions))
{
@ -1504,6 +1507,7 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *message,
{
args.isStaffOrBroadcaster = true;
}
args.isAction = isAction;
auto *channel = dynamic_cast<TwitchChannel *>(chan.get());
@ -1605,24 +1609,22 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *message,
}
}
MessageBuilder builder(channel, message, args, content, isAction);
builder.setMessageOffset(messageOffset);
args.allowIgnore = !isSub;
auto [msg, alert] = MessageBuilder::makeIrcMessage(
channel, message, args, content, messageOffset, replyCtx.thread,
replyCtx.parent);
builder.setThread(std::move(replyCtx.thread));
builder.setParent(std::move(replyCtx.parent));
if (replyCtx.highlight)
{
builder.message().flags.set(MessageFlag::SubscribedThread);
}
if (isSub || !builder.isIgnored())
if (msg)
{
if (isSub)
{
builder->flags.set(MessageFlag::Subscription);
builder->flags.unset(MessageFlag::Highlighted);
msg->flags.set(MessageFlag::Subscription);
msg->flags.unset(MessageFlag::Highlighted);
}
if (replyCtx.highlight)
{
msg->flags.set(MessageFlag::SubscribedThread);
}
auto msg = builder.build();
IrcMessageHandler::setSimilarityFlags(msg, chan);
@ -1630,7 +1632,7 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *message,
(!getSettings()->hideSimilar &&
getSettings()->shownSimilarTriggerHighlights))
{
builder.triggerHighlights();
MessageBuilder::triggerHighlights(channel, alert);
}
const auto highlighted = msg->flags.has(MessageFlag::Highlighted);

View file

@ -153,6 +153,7 @@ TwitchIrcServer::TwitchIrcServer()
, liveChannel(new Channel("/live", Channel::Type::TwitchLive))
, automodChannel(new Channel("/automod", Channel::Type::TwitchAutomod))
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
, channelNamesById_(512)
{
// Initialize the connections
// XXX: don't create write connection if there is no separate write connection.
@ -1128,6 +1129,38 @@ std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
return Channel::getEmpty();
}
std::optional<QString> TwitchIrcServer::getOrPopulateChannelCache(
const QString &channelId)
{
{
const auto cache = this->channelNamesById_.access();
if (cache->exists(channelId))
{
return cache->get(channelId);
}
// prevent multiple helix requests for single user
cache->put(channelId, "");
}
getHelix()->getUserById(
channelId,
[this](const HelixUser &user) {
const auto cache = this->channelNamesById_.access();
cache->put(user.id, user.login);
},
[this, &channelId] {
const auto cache = this->channelNamesById_.access();
if (cache->exists(channelId) && cache->get(channelId).isEmpty())
{
// invalidate cache so another helix request can be attempted
cache->remove(channelId);
}
});
return {};
}
QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
{
if (dirtyChannelName.startsWith('#'))

View file

@ -3,10 +3,12 @@
#include "common/Atomic.hpp"
#include "common/Channel.hpp"
#include "common/Common.hpp"
#include "common/UniqueAccess.hpp"
#include "providers/irc/IrcConnection2.hpp"
#include "util/RatelimitBucket.hpp"
#include <IrcMessage>
#include <lrucache/lrucache.hpp>
#include <pajlada/signals/signal.hpp>
#include <pajlada/signals/signalholder.hpp>
@ -43,6 +45,9 @@ public:
virtual ChannelPtr getOrAddChannel(const QString &dirtyChannelName) = 0;
virtual ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) = 0;
virtual std::optional<QString> getOrPopulateChannelCache(
const QString &channelId) = 0;
virtual void addFakeMessage(const QString &data) = 0;
virtual void addGlobalSystemMessage(const QString &messageText) = 0;
@ -95,6 +100,9 @@ public:
std::shared_ptr<Channel> getChannelOrEmptyByID(
const QString &channelID) override;
std::optional<QString> getOrPopulateChannelCache(
const QString &channelId) override;
void reloadAllBTTVChannelEmotes();
void reloadAllFFZChannelEmotes();
void reloadAllSevenTVChannelEmotes();
@ -190,6 +198,9 @@ private:
// https://dev.twitch.tv/docs/irc/guide#rate-limits
QObjectPtr<RatelimitBucket> joinBucket_;
// cached channel id => name for resolving Shared Chat members
UniqueAccess<cache::lru_cache<QString, QString>> channelNamesById_;
QTimer reconnectTimer_;
int falloffCounter_ = 1;

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

@ -877,10 +877,13 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,
break;
case WM_DPICHANGED: {
// wait for Qt to process this message
postToThread([] {
getApp()->getWindows()->invalidateChannelViewBuffers();
});
if (this->flags_.has(ClearBuffersOnDpiChange))
{
// wait for Qt to process this message
postToThread([] {
getApp()->getWindows()->invalidateChannelViewBuffers();
});
}
}
break;

View file

@ -37,6 +37,7 @@ public:
Dialog = 1 << 6,
DisableLayoutSave = 1 << 7,
BoundsCheckOnShow = 1 << 8,
ClearBuffersOnDpiChange = 1 << 9,
};
enum ActionOnFocusLoss { Nothing, Delete, Close, Hide };

View file

@ -36,9 +36,9 @@ namespace {
DraggablePopup::DraggablePopup(bool closeAutomatically, QWidget *parent)
: BaseWindow(
closeAutomatically
? popupFlagsCloseAutomatically | BaseWindow::DisableLayoutSave
: popupFlags | BaseWindow::DisableLayoutSave,
(closeAutomatically ? popupFlagsCloseAutomatically : popupFlags) |
BaseWindow::DisableLayoutSave |
BaseWindow::ClearBuffersOnDpiChange,
parent)
, lifetimeHack_(std::make_shared<bool>(false))
, dragTimer_(this)

View file

@ -7,6 +7,7 @@
#include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp"
#include "util/PostToThread.hpp"
#include "widgets/BaseWidget.hpp"
#include "widgets/helper/ChannelView.hpp"
#include "widgets/helper/InvisibleSizeGrip.hpp"
@ -312,6 +313,13 @@ bool OverlayWindow::nativeEvent(const QByteArray &eventType, void *message,
}
break;
# endif
case WM_DPICHANGED: {
// wait for Qt to process this message, same as in BaseWindow
postToThread([] {
getApp()->getWindows()->invalidateChannelViewBuffers();
});
}
break;
default:
return QWidget::nativeEvent(eventType, message, result);

View file

@ -52,7 +52,9 @@
namespace chatterino {
Window::Window(WindowType type, QWidget *parent)
: BaseWindow(BaseWindow::EnableCustomFrame, parent)
: BaseWindow(
{BaseWindow::EnableCustomFrame, BaseWindow::ClearBuffersOnDpiChange},
parent)
, type_(type)
, notebook_(new SplitNotebook(this))
{

View file

@ -153,7 +153,7 @@
"searchText": "mm2pl mm2pl: Kappa ",
"serverReceivedTime": "2022-09-03T10:31:42Z",
"timeoutUser": "",
"usernameColor": "#ff000000"
"usernameColor": "#ffdaa521"
}
]
}

View file

@ -153,7 +153,7 @@
"searchText": "mm2pl mm2pl: Kappa ",
"serverReceivedTime": "2022-09-03T10:31:42Z",
"timeoutUser": "",
"usernameColor": "#ff000000"
"usernameColor": "#ffdaa521"
}
]
}

View file

@ -153,7 +153,7 @@
"searchText": "mm2pl mm2pl: Keepo ",
"serverReceivedTime": "2022-09-03T10:31:35Z",
"timeoutUser": "",
"usernameColor": "#ff000000"
"usernameColor": "#ffdaa521"
}
]
}

View file

@ -221,7 +221,7 @@
"searchText": "mm2pl mm2pl: Kappa Keepo PogChamp ",
"serverReceivedTime": "2022-09-03T10:31:42Z",
"timeoutUser": "",
"usernameColor": "#ff000000"
"usernameColor": "#ffdaa521"
}
]
}

View file

@ -64,6 +64,24 @@
"trailingSpace": true,
"type": "TwitchModerationElement"
},
{
"emote": {
"homePage": "https://link.twitch.tv/SharedChatViewer",
"images": {
"1x": ""
},
"name": "SharedChat_shared_chat_test_01",
"tooltip": "Shared Message from shared_chat_test_01"
},
"flags": "BadgeSharedChannel",
"link": {
"type": "UserInfo",
"value": "shared_chat_test_01"
},
"tooltip": "Shared Message from shared_chat_test_01",
"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": "SharedChat_twitchdev",
"tooltip": "Shared Message from twitchdev"
},
"flags": "BadgeSharedChannel",
"link": {
"type": "UserInfo",
"value": "twitchdev"
},
"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": "SharedChat_twitchdev",
"tooltip": "Shared Message from twitchdev"
},
"flags": "BadgeSharedChannel",
"link": {
"type": "UserInfo",
"value": "twitchdev"
},
"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": "SharedChat_shared_chat_test_01",
"tooltip": "Shared Message from shared_chat_test_01"
},
"flags": "BadgeSharedChannel",
"link": {
"type": "UserInfo",
"value": "shared_chat_test_01"
},
"tooltip": "Shared Message from shared_chat_test_01",
"trailingSpace": true,
"type": "BadgeElement"
},
{
"emote": {
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",

View file

@ -285,9 +285,9 @@ TEST_F(FiltersF, TypingContextChecks)
QString originalMessage = privmsg->content();
MessageBuilder builder(&channel, privmsg, MessageParseArgs{});
auto [msg, alert] = MessageBuilder::makeIrcMessage(
&channel, privmsg, MessageParseArgs{}, originalMessage, 0);
auto msg = builder.build();
EXPECT_NE(msg.get(), nullptr);
auto contextMap = buildContextMap(msg, &channel);