From af4e3f50625847f2e1e9930da61e7ce395c92315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Sun, 21 Mar 2021 15:19:49 +0100 Subject: [PATCH] Refactored loading emotes and emotesets (#2539) Co-authored-by: Rasmus Karlsson --- src/providers/twitch/TwitchAccount.cpp | 186 ++++++++++--------------- src/providers/twitch/TwitchAccount.hpp | 26 +++- src/providers/twitch/api/Kraken.cpp | 22 +++ src/providers/twitch/api/Kraken.hpp | 25 +++- 4 files changed, 140 insertions(+), 119 deletions(-) diff --git a/src/providers/twitch/TwitchAccount.cpp b/src/providers/twitch/TwitchAccount.cpp index 430868359..3f889f1ce 100644 --- a/src/providers/twitch/TwitchAccount.cpp +++ b/src/providers/twitch/TwitchAccount.cpp @@ -11,6 +11,7 @@ #include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchUser.hpp" #include "providers/twitch/api/Helix.hpp" +#include "providers/twitch/api/Kraken.hpp" #include "singletons/Emotes.hpp" #include "util/RapidjsonHelpers.hpp" @@ -176,39 +177,70 @@ void TwitchAccount::loadEmotes() qCDebug(chatterinoTwitch) << "Loading Twitch emotes for user" << this->getUserName(); - const auto &clientID = this->getOAuthClient(); - const auto &oauthToken = this->getOAuthToken(); - - if (clientID.isEmpty() || oauthToken.isEmpty()) + if (this->getOAuthClient().isEmpty() || this->getOAuthToken().isEmpty()) { qCDebug(chatterinoTwitch) << "Missing Client ID or OAuth token"; return; } - QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + - "/emotes"); + getKraken()->getUserEmotes( + this, + [this](KrakenEmoteSets data) { + // clear emote data + auto emoteData = this->emotes_.access(); + emoteData->emoteSets.clear(); + emoteData->allEmoteNames.clear(); - NetworkRequest(url) - - .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) - .onError([=](NetworkResult result) { - qCWarning(chatterinoTwitch) - << "[TwitchAccount::loadEmotes] Error" << result.status(); - if (result.status() == 203) + // no emotes available + if (data.emoteSets.isEmpty()) { - // onFinished(FollowResult_NotFollowing); + qCWarning(chatterinoTwitch) + << "\"emoticon_sets\" either empty or not present in " + "Kraken::getUserEmotes response"; + return; } - else - { - // onFinished(FollowResult_Failed); - } - }) - .onSuccess([=](auto result) -> Outcome { - this->parseEmotes(result.parseRapidJson()); - return Success; - }) - .execute(); + for (auto emoteSetIt = data.emoteSets.begin(); + emoteSetIt != data.emoteSets.end(); ++emoteSetIt) + { + auto emoteSet = std::make_shared(); + + emoteSet->key = emoteSetIt.key(); + this->loadEmoteSetData(emoteSet); + + for (const auto emoteArrObj : emoteSetIt.value().toArray()) + { + if (!emoteArrObj.isObject()) + { + qCWarning(chatterinoTwitch) + << QString("Emote value from set %1 was invalid") + .arg(emoteSet->key); + } + KrakenEmote krakenEmote(emoteArrObj.toObject()); + + auto id = EmoteId{krakenEmote.id}; + auto code = EmoteName{krakenEmote.code}; + + auto cleanCode = + EmoteName{TwitchEmotes::cleanUpEmoteCode(code)}; + emoteSet->emotes.emplace_back(TwitchEmote{id, cleanCode}); + emoteData->allEmoteNames.push_back(cleanCode); + + auto emote = + getApp()->emotes->twitch.getOrCreateEmote(id, code); + emoteData->emotes.emplace(code, emote); + } + + std::sort(emoteSet->emotes.begin(), emoteSet->emotes.end(), + [](const TwitchEmote &l, const TwitchEmote &r) { + return l.name.string < r.name.string; + }); + emoteData->emoteSets.emplace_back(emoteSet); + } + }, + [] { + // request failed + }); } AccessGuard @@ -256,71 +288,6 @@ void TwitchAccount::autoModDeny(const QString msgID) .execute(); } -void TwitchAccount::parseEmotes(const rapidjson::Document &root) -{ - auto emoteData = this->emotes_.access(); - - emoteData->emoteSets.clear(); - emoteData->allEmoteNames.clear(); - - auto emoticonSets = root.FindMember("emoticon_sets"); - if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject()) - { - qCWarning(chatterinoTwitch) - << "No emoticon_sets in load emotes response"; - return; - } - - for (const auto &emoteSetJSON : emoticonSets->value.GetObject()) - { - auto emoteSet = std::make_shared(); - - emoteSet->key = emoteSetJSON.name.GetString(); - - this->loadEmoteSetData(emoteSet); - - for (const rapidjson::Value &emoteJSON : emoteSetJSON.value.GetArray()) - { - if (!emoteJSON.IsObject()) - { - qCWarning(chatterinoTwitch) << "Emote value was invalid"; - return; - } - - uint64_t idNumber; - if (!rj::getSafe(emoteJSON, "id", idNumber)) - { - qCWarning(chatterinoTwitch) << "No ID key found in Emote value"; - return; - } - - QString _code; - if (!rj::getSafe(emoteJSON, "code", _code)) - { - qCWarning(chatterinoTwitch) - << "No code key found in Emote value"; - return; - } - - auto code = EmoteName{_code}; - auto id = EmoteId{QString::number(idNumber)}; - - auto cleanCode = EmoteName{TwitchEmotes::cleanUpEmoteCode(code)}; - emoteSet->emotes.emplace_back(TwitchEmote{id, cleanCode}); - emoteData->allEmoteNames.push_back(cleanCode); - - auto emote = getApp()->emotes->twitch.getOrCreateEmote(id, code); - emoteData->emotes.emplace(code, emote); - } - - std::sort(emoteSet->emotes.begin(), emoteSet->emotes.end(), - [](const TwitchEmote &l, const TwitchEmote &r) { - return l.name.string < r.name.string; - }); - emoteData->emoteSets.emplace_back(emoteSet); - } -}; - void TwitchAccount::loadEmoteSetData(std::shared_ptr emoteSet) { if (!emoteSet) @@ -340,44 +307,35 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr emoteSet) NetworkRequest(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key)) .cache() - .onError([](NetworkResult result) { - qCWarning(chatterinoTwitch) << "Error code" << result.status() - << "while loading emote set data"; - }) - .onSuccess([emoteSet](auto result) -> Outcome { - auto root = result.parseRapidJson(); - if (!root.IsObject()) + .onSuccess([emoteSet](NetworkResult result) -> Outcome { + auto rootOld = result.parseRapidJson(); + auto root = result.parseJson(); + if (root.isEmpty()) { return Failure; } - std::string emoteSetID; - QString channelName; - QString type; - if (!rj::getSafe(root, "channel_name", channelName)) - { - return Failure; - } + TwitchEmoteSetResolverResponse response(root); - if (!rj::getSafe(root, "type", type)) - { - return Failure; - } - - qCDebug(chatterinoTwitch) - << "Loaded twitch emote set data for" << emoteSet->key; - - auto name = channelName; + auto name = response.channelName; name.detach(); name[0] = name[0].toUpper(); emoteSet->text = name; + emoteSet->type = response.type; + emoteSet->channelName = response.channelName; - emoteSet->type = type; - emoteSet->channelName = channelName; + qCDebug(chatterinoTwitch) + << QString("Loaded twitch emote set data for %1") + .arg(emoteSet->key); return Success; }) + .onError([](NetworkResult result) { + qCWarning(chatterinoTwitch) + << QString("Error code %1 while loading emote set data") + .arg(result.status()); + }) .execute(); } diff --git a/src/providers/twitch/TwitchAccount.hpp b/src/providers/twitch/TwitchAccount.hpp index d1101fe46..1524a05a0 100644 --- a/src/providers/twitch/TwitchAccount.hpp +++ b/src/providers/twitch/TwitchAccount.hpp @@ -23,6 +23,31 @@ enum FollowResult { FollowResult_Failed, }; +struct TwitchEmoteSetResolverResponse { + const QString channelName; + const QString channelId; + const QString type; + const int tier; + const bool isCustom; + // Example response: + // { + // "channel_name": "zneix", + // "channel_id": "99631238", + // "type": "", + // "tier": 1, + // "custom": false + // } + + TwitchEmoteSetResolverResponse(QJsonObject jsonObject) + : channelName(jsonObject.value("channel_name").toString()) + , channelId(jsonObject.value("channel_id").toString()) + , type(jsonObject.value("type").toString()) + , tier(jsonObject.value("tier").toInt()) + , isCustom(jsonObject.value("custom").toBool()) + { + } +}; + class TwitchAccount : public Account { public: @@ -91,7 +116,6 @@ public: void autoModDeny(const QString msgID); private: - void parseEmotes(const rapidjson::Document &document); void loadEmoteSetData(std::shared_ptr emoteSet); QString oauthClient_; diff --git a/src/providers/twitch/api/Kraken.cpp b/src/providers/twitch/api/Kraken.cpp index 2f64dd0d4..f906b6fb0 100644 --- a/src/providers/twitch/api/Kraken.cpp +++ b/src/providers/twitch/api/Kraken.cpp @@ -8,6 +8,28 @@ namespace chatterino { static Kraken *instance = nullptr; +void Kraken::getUserEmotes(TwitchAccount *account, + ResultCallback successCallback, + KrakenFailureCallback failureCallback) +{ + this->makeRequest(QString("users/%1/emotes").arg(account->getUserId()), {}) + .authorizeTwitchV5(account->getOAuthClient(), account->getOAuthToken()) + .onSuccess([successCallback, failureCallback](auto result) -> Outcome { + auto data = result.parseJson(); + + KrakenEmoteSets emoteSets(data); + + successCallback(emoteSets); + + return Success; + }) + .onError([failureCallback](NetworkResult /*result*/) { + // TODO: make better xd + failureCallback(); + }) + .execute(); +} + NetworkRequest Kraken::makeRequest(QString url, QUrlQuery urlQuery) { assert(!url.startsWith("/")); diff --git a/src/providers/twitch/api/Kraken.hpp b/src/providers/twitch/api/Kraken.hpp index 42e6a7a4d..566eae594 100644 --- a/src/providers/twitch/api/Kraken.hpp +++ b/src/providers/twitch/api/Kraken.hpp @@ -1,6 +1,7 @@ #pragma once #include "common/NetworkRequest.hpp" +#include "providers/twitch/TwitchAccount.hpp" #include #include @@ -14,11 +15,22 @@ using KrakenFailureCallback = std::function; template using ResultCallback = std::function; -struct KrakenChannel { - const QString status; +struct KrakenEmoteSets { + const QJsonObject emoteSets; - KrakenChannel(QJsonObject jsonObject) - : status(jsonObject.value("status").toString()) + KrakenEmoteSets(QJsonObject jsonObject) + : emoteSets(jsonObject.value("emoticon_sets").toObject()) + { + } +}; + +struct KrakenEmote { + const QString code; + const QString id; + + KrakenEmote(QJsonObject jsonObject) + : code(jsonObject.value("code").toString()) + , id(QString::number(jsonObject.value("id").toInt())) { } }; @@ -26,6 +38,11 @@ struct KrakenChannel { class Kraken final : boost::noncopyable { public: + // https://dev.twitch.tv/docs/v5/reference/users#get-user-emotes + void getUserEmotes(TwitchAccount *account, + ResultCallback successCallback, + KrakenFailureCallback failureCallback); + void update(QString clientId, QString oauthToken); static void initialize();