From 83bdb6e6929f16e9472835cf1010c45b730c9ae7 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 9 Sep 2023 13:00:13 +0200 Subject: [PATCH] Migrate to the new Get Channel Followers Helix endpoint, fixing follower count not showing up in usercards Fixes #4808 --- CHANGELOG.md | 1 + mocks/include/mocks/Helix.hpp | 17 ++---- src/providers/twitch/api/Helix.cpp | 49 +++++++--------- src/providers/twitch/api/Helix.hpp | 84 +++++++++------------------ src/widgets/dialogs/UserInfoPopup.cpp | 7 ++- 5 files changed, 60 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 200326e1709..98278aa5666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unversioned +- Minor: Migrate to the new Get Channel Followers Helix endpoint, fixing follower count not showing up in usercards. (#4809) - Bugfix: Fixed a data race when disconnecting from Twitch PubSub. (#4771) - Bugfix: Fixed `/shoutout` command not working with usernames starting with @'s (e.g. `/shoutout @forsen`). (#4800) - Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791) diff --git a/mocks/include/mocks/Helix.hpp b/mocks/include/mocks/Helix.hpp index 3b01c5b0dd6..4a64bf8c4fc 100644 --- a/mocks/include/mocks/Helix.hpp +++ b/mocks/include/mocks/Helix.hpp @@ -31,17 +31,12 @@ class Helix : public IHelix HelixFailureCallback failureCallback), (override)); - MOCK_METHOD(void, fetchUsersFollows, - (QString fromId, QString toId, - ResultCallback successCallback, - HelixFailureCallback failureCallback), - (override)); - - MOCK_METHOD(void, getUserFollowers, - (QString userId, - ResultCallback successCallback, - HelixFailureCallback failureCallback), - (override)); + MOCK_METHOD( + void, getChannelFollowers, + (QString broadcasterID, + ResultCallback successCallback, + std::function failureCallback), + (override)); MOCK_METHOD(void, fetchStreams, (QStringList userIds, QStringList userLogins, diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp index d190b37956a..48f3ac2564a 100644 --- a/src/providers/twitch/api/Helix.cpp +++ b/src/providers/twitch/api/Helix.cpp @@ -126,52 +126,43 @@ void Helix::getUserById(QString userId, failureCallback); } -void Helix::fetchUsersFollows( - QString fromId, QString toId, - ResultCallback successCallback, - HelixFailureCallback failureCallback) +void Helix::getChannelFollowers( + QString broadcasterID, + ResultCallback successCallback, + std::function failureCallback) { - assert(!fromId.isEmpty() || !toId.isEmpty()); + assert(!broadcasterID.isEmpty()); QUrlQuery urlQuery; - - if (!fromId.isEmpty()) - { - urlQuery.addQueryItem("from_id", fromId); - } - - if (!toId.isEmpty()) - { - urlQuery.addQueryItem("to_id", toId); - } + urlQuery.addQueryItem("broadcaster_id", broadcasterID); // TODO: set on success and on error - this->makeGet("users/follows", urlQuery) + this->makeGet("channels/followers", urlQuery) .onSuccess([successCallback, failureCallback](auto result) -> Outcome { auto root = result.parseJson(); if (root.empty()) { - failureCallback(); + failureCallback("Bad JSON response"); return Failure; } - successCallback(HelixUsersFollowsResponse(root)); + successCallback(HelixGetChannelFollowersResponse(root)); return Success; }) - .onError([failureCallback](auto /*result*/) { - // TODO: make better xd - failureCallback(); + .onError([failureCallback](auto result) { + auto root = result.parseJson(); + if (root.empty()) + { + failureCallback("Unknown error"); + return; + } + + // Forward "message" from Twitch + HelixError error(root); + failureCallback(error.message); }) .execute(); } -void Helix::getUserFollowers( - QString userId, ResultCallback successCallback, - HelixFailureCallback failureCallback) -{ - this->fetchUsersFollows("", std::move(userId), std::move(successCallback), - std::move(failureCallback)); -} - void Helix::fetchStreams( QStringList userIds, QStringList userLogins, ResultCallback> successCallback, diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp index dc85938157a..f65d6e25a2e 100644 --- a/src/providers/twitch/api/Helix.hpp +++ b/src/providers/twitch/api/Helix.hpp @@ -45,44 +45,12 @@ struct HelixUser { } }; -struct HelixUsersFollowsRecord { - QString fromId; - QString fromName; - QString toId; - QString toName; - QString followedAt; // date time object - - HelixUsersFollowsRecord() - : fromId("") - , fromName("") - , toId("") - , toName("") - , followedAt("") - { - } - - explicit HelixUsersFollowsRecord(QJsonObject jsonObject) - : fromId(jsonObject.value("from_id").toString()) - , fromName(jsonObject.value("from_name").toString()) - , toId(jsonObject.value("to_id").toString()) - , toName(jsonObject.value("to_name").toString()) - , followedAt(jsonObject.value("followed_at").toString()) - { - } -}; - -struct HelixUsersFollowsResponse { +struct HelixGetChannelFollowersResponse { int total; - std::vector data; - explicit HelixUsersFollowsResponse(QJsonObject jsonObject) + + explicit HelixGetChannelFollowersResponse(const QJsonObject &jsonObject) : total(jsonObject.value("total").toInt()) { - const auto &jsonData = jsonObject.value("data").toArray(); - std::transform(jsonData.begin(), jsonData.end(), - std::back_inserter(this->data), - [](const QJsonValue &record) { - return HelixUsersFollowsRecord(record.toObject()); - }); } }; @@ -720,6 +688,22 @@ enum class HelixGetGlobalBadgesError { Forwarded, }; +struct HelixError { + /// Text version of the HTTP error that happened (e.g. Bad Request) + QString error; + /// Number version of the HTTP error that happened (e.g. 400) + int status; + /// The error message string + QString message; + + explicit HelixError(const QJsonObject &json) + : error(json["error"].toString()) + , status(json["status"].toInt()) + , message(json["message"].toString()) + { + } +}; + using HelixGetChannelBadgesError = HelixGetGlobalBadgesError; class IHelix @@ -740,16 +724,11 @@ class IHelix ResultCallback successCallback, HelixFailureCallback failureCallback) = 0; - // https://dev.twitch.tv/docs/api/reference#get-users-follows - virtual void fetchUsersFollows( - QString fromId, QString toId, - ResultCallback successCallback, - HelixFailureCallback failureCallback) = 0; - - virtual void getUserFollowers( - QString userId, - ResultCallback successCallback, - HelixFailureCallback failureCallback) = 0; + // https://dev.twitch.tv/docs/api/reference/#get-channel-followers + virtual void getChannelFollowers( + QString broadcasterID, + ResultCallback successCallback, + std::function failureCallback) = 0; // https://dev.twitch.tv/docs/api/reference#get-streams virtual void fetchStreams( @@ -1064,16 +1043,11 @@ class Helix final : public IHelix void getUserById(QString userId, ResultCallback successCallback, HelixFailureCallback failureCallback) final; - // https://dev.twitch.tv/docs/api/reference#get-users-follows - void fetchUsersFollows( - QString fromId, QString toId, - ResultCallback successCallback, - HelixFailureCallback failureCallback) final; - - void getUserFollowers( - QString userId, - ResultCallback successCallback, - HelixFailureCallback failureCallback) final; + // https://dev.twitch.tv/docs/api/reference/#get-channel-followers + void getChannelFollowers( + QString broadcasterID, + ResultCallback successCallback, + std::function failureCallback) final; // https://dev.twitch.tv/docs/api/reference#get-streams void fetchStreams(QStringList userIds, QStringList userLogins, diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index d3d9f952e1b..481c35dacb6 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -817,7 +817,7 @@ void UserInfoPopup::updateUserData() this->loadAvatar(user.profileImageUrl); } - getHelix()->getUserFollowers( + getHelix()->getChannelFollowers( user.id, [this, hack](const auto &followers) { if (!hack.lock()) @@ -827,8 +827,9 @@ void UserInfoPopup::updateUserData() this->ui_.followerCountLabel->setText( TEXT_FOLLOWERS.arg(localizeNumbers(followers.total))); }, - [] { - // on failure + [](const auto &errorMessage) { + qCWarning(chatterinoTwitch) + << "Error getting followers:" << errorMessage; }); // get ignore state