Refactored loading emotes and emotesets (#2539)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
Paweł 2021-03-21 15:19:49 +01:00 committed by GitHub
parent e8a42a13f8
commit af4e3f5062
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 140 additions and 119 deletions

View file

@ -11,6 +11,7 @@
#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchCommon.hpp"
#include "providers/twitch/TwitchUser.hpp" #include "providers/twitch/TwitchUser.hpp"
#include "providers/twitch/api/Helix.hpp" #include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/api/Kraken.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
#include "util/RapidjsonHelpers.hpp" #include "util/RapidjsonHelpers.hpp"
@ -176,39 +177,70 @@ void TwitchAccount::loadEmotes()
qCDebug(chatterinoTwitch) qCDebug(chatterinoTwitch)
<< "Loading Twitch emotes for user" << this->getUserName(); << "Loading Twitch emotes for user" << this->getUserName();
const auto &clientID = this->getOAuthClient(); if (this->getOAuthClient().isEmpty() || this->getOAuthToken().isEmpty())
const auto &oauthToken = this->getOAuthToken();
if (clientID.isEmpty() || oauthToken.isEmpty())
{ {
qCDebug(chatterinoTwitch) << "Missing Client ID or OAuth token"; qCDebug(chatterinoTwitch) << "Missing Client ID or OAuth token";
return; return;
} }
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + getKraken()->getUserEmotes(
"/emotes"); this,
[this](KrakenEmoteSets data) {
// clear emote data
auto emoteData = this->emotes_.access();
emoteData->emoteSets.clear();
emoteData->allEmoteNames.clear();
NetworkRequest(url) // no emotes available
if (data.emoteSets.isEmpty())
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken()) {
.onError([=](NetworkResult result) {
qCWarning(chatterinoTwitch) qCWarning(chatterinoTwitch)
<< "[TwitchAccount::loadEmotes] Error" << result.status(); << "\"emoticon_sets\" either empty or not present in "
if (result.status() == 203) "Kraken::getUserEmotes response";
{ return;
// onFinished(FollowResult_NotFollowing);
} }
else
{
// onFinished(FollowResult_Failed);
}
})
.onSuccess([=](auto result) -> Outcome {
this->parseEmotes(result.parseRapidJson());
return Success; for (auto emoteSetIt = data.emoteSets.begin();
}) emoteSetIt != data.emoteSets.end(); ++emoteSetIt)
.execute(); {
auto emoteSet = std::make_shared<EmoteSet>();
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<const TwitchAccount::TwitchAccountEmoteData> AccessGuard<const TwitchAccount::TwitchAccountEmoteData>
@ -256,71 +288,6 @@ void TwitchAccount::autoModDeny(const QString msgID)
.execute(); .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>();
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> emoteSet) void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
{ {
if (!emoteSet) if (!emoteSet)
@ -340,44 +307,35 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
NetworkRequest(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key)) NetworkRequest(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key))
.cache() .cache()
.onError([](NetworkResult result) { .onSuccess([emoteSet](NetworkResult result) -> Outcome {
qCWarning(chatterinoTwitch) << "Error code" << result.status() auto rootOld = result.parseRapidJson();
<< "while loading emote set data"; auto root = result.parseJson();
}) if (root.isEmpty())
.onSuccess([emoteSet](auto result) -> Outcome {
auto root = result.parseRapidJson();
if (!root.IsObject())
{ {
return Failure; return Failure;
} }
std::string emoteSetID; TwitchEmoteSetResolverResponse response(root);
QString channelName;
QString type;
if (!rj::getSafe(root, "channel_name", channelName))
{
return Failure;
}
if (!rj::getSafe(root, "type", type)) auto name = response.channelName;
{
return Failure;
}
qCDebug(chatterinoTwitch)
<< "Loaded twitch emote set data for" << emoteSet->key;
auto name = channelName;
name.detach(); name.detach();
name[0] = name[0].toUpper(); name[0] = name[0].toUpper();
emoteSet->text = name; emoteSet->text = name;
emoteSet->type = response.type;
emoteSet->channelName = response.channelName;
emoteSet->type = type; qCDebug(chatterinoTwitch)
emoteSet->channelName = channelName; << QString("Loaded twitch emote set data for %1")
.arg(emoteSet->key);
return Success; return Success;
}) })
.onError([](NetworkResult result) {
qCWarning(chatterinoTwitch)
<< QString("Error code %1 while loading emote set data")
.arg(result.status());
})
.execute(); .execute();
} }

View file

@ -23,6 +23,31 @@ enum FollowResult {
FollowResult_Failed, 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 class TwitchAccount : public Account
{ {
public: public:
@ -91,7 +116,6 @@ public:
void autoModDeny(const QString msgID); void autoModDeny(const QString msgID);
private: private:
void parseEmotes(const rapidjson::Document &document);
void loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet); void loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet);
QString oauthClient_; QString oauthClient_;

View file

@ -8,6 +8,28 @@ namespace chatterino {
static Kraken *instance = nullptr; static Kraken *instance = nullptr;
void Kraken::getUserEmotes(TwitchAccount *account,
ResultCallback<KrakenEmoteSets> 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) NetworkRequest Kraken::makeRequest(QString url, QUrlQuery urlQuery)
{ {
assert(!url.startsWith("/")); assert(!url.startsWith("/"));

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "common/NetworkRequest.hpp" #include "common/NetworkRequest.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
@ -14,11 +15,22 @@ using KrakenFailureCallback = std::function<void()>;
template <typename... T> template <typename... T>
using ResultCallback = std::function<void(T...)>; using ResultCallback = std::function<void(T...)>;
struct KrakenChannel { struct KrakenEmoteSets {
const QString status; const QJsonObject emoteSets;
KrakenChannel(QJsonObject jsonObject) KrakenEmoteSets(QJsonObject jsonObject)
: status(jsonObject.value("status").toString()) : 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 class Kraken final : boost::noncopyable
{ {
public: public:
// https://dev.twitch.tv/docs/v5/reference/users#get-user-emotes
void getUserEmotes(TwitchAccount *account,
ResultCallback<KrakenEmoteSets> successCallback,
KrakenFailureCallback failureCallback);
void update(QString clientId, QString oauthToken); void update(QString clientId, QString oauthToken);
static void initialize(); static void initialize();