Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into chatterino7
Browse files Browse the repository at this point in the history
Now we're on commit de4f6a9; Changes from upstream we've pulled:

- Major: Fixed constant disconnections with more than 20 channels by rate-limiting outgoing JOIN messages. (Chatterino#3112, Chatterino#3115)
  • Loading branch information
zneix committed Aug 4, 2021
2 parents 10af9b3 + de4f6a9 commit 5afdaeb
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 273 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
## 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)
- 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)
Expand Down
3 changes: 3 additions & 0 deletions chatterino.pro
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,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 \
Expand Down Expand Up @@ -515,6 +516,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 \
Expand Down Expand Up @@ -589,6 +591,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 \
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,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
Expand Down
100 changes: 16 additions & 84 deletions src/controllers/commands/CommandController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
{
Expand Down
19 changes: 17 additions & 2 deletions src/providers/irc/AbstractIrcServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -224,7 +239,7 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
{
if (this->readConnection_->isConnected())
{
this->readConnection_->sendRaw("JOIN #" + channelName);
this->joinBucket_->send(channelName);
}
}
}
Expand Down Expand Up @@ -284,7 +299,7 @@ void AbstractIrcServer::onReadConnected(IrcConnection *connection)
{
if (auto channel = weak.lock())
{
connection->sendRaw("JOIN #" + channel->getName());
this->joinBucket_->send(channel->getName());
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/providers/irc/AbstractIrcServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "common/Common.hpp"
#include "providers/irc/IrcConnection2.hpp"
#include "util/RatelimitBucket.hpp"

namespace chatterino {

Expand Down Expand Up @@ -88,6 +89,10 @@ class AbstractIrcServer : public QObject
QObjectPtr<IrcConnection> writeConnection_ = nullptr;
QObjectPtr<IrcConnection> readConnection_ = nullptr;

// Our rate limiting bucket for the Twitch join rate limits
// https://dev.twitch.tv/docs/irc/guide#rate-limits
QObjectPtr<RatelimitBucket> joinBucket_;

QTimer reconnectTimer_;
int falloffCounter_ = 1;

Expand Down
17 changes: 0 additions & 17 deletions src/providers/twitch/TwitchAccount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,23 +186,6 @@ void TwitchAccount::unblockUser(QString userId, std::function<void()> onSuccess,
std::move(onFailure));
}

void TwitchAccount::checkFollow(const QString targetUserID,
std::function<void(FollowResult)> 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<const std::set<TwitchUser>> TwitchAccount::accessBlocks()
const
{
Expand Down
3 changes: 0 additions & 3 deletions src/providers/twitch/TwitchAccount.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,6 @@ class TwitchAccount : public Account
void unblockUser(QString userId, std::function<void()> onSuccess,
std::function<void()> onFailure);

void checkFollow(const QString targetUserID,
std::function<void(FollowResult)> onFinished);

SharedAccessGuard<const std::set<QString>> accessBlockedUserIds() const;
SharedAccessGuard<const std::set<TwitchUser>> accessBlocks() const;

Expand Down
63 changes: 0 additions & 63 deletions src/providers/twitch/api/Helix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,25 +142,6 @@ void Helix::getUserFollowers(
std::move(failureCallback));
}

void Helix::getUserFollow(
QString userId, QString targetId,
ResultCallback<bool, HelixUsersFollowsRecord> 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<std::vector<HelixStream>> successCallback,
Expand Down Expand Up @@ -354,50 +335,6 @@ void Helix::getGameById(QString gameId,
failureCallback);
}

void Helix::followUser(QString userId, QString targetId,
std::function<void()> 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<void()> 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<HelixClip> successCallback,
std::function<void(HelixClipError)> failureCallback,
Expand Down
15 changes: 0 additions & 15 deletions src/providers/twitch/api/Helix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,11 +341,6 @@ class Helix final : boost::noncopyable
ResultCallback<HelixUsersFollowsResponse> successCallback,
HelixFailureCallback failureCallback);

void getUserFollow(
QString userId, QString targetId,
ResultCallback<bool, HelixUsersFollowsRecord> successCallback,
HelixFailureCallback failureCallback);

// https://dev.twitch.tv/docs/api/reference#get-streams
void fetchStreams(QStringList userIds, QStringList userLogins,
ResultCallback<std::vector<HelixStream>> successCallback,
Expand All @@ -372,16 +367,6 @@ class Helix final : boost::noncopyable
void getGameById(QString gameId, ResultCallback<HelixGame> successCallback,
HelixFailureCallback failureCallback);

// https://dev.twitch.tv/docs/api/reference#create-user-follows
void followUser(QString userId, QString targetId,
std::function<void()> successCallback,
HelixFailureCallback failureCallback);

// https://dev.twitch.tv/docs/api/reference#delete-user-follows
void unfollowUser(QString userId, QString targetlId,
std::function<void()> successCallback,
HelixFailureCallback failureCallback);

// https://dev.twitch.tv/docs/api/reference#create-clip
void createClip(QString channelId,
ResultCallback<HelixClip> successCallback,
Expand Down
20 changes: 0 additions & 20 deletions src/providers/twitch/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

1 comment on commit 5afdaeb

@zneix
Copy link
Author

@zneix zneix commented on 5afdaeb Aug 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to mention this change in the commit message:

Please sign in to comment.