Fixed newly uploaded subscriber emotes not being available (#2992)

Co-authored-by: Felanbird <41973452+Felanbird@users.noreply.github.com>
Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
Paweł
2021-07-17 17:18:17 +02:00
committed by GitHub
parent 91ab8b90a0
commit 7e13564c24
10 changed files with 214 additions and 145 deletions
+166 -111
View File
@@ -21,6 +21,35 @@
#include "util/QStringHash.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace {
std::vector<QStringList> getEmoteSetBatches(QStringList emoteSetKeys)
{
// splitting emoteSetKeys to batches of 100, because Ivr API endpoint accepts a maximum of 100 emotesets at once
constexpr int batchSize = 100;
int batchCount = (emoteSetKeys.size() / batchSize) + 1;
std::vector<QStringList> batches;
batches.reserve(batchCount);
for (int i = 0; i < batchCount; i++)
{
QStringList batch;
int last = std::min(batchSize, emoteSetKeys.size() - batchSize * i);
for (int j = 0; j < last; j++)
{
batch.push_back(emoteSetKeys.at(j + (batchSize * i)));
}
batches.emplace_back(batch);
}
return batches;
}
} // namespace
namespace chatterino {
TwitchAccount::TwitchAccount(const QString &username, const QString &oauthToken,
const QString &oauthClient, const QString &userID)
@@ -116,7 +145,7 @@ void TwitchAccount::loadBlocks()
}
},
[] {
qDebug() << "Fetching blocks failed!";
qCWarning(chatterinoTwitch) << "Fetching blocks failed!";
});
}
@@ -196,77 +225,25 @@ void TwitchAccount::loadEmotes()
if (this->getOAuthClient().isEmpty() || this->getOAuthToken().isEmpty())
{
qCDebug(chatterinoTwitch) << "Missing Client ID and/or OAuth token";
qCDebug(chatterinoTwitch)
<< "Aborted loadEmotes due to missing Client ID and/or OAuth token";
return;
}
// Getting subscription emotes from kraken
getKraken()->getUserEmotes(
this,
[this](KrakenEmoteSets data) {
// no emotes available
if (data.emoteSets.isEmpty())
{
qCWarning(chatterinoTwitch)
<< "\"emoticon_sets\" either empty or not present in "
"Kraken::getUserEmotes response";
return;
}
{
auto emoteData = this->emotes_.access();
emoteData->emoteSets.clear();
emoteData->emotes.clear();
qCDebug(chatterinoTwitch) << "Cleared emotes!";
}
{
// Clearing emote data
auto emoteData = this->emotes_.access();
emoteData->emoteSets.clear();
emoteData->emotes.clear();
for (auto emoteSetIt = data.emoteSets.begin();
emoteSetIt != data.emoteSets.end(); ++emoteSetIt)
{
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);
continue;
}
KrakenEmote krakenEmote(emoteArrObj.toObject());
auto id = EmoteId{krakenEmote.id};
auto code = EmoteName{
TwitchEmotes::cleanUpEmoteCode(krakenEmote.code)};
emoteSet->emotes.emplace_back(TwitchEmote{id, code});
if (!emoteSet->local)
{
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);
}
}
// Getting userstate emotes from Ivr
this->loadUserstateEmotes();
},
[] {
// kraken request failed
});
// TODO(zneix): Once Helix adds Get User Emotes we could remove this hacky solution
// For now, this is necessary as Kraken's equivalent doesn't return all emotes
// See: https://twitch.uservoice.com/forums/310213-developers/suggestions/43599900
this->loadUserstateEmotes([=] {
// Fill up emoteData with emote sets that were returned in a Kraken call, but aren't present in emoteData.
this->loadKrakenEmotes();
});
}
bool TwitchAccount::setUserstateEmoteSets(QStringList newEmoteSets)
@@ -284,10 +261,11 @@ bool TwitchAccount::setUserstateEmoteSets(QStringList newEmoteSets)
return true;
}
void TwitchAccount::loadUserstateEmotes()
void TwitchAccount::loadUserstateEmotes(std::function<void()> callback)
{
if (this->userstateEmoteSets_.isEmpty())
{
callback();
return;
}
@@ -303,7 +281,7 @@ void TwitchAccount::loadUserstateEmotes()
}
// filter out emote sets from userstate message, which are not in fetched emote set list
for (const auto &emoteSetKey : this->userstateEmoteSets_)
for (const auto &emoteSetKey : qAsConst(this->userstateEmoteSets_))
{
if (!krakenEmoteSetKeys.contains(emoteSetKey))
{
@@ -314,54 +292,50 @@ void TwitchAccount::loadUserstateEmotes()
// return if there are no new emote sets
if (newEmoteSetKeys.isEmpty())
{
callback();
return;
}
qCDebug(chatterinoTwitch) << QString("Loading %1 emotesets from IVR: %2")
.arg(newEmoteSetKeys.size())
.arg(newEmoteSetKeys.join(", "));
// splitting newEmoteSetKeys to batches of 100, because Ivr API endpoint accepts a maximum of 100 emotesets at once
constexpr int batchSize = 100;
std::vector<QStringList> batches;
int batchCount = (newEmoteSetKeys.size() / batchSize) + 1;
batches.reserve(batchCount);
for (int i = 0; i < batchCount; i++)
{
QStringList batch;
int last = std::min(batchSize, newEmoteSetKeys.size() - batchSize * i);
for (int j = batchSize * i; j < last; j++)
{
batch.push_back(newEmoteSetKeys.at(j));
}
batches.emplace_back(batch);
}
// requesting emotes
for (const auto &batch : batches)
auto batches = getEmoteSetBatches(newEmoteSetKeys);
for (int i = 0; i < batches.size(); i++)
{
qCDebug(chatterinoTwitch)
<< QString(
"Loading %1 emotesets from IVR; batch %2/%3 (%4 sets): %5")
.arg(newEmoteSetKeys.size())
.arg(i + 1)
.arg(batches.size())
.arg(batches.at(i).size())
.arg(batches.at(i).join(","));
getIvr()->getBulkEmoteSets(
batch.join(","),
batches.at(i).join(","),
[this](QJsonArray emoteSetArray) {
auto emoteData = this->emotes_.access();
auto localEmoteData = this->localEmotes_.access();
for (auto emoteSet : emoteSetArray)
for (auto emoteSet_ : emoteSetArray)
{
auto newUserEmoteSet = std::make_shared<EmoteSet>();
auto emoteSet = std::make_shared<EmoteSet>();
IvrEmoteSet ivrEmoteSet(emoteSet.toObject());
IvrEmoteSet ivrEmoteSet(emoteSet_.toObject());
newUserEmoteSet->key = ivrEmoteSet.setId;
QString setKey = ivrEmoteSet.setId;
emoteSet->key = setKey;
auto name = ivrEmoteSet.login;
name.detach();
name[0] = name[0].toUpper();
// check if the emoteset is already in emoteData
auto isAlreadyFetched =
std::find_if(emoteData->emoteSets.begin(),
emoteData->emoteSets.end(),
[setKey](std::shared_ptr<EmoteSet> set) {
return (set->key == setKey);
});
if (isAlreadyFetched != emoteData->emoteSets.end())
{
continue;
}
newUserEmoteSet->text = name;
newUserEmoteSet->channelName = ivrEmoteSet.login;
emoteSet->channelName = ivrEmoteSet.login;
emoteSet->text = ivrEmoteSet.displayName;
for (const auto &emoteObj : ivrEmoteSet.emotes)
{
@@ -371,8 +345,7 @@ void TwitchAccount::loadUserstateEmotes()
auto code = EmoteName{
TwitchEmotes::cleanUpEmoteCode(ivrEmote.code)};
newUserEmoteSet->emotes.push_back(
TwitchEmote{id, code});
emoteSet->emotes.push_back(TwitchEmote{id, code});
auto emote =
getApp()->emotes->twitch.getOrCreateEmote(id, code);
@@ -380,7 +353,7 @@ void TwitchAccount::loadUserstateEmotes()
// Follower emotes can be only used in their origin channel
if (ivrEmote.emoteType == "FOLLOWER")
{
newUserEmoteSet->local = true;
emoteSet->local = true;
// EmoteMap for target channel wasn't initialized yet, doing it now
if (localEmoteData->find(ivrEmoteSet.channelId) ==
@@ -398,16 +371,25 @@ void TwitchAccount::loadUserstateEmotes()
emoteData->emotes.emplace(code, emote);
}
}
std::sort(newUserEmoteSet->emotes.begin(),
newUserEmoteSet->emotes.end(),
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(newUserEmoteSet);
emoteData->emoteSets.emplace_back(emoteSet);
}
},
[] {
// fetching emotes failed, ivr API might be down
},
[=] {
// XXX(zneix): We check if this is the last iteration and if so, call the callback
if (i + 1 == batches.size())
{
qCDebug(chatterinoTwitch)
<< "Finished loading emotes from IVR, attempting to "
"load Kraken emotes now";
callback();
}
});
};
}
@@ -517,6 +499,79 @@ void TwitchAccount::autoModDeny(const QString msgID, ChannelPtr channel)
});
}
void TwitchAccount::loadKrakenEmotes()
{
getKraken()->getUserEmotes(
this,
[this](KrakenEmoteSets data) {
// no emotes available
if (data.emoteSets.isEmpty())
{
qCWarning(chatterinoTwitch)
<< "\"emoticon_sets\" either empty or not present in "
"Kraken::getUserEmotes response";
return;
}
auto emoteData = this->emotes_.access();
for (auto emoteSetIt = data.emoteSets.begin();
emoteSetIt != data.emoteSets.end(); ++emoteSetIt)
{
auto emoteSet = std::make_shared<EmoteSet>();
QString setKey = emoteSetIt.key();
emoteSet->key = setKey;
this->loadEmoteSetData(emoteSet);
// check if the emoteset is already in emoteData
auto isAlreadyFetched = std::find_if(
emoteData->emoteSets.begin(), emoteData->emoteSets.end(),
[setKey](std::shared_ptr<EmoteSet> set) {
return (set->key == setKey);
});
if (isAlreadyFetched != emoteData->emoteSets.end())
{
continue;
}
for (const auto emoteArrObj : emoteSetIt->toArray())
{
if (!emoteArrObj.isObject())
{
qCWarning(chatterinoTwitch)
<< QString("Emote value from set %1 was invalid")
.arg(emoteSet->key);
continue;
}
KrakenEmote krakenEmote(emoteArrObj.toObject());
auto id = EmoteId{krakenEmote.id};
auto code = EmoteName{
TwitchEmotes::cleanUpEmoteCode(krakenEmote.code)};
emoteSet->emotes.emplace_back(TwitchEmote{id, code});
if (!emoteSet->local)
{
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);
}
},
[] {
// kraken request failed
});
}
void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
{
if (!emoteSet)
@@ -546,7 +601,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
if (emoteSetData.ownerId.isEmpty() ||
emoteSetData.setId != emoteSet->key)
{
qCWarning(chatterinoTwitch)
qCDebug(chatterinoTwitch)
<< QString("Failed to fetch emoteSetData for %1, assuming "
"Twitch is the owner")
.arg(emoteSet->key);