diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fc6cf37..4a93c4a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Minor: Added `flags.action` filter variable, allowing you to filter on `/me` messages. (#5397) - Minor: The size of the emote popup is now saved. (#5415) - Minor: Added the ability to duplicate tabs. (#5277) +- Minor: Improved error messages for channel update commands. (#5429) - Minor: Moderators can now see when users are warned. (#5441) - Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426) - Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378) diff --git a/mocks/include/mocks/Helix.hpp b/mocks/include/mocks/Helix.hpp index f53f62dd..0f0c4110 100644 --- a/mocks/include/mocks/Helix.hpp +++ b/mocks/include/mocks/Helix.hpp @@ -119,12 +119,12 @@ public: HelixFailureCallback failureCallback), (override)); - MOCK_METHOD(void, updateChannel, - (QString broadcasterId, QString gameId, QString language, - QString title, - std::function successCallback, - HelixFailureCallback failureCallback), - (override)); + MOCK_METHOD( + void, updateChannel, + (QString broadcasterId, QString gameId, QString language, QString title, + std::function successCallback, + (FailureCallback failureCallback)), + (override)); MOCK_METHOD(void, manageAutoModMessages, (QString userID, QString msgID, QString action, diff --git a/src/controllers/commands/builtin/twitch/UpdateChannel.cpp b/src/controllers/commands/builtin/twitch/UpdateChannel.cpp index bcda86f3..bb026bf4 100644 --- a/src/controllers/commands/builtin/twitch/UpdateChannel.cpp +++ b/src/controllers/commands/builtin/twitch/UpdateChannel.cpp @@ -7,6 +7,58 @@ #include "providers/twitch/api/Helix.hpp" #include "providers/twitch/TwitchChannel.hpp" +namespace { + +using namespace chatterino; + +QString formatUpdateChannelError(const char *updateType, + HelixUpdateChannelError error, + const QString &message) +{ + using Error = HelixUpdateChannelError; + + QString errorMessage = QString("Failed to set %1 - ").arg(updateType); + + switch (error) + { + case Error::UserMissingScope: { + errorMessage += "Missing required scope. " + "Re-login with your " + "account and try again."; + } + break; + + case Error::UserNotAuthorized: { + errorMessage += QString("You must be the broadcaster " + "to set the %1.") + .arg(updateType); + } + break; + + case Error::Ratelimited: { + errorMessage += "You are being ratelimited by Twitch. Try " + "again in a few seconds."; + } + break; + + case Error::Forwarded: { + errorMessage += message; + } + break; + + case Error::Unknown: + default: { + errorMessage += + QString("An unknown error has occurred (%1).").arg(message); + } + break; + } + + return errorMessage; +} + +} // namespace + namespace chatterino::commands { QString setTitle(const CommandContext &ctx) @@ -30,8 +82,8 @@ QString setTitle(const CommandContext &ctx) return ""; } - auto status = ctx.twitchChannel->accessStreamStatus(); auto title = ctx.words.mid(1).join(" "); + getHelix()->updateChannel( ctx.twitchChannel->roomId(), "", "", title, [channel{ctx.channel}, title](const auto &result) { @@ -40,10 +92,10 @@ QString setTitle(const CommandContext &ctx) channel->addMessage( makeSystemMessage(QString("Updated title to %1").arg(title))); }, - [channel{ctx.channel}] { - channel->addMessage( - makeSystemMessage("Title update failed! Are you " - "missing the required scope?")); + [channel{ctx.channel}](auto error, auto message) { + auto errorMessage = + formatUpdateChannelError("title", error, message); + channel->addMessage(makeSystemMessage(errorMessage)); }); return ""; @@ -105,10 +157,10 @@ QString setGame(const CommandContext &ctx) channel->addMessage(makeSystemMessage( QString("Updated game to %1").arg(matchedGame.name))); }, - [channel] { - channel->addMessage( - makeSystemMessage("Game update failed! Are you " - "missing the required scope?")); + [channel](auto error, auto message) { + auto errorMessage = + formatUpdateChannelError("game", error, message); + channel->addMessage(makeSystemMessage(errorMessage)); }); }, [channel{ctx.channel}] { diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index daf17021..460acfdb 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -613,11 +613,13 @@ void Helix::unblockUser(QString targetUserId, const QObject *caller, .execute(); } -void Helix::updateChannel(QString broadcasterId, QString gameId, - QString language, QString title, - std::function successCallback, - HelixFailureCallback failureCallback) +void Helix::updateChannel( + QString broadcasterId, QString gameId, QString language, QString title, + std::function successCallback, + FailureCallback failureCallback) { + using Error = HelixUpdateChannelError; + QUrlQuery urlQuery; auto obj = QJsonObject(); if (!gameId.isEmpty()) @@ -646,7 +648,61 @@ void Helix::updateChannel(QString broadcasterId, QString gameId, successCallback(result); }) .onError([failureCallback](NetworkResult result) { - failureCallback(); + if (!result.status()) + { + failureCallback(Error::Unknown, result.formatError()); + return; + } + + auto obj = result.parseJson(); + auto message = obj.value("message").toString(); + + switch (*result.status()) + { + case 401: { + if (message.startsWith("Missing scope", + Qt::CaseInsensitive)) + { + failureCallback(Error::UserMissingScope, message); + } + else if (message.compare( + "The ID in broadcaster_id must match the user " + "ID found in the request's OAuth token.", + Qt::CaseInsensitive) == 0) + { + failureCallback(Error::UserNotAuthorized, message); + } + else + { + failureCallback(Error::Forwarded, message); + } + } + break; + + case 400: + case 403: { + failureCallback(Error::Forwarded, message); + } + break; + + case 429: { + failureCallback(Error::Ratelimited, message); + } + break; + + case 500: { + failureCallback(Error::Unknown, message); + } + break; + + default: { + qCDebug(chatterinoTwitch) + << "Helix update channel, unhandled error data:" + << result.formatError() << result.getData() << obj; + failureCallback(Error::Unknown, message); + } + break; + } }) .execute(); } diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp index 0d5412ba..c24e44bd 100644 --- a/src/providers/twitch/api/Helix.hpp +++ b/src/providers/twitch/api/Helix.hpp @@ -595,6 +595,19 @@ enum class HelixUpdateChatSettingsError { // update chat settings Forwarded, }; // update chat settings +/// Error type for Helix::updateChannel +/// +/// Used in the /settitle and /setgame commands +enum class HelixUpdateChannelError { + Unknown, + UserMissingScope, + UserNotAuthorized, + Ratelimited, + + // The error message is forwarded directly from the Twitch API + Forwarded, +}; + enum class HelixBanUserError { // /timeout, /ban Unknown, UserMissingScope, @@ -862,7 +875,7 @@ public: virtual void updateChannel( QString broadcasterId, QString gameId, QString language, QString title, std::function successCallback, - HelixFailureCallback failureCallback) = 0; + FailureCallback failureCallback) = 0; // https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages virtual void manageAutoModMessages( @@ -1183,7 +1196,8 @@ public: void updateChannel(QString broadcasterId, QString gameId, QString language, QString title, std::function successCallback, - HelixFailureCallback failureCallback) final; + FailureCallback + failureCallback) final; // https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages void manageAutoModMessages(