diff --git a/CHANGELOG.md b/CHANGELOG.md index 2163c1a9164..8498b55a69c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Minor: Remove incognito browser support for `iexplore`, because internet explorer is EOL. (#5810) - Minor: When (re-)connecting, visible channels are now joined first. (#5850) - Minor: Added the ability to filter on messages by the author's user ID (example: `author.user_id == "22484632"`). (#5862) +- Minor: Improved error messaging of the `/clip` command. (#5879) - Bugfix: Fixed a potential way to escape the Lua Plugin sandbox. (#5846) - Bugfix: Fixed a crash relating to Lua HTTP. (#5800) - Bugfix: Fixed a crash that could occur on Linux and macOS when clicking "Install" from the update prompt. (#5818) diff --git a/mocks/include/mocks/Helix.hpp b/mocks/include/mocks/Helix.hpp index 02913efed5a..ecc6e84dbde 100644 --- a/mocks/include/mocks/Helix.hpp +++ b/mocks/include/mocks/Helix.hpp @@ -78,7 +78,7 @@ class Helix : public IHelix MOCK_METHOD(void, createClip, (QString channelId, ResultCallback successCallback, - std::function failureCallback, + std::function failureCallback, std::function finallyCallback), (override)); diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 0371fd2953b..a273c1d1ed3 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -67,13 +67,21 @@ namespace { #endif constexpr int CLIP_CREATION_COOLDOWN = 5000; const QString CLIPS_LINK("https://clips.twitch.tv/%1"); + const QString CLIPS_FAILURE_CLIPS_UNAVAILABLE_TEXT( + "Failed to create a clip - clips are temporarily unavailable: %1"); const QString CLIPS_FAILURE_CLIPS_DISABLED_TEXT( - "Failed to create a clip - the streamer has clips disabled entirely or " - "requires a certain subscriber or follower status to create clips."); + "Failed to create a clip - the streamer has clips disabled in their " + "channel."); + const QString CLIPS_FAILURE_CLIPS_RESTRICTED_TEXT( + "Failed to create a clip - the streamer has restricted clip creation " + "to subscribers, or followers of an unknown duration."); + const QString CLIPS_FAILURE_CLIPS_RESTRICTED_CATEGORY_TEXT( + "Failed to create a clip - the streamer has disabled clips while in " + "this category."); const QString CLIPS_FAILURE_NOT_AUTHENTICATED_TEXT( "Failed to create a clip - you need to re-authenticate."); const QString CLIPS_FAILURE_UNKNOWN_ERROR_TEXT( - "Failed to create a clip - an unknown error occurred."); + "Failed to create a clip: %1"); const QString LOGIN_PROMPT_TEXT("Click here to add your account again."); const Link ACCOUNTS_LINK(Link::OpenAccountsPage, QString()); @@ -1771,7 +1779,7 @@ void TwitchChannel::createClip() this->addMessage(builder.release(), MessageContext::Original); }, // failureCallback - [this](auto error) { + [this](auto error, auto errorMessage) { MessageBuilder builder; QString text; builder.message().flags.set(MessageFlag::System); @@ -1780,6 +1788,15 @@ void TwitchChannel::createClip() switch (error) { + case HelixClipError::ClipsUnavailable: { + builder.emplace( + CLIPS_FAILURE_CLIPS_UNAVAILABLE_TEXT.arg(errorMessage), + MessageElementFlag::Text, MessageColor::System); + text = + CLIPS_FAILURE_CLIPS_UNAVAILABLE_TEXT.arg(errorMessage); + } + break; + case HelixClipError::ClipsDisabled: { builder.emplace( CLIPS_FAILURE_CLIPS_DISABLED_TEXT, @@ -1788,6 +1805,22 @@ void TwitchChannel::createClip() } break; + case HelixClipError::ClipsRestricted: { + builder.emplace( + CLIPS_FAILURE_CLIPS_RESTRICTED_TEXT, + MessageElementFlag::Text, MessageColor::System); + text = CLIPS_FAILURE_CLIPS_RESTRICTED_TEXT; + } + break; + + case HelixClipError::ClipsRestrictedCategory: { + builder.emplace( + CLIPS_FAILURE_CLIPS_RESTRICTED_CATEGORY_TEXT, + MessageElementFlag::Text, MessageColor::System); + text = CLIPS_FAILURE_CLIPS_RESTRICTED_CATEGORY_TEXT; + } + break; + case HelixClipError::UserNotAuthenticated: { builder.emplace( CLIPS_FAILURE_NOT_AUTHENTICATED_TEXT, @@ -1807,9 +1840,9 @@ void TwitchChannel::createClip() case HelixClipError::Unknown: default: { builder.emplace( - CLIPS_FAILURE_UNKNOWN_ERROR_TEXT, + CLIPS_FAILURE_UNKNOWN_ERROR_TEXT.arg(errorMessage), MessageElementFlag::Text, MessageColor::System); - text = CLIPS_FAILURE_UNKNOWN_ERROR_TEXT; + text = CLIPS_FAILURE_UNKNOWN_ERROR_TEXT.arg(errorMessage); } break; } diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index 977cbd5f675..223317a790b 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -351,10 +351,10 @@ void Helix::getGameById(QString gameId, failureCallback); } -void Helix::createClip(QString channelId, - ResultCallback successCallback, - std::function failureCallback, - std::function finallyCallback) +void Helix::createClip( + QString channelId, ResultCallback successCallback, + std::function failureCallback, + std::function finallyCallback) { QUrlQuery urlQuery; urlQuery.addQueryItem("broadcaster_id", channelId); @@ -367,7 +367,7 @@ void Helix::createClip(QString channelId, if (!data.isArray()) { - failureCallback(HelixClipError::Unknown); + failureCallback(HelixClipError::Unknown, "No clip was created"); return; } @@ -376,17 +376,45 @@ void Helix::createClip(QString channelId, successCallback(clip); }) .onError([failureCallback](auto result) { + auto obj = result.parseJson(); + auto message = obj.value("message").toString(); switch (result.status().value_or(0)) { case 503: { - // Channel has disabled clip-creation, or channel has made cliops only creatable by followers and the user is not a follower (or subscriber) - failureCallback(HelixClipError::ClipsDisabled); + // We should not necessarily handle this, so the error messaging will use `message` if it exists + failureCallback(HelixClipError::ClipsUnavailable, message); } break; case 401: { // User does not have the required scope to be able to create clips, user must reauthenticate - failureCallback(HelixClipError::UserNotAuthenticated); + failureCallback(HelixClipError::UserNotAuthenticated, + message); + } + break; + + case 403: { + if (message.contains("restricted for this channel")) + { + failureCallback(HelixClipError::ClipsDisabled, message); + } + else if (message.contains("User does not have permissions")) + { + failureCallback(HelixClipError::ClipsRestricted, + message); + } + else if (message.contains("restricted for this category")) + { + failureCallback(HelixClipError::ClipsRestrictedCategory, + message); + } + else + { + qCDebug(chatterinoTwitch) + << "Failed to create a clip: " + << result.formatError() << result.getData(); + failureCallback(HelixClipError::Unknown, message); + } } break; @@ -394,7 +422,7 @@ void Helix::createClip(QString channelId, qCDebug(chatterinoTwitch) << "Failed to create a clip: " << result.formatError() << result.getData(); - failureCallback(HelixClipError::Unknown); + failureCallback(HelixClipError::Unknown, message); } break; } diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp index e62920682a9..7a3c6c6f9de 100644 --- a/src/providers/twitch/api/Helix.hpp +++ b/src/providers/twitch/api/Helix.hpp @@ -474,7 +474,10 @@ enum class HelixAnnouncementColor { enum class HelixClipError { Unknown, + ClipsUnavailable, ClipsDisabled, + ClipsRestricted, + ClipsRestrictedCategory, UserNotAuthenticated, }; @@ -864,10 +867,10 @@ class IHelix HelixFailureCallback failureCallback) = 0; // https://dev.twitch.tv/docs/api/reference#create-clip - virtual void createClip(QString channelId, - ResultCallback successCallback, - std::function failureCallback, - std::function finallyCallback) = 0; + virtual void createClip( + QString channelId, ResultCallback successCallback, + std::function failureCallback, + std::function finallyCallback) = 0; // https://dev.twitch.tv/docs/api/reference#get-channel-information virtual void fetchChannels( @@ -1207,10 +1210,10 @@ class Helix final : public IHelix HelixFailureCallback failureCallback) final; // https://dev.twitch.tv/docs/api/reference#create-clip - void createClip(QString channelId, - ResultCallback successCallback, - std::function failureCallback, - std::function finallyCallback) final; + void createClip( + QString channelId, ResultCallback successCallback, + std::function failureCallback, + std::function finallyCallback) final; // https://dev.twitch.tv/docs/api/reference#get-channel-information void fetchChannels(