From d7e8f4eabd25d55f69b3de96f7ab5207e923092a Mon Sep 17 00:00:00 2001 From: Mm2PL Date: Sun, 1 Aug 2021 14:08:03 +0200 Subject: [PATCH 01/19] Add channel.live filter variable (#3092) Co-authored-by: pajlada --- CHANGELOG.md | 1 + src/controllers/filters/parser/FilterParser.cpp | 17 +++++++++++++++-- src/controllers/filters/parser/Tokenizer.hpp | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4f669847..be449aabc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Minor: Received IRC messages use `time` message tag for timestamp if it's available. (#3021) - Minor: Added informative messages for recent-messages API's errors. (#3029) - Minor: Added section with helpful Chatterino-related links to the About page. (#3068) +- Minor: Added `channel.live` filter variable (#3092) - Bugfix: Fixed "smiley" emotes being unable to be "Tabbed" with autocompletion, introduced in v2.3.3. (#3010) - Bugfix: Fixed PubSub not properly trying to resolve pending listens when the pending listens list was larger than 50. (#3037) - Bugfix: Copy buttons in usercard now show properly in light mode (#3057) diff --git a/src/controllers/filters/parser/FilterParser.cpp b/src/controllers/filters/parser/FilterParser.cpp index d7cd18822..7d5e8ad3b 100644 --- a/src/controllers/filters/parser/FilterParser.cpp +++ b/src/controllers/filters/parser/FilterParser.cpp @@ -61,8 +61,7 @@ ContextMap buildContextMap(const MessagePtr &m) subLength = m->badgeInfos.at(subBadge).toInt(); } } - - return { + ContextMap vars = { {"author.badges", std::move(badges)}, {"author.color", m->usernameColor}, {"author.name", m->displayName}, @@ -82,6 +81,20 @@ ContextMap buildContextMap(const MessagePtr &m) {"message.content", m->messageText}, {"message.length", m->messageText.length()}, }; + { + using namespace chatterino; + auto channel = getApp()->twitch2->getChannelOrEmpty(m->channelName); + auto *tc = dynamic_cast(channel.get()); + if (!channel->isEmpty() && tc) + { + vars["channel.live"] = tc->isLive(); + } + else + { + vars["channel.live"] = false; + } + } + return vars; } FilterParser::FilterParser(const QString &text) diff --git a/src/controllers/filters/parser/Tokenizer.hpp b/src/controllers/filters/parser/Tokenizer.hpp index 78ff27064..8f9b5824b 100644 --- a/src/controllers/filters/parser/Tokenizer.hpp +++ b/src/controllers/filters/parser/Tokenizer.hpp @@ -17,6 +17,7 @@ static const QMap validIdentifiersMap = { {"author.sub_length", "author sub length"}, {"channel.name", "channel name"}, {"channel.watching", "/watching channel?"}, + {"channel.live", "Channel live?"}, {"flags.highlighted", "highlighted?"}, {"flags.points_redeemed", "redeemed points?"}, {"flags.sub_message", "sub/resub message?"}, From 77f683577f927039296a7616b6cfc8a927d2e15c Mon Sep 17 00:00:00 2001 From: Mm2PL Date: Sun, 1 Aug 2021 14:38:07 +0200 Subject: [PATCH 02/19] Use double spaces instead of Chatterino character when possible (#3081) --- CHANGELOG.md | 1 + src/providers/twitch/TwitchChannel.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be449aabc..84dd05626 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Minor: Received IRC messages use `time` message tag for timestamp if it's available. (#3021) - Minor: Added informative messages for recent-messages API's errors. (#3029) - Minor: Added section with helpful Chatterino-related links to the About page. (#3068) +- Minor: Now uses spaces instead of magic Unicode character for sending duplicate messages (#3081) - Minor: Added `channel.live` filter variable (#3092) - Bugfix: Fixed "smiley" emotes being unable to be "Tabbed" with autocompletion, introduced in v2.3.3. (#3010) - Bugfix: Fixed PubSub not properly trying to resolve pending listens when the pending listens list was larger than 50. (#3037) diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 4571cca22..95b3f6944 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -375,7 +375,17 @@ void TwitchChannel::sendMessage(const QString &message) { if (parsedMessage == this->lastSentMessage_) { - parsedMessage.append(MAGIC_MESSAGE_SUFFIX); + auto spaceIndex = parsedMessage.indexOf(' '); + if (spaceIndex == -1) + { + // no spaces found, fall back to old magic character + parsedMessage.append(MAGIC_MESSAGE_SUFFIX); + } + else + { + // replace the space we found in spaceIndex with two spaces + parsedMessage.replace(spaceIndex, 1, " "); + } } } } From 784fdd28b20f3e95f790cd82ffc0f3416ce08dcc Mon Sep 17 00:00:00 2001 From: pajlada Date: Sun, 1 Aug 2021 15:44:04 +0200 Subject: [PATCH 03/19] Check for ignored phrases/users in channel point redemptions (#3102) --- CHANGELOG.md | 1 + chatterino.pro | 1 + src/CMakeLists.txt | 2 + src/controllers/ignores/IgnoreController.cpp | 63 +++++++++++++++++++ src/controllers/ignores/IgnoreController.hpp | 12 ++++ src/messages/SharedMessageBuilder.cpp | 18 ++---- src/providers/twitch/TwitchChannel.cpp | 3 +- src/providers/twitch/TwitchMessageBuilder.cpp | 61 +++++++----------- src/providers/twitch/TwitchMessageBuilder.hpp | 3 +- 9 files changed, 108 insertions(+), 56 deletions(-) create mode 100644 src/controllers/ignores/IgnoreController.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 84dd05626..26fedd43b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Bugfix: Copy buttons in usercard now show properly in light mode (#3057) - Bugfix: Fixed comma appended to username completion when not at the beginning of the message. (#3060) - Bugfix: Fixed bug misplacing chat when zooming on Chrome with Chatterino Native Host extension (#1936) +- Bugfix: Channel point redemptions from ignored users are now properly blocked. (#3102) - Dev: Ubuntu packages are now available (#2936) - Dev: Disabled update checker on Flatpak. (#3051) - Dev: Add logging for HTTP requests (#2991) diff --git a/chatterino.pro b/chatterino.pro index b858cec90..633528629 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -159,6 +159,7 @@ SOURCES += \ src/controllers/highlights/HighlightModel.cpp \ src/controllers/highlights/HighlightPhrase.cpp \ src/controllers/highlights/UserHighlightModel.cpp \ + src/controllers/ignores/IgnoreController.cpp \ src/controllers/ignores/IgnoreModel.cpp \ src/controllers/moderationactions/ModerationAction.cpp \ src/controllers/moderationactions/ModerationActionModel.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e935e7366..31d7a1a98 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -88,6 +88,8 @@ set(SOURCE_FILES controllers/highlights/UserHighlightModel.cpp controllers/highlights/UserHighlightModel.hpp + controllers/ignores/IgnoreController.cpp + controllers/ignores/IgnoreController.hpp controllers/ignores/IgnoreModel.cpp controllers/ignores/IgnoreModel.hpp diff --git a/src/controllers/ignores/IgnoreController.cpp b/src/controllers/ignores/IgnoreController.cpp new file mode 100644 index 000000000..e36feead0 --- /dev/null +++ b/src/controllers/ignores/IgnoreController.cpp @@ -0,0 +1,63 @@ +#include "controllers/ignores/IgnoreController.hpp" + +#include "common/QLogging.hpp" +#include "controllers/ignores/IgnorePhrase.hpp" +#include "singletons/Settings.hpp" + +namespace chatterino { + +bool isIgnoredMessage(IgnoredMessageParameters &¶ms) +{ + if (!params.message.isEmpty()) + { + // TODO(pajlada): Do we need to check if the phrase is valid first? + auto phrases = getCSettings().ignoredMessages.readOnly(); + for (const auto &phrase : *phrases) + { + if (phrase.isBlock() && phrase.isMatch(params.message)) + { + qCDebug(chatterinoMessage) + << "Blocking message because it contains ignored phrase" + << phrase.getPattern(); + return true; + } + } + } + + if (!params.twitchUserID.isEmpty() && + getSettings()->enableTwitchBlockedUsers) + { + auto sourceUserID = params.twitchUserID; + + auto blocks = + getApp()->accounts->twitch.getCurrent()->accessBlockedUserIds(); + + if (auto it = blocks->find(sourceUserID); it != blocks->end()) + { + switch (static_cast( + getSettings()->showBlockedUsersMessages.getValue())) + { + case ShowIgnoredUsersMessages::IfModerator: + if (params.isMod || params.isBroadcaster) + { + return false; + } + break; + case ShowIgnoredUsersMessages::IfBroadcaster: + if (params.isBroadcaster) + { + return false; + } + break; + case ShowIgnoredUsersMessages::Never: + break; + } + + return true; + } + } + + return false; +} + +} // namespace chatterino diff --git a/src/controllers/ignores/IgnoreController.hpp b/src/controllers/ignores/IgnoreController.hpp index fed12f12c..4c2048621 100644 --- a/src/controllers/ignores/IgnoreController.hpp +++ b/src/controllers/ignores/IgnoreController.hpp @@ -1,7 +1,19 @@ #pragma once +#include + namespace chatterino { enum class ShowIgnoredUsersMessages { Never, IfModerator, IfBroadcaster }; +struct IgnoredMessageParameters { + QString message; + + QString twitchUserID; + bool isMod; + bool isBroadcaster; +}; + +bool isIgnoredMessage(IgnoredMessageParameters &¶ms); + } // namespace chatterino diff --git a/src/messages/SharedMessageBuilder.cpp b/src/messages/SharedMessageBuilder.cpp index 1235af3c6..cdc8e59f0 100644 --- a/src/messages/SharedMessageBuilder.cpp +++ b/src/messages/SharedMessageBuilder.cpp @@ -2,6 +2,7 @@ #include "Application.hpp" #include "common/QLogging.hpp" +#include "controllers/ignores/IgnoreController.hpp" #include "controllers/ignores/IgnorePhrase.hpp" #include "messages/Message.hpp" #include "messages/MessageElement.hpp" @@ -104,20 +105,9 @@ void SharedMessageBuilder::parse() bool SharedMessageBuilder::isIgnored() const { - // TODO(pajlada): Do we need to check if the phrase is valid first? - auto phrases = getCSettings().ignoredMessages.readOnly(); - for (const auto &phrase : *phrases) - { - if (phrase.isBlock() && phrase.isMatch(this->originalMessage_)) - { - qCDebug(chatterinoMessage) - << "Blocking message because it contains ignored phrase" - << phrase.getPattern(); - return true; - } - } - - return false; + return isIgnoredMessage({ + /*.message = */ this->originalMessage_, + }); } void SharedMessageBuilder::parseUsernameColor() diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 95b3f6944..94c458a55 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -286,7 +286,8 @@ void TwitchChannel::addChannelPointReward(const ChannelPointReward &reward) if (!reward.isUserInputRequired) { MessageBuilder builder; - TwitchMessageBuilder::appendChannelPointRewardMessage(reward, &builder); + TwitchMessageBuilder::appendChannelPointRewardMessage( + reward, &builder, this->isMod(), this->isBroadcaster()); this->addMessage(builder.release()); return; } diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index f79104799..97c36f0d7 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -114,44 +114,12 @@ TwitchMessageBuilder::TwitchMessageBuilder( bool TwitchMessageBuilder::isIgnored() const { - if (SharedMessageBuilder::isIgnored()) - { - return true; - } - - auto app = getApp(); - - if (getSettings()->enableTwitchBlockedUsers && - this->tags.contains("user-id")) - { - auto sourceUserID = this->tags.value("user-id").toString(); - - auto blocks = - app->accounts->twitch.getCurrent()->accessBlockedUserIds(); - - if (auto it = blocks->find(sourceUserID); it != blocks->end()) - { - switch (static_cast( - getSettings()->showBlockedUsersMessages.getValue())) - { - case ShowIgnoredUsersMessages::IfModerator: - if (this->channel->isMod() || - this->channel->isBroadcaster()) - return false; - break; - case ShowIgnoredUsersMessages::IfBroadcaster: - if (this->channel->isBroadcaster()) - return false; - break; - case ShowIgnoredUsersMessages::Never: - break; - } - - return true; - } - } - - return false; + return isIgnoredMessage({ + /*.message = */ this->originalMessage_, + /*.twitchUserID = */ this->tags.value("user-id").toString(), + /*.isMod = */ this->channel->isMod(), + /*.isBroadcaster = */ this->channel->isBroadcaster(), + }); } void TwitchMessageBuilder::triggerHighlights() @@ -190,7 +158,9 @@ MessagePtr TwitchMessageBuilder::build() this->args.channelPointRewardId); if (reward) { - this->appendChannelPointRewardMessage(reward.get(), this); + this->appendChannelPointRewardMessage( + reward.get(), this, this->channel->isMod(), + this->channel->isBroadcaster()); } } @@ -1261,8 +1231,19 @@ Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string) } void TwitchMessageBuilder::appendChannelPointRewardMessage( - const ChannelPointReward &reward, MessageBuilder *builder) + const ChannelPointReward &reward, MessageBuilder *builder, bool isMod, + bool isBroadcaster) { + if (isIgnoredMessage({ + /*.message = */ "", + /*.twitchUserID = */ reward.user.id, + /*.isMod = */ isMod, + /*.isBroadcaster = */ isBroadcaster, + })) + { + return; + } + builder->emplace(); QString redeemed = "Redeemed"; QStringList textList; diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index 3e14412b7..7cf68494a 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -46,7 +46,8 @@ public: MessagePtr build() override; static void appendChannelPointRewardMessage( - const ChannelPointReward &reward, MessageBuilder *builder); + const ChannelPointReward &reward, MessageBuilder *builder, bool isMod, + bool isBroadcaster); // Message in the /live chat for channel going live static void liveMessage(const QString &channelName, From 95044efeedd8315ac700c4242e528d203da36a01 Mon Sep 17 00:00:00 2001 From: pajlada Date: Tue, 3 Aug 2021 09:39:27 +0200 Subject: [PATCH 04/19] Fix filter crash introduced in #3092 (#3110) --- CHANGELOG.md | 2 +- src/controllers/filters/FilterRecord.hpp | 5 ----- src/controllers/filters/FilterSet.hpp | 5 +++-- src/controllers/filters/parser/FilterParser.cpp | 14 ++++---------- src/controllers/filters/parser/FilterParser.hpp | 9 +++++++-- src/widgets/helper/ChannelView.cpp | 2 +- src/widgets/helper/SearchPopup.cpp | 2 +- 7 files changed, 17 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26fedd43b..7c1f4762b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - Minor: Added informative messages for recent-messages API's errors. (#3029) - Minor: Added section with helpful Chatterino-related links to the About page. (#3068) - Minor: Now uses spaces instead of magic Unicode character for sending duplicate messages (#3081) -- Minor: Added `channel.live` filter variable (#3092) +- Minor: Added `channel.live` filter variable (#3092, #3110) - Bugfix: Fixed "smiley" emotes being unable to be "Tabbed" with autocompletion, introduced in v2.3.3. (#3010) - Bugfix: Fixed PubSub not properly trying to resolve pending listens when the pending listens list was larger than 50. (#3037) - Bugfix: Copy buttons in usercard now show properly in light mode (#3057) diff --git a/src/controllers/filters/FilterRecord.hpp b/src/controllers/filters/FilterRecord.hpp index 8d699f828..5f0eb750c 100644 --- a/src/controllers/filters/FilterRecord.hpp +++ b/src/controllers/filters/FilterRecord.hpp @@ -60,11 +60,6 @@ public: return this->parser_->valid(); } - bool filter(const MessagePtr &message) const - { - return this->parser_->execute(message); - } - bool filter(const filterparser::ContextMap &context) const { return this->parser_->execute(context); diff --git a/src/controllers/filters/FilterSet.hpp b/src/controllers/filters/FilterSet.hpp index 52a953e54..687f79964 100644 --- a/src/controllers/filters/FilterSet.hpp +++ b/src/controllers/filters/FilterSet.hpp @@ -36,12 +36,13 @@ public: this->listener_.disconnect(); } - bool filter(const MessagePtr &m) const + bool filter(const MessagePtr &m, ChannelPtr channel) const { if (this->filters_.size() == 0) return true; - filterparser::ContextMap context = filterparser::buildContextMap(m); + filterparser::ContextMap context = + filterparser::buildContextMap(m, channel.get()); for (const auto &f : this->filters_.values()) { if (!f->valid() || !f->filter(context)) diff --git a/src/controllers/filters/parser/FilterParser.cpp b/src/controllers/filters/parser/FilterParser.cpp index 7d5e8ad3b..c4dca050a 100644 --- a/src/controllers/filters/parser/FilterParser.cpp +++ b/src/controllers/filters/parser/FilterParser.cpp @@ -1,12 +1,13 @@ #include "FilterParser.hpp" #include "Application.hpp" +#include "common/Channel.hpp" #include "controllers/filters/parser/Types.hpp" #include "providers/twitch/TwitchIrcServer.hpp" namespace filterparser { -ContextMap buildContextMap(const MessagePtr &m) +ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel) { auto watchingChannel = chatterino::getApp()->twitch.server->watchingChannel.get(); @@ -83,9 +84,8 @@ ContextMap buildContextMap(const MessagePtr &m) }; { using namespace chatterino; - auto channel = getApp()->twitch2->getChannelOrEmpty(m->channelName); - auto *tc = dynamic_cast(channel.get()); - if (!channel->isEmpty() && tc) + auto *tc = dynamic_cast(channel); + if (channel && !channel->isEmpty() && tc) { vars["channel.live"] = tc->isLive(); } @@ -104,12 +104,6 @@ FilterParser::FilterParser(const QString &text) { } -bool FilterParser::execute(const MessagePtr &message) const -{ - auto context = buildContextMap(message); - return this->execute(context); -} - bool FilterParser::execute(const ContextMap &context) const { return this->builtExpression_->execute(context).toBool(); diff --git a/src/controllers/filters/parser/FilterParser.hpp b/src/controllers/filters/parser/FilterParser.hpp index 0c144fae5..70037993e 100644 --- a/src/controllers/filters/parser/FilterParser.hpp +++ b/src/controllers/filters/parser/FilterParser.hpp @@ -3,15 +3,20 @@ #include "controllers/filters/parser/Tokenizer.hpp" #include "controllers/filters/parser/Types.hpp" +namespace chatterino { + +class Channel; + +} // namespace chatterino + namespace filterparser { -ContextMap buildContextMap(const MessagePtr &m); +ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel); class FilterParser { public: FilterParser(const QString &text); - bool execute(const MessagePtr &message) const; bool execute(const ContextMap &context) const; bool valid() const; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 8f04bab29..c28108449 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -754,7 +754,7 @@ bool ChannelView::shouldIncludeMessage(const MessagePtr &m) const m->loginName, Qt::CaseInsensitive) == 0) return true; - return this->channelFilters_->filter(m); + return this->channelFilters_->filter(m, this->channel_); } return true; diff --git a/src/widgets/helper/SearchPopup.cpp b/src/widgets/helper/SearchPopup.cpp index e6f8c8e89..0f87bcf27 100644 --- a/src/widgets/helper/SearchPopup.cpp +++ b/src/widgets/helper/SearchPopup.cpp @@ -44,7 +44,7 @@ ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName, } if (accept && filterSet) - accept = filterSet->filter(message); + accept = filterSet->filter(message, channel); // If all predicates match, add the message to the channel if (accept) From 28dcdb238b31228fc93dcb510dc4fb1418afb4e2 Mon Sep 17 00:00:00 2001 From: pajlada Date: Tue, 3 Aug 2021 17:55:04 +0200 Subject: [PATCH 05/19] Remove JOINs from write connection (#3112) Co-authored-by: zneix --- CHANGELOG.md | 1 + src/providers/irc/AbstractIrcServer.cpp | 13 ------------ src/providers/twitch/TwitchIrcServer.cpp | 27 +++--------------------- 3 files changed, 4 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1f4762b..533ab79b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Major: Newly uploaded Twitch emotes are once again present in emote picker and can be autocompleted with Tab as well. (#2992) - Major: Added the ability to add nicknames for users. (#137, #2981) +- Major: Work on rate-limiting JOINs and PARTs. (#3112) - Minor: Added autocompletion in /whispers for Twitch emotes, Global Bttv/Ffz emotes and emojis. (#2999, #3033) - Minor: Received Twitch messages now use the exact same timestamp (obtained from Twitch's server) for every Chatterino user instead of assuming message timestamp on client's side. (#3021) - Minor: Received IRC messages use `time` message tag for timestamp if it's available. (#3021) diff --git a/src/providers/irc/AbstractIrcServer.cpp b/src/providers/irc/AbstractIrcServer.cpp index be9fc4f88..3dfb657cd 100644 --- a/src/providers/irc/AbstractIrcServer.cpp +++ b/src/providers/irc/AbstractIrcServer.cpp @@ -214,11 +214,6 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName) { this->readConnection_->sendRaw("PART #" + channelName); } - - if (this->writeConnection_ && this->hasSeparateWriteConnection()) - { - this->writeConnection_->sendRaw("PART #" + channelName); - } })); // join irc channel @@ -232,14 +227,6 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName) this->readConnection_->sendRaw("JOIN #" + channelName); } } - - if (this->writeConnection_ && this->hasSeparateWriteConnection()) - { - if (this->readConnection_->isConnected()) - { - this->writeConnection_->sendRaw("JOIN #" + channelName); - } - } } return chan; diff --git a/src/providers/twitch/TwitchIrcServer.cpp b/src/providers/twitch/TwitchIrcServer.cpp index 3aede1400..8a0aa1642 100644 --- a/src/providers/twitch/TwitchIrcServer.cpp +++ b/src/providers/twitch/TwitchIrcServer.cpp @@ -197,34 +197,13 @@ void TwitchIrcServer::writeConnectionMessageReceived( // Below commands enabled through the twitch.tv/commands CAP REQ if (command == "USERSTATE") { - // Received USERSTATE upon PRIVMSGing + // Received USERSTATE upon sending PRIVMSG messages handler.handleUserStateMessage(message); } else if (command == "NOTICE") { - static std::unordered_set readConnectionOnlyIDs{ - "host_on", - "host_off", - "host_target_went_offline", - "emote_only_on", - "emote_only_off", - "slow_on", - "slow_off", - "subs_on", - "subs_off", - "r9k_on", - "r9k_off", - - // Display for user who times someone out. This implies you're a - // moderator, at which point you will be connected to PubSub and receive - // a better message from there. - "timeout_success", - "ban_success", - - // Channel suspended notices - "msg_channel_suspended", - }; - + // List of expected NOTICE messages on write connection + // https://git.kotmisia.pl/Mm2PL/docs/src/branch/master/irc_msg_ids.md#command-results handler.handleNoticeMessage( static_cast(message)); } From 0c5abb81495d630967547b36027f6a3048b505ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Wed, 4 Aug 2021 22:41:27 +0200 Subject: [PATCH 06/19] Deprecated /(un)follow commands and respective usercard action (#3078) /(un)follow commands are marked as deprecated and link to the issue this PR is closing. follow button on the usercard is removed completely Co-authored-by: pajlada Co-authored-by: Felanbird <41973452+Felanbird@users.noreply.github.com> --- CHANGELOG.md | 1 + .../commands/CommandController.cpp | 100 +++--------------- src/providers/twitch/TwitchAccount.cpp | 17 --- src/providers/twitch/TwitchAccount.hpp | 3 - src/providers/twitch/api/Helix.cpp | 63 ----------- src/providers/twitch/api/Helix.hpp | 15 --- src/providers/twitch/api/README.md | 20 ---- src/widgets/dialogs/UserInfoPopup.cpp | 67 ------------ src/widgets/dialogs/UserInfoPopup.hpp | 1 - 9 files changed, 17 insertions(+), 270 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 533ab79b9..baafb17d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unversioned - Major: Newly uploaded Twitch emotes are once again present in emote picker and can be autocompleted with Tab as well. (#2992) +- Major: Deprecated `/(un)follow` commands and (un)following in the usercards as Twitch has removed this feature for 3rd party applications. (#3076, #3078) - Major: Added the ability to add nicknames for users. (#137, #2981) - Major: Work on rate-limiting JOINs and PARTs. (#3112) - Minor: Added autocompletion in /whispers for Twitch emotes, Global Bttv/Ffz emotes and emojis. (#2999, #3033) diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index fcca4a2f9..333c29257 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -373,6 +373,22 @@ void CommandController::initialize(Settings &, Paths &paths) return ""; }); + this->registerCommand("/follow", [](const auto &words, auto channel) { + channel->addMessage(makeSystemMessage( + "Twitch has removed the ability to follow users through " + "third-party applications. For more information, see " + "https://github.com/Chatterino/chatterino2/issues/3076")); + return ""; + }); + + this->registerCommand("/unfollow", [](const auto &words, auto channel) { + channel->addMessage(makeSystemMessage( + "Twitch has removed the ability to unfollow users through " + "third-party applications. For more information, see " + "https://github.com/Chatterino/chatterino2/issues/3076")); + return ""; + }); + /// Supported commands this->registerCommand( @@ -407,90 +423,6 @@ void CommandController::initialize(Settings &, Paths &paths) this->registerCommand("/unblock", unblockLambda); - this->registerCommand("/follow", [](const auto &words, auto channel) { - if (words.size() < 2) - { - channel->addMessage(makeSystemMessage("Usage: /follow [user]")); - return ""; - } - - auto currentUser = getApp()->accounts->twitch.getCurrent(); - - if (currentUser->isAnon()) - { - channel->addMessage( - makeSystemMessage("You must be logged in to follow someone!")); - return ""; - } - - auto target = words.at(1); - - getHelix()->getUserByName( - target, - [currentUser, channel, target](const auto &targetUser) { - getHelix()->followUser( - currentUser->getUserId(), targetUser.id, - [channel, target]() { - channel->addMessage(makeSystemMessage( - "You successfully followed " + target)); - }, - [channel, target]() { - channel->addMessage(makeSystemMessage( - QString("User %1 could not be followed, an unknown " - "error occurred!") - .arg(target))); - }); - }, - [channel, target] { - channel->addMessage( - makeSystemMessage(QString("User %1 could not be followed, " - "no user with that name found!") - .arg(target))); - }); - - return ""; - }); - - this->registerCommand("/unfollow", [](const auto &words, auto channel) { - if (words.size() < 2) - { - channel->addMessage(makeSystemMessage("Usage: /unfollow [user]")); - return ""; - } - - auto currentUser = getApp()->accounts->twitch.getCurrent(); - - if (currentUser->isAnon()) - { - channel->addMessage(makeSystemMessage( - "You must be logged in to unfollow someone!")); - return ""; - } - - auto target = words.at(1); - - getHelix()->getUserByName( - target, - [currentUser, channel, target](const auto &targetUser) { - getHelix()->unfollowUser( - currentUser->getUserId(), targetUser.id, - [channel, target]() { - channel->addMessage(makeSystemMessage( - "You successfully unfollowed " + target)); - }, - [channel, target]() { - channel->addMessage(makeSystemMessage( - "An error occurred while unfollowing " + target)); - }); - }, - [channel, target] { - channel->addMessage(makeSystemMessage( - QString("User %1 could not be followed!").arg(target))); - }); - - return ""; - }); - this->registerCommand("/user", [](const auto &words, auto channel) { if (words.size() < 2) { diff --git a/src/providers/twitch/TwitchAccount.cpp b/src/providers/twitch/TwitchAccount.cpp index 416d5ba61..237ba2df0 100644 --- a/src/providers/twitch/TwitchAccount.cpp +++ b/src/providers/twitch/TwitchAccount.cpp @@ -186,23 +186,6 @@ void TwitchAccount::unblockUser(QString userId, std::function onSuccess, std::move(onFailure)); } -void TwitchAccount::checkFollow(const QString targetUserID, - std::function onFinished) -{ - const auto onResponse = [onFinished](bool following, const auto &record) { - if (!following) - { - onFinished(FollowResult_NotFollowing); - return; - } - - onFinished(FollowResult_Following); - }; - - getHelix()->getUserFollow(this->getUserId(), targetUserID, onResponse, - [] {}); -} - SharedAccessGuard> TwitchAccount::accessBlocks() const { diff --git a/src/providers/twitch/TwitchAccount.hpp b/src/providers/twitch/TwitchAccount.hpp index af56afaf1..36e7d0ad6 100644 --- a/src/providers/twitch/TwitchAccount.hpp +++ b/src/providers/twitch/TwitchAccount.hpp @@ -108,9 +108,6 @@ public: void unblockUser(QString userId, std::function onSuccess, std::function onFailure); - void checkFollow(const QString targetUserID, - std::function onFinished); - SharedAccessGuard> accessBlockedUserIds() const; SharedAccessGuard> accessBlocks() const; diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index 2fe734a53..12b812d71 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -142,25 +142,6 @@ void Helix::getUserFollowers( std::move(failureCallback)); } -void Helix::getUserFollow( - QString userId, QString targetId, - ResultCallback successCallback, - HelixFailureCallback failureCallback) -{ - this->fetchUsersFollows( - std::move(userId), std::move(targetId), - [successCallback](const auto &response) { - if (response.data.empty()) - { - successCallback(false, HelixUsersFollowsRecord()); - return; - } - - successCallback(true, response.data[0]); - }, - std::move(failureCallback)); -} - void Helix::fetchStreams( QStringList userIds, QStringList userLogins, ResultCallback> successCallback, @@ -354,50 +335,6 @@ void Helix::getGameById(QString gameId, failureCallback); } -void Helix::followUser(QString userId, QString targetId, - std::function successCallback, - HelixFailureCallback failureCallback) -{ - QUrlQuery urlQuery; - - urlQuery.addQueryItem("from_id", userId); - urlQuery.addQueryItem("to_id", targetId); - - this->makeRequest("users/follows", urlQuery) - .type(NetworkRequestType::Post) - .onSuccess([successCallback](auto /*result*/) -> Outcome { - successCallback(); - return Success; - }) - .onError([failureCallback](auto /*result*/) { - // TODO: make better xd - failureCallback(); - }) - .execute(); -} - -void Helix::unfollowUser(QString userId, QString targetId, - std::function successCallback, - HelixFailureCallback failureCallback) -{ - QUrlQuery urlQuery; - - urlQuery.addQueryItem("from_id", userId); - urlQuery.addQueryItem("to_id", targetId); - - this->makeRequest("users/follows", urlQuery) - .type(NetworkRequestType::Delete) - .onSuccess([successCallback](auto /*result*/) -> Outcome { - successCallback(); - return Success; - }) - .onError([failureCallback](auto /*result*/) { - // TODO: make better xd - failureCallback(); - }) - .execute(); -} - void Helix::createClip(QString channelId, ResultCallback successCallback, std::function failureCallback, diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp index 0b437765c..7648243a2 100644 --- a/src/providers/twitch/api/Helix.hpp +++ b/src/providers/twitch/api/Helix.hpp @@ -341,11 +341,6 @@ public: ResultCallback successCallback, HelixFailureCallback failureCallback); - void getUserFollow( - QString userId, QString targetId, - ResultCallback successCallback, - HelixFailureCallback failureCallback); - // https://dev.twitch.tv/docs/api/reference#get-streams void fetchStreams(QStringList userIds, QStringList userLogins, ResultCallback> successCallback, @@ -372,16 +367,6 @@ public: void getGameById(QString gameId, ResultCallback successCallback, HelixFailureCallback failureCallback); - // https://dev.twitch.tv/docs/api/reference#create-user-follows - void followUser(QString userId, QString targetId, - std::function successCallback, - HelixFailureCallback failureCallback); - - // https://dev.twitch.tv/docs/api/reference#delete-user-follows - void unfollowUser(QString userId, QString targetlId, - std::function successCallback, - HelixFailureCallback failureCallback); - // https://dev.twitch.tv/docs/api/reference#create-clip void createClip(QString channelId, ResultCallback successCallback, diff --git a/src/providers/twitch/api/README.md b/src/providers/twitch/api/README.md index 15c52cca9..6f1b46a03 100644 --- a/src/providers/twitch/api/README.md +++ b/src/providers/twitch/api/README.md @@ -47,26 +47,6 @@ URL: https://dev.twitch.tv/docs/api/reference#get-streams - `TwitchChannel` to get live status, game, title, and viewer count of a channel - `NotificationController` to provide notifications for channels you might not have open in Chatterino, but are still interested in getting notifications for -### Follow User - -URL: https://dev.twitch.tv/docs/api/reference#create-user-follows -Requires `user:edit:follows` scope - -- We implement this in `providers/twitch/api/Helix.cpp followUser` - Used in: - - `widgets/dialogs/UserInfoPopup.cpp` to follow a user by ticking follow checkbox in usercard - - `controllers/commands/CommandController.cpp` in /follow command - -### Unfollow User - -URL: https://dev.twitch.tv/docs/api/reference#delete-user-follows -Requires `user:edit:follows` scope - -- We implement this in `providers/twitch/api/Helix.cpp unfollowUser` - Used in: - - `widgets/dialogs/UserInfoPopup.cpp` to unfollow a user by unticking follow checkbox in usercard - - `controllers/commands/CommandController.cpp` in /unfollow command - ### Create Clip URL: https://dev.twitch.tv/docs/api/reference#create-clip diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index 1f5c62afe..706ca839d 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -233,7 +233,6 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically, QWidget *parent) { user->addStretch(1); - user.emplace("Follow").assign(&this->ui_.follow); user.emplace("Block").assign(&this->ui_.block); user.emplace("Ignore highlights") .assign(&this->ui_.ignoreHighlights); @@ -403,56 +402,6 @@ void UserInfoPopup::scaleChangedEvent(float /*scale*/) void UserInfoPopup::installEvents() { - std::weak_ptr hack = this->hack_; - - // follow - QObject::connect( - this->ui_.follow, &QCheckBox::stateChanged, - [this](int newState) mutable { - auto currentUser = getApp()->accounts->twitch.getCurrent(); - - const auto reenableFollowCheckbox = [this] { - this->ui_.follow->setEnabled(true); - }; - - if (!this->ui_.follow->isEnabled()) - { - // We received a state update while the checkbox was disabled - // This can only happen from the "check current follow state" call - // The state has been updated to properly reflect the users current follow state - reenableFollowCheckbox(); - return; - } - - switch (newState) - { - case Qt::CheckState::Unchecked: { - this->ui_.follow->setEnabled(false); - getHelix()->unfollowUser(currentUser->getUserId(), - this->userId_, - reenableFollowCheckbox, [] { - // - }); - } - break; - - case Qt::CheckState::PartiallyChecked: { - // We deliberately ignore this state - } - break; - - case Qt::CheckState::Checked: { - this->ui_.follow->setEnabled(false); - getHelix()->followUser(currentUser->getUserId(), - this->userId_, - reenableFollowCheckbox, [] { - // - }); - } - break; - } - }); - std::shared_ptr ignoreNext = std::make_shared(false); // block @@ -616,8 +565,6 @@ void UserInfoPopup::updateLatestMessages() void UserInfoPopup::updateUserData() { - this->ui_.follow->setEnabled(false); - std::weak_ptr hack = this->hack_; auto currentUser = getApp()->accounts->twitch.getCurrent(); @@ -683,19 +630,6 @@ void UserInfoPopup::updateUserData() // on failure }); - // get follow state - currentUser->checkFollow(user.id, [this, hack](auto result) { - if (!hack.lock()) - { - return; - } - if (result != FollowResult_Failed) - { - this->ui_.follow->setChecked(result == FollowResult_Following); - this->ui_.follow->setEnabled(true); - } - }); - // get ignore state bool isIgnoring = false; @@ -772,7 +706,6 @@ void UserInfoPopup::updateUserData() getHelix()->getUserByName(this->userName_, onUserFetched, onUserFetchFailed); - this->ui_.follow->setEnabled(false); this->ui_.block->setEnabled(false); this->ui_.ignoreHighlights->setEnabled(false); } diff --git a/src/widgets/dialogs/UserInfoPopup.hpp b/src/widgets/dialogs/UserInfoPopup.hpp index c0b868b9d..ed195d033 100644 --- a/src/widgets/dialogs/UserInfoPopup.hpp +++ b/src/widgets/dialogs/UserInfoPopup.hpp @@ -59,7 +59,6 @@ private: Label *followageLabel = nullptr; Label *subageLabel = nullptr; - QCheckBox *follow = nullptr; QCheckBox *block = nullptr; QCheckBox *ignoreHighlights = nullptr; From de4f6a9d51ed1cdf43433e8d3d239972a2f5c9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Wed, 4 Aug 2021 23:18:34 +0200 Subject: [PATCH 07/19] Rate limit outgoing JOIN messages (#3115) Co-authored-by: Rasmus Karlsson Co-authored-by: Tal Neoran --- CHANGELOG.md | 2 +- chatterino.pro | 3 ++ src/CMakeLists.txt | 2 ++ src/providers/irc/AbstractIrcServer.cpp | 19 ++++++++-- src/providers/irc/AbstractIrcServer.hpp | 5 +++ src/util/RatelimitBucket.cpp | 45 ++++++++++++++++++++++++ src/util/RatelimitBucket.hpp | 40 +++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/src/RatelimitBucket.cpp | 46 +++++++++++++++++++++++++ 9 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 src/util/RatelimitBucket.cpp create mode 100644 src/util/RatelimitBucket.hpp create mode 100644 tests/src/RatelimitBucket.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index baafb17d9..7d1f45185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ - Major: Newly uploaded Twitch emotes are once again present in emote picker and can be autocompleted with Tab as well. (#2992) - Major: Deprecated `/(un)follow` commands and (un)following in the usercards as Twitch has removed this feature for 3rd party applications. (#3076, #3078) - Major: Added the ability to add nicknames for users. (#137, #2981) -- Major: Work on rate-limiting JOINs and PARTs. (#3112) +- Major: Fixed constant disconnections with more than 20 channels by rate-limiting outgoing JOIN messages. (#3112, #3115) - Minor: Added autocompletion in /whispers for Twitch emotes, Global Bttv/Ffz emotes and emojis. (#2999, #3033) - Minor: Received Twitch messages now use the exact same timestamp (obtained from Twitch's server) for every Chatterino user instead of assuming message timestamp on client's side. (#3021) - Minor: Received IRC messages use `time` message tag for timestamp if it's available. (#3021) diff --git a/chatterino.pro b/chatterino.pro index 633528629..cb5104bb9 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -252,6 +252,7 @@ SOURCES += \ src/util/LayoutHelper.cpp \ src/util/NuulsUploader.cpp \ src/util/RapidjsonHelpers.cpp \ + src/util/RatelimitBucket.cpp \ src/util/SplitCommand.cpp \ src/util/StreamerMode.cpp \ src/util/StreamLink.cpp \ @@ -509,6 +510,7 @@ HEADERS += \ src/util/rangealgorithm.hpp \ src/util/RapidjsonHelpers.hpp \ src/util/RapidJsonSerializeQString.hpp \ + src/util/RatelimitBucket.hpp \ src/util/RemoveScrollAreaBackground.hpp \ src/util/SampleCheerMessages.hpp \ src/util/SampleLinks.hpp \ @@ -583,6 +585,7 @@ HEADERS += \ src/widgets/settingspages/IgnoresPage.hpp \ src/widgets/settingspages/KeyboardSettingsPage.hpp \ src/widgets/settingspages/ModerationPage.hpp \ + src/widgets/settingspages/NicknamesPage.hpp \ src/widgets/settingspages/NotificationPage.hpp \ src/widgets/settingspages/SettingsPage.hpp \ src/widgets/splits/ClosedSplits.hpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 31d7a1a98..024e6ff62 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -292,6 +292,8 @@ set(SOURCE_FILES util/NuulsUploader.hpp util/RapidjsonHelpers.cpp util/RapidjsonHelpers.hpp + util/RatelimitBucket.cpp + util/RatelimitBucket.hpp util/SplitCommand.cpp util/SplitCommand.hpp util/StreamLink.cpp diff --git a/src/providers/irc/AbstractIrcServer.cpp b/src/providers/irc/AbstractIrcServer.cpp index 3dfb657cd..0ccebdec9 100644 --- a/src/providers/irc/AbstractIrcServer.cpp +++ b/src/providers/irc/AbstractIrcServer.cpp @@ -15,6 +15,10 @@ const int RECONNECT_BASE_INTERVAL = 2000; // 60 falloff counter means it will try to reconnect at most every 60*2 seconds const int MAX_FALLOFF_COUNTER = 60; +// Ratelimits for joinBucket_ +const int JOIN_RATELIMIT_BUDGET = 18; +const int JOIN_RATELIMIT_COOLDOWN = 10500; + AbstractIrcServer::AbstractIrcServer() { // Initialize the connections @@ -23,6 +27,17 @@ AbstractIrcServer::AbstractIrcServer() this->writeConnection_->moveToThread( QCoreApplication::instance()->thread()); + // Apply a leaky bucket rate limiting to JOIN messages + auto actuallyJoin = [&](QString message) { + if (!this->channels.contains(message)) + { + return; + } + this->readConnection_->sendRaw("JOIN #" + message); + }; + this->joinBucket_.reset(new RatelimitBucket( + JOIN_RATELIMIT_BUDGET, JOIN_RATELIMIT_COOLDOWN, actuallyJoin, this)); + QObject::connect(this->writeConnection_.get(), &Communi::IrcConnection::messageReceived, this, [this](auto msg) { @@ -224,7 +239,7 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName) { if (this->readConnection_->isConnected()) { - this->readConnection_->sendRaw("JOIN #" + channelName); + this->joinBucket_->send(channelName); } } } @@ -284,7 +299,7 @@ void AbstractIrcServer::onReadConnected(IrcConnection *connection) { if (auto channel = weak.lock()) { - connection->sendRaw("JOIN #" + channel->getName()); + this->joinBucket_->send(channel->getName()); } } diff --git a/src/providers/irc/AbstractIrcServer.hpp b/src/providers/irc/AbstractIrcServer.hpp index 7cfc38f5e..40da3ba20 100644 --- a/src/providers/irc/AbstractIrcServer.hpp +++ b/src/providers/irc/AbstractIrcServer.hpp @@ -8,6 +8,7 @@ #include "common/Common.hpp" #include "providers/irc/IrcConnection2.hpp" +#include "util/RatelimitBucket.hpp" namespace chatterino { @@ -88,6 +89,10 @@ private: QObjectPtr writeConnection_ = nullptr; QObjectPtr readConnection_ = nullptr; + // Our rate limiting bucket for the Twitch join rate limits + // https://dev.twitch.tv/docs/irc/guide#rate-limits + QObjectPtr joinBucket_; + QTimer reconnectTimer_; int falloffCounter_ = 1; diff --git a/src/util/RatelimitBucket.cpp b/src/util/RatelimitBucket.cpp new file mode 100644 index 000000000..c33f3a30a --- /dev/null +++ b/src/util/RatelimitBucket.cpp @@ -0,0 +1,45 @@ +#include "RatelimitBucket.hpp" + +#include + +namespace chatterino { + +RatelimitBucket::RatelimitBucket(int budget, int cooldown, + std::function callback, + QObject *parent) + : QObject(parent) + , budget_(budget) + , cooldown_(cooldown) + , callback_(callback) +{ +} + +void RatelimitBucket::send(QString channel) +{ + this->queue_.append(channel); + + if (this->budget_ > 0) + { + this->handleOne(); + } +} + +void RatelimitBucket::handleOne() +{ + if (queue_.isEmpty()) + { + return; + } + + auto item = queue_.takeFirst(); + + this->budget_--; + callback_(item); + + QTimer::singleShot(cooldown_, this, [this] { + this->budget_++; + this->handleOne(); + }); +} + +} // namespace chatterino diff --git a/src/util/RatelimitBucket.hpp b/src/util/RatelimitBucket.hpp new file mode 100644 index 000000000..89ecdc570 --- /dev/null +++ b/src/util/RatelimitBucket.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace chatterino { + +class RatelimitBucket : public QObject +{ +public: + RatelimitBucket(int budget, int cooldown, + std::function callback, QObject *parent); + + void send(QString channel); + +private: + /** + * @brief budget_ denotes the amount of calls that can be handled before we need to wait for the cooldown + **/ + int budget_; + + /** + * @brief This is the amount of time in milliseconds it takes for one used up budget to be put back into the bucket for use elsewhere + **/ + const int cooldown_; + + std::function callback_; + QList queue_; + + /** + * @brief Run the callback on one entry in the queue. + * + * This will start a timer that runs after cooldown_ milliseconds that + * gives back one "token" to the bucket and calls handleOne again. + **/ + void handleOne(); +}; + +} // namespace chatterino diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e7e2c96b0..74474c2e5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,6 +11,7 @@ set(test_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/ExponentialBackoff.cpp ${CMAKE_CURRENT_LIST_DIR}/src/TwitchAccount.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Helpers.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/RatelimitBucket.cpp ) add_executable(${PROJECT_NAME} ${test_SOURCES}) diff --git a/tests/src/RatelimitBucket.cpp b/tests/src/RatelimitBucket.cpp new file mode 100644 index 000000000..c92a42234 --- /dev/null +++ b/tests/src/RatelimitBucket.cpp @@ -0,0 +1,46 @@ +#include "util/RatelimitBucket.hpp" + +#include +#include +#include +#include + +#include +#include + +using namespace chatterino; + +TEST(RatelimitBucket, BatchTwoParts) +{ + const int cooldown = 100; + int n = 0; + auto cb = [&n](QString msg) { + qDebug() << msg; + ++n; + }; + auto bucket = std::make_unique(5, cooldown, cb, nullptr); + bucket->send("1"); + EXPECT_EQ(n, 1); + + bucket->send("2"); + EXPECT_EQ(n, 2); + + bucket->send("3"); + EXPECT_EQ(n, 3); + + bucket->send("4"); + EXPECT_EQ(n, 4); + + bucket->send("5"); + EXPECT_EQ(n, 5); + + bucket->send("6"); + // Rate limit reached, n will not have changed yet. If we wait for the cooldown to run, n should have changed + EXPECT_EQ(n, 5); + + QCoreApplication::processEvents(); + std::this_thread::sleep_for(std::chrono::milliseconds{cooldown}); + QCoreApplication::processEvents(); + + EXPECT_EQ(n, 6); +} From 4e5170799fb437ff1196d748784907c7d484f42d Mon Sep 17 00:00:00 2001 From: fourtf Date: Thu, 5 Aug 2021 10:44:20 +0200 Subject: [PATCH 08/19] updated version number to v2.3.4 --- CHANGELOG.md | 2 ++ CMakeLists.txt | 2 +- resources/com.chatterino.chatterino.appdata.xml | 2 +- src/common/Version.hpp | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1f45185..6983eba34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unversioned +## 2.3.4 + - Major: Newly uploaded Twitch emotes are once again present in emote picker and can be autocompleted with Tab as well. (#2992) - Major: Deprecated `/(un)follow` commands and (un)following in the usercards as Twitch has removed this feature for 3rd party applications. (#3076, #3078) - Major: Added the ability to add nicknames for users. (#137, #2981) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22fab10c3..5e69412d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/sanitizers-cmake/cmake" ) -project(chatterino VERSION 2.3.3) +project(chatterino VERSION 2.3.4) option(BUILD_APP "Build Chatterino" ON) option(BUILD_TESTS "Build the tests for Chatterino" OFF) diff --git a/resources/com.chatterino.chatterino.appdata.xml b/resources/com.chatterino.chatterino.appdata.xml index a0802b163..5c8314f08 100644 --- a/resources/com.chatterino.chatterino.appdata.xml +++ b/resources/com.chatterino.chatterino.appdata.xml @@ -32,6 +32,6 @@ chatterino - + diff --git a/src/common/Version.hpp b/src/common/Version.hpp index 29c88e21a..1a858c0ea 100644 --- a/src/common/Version.hpp +++ b/src/common/Version.hpp @@ -3,7 +3,7 @@ #include #include -#define CHATTERINO_VERSION "2.3.3" +#define CHATTERINO_VERSION "2.3.4" #if defined(Q_OS_WIN) # define CHATTERINO_OS "win" From 9d90de6b8c2e3f9573400fb0396e77aa48e31112 Mon Sep 17 00:00:00 2001 From: Tal Neoran Date: Sat, 7 Aug 2021 01:04:09 +0300 Subject: [PATCH 09/19] Handle moderation mode and filters in split popup (#3130) --- CHANGELOG.md | 2 ++ src/widgets/splits/Split.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6983eba34..ad4963847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unversioned +- Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130) + ## 2.3.4 - Major: Newly uploaded Twitch emotes are once again present in emote picker and can be autocompleted with Tab as well. (#2992) diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index e601d35ab..2263ee7fc 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -624,8 +624,10 @@ void Split::popup() window.getNotebook().getOrAddSelectedPage())); split->setChannel(this->getIndirectChannel()); - window.getNotebook().getOrAddSelectedPage()->appendSplit(split); + split->setModerationMode(this->getModerationMode()); + split->setFilters(this->getFilters()); + window.getNotebook().getOrAddSelectedPage()->appendSplit(split); window.show(); } From cd7758a28ef430ff92c957d3dee9b13f65b5ef43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Sat, 7 Aug 2021 11:01:22 +0200 Subject: [PATCH 10/19] Ensure we don't attempt to index an empty array (#3122) --- src/providers/twitch/api/Helix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index 12b812d71..7895995d2 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -712,7 +712,7 @@ void Helix::getEmoteSetData(QString emoteSetId, QJsonObject root = result.parseJson(); auto data = root.value("data"); - if (!data.isArray()) + if (!data.isArray() || data.toArray().isEmpty()) { failureCallback(); return Failure; From 5cb1022ccf1bd9e7189600279bac9204949d7057 Mon Sep 17 00:00:00 2001 From: Tal Neoran Date: Sat, 7 Aug 2021 12:30:56 +0300 Subject: [PATCH 11/19] Fix badge highlights using the same color (#3134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł --- CHANGELOG.md | 1 + src/widgets/settingspages/HighlightingPage.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad4963847..f779cc74a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unversioned - Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130) +- Bugfix: Fixed a bug that caused all badge highlights to use the same color. (#3132, #3134) ## 2.3.4 diff --git a/src/widgets/settingspages/HighlightingPage.cpp b/src/widgets/settingspages/HighlightingPage.cpp index d8aa170ca..29c627dba 100644 --- a/src/widgets/settingspages/HighlightingPage.cpp +++ b/src/widgets/settingspages/HighlightingPage.cpp @@ -195,7 +195,7 @@ HighlightingPage::HighlightingPage() } getSettings()->highlightedBadges.append(HighlightBadge{ s->badgeName(), s->displayName(), false, false, "", - ColorProvider::instance().color( + *ColorProvider::instance().color( ColorType::SelfHighlight)}); } }); From 961803debad3c8db8753bca302887d633c8769ad Mon Sep 17 00:00:00 2001 From: nilyt <78598394+nilyt@users.noreply.github.com> Date: Sat, 7 Aug 2021 10:37:02 +0000 Subject: [PATCH 12/19] Allow building against Qt 5.11 (#3105) Co-authored-by: pajlada --- CHANGELOG.md | 1 + chatterino.pro | 2 +- src/widgets/splits/SplitInput.cpp | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f779cc74a..cb22f6b22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Bugfix: Fixed comma appended to username completion when not at the beginning of the message. (#3060) - Bugfix: Fixed bug misplacing chat when zooming on Chrome with Chatterino Native Host extension (#1936) - Bugfix: Channel point redemptions from ignored users are now properly blocked. (#3102) +- Dev: Allow building against Qt 5.11 (#3105) - Dev: Ubuntu packages are now available (#2936) - Dev: Disabled update checker on Flatpak. (#3051) - Dev: Add logging for HTTP requests (#2991) diff --git a/chatterino.pro b/chatterino.pro index cb5104bb9..2586ac790 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -14,7 +14,7 @@ CCACHE_BIN = $$system(which ccache) CONFIG+=ccache } -MINIMUM_REQUIRED_QT_VERSION = 5.12.0 +MINIMUM_REQUIRED_QT_VERSION = 5.11.0 !versionAtLeast(QT_VERSION, $$MINIMUM_REQUIRED_QT_VERSION) { error("You're trying to compile with Qt $$QT_VERSION, but minimum required Qt version is $$MINIMUM_REQUIRED_QT_VERSION") diff --git a/src/widgets/splits/SplitInput.cpp b/src/widgets/splits/SplitInput.cpp index 41aea0132..26888f365 100644 --- a/src/widgets/splits/SplitInput.cpp +++ b/src/widgets/splits/SplitInput.cpp @@ -134,14 +134,18 @@ void SplitInput::themeChangedEvent() QPalette palette, placeholderPalette; palette.setColor(QPalette::WindowText, this->theme->splits.input.text); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) placeholderPalette.setColor( QPalette::PlaceholderText, this->theme->messages.textColors.chatPlaceholder); +#endif this->updateEmoteButton(); this->ui_.textEditLength->setPalette(palette); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) this->ui_.textEdit->setPalette(placeholderPalette); +#endif this->ui_.textEdit->setStyleSheet(this->theme->splits.input.styleSheet); this->ui_.hbox->setMargin( From 54f6e4f9e9ade55a28dd2d35961466323f217ee2 Mon Sep 17 00:00:00 2001 From: pajlada Date: Sat, 7 Aug 2021 13:13:05 +0200 Subject: [PATCH 13/19] Remove twitchemotes.com-related functionality (#3136) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł --- CHANGELOG.md | 1 + src/providers/twitch/TwitchEmotes.cpp | 2 +- src/widgets/helper/ChannelView.cpp | 6 +----- src/widgets/settingspages/AboutPage.cpp | 1 - 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb22f6b22..8021d7f58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unversioned +- Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136) - Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130) - Bugfix: Fixed a bug that caused all badge highlights to use the same color. (#3132, #3134) diff --git a/src/providers/twitch/TwitchEmotes.cpp b/src/providers/twitch/TwitchEmotes.cpp index 38593a03a..9a84bcabf 100644 --- a/src/providers/twitch/TwitchEmotes.cpp +++ b/src/providers/twitch/TwitchEmotes.cpp @@ -57,7 +57,7 @@ EmotePtr TwitchEmotes::getOrCreateEmote(const EmoteId &id, Image::fromUrl(getEmoteLink(id, "3.0"), 0.25), }, Tooltip{name.toHtmlEscaped() + "
Twitch Emote"}, - Url{QString("https://twitchemotes.com/emotes/%1").arg(id.string)}}); + }); } return shared; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index c28108449..064a25cd9 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -108,11 +108,7 @@ namespace { }); }; - if (creatorFlags.has(MessageElementFlag::TwitchEmote)) - { - addPageLink("TwitchEmotes"); - } - else if (creatorFlags.has(MessageElementFlag::BttvEmote)) + if (creatorFlags.has(MessageElementFlag::BttvEmote)) { addPageLink("BTTV"); } diff --git a/src/widgets/settingspages/AboutPage.cpp b/src/widgets/settingspages/AboutPage.cpp index 44033ac6c..ea0c7bd43 100644 --- a/src/widgets/settingspages/AboutPage.cpp +++ b/src/widgets/settingspages/AboutPage.cpp @@ -161,7 +161,6 @@ AboutPage::AboutPage() l.emplace("Google emojis provided by Google")->setOpenExternalLinks(true); l.emplace("Emoji datasource provided by Cal Henderson" "(show license)")->setOpenExternalLinks(true); - l.emplace("Twitch emote data provided by twitchemotes.com through the Chatterino API")->setOpenExternalLinks(true); // clang-format on } From 359db173862adb32eb26ddab20e53fa445b859a6 Mon Sep 17 00:00:00 2001 From: apa420 <17131426+apa420@users.noreply.github.com> Date: Sat, 7 Aug 2021 23:59:25 +0200 Subject: [PATCH 14/19] Added links to discussions when trying to create an issue (#3145) --- .github/ISSUE_TEMPLATE/config.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..8ac1ca177 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Suggestions or feature request + url: https://github.com/chatterino/chatterino2/discussions/categories/ideas + about: Got something you think should change or be added? Search for or start a new discussion! + - name: Help + url: https://github.com/chatterino/chatterino2/discussions/categories/q-a + about: Chatterino2 not working as you'd expect? Not sure it's a bug? Check the Q&A section! From d0d32583a2b0815f7d6786feb584517bb43536ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Sun, 8 Aug 2021 12:37:37 +0200 Subject: [PATCH 15/19] Prepare CMake for Qt6 (#3103) --- CHANGELOG.md | 1 + CMakeLists.txt | 25 ++++++++++++++++--------- src/CMakeLists.txt | 22 +++++++++++----------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8021d7f58..6d864f1d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136) - Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130) - Bugfix: Fixed a bug that caused all badge highlights to use the same color. (#3132, #3134) +- Dev: Renamed CMake's build option `USE_SYSTEM_QT5KEYCHAIN` to `USE_SYSTEM_QTKEYCHAIN`. (#3103) ## 2.3.4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e69412d8..5d8419cc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,11 +13,18 @@ option(BUILD_APP "Build Chatterino" ON) option(BUILD_TESTS "Build the tests for Chatterino" OFF) option(USE_SYSTEM_PAJLADA_SETTINGS "Use system pajlada settings library" OFF) option(USE_SYSTEM_LIBCOMMUNI "Use system communi library" OFF) -option(USE_SYSTEM_QT5KEYCHAIN "Use system Qt5Keychain library" OFF) +option(USE_SYSTEM_QTKEYCHAIN "Use system QtKeychain library" OFF) option(USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) +option(BUILD_WITH_QT6 "Use Qt6 instead of default Qt5" OFF) option(USE_CONAN "Use conan" OFF) +if (BUILD_WITH_QT6) + set(MAJOR_QT_VERSION "6") +else() + set(MAJOR_QT_VERSION "5") +endif() + if (USE_CONAN OR CONAN_EXPORTED) include(${CMAKE_CURRENT_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup(TARGETS NO_OUTPUT_DIRS) @@ -31,7 +38,7 @@ endif () include(${CMAKE_CURRENT_LIST_DIR}/cmake/GIT.cmake) -find_package(Qt5 REQUIRED +find_package(Qt${MAJOR_QT_VERSION} REQUIRED COMPONENTS Core Widgets @@ -72,17 +79,17 @@ endif() # Link QtKeychain statically option(QTKEYCHAIN_STATIC "" ON) -if (USE_SYSTEM_QT5KEYCHAIN) - find_package(Qt5Keychain REQUIRED) +if (USE_SYSTEM_QTKEYCHAIN) + find_package(Qt${MAJOR_QT_VERSION}Keychain REQUIRED) else() - set(QT5KEYCHAIN_ROOT_LIB_FOLDER "${CMAKE_SOURCE_DIR}/lib/qtkeychain") - if (NOT EXISTS "${QT5KEYCHAIN_ROOT_LIB_FOLDER}/CMakeLists.txt") + set(QTKEYCHAIN_ROOT_LIB_FOLDER "${CMAKE_SOURCE_DIR}/lib/qtkeychain") + if (NOT EXISTS "${QTKEYCHAIN_ROOT_LIB_FOLDER}/CMakeLists.txt") message(FATAL_ERROR "Submodules probably not loaded, unable to find lib/qtkeychain/CMakeLists.txt") endif() - add_subdirectory("${QT5KEYCHAIN_ROOT_LIB_FOLDER}" EXCLUDE_FROM_ALL) - if (NOT TARGET qt5keychain) - message(FATAL_ERROR "qt5keychain target was not created :@") + add_subdirectory("${QTKEYCHAIN_ROOT_LIB_FOLDER}" EXCLUDE_FROM_ALL) + if (NOT TARGET qt${MAJOR_QT_VERSION}keychain) + message(FATAL_ERROR "qt${MAJOR_QT_VERSION}keychain target was not created :@") endif() endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 024e6ff62..78015123f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -486,16 +486,16 @@ add_library(${LIBRARY_PROJECT} OBJECT ${SOURCE_FILES}) target_link_libraries(${LIBRARY_PROJECT} PUBLIC - Qt5::Core - Qt5::Widgets - Qt5::Gui - Qt5::Network - Qt5::Multimedia - Qt5::Svg - Qt5::Concurrent + Qt${MAJOR_QT_VERSION}::Core + Qt${MAJOR_QT_VERSION}::Widgets + Qt${MAJOR_QT_VERSION}::Gui + Qt${MAJOR_QT_VERSION}::Network + Qt${MAJOR_QT_VERSION}::Multimedia + Qt${MAJOR_QT_VERSION}::Svg + Qt${MAJOR_QT_VERSION}::Concurrent LibCommuni::LibCommuni - qt5keychain + qt${MAJOR_QT_VERSION}keychain Pajlada::Serialize Pajlada::Settings Pajlada::Signals @@ -524,8 +524,8 @@ if (BUILD_APP) ) if (MSVC) - get_target_property(Qt5_Core_Location Qt5::Core LOCATION) - get_filename_component(QT_BIN_DIR ${Qt5_Core_Location} DIRECTORY) + get_target_property(Qt_Core_Location Qt${MAJOR_QT_VERSION}::Core LOCATION) + get_filename_component(QT_BIN_DIR ${Qt_Core_Location} DIRECTORY) set(WINDEPLOYQT_COMMAND "${QT_BIN_DIR}/windeployqt.exe" $ --release --no-compiler-runtime --no-translations --no-opengl-sw) install(TARGETS ${EXECUTABLE_PROJECT} @@ -582,7 +582,7 @@ target_compile_definitions(${LIBRARY_PROJECT} PUBLIC CHATTERINO_GIT_RELEASE=\"${GIT_RELEASE}\" CHATTERINO_GIT_COMMIT=\"${GIT_COMMIT}\" ) -if (USE_SYSTEM_QT5KEYCHAIN) +if (USE_SYSTEM_QTKEYCHAIN) target_compile_definitions(${LIBRARY_PROJECT} PUBLIC CMAKE_BUILD ) From 6151cd5b058c1623d652800effc088971d66e0a3 Mon Sep 17 00:00:00 2001 From: James Upjohn Date: Sun, 8 Aug 2021 22:59:28 +1200 Subject: [PATCH 16/19] Show system message when reloading subscriber emotes (#3135) Co-authored-by: Rasmus Karlsson --- CHANGELOG.md | 1 + src/providers/twitch/TwitchAccount.cpp | 9 +++++++-- src/providers/twitch/TwitchAccount.hpp | 2 +- src/widgets/splits/Split.cpp | 2 +- src/widgets/splits/SplitHeader.cpp | 7 ++----- src/widgets/splits/SplitHeader.hpp | 1 - 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d864f1d7..397e15c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unversioned - Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136) +- Minor: Display a system message when reloading subscription emotes to match BTTV/FFZ behavior (#3135) - Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130) - Bugfix: Fixed a bug that caused all badge highlights to use the same color. (#3132, #3134) - Dev: Renamed CMake's build option `USE_SYSTEM_QT5KEYCHAIN` to `USE_SYSTEM_QTKEYCHAIN`. (#3103) diff --git a/src/providers/twitch/TwitchAccount.cpp b/src/providers/twitch/TwitchAccount.cpp index 237ba2df0..786a0cbe3 100644 --- a/src/providers/twitch/TwitchAccount.cpp +++ b/src/providers/twitch/TwitchAccount.cpp @@ -198,7 +198,7 @@ SharedAccessGuard> TwitchAccount::accessBlockedUserIds() return this->ignoresUserIds_.accessConst(); } -void TwitchAccount::loadEmotes() +void TwitchAccount::loadEmotes(std::weak_ptr weakChannel) { qCDebug(chatterinoTwitch) << "Loading Twitch emotes for user" << this->getUserName(); @@ -220,9 +220,14 @@ void TwitchAccount::loadEmotes() // TODO(zneix): Once Helix adds Get User Emotes we could remove this hacky solution // For now, this is necessary as Kraken's equivalent doesn't return all emotes // See: https://twitch.uservoice.com/forums/310213-developers/suggestions/43599900 - this->loadUserstateEmotes([=] { + this->loadUserstateEmotes([this, weakChannel] { // Fill up emoteData with emote sets that were returned in a Kraken call, but aren't present in emoteData. this->loadKrakenEmotes(); + if (auto channel = weakChannel.lock(); channel != nullptr) + { + channel->addMessage( + makeSystemMessage("Twitch subscriber emotes reloaded.")); + } }); } diff --git a/src/providers/twitch/TwitchAccount.hpp b/src/providers/twitch/TwitchAccount.hpp index 36e7d0ad6..034912a24 100644 --- a/src/providers/twitch/TwitchAccount.hpp +++ b/src/providers/twitch/TwitchAccount.hpp @@ -111,7 +111,7 @@ public: SharedAccessGuard> accessBlockedUserIds() const; SharedAccessGuard> accessBlocks() const; - void loadEmotes(); + void loadEmotes(std::weak_ptr weakChannel = {}); // loadUserstateEmotes loads emote sets that are part of the USERSTATE emote-sets key // this function makes sure not to load emote sets that have already been loaded void loadUserstateEmotes(std::function callback); diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 2263ee7fc..8c84a1476 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -870,8 +870,8 @@ void Split::showSearch() void Split::reloadChannelAndSubscriberEmotes() { - getApp()->accounts->twitch.getCurrent()->loadEmotes(); auto channel = this->getChannel(); + getApp()->accounts->twitch.getCurrent()->loadEmotes(channel); if (auto twitchChannel = dynamic_cast(channel.get())) { diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index 894391b38..635e3135f 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -916,10 +916,6 @@ void SplitHeader::themeChangedEvent() } } -void SplitHeader::moveSplit() -{ -} - void SplitHeader::reloadChannelEmotes() { auto channel = this->split_->getChannel(); @@ -933,7 +929,8 @@ void SplitHeader::reloadChannelEmotes() void SplitHeader::reloadSubscriberEmotes() { - getApp()->accounts->twitch.getCurrent()->loadEmotes(); + auto channel = this->split_->getChannel(); + getApp()->accounts->twitch.getCurrent()->loadEmotes(channel); } void SplitHeader::reconnect() diff --git a/src/widgets/splits/SplitHeader.hpp b/src/widgets/splits/SplitHeader.hpp index 4e68c717d..dea611ac2 100644 --- a/src/widgets/splits/SplitHeader.hpp +++ b/src/widgets/splits/SplitHeader.hpp @@ -85,7 +85,6 @@ private: std::vector channelConnections_; public slots: - void moveSplit(); void reloadChannelEmotes(); void reloadSubscriberEmotes(); void reconnect(); From 7309fd8668a92b213a109067021efaab38cbe81f Mon Sep 17 00:00:00 2001 From: sando Date: Sun, 8 Aug 2021 21:23:54 +1000 Subject: [PATCH 17/19] Strip leading @ and trailing , from /user and /usercard commands (#3143) Co-authored-by: Rasmus Karlsson --- CHANGELOG.md | 1 + .../commands/CommandController.cpp | 41 ++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 397e15c2c..f7cca4f52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unversioned - Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136) +- Minor: Strip leading @ and trailing , from username in /user and /usercard commands. (#3143) - Minor: Display a system message when reloading subscription emotes to match BTTV/FFZ behavior (#3135) - Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130) - Bugfix: Fixed a bug that caused all badge highlights to use the same color. (#3132, #3134) diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index 333c29257..8e977af9b 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -70,6 +70,32 @@ static const QStringList twitchDefaultCommands{ static const QStringList whisperCommands{"/w", ".w"}; +// stripUserName removes any @ prefix or , suffix to make it more suitable for command use +void stripUserName(QString &userName) +{ + if (userName.startsWith('@')) + { + userName.remove(0, 1); + } + if (userName.endsWith(',')) + { + userName.chop(1); + } +} + +// stripChannelName removes any @ prefix or , suffix to make it more suitable for command use +void stripChannelName(QString &channelName) +{ + if (channelName.startsWith('@') || channelName.startsWith('#')) + { + channelName.remove(0, 1); + } + if (channelName.endsWith(',')) + { + channelName.chop(1); + } +} + void sendWhisperMessage(const QString &text) { // (hemirt) pajlada: "we should not be sending whispers through jtv, but @@ -430,16 +456,17 @@ void CommandController::initialize(Settings &, Paths &paths) makeSystemMessage("Usage /user [user] (channel)")); return ""; } + QString userName = words[1]; + stripUserName(userName); + QString channelName = channel->getName(); + if (words.size() > 2) { channelName = words[2]; - if (channelName[0] == '#') - { - channelName.remove(0, 1); - } + stripChannelName(channelName); } - openTwitchUsercard(channelName, words[1]); + openTwitchUsercard(channelName, userName); return ""; }); @@ -451,10 +478,12 @@ void CommandController::initialize(Settings &, Paths &paths) return ""; } + QString userName = words[1]; + stripUserName(userName); auto *userPopup = new UserInfoPopup( getSettings()->autoCloseUserPopup, static_cast(&(getApp()->windows->getMainWindow()))); - userPopup->setData(words[1], channel); + userPopup->setData(userName, channel); userPopup->move(QCursor::pos()); userPopup->show(); return ""; From d0f817a60bda0011c7811ca63772d8d5cd51b02d Mon Sep 17 00:00:00 2001 From: pajlada Date: Sun, 8 Aug 2021 14:16:30 +0200 Subject: [PATCH 18/19] Add basic benchmark (#3038) * Add basic benchmark * Add basic documentation for how to run and add tests/benchmarks * Update benchmark example output * Add changelog entry Co-authored-by: zneix --- CHANGELOG.md | 1 + CMakeLists.txt | 12 +++++- benchmarks/.clang-format | 35 +++++++++++++++ benchmarks/CMakeLists.txt | 28 ++++++++++++ benchmarks/src/Emojis.cpp | 57 ++++++++++++++++++++++++ benchmarks/src/main.cpp | 18 ++++++++ docs/test-and-benchmark.md | 88 ++++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + 8 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 benchmarks/.clang-format create mode 100644 benchmarks/CMakeLists.txt create mode 100644 benchmarks/src/Emojis.cpp create mode 100644 benchmarks/src/main.cpp create mode 100644 docs/test-and-benchmark.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f7cca4f52..1c62bf8bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130) - Bugfix: Fixed a bug that caused all badge highlights to use the same color. (#3132, #3134) - Dev: Renamed CMake's build option `USE_SYSTEM_QT5KEYCHAIN` to `USE_SYSTEM_QTKEYCHAIN`. (#3103) +- Dev: Add benchmarks that can be compiled with the `BUILD_BENCHMARKS` CMake flag. Off by default. (#3038) ## 2.3.4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d8419cc5..6b5e7a470 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ project(chatterino VERSION 2.3.4) option(BUILD_APP "Build Chatterino" ON) option(BUILD_TESTS "Build the tests for Chatterino" OFF) +option(BUILD_BENCHMARKS "Build the benchmarks for Chatterino" OFF) option(USE_SYSTEM_PAJLADA_SETTINGS "Use system pajlada settings library" OFF) option(USE_SYSTEM_LIBCOMMUNI "Use system communi library" OFF) option(USE_SYSTEM_QTKEYCHAIN "Use system QtKeychain library" OFF) @@ -101,6 +102,11 @@ if (BUILD_TESTS) find_package(GTest REQUIRED) endif () +if (BUILD_BENCHMARKS) + # Include system benchmark (Google Benchmark) + find_package(benchmark REQUIRED) +endif () + find_package(PajladaSerialize REQUIRED) find_package(PajladaSignals REQUIRED) find_package(LRUCache REQUIRED) @@ -118,7 +124,7 @@ endif() set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if (BUILD_TESTS) +if (BUILD_TESTS OR BUILD_BENCHMARKS) add_definitions(-DCHATTERINO_TEST) endif () @@ -129,4 +135,8 @@ if (BUILD_TESTS) add_subdirectory(tests) endif () +if (BUILD_BENCHMARKS) + add_subdirectory(benchmarks) +endif () + feature_summary(WHAT ALL) diff --git a/benchmarks/.clang-format b/benchmarks/.clang-format new file mode 100644 index 000000000..f34c1465b --- /dev/null +++ b/benchmarks/.clang-format @@ -0,0 +1,35 @@ +Language: Cpp + +AccessModifierOffset: -4 +AlignEscapedNewlinesLeft: true +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: false +AlwaysBreakBeforeMultilineStrings: false +BasedOnStyle: Google +BraceWrapping: { + AfterClass: 'true' + AfterControlStatement: 'true' + AfterFunction: 'true' + AfterNamespace: 'false' + BeforeCatch: 'true' + BeforeElse: 'true' +} +BreakBeforeBraces: Custom +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 80 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +DerivePointerBinding: false +FixNamespaceComments: true +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +IndentPPDirectives: AfterHash +IncludeBlocks: Preserve +NamespaceIndentation: Inner +PointerBindsToType: false +SpacesBeforeTrailingComments: 2 +Standard: Auto +ReflowComments: false diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 000000000..6435f1398 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,28 @@ +project(chatterino-benchmark) + +set(benchmark_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/main.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp + # Add your new file above this line! + ) + +add_executable(${PROJECT_NAME} ${benchmark_SOURCES}) +add_sanitizers(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} PRIVATE chatterino-lib) + +target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark) + +target_compile_definitions(${PROJECT_NAME} PRIVATE + CHATTERINO_TEST + ) + +set_target_properties(${PROJECT_NAME} + PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin" + ) diff --git a/benchmarks/src/Emojis.cpp b/benchmarks/src/Emojis.cpp new file mode 100644 index 000000000..7eb5106e3 --- /dev/null +++ b/benchmarks/src/Emojis.cpp @@ -0,0 +1,57 @@ +#include "providers/emoji/Emojis.hpp" + +#include +#include +#include + +using namespace chatterino; + +static void BM_ShortcodeParsing(benchmark::State &state) +{ + Emojis emojis; + + emojis.load(); + + struct TestCase { + QString input; + QString expectedOutput; + }; + + std::vector tests{ + { + // input + "foo :penguin: bar", + // expected output + "foo 🐧 bar", + }, + { + // input + "foo :nonexistantcode: bar", + // expected output + "foo :nonexistantcode: bar", + }, + { + // input + ":male-doctor:", + // expected output + "👨‍⚕️", + }, + }; + + for (auto _ : state) + { + for (const auto &test : tests) + { + auto output = emojis.replaceShortCodes(test.input); + + auto matches = output == test.expectedOutput; + if (!matches && !output.endsWith(QChar(0xFE0F))) + { + // Try to append 0xFE0F if needed + output = output.append(QChar(0xFE0F)); + } + } + } +} + +BENCHMARK(BM_ShortcodeParsing); diff --git a/benchmarks/src/main.cpp b/benchmarks/src/main.cpp new file mode 100644 index 000000000..501b3aa51 --- /dev/null +++ b/benchmarks/src/main.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + ::benchmark::Initialize(&argc, argv); + + QtConcurrent::run([&app] { + ::benchmark::RunSpecifiedBenchmarks(); + + app.exit(0); + }); + + return app.exec(); +} diff --git a/docs/test-and-benchmark.md b/docs/test-and-benchmark.md new file mode 100644 index 000000000..e881db6be --- /dev/null +++ b/docs/test-and-benchmark.md @@ -0,0 +1,88 @@ +# Test and Benchmark + +Chatterino includes a set of unit tests and benchmarks. These can be built using cmake by adding the `-DBUILD_TESTS=On` and `-DBUILD_BENCHMARKS=On` flags respectively. + +## Adding your own test + +1. Create a new file for the file you're adding tests for. If you're creating tests for `src/providers/emoji/Emojis.cpp`, create `tests/src/Emojis.cpp`. +2. Add the newly created file to `tests/CMakeLists.txt` in the `test_SOURCES` variable (see the comment near it) + +See `tests/src/Emojis.cpp` for simple tests you can base your tests off of. + +Read up on http://google.github.io/googletest/primer.html to figure out how GoogleTest works. + +## Building and running tests + +```sh +mkdir build-tests +cd build-tests +cmake -DBUILD_TESTS=On .. +make +./bin/chatterino-test +``` + +### Example output + +``` +[==========] Running 26 tests from 8 test suites. +[----------] Global test environment set-up. +[----------] 2 tests from AccessGuardLocker +[ RUN ] AccessGuardLocker.NonConcurrentUsage +[ OK ] AccessGuardLocker.NonConcurrentUsage (0 ms) +[ RUN ] AccessGuardLocker.ConcurrentUsage +[ OK ] AccessGuardLocker.ConcurrentUsage (686 ms) +[----------] 2 tests from AccessGuardLocker (686 ms total) + +[----------] 4 tests from NetworkCommon +[ RUN ] NetworkCommon.parseHeaderList1 +[ OK ] NetworkCommon.parseHeaderList1 (0 ms) +[ RUN ] NetworkCommon.parseHeaderListTrimmed +[ OK ] NetworkCommon.parseHeaderListTrimmed (0 ms) +[ RUN ] NetworkCommon.parseHeaderListColonInValue +... +[ RUN ] TwitchAccount.NotEnoughForMoreThanOneBatch +[ OK ] TwitchAccount.NotEnoughForMoreThanOneBatch (0 ms) +[ RUN ] TwitchAccount.BatchThreeParts +[ OK ] TwitchAccount.BatchThreeParts (0 ms) +[----------] 3 tests from TwitchAccount (2 ms total) + +[----------] Global test environment tear-down +[==========] 26 tests from 8 test suites ran. (10297 ms total) +[ PASSED ] 26 tests. +``` + +## Adding your own benchmark + +1. Create a new file for the file you're adding benchmark for. If you're creating benchmarks for `src/providers/emoji/Emojis.cpp`, create `benchmarks/src/Emojis.cpp`. +2. Add the newly created file to `benchmarks/CMakeLists.txt` in the `benchmark_SOURCES` variable (see the comment near it) + +See `benchmarks/src/Emojis.cpp` for simple benchmark you can base your benchmarks off of. + +## Building and running benchmarks + +```sh +mkdir build-benchmarks +cd build-benchmarks +cmake -DBUILD_BENCHMARKS=On .. +make +./bin/chatterino-benchmark +``` + +### Example output + +``` +2021-07-18T13:12:11+02:00 +Running ./bin/chatterino-benchmark +Run on (12 X 4000 MHz CPU s) +CPU Caches: + L1 Data 32 KiB (x6) + L1 Instruction 32 KiB (x6) + L2 Unified 256 KiB (x6) + L3 Unified 15360 KiB (x1) +Load Average: 2.86, 3.08, 3.51 +***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. +-------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------- +BM_ShortcodeParsing 2394 ns 2389 ns 278933 +``` diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 74474c2e5..8b1fafe99 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,7 @@ set(test_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/TwitchAccount.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Helpers.cpp ${CMAKE_CURRENT_LIST_DIR}/src/RatelimitBucket.cpp + # Add your new file above this line! ) add_executable(${PROJECT_NAME} ${test_SOURCES}) From a216e11755aaa13968c5104460e631604420edef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Aug 2021 13:18:05 +0000 Subject: [PATCH 19/19] Bump lib/libcommuni from `c613600` to `95f0547` (#3149) Bumps [lib/libcommuni](https://github.com/Chatterino/libcommuni) from `c613600` to `95f0547`. - [Release notes](https://github.com/Chatterino/libcommuni/releases) - [Commits](https://github.com/Chatterino/libcommuni/compare/c613600e6a52e6d3166247a05205cf1c755d4868...95f05478de1623767282d8019ea8f3a4b1178b35) --- updated-dependencies: - dependency-name: lib/libcommuni dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- lib/libcommuni | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libcommuni b/lib/libcommuni index c613600e6..95f05478d 160000 --- a/lib/libcommuni +++ b/lib/libcommuni @@ -1 +1 @@ -Subproject commit c613600e6a52e6d3166247a05205cf1c755d4868 +Subproject commit 95f05478de1623767282d8019ea8f3a4b1178b35