Skip to content

Commit

Permalink
Improved error messaging for Update Channel API (#5429)
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeRYW authored Jun 9, 2024
1 parent b81a947 commit 25284fc
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions mocks/include/mocks/Helix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ class Helix : public IHelix
HelixFailureCallback failureCallback),
(override));

MOCK_METHOD(void, updateChannel,
(QString broadcasterId, QString gameId, QString language,
QString title,
std::function<void(NetworkResult)> successCallback,
HelixFailureCallback failureCallback),
(override));
MOCK_METHOD(
void, updateChannel,
(QString broadcasterId, QString gameId, QString language, QString title,
std::function<void(NetworkResult)> successCallback,
(FailureCallback<HelixUpdateChannelError, QString> failureCallback)),
(override));

MOCK_METHOD(void, manageAutoModMessages,
(QString userID, QString msgID, QString action,
Expand Down
70 changes: 61 additions & 9 deletions src/controllers/commands/builtin/twitch/UpdateChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -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 "";
Expand Down Expand Up @@ -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}] {
Expand Down
66 changes: 61 additions & 5 deletions src/providers/twitch/api/Helix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void(NetworkResult)> successCallback,
HelixFailureCallback failureCallback)
void Helix::updateChannel(
QString broadcasterId, QString gameId, QString language, QString title,
std::function<void(NetworkResult)> successCallback,
FailureCallback<HelixUpdateChannelError, QString> failureCallback)
{
using Error = HelixUpdateChannelError;

QUrlQuery urlQuery;
auto obj = QJsonObject();
if (!gameId.isEmpty())
Expand Down Expand Up @@ -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();
}
Expand Down
18 changes: 16 additions & 2 deletions src/providers/twitch/api/Helix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -862,7 +875,7 @@ class IHelix
virtual void updateChannel(
QString broadcasterId, QString gameId, QString language, QString title,
std::function<void(NetworkResult)> successCallback,
HelixFailureCallback failureCallback) = 0;
FailureCallback<HelixUpdateChannelError, QString> failureCallback) = 0;

// https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages
virtual void manageAutoModMessages(
Expand Down Expand Up @@ -1183,7 +1196,8 @@ class Helix final : public IHelix
void updateChannel(QString broadcasterId, QString gameId, QString language,
QString title,
std::function<void(NetworkResult)> successCallback,
HelixFailureCallback failureCallback) final;
FailureCallback<HelixUpdateChannelError, QString>
failureCallback) final;

// https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages
void manageAutoModMessages(
Expand Down

0 comments on commit 25284fc

Please sign in to comment.