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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user