mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Refactored loading emotes and emotesets (#2539)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
e8a42a13f8
commit
af4e3f5062
4 changed files with 140 additions and 119 deletions
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
|
@ -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("/"));
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue