From a275a1793a02fac09b21bf459224223a12c729ee Mon Sep 17 00:00:00 2001 From: Aiden Date: Sat, 1 Oct 2022 16:10:06 +0100 Subject: [PATCH] Migrate /unban and /untimeout to Helix API (#4026) Co-authored-by: Rasmus Karlsson --- CHANGELOG.md | 2 + .../commands/CommandController.cpp | 141 ++++++++++++++++++ src/providers/twitch/api/Helix.cpp | 112 ++++++++++++++ src/providers/twitch/api/Helix.hpp | 31 ++++ tests/src/HighlightController.cpp | 14 +- 5 files changed, 297 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 710e734f3..72faba7e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ - Minor: Migrated /unmod command to Helix API. (#4001) - Minor: Migrated /vip command to Helix API. (#4010) - Minor: Migrated /unvip command to Helix API. (#4025) +- Minor: Migrated /untimeout to Helix API. (#4026) +- Minor: Migrated /unban to Helix API. (#4026) - Bugfix: Connection to Twitch PubSub now recovers more reliably. (#3643, #3716) - Bugfix: Fixed a crash that can occur when closing and quickly reopening a split, then running a command. (#3852) - Bugfix: Fixed a crash that can occur when changing channels. (#3799) diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index 8202e6c40..ba1b87fad 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -1819,6 +1819,147 @@ void CommandController::initialize(Settings &, Paths &paths) return ""; }); + + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + auto unbanLambda = [](auto words, auto channel) { + auto commandName = words.at(0).toLower(); + if (words.size() < 2) + { + channel->addMessage(makeSystemMessage( + QString("Usage: \"%1 \" - Removes a ban on a user.") + .arg(commandName))); + return ""; + } + + auto currentUser = getApp()->accounts->twitch.getCurrent(); + if (currentUser->isAnon()) + { + channel->addMessage( + makeSystemMessage("You must be logged in to unban someone!")); + return ""; + } + + auto *twitchChannel = dynamic_cast(channel.get()); + if (twitchChannel == nullptr) + { + channel->addMessage(makeSystemMessage( + QString("The %1 command only works in Twitch channels") + .arg(commandName))); + return ""; + } + + auto target = words.at(1); + stripChannelName(target); + + getHelix()->getUserByName( + target, + [channel, currentUser, twitchChannel, + target](const auto &targetUser) { + getHelix()->unbanUser( + twitchChannel->roomId(), currentUser->getUserId(), + targetUser.id, + [] { + // No response for unbans, they're emitted over pubsub/IRC instead + }, + [channel, target, targetUser](auto error, auto message) { + using Error = HelixUnbanUserError; + + QString errorMessage = + QString("Failed to unban user - "); + + switch (error) + { + case Error::ConflictingOperation: { + errorMessage += + "There was a conflicting ban operation on " + "this user. Please try again."; + } + break; + + case Error::Forwarded: { + errorMessage += message; + } + break; + + case Error::Ratelimited: { + errorMessage += + "You are being ratelimited by Twitch. Try " + "again in a few seconds."; + } + break; + + case Error::TargetNotBanned: { + // Equivalent IRC error + errorMessage = + QString( + "%1 is not banned from this channel.") + .arg(targetUser.displayName); + } + break; + + case Error::UserMissingScope: { + // TODO(pajlada): Phrase MISSING_REQUIRED_SCOPE + errorMessage += "Missing required scope. " + "Re-login with your " + "account and try again."; + } + break; + + case Error::UserNotAuthorized: { + // TODO(pajlada): Phrase MISSING_PERMISSION + errorMessage += "You don't have permission to " + "perform that action."; + } + break; + + case Error::Unknown: { + errorMessage += + "An unknown error has occurred."; + } + break; + } + + channel->addMessage(makeSystemMessage(errorMessage)); + }); + }, + [channel, target] { + // Equivalent error from IRC + channel->addMessage(makeSystemMessage( + QString("Invalid username: %1").arg(target))); + }); + + return ""; + }; // These changes are from the helix-command-migration/unban-untimeout branch + + this->registerCommand("/unban", [unbanLambda](const QStringList &words, + auto channel) { + return unbanLambda(words, channel); + }); // These changes are from the helix-command-migration/unban-untimeout branch + + this->registerCommand("/untimeout", [unbanLambda](const QStringList &words, + auto channel) { + return unbanLambda(words, channel); + }); // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch + // These changes are from the helix-command-migration/unban-untimeout branch } void CommandController::save() diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index c12cf4d3b..e2c3999d3 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -1335,6 +1335,118 @@ void Helix::removeChannelVIP( .execute(); } +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +void Helix::unbanUser( + QString broadcasterID, QString moderatorID, QString userID, + ResultCallback<> successCallback, + FailureCallback failureCallback) +{ + using Error = HelixUnbanUserError; + + QUrlQuery urlQuery; + + urlQuery.addQueryItem("broadcaster_id", broadcasterID); + urlQuery.addQueryItem("moderator_id", moderatorID); + urlQuery.addQueryItem("user_id", userID); + + this->makeRequest("moderation/bans", urlQuery) + .type(NetworkRequestType::Delete) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + if (result.status() != 204) + { + qCWarning(chatterinoTwitch) + << "Success result for unbanning user was" + << result.status() << "but we only expected it to be 204"; + } + + successCallback(); + return Success; + }) + .onError([failureCallback](auto result) { + auto obj = result.parseJson(); + auto message = obj.value("message").toString(); + + switch (result.status()) + { + case 400: { + if (message.startsWith("The user in the user_id query " + "parameter is not banned", + Qt::CaseInsensitive)) + { + failureCallback(Error::TargetNotBanned, message); + } + else + { + failureCallback(Error::Forwarded, message); + } + } + break; + + case 409: { + failureCallback(Error::ConflictingOperation, message); + } + break; + + case 401: { + if (message.startsWith("Missing scope", + Qt::CaseInsensitive)) + { + // Handle this error specifically because its API error is especially unfriendly + failureCallback(Error::UserMissingScope, message); + } + else if (message.compare("incorrect user authorization", + Qt::CaseInsensitive) == 0 || + message.startsWith("the id in broadcaster_id must " + "match the user id", + Qt::CaseInsensitive)) + { + // This error is particularly ugly, but is the equivalent to a user not having permissions + failureCallback(Error::UserNotAuthorized, message); + } + else + { + failureCallback(Error::Forwarded, message); + } + } + break; + + case 429: { + failureCallback(Error::Ratelimited, message); + } + break; + + default: { + qCDebug(chatterinoTwitch) + << "Unhandled error unbanning user:" << result.status() + << result.getData() << obj; + failureCallback(Error::Unknown, message); + } + break; + } + }) + .execute(); +} // These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch +// These changes are from the helix-command-migration/unban-untimeout branch + NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery) { assert(!url.startsWith("/")); diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp index 20526cd37..3bc6af36f 100644 --- a/src/providers/twitch/api/Helix.hpp +++ b/src/providers/twitch/api/Helix.hpp @@ -401,6 +401,19 @@ enum class HelixRemoveChannelVIPError { Forwarded, }; +// These changes are from the helix-command-migration/unban-untimeout branch +enum class HelixUnbanUserError { + Unknown, + UserMissingScope, + UserNotAuthorized, + Ratelimited, + ConflictingOperation, + TargetNotBanned, + + // The error message is forwarded directly from the Twitch API + Forwarded, +}; // These changes are from the helix-command-migration/unban-untimeout branch + class IHelix { public: @@ -567,6 +580,15 @@ public: FailureCallback failureCallback) = 0; + // These changes are from the helix-command-migration/unban-untimeout branch + // https://dev.twitch.tv/docs/api/reference#unban-user + // These changes are from the helix-command-migration/unban-untimeout branch + virtual void unbanUser( + QString broadcasterID, QString moderatorID, QString userID, + ResultCallback<> successCallback, + FailureCallback failureCallback) = 0; + // These changes are from the helix-command-migration/unban-untimeout branch + virtual void update(QString clientId, QString oauthToken) = 0; }; @@ -727,6 +749,15 @@ public: FailureCallback failureCallback) final; + // These changes are from the helix-command-migration/unban-untimeout branch + // https://dev.twitch.tv/docs/api/reference#unban-user + // These changes are from the helix-command-migration/unban-untimeout branch + void unbanUser( + QString broadcasterID, QString moderatorID, QString userID, + ResultCallback<> successCallback, + FailureCallback failureCallback) final; + // These changes are from the helix-command-migration/unban-untimeout branch + void update(QString clientId, QString oauthToken) final; static void initialize(); diff --git a/tests/src/HighlightController.cpp b/tests/src/HighlightController.cpp index 2f07776b3..f818ea7a3 100644 --- a/tests/src/HighlightController.cpp +++ b/tests/src/HighlightController.cpp @@ -257,12 +257,20 @@ public: (FailureCallback failureCallback)), (override)); + // The extra parenthesis around the failure callback is because its type contains a comma + MOCK_METHOD(void, removeChannelVIP, + (QString broadcasterID, QString userID, + ResultCallback<> successCallback, + (FailureCallback + failureCallback)), + (override)); + // The extra parenthesis around the failure callback is because its type contains a comma MOCK_METHOD( - void, removeChannelVIP, - (QString broadcasterID, QString userID, + void, unbanUser, + (QString broadcasterID, QString moderatorID, QString userID, ResultCallback<> successCallback, - (FailureCallback failureCallback)), + (FailureCallback failureCallback)), (override)); MOCK_METHOD(void, update, (QString clientId, QString oauthToken),