mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
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:
parent
91ab8b90a0
commit
7e13564c24
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Unversioned
|
## Unversioned
|
||||||
|
|
||||||
|
- Major: Newly uploaded Twitch emotes are once again present in emote picker and can be autocompleted with Tab as well. (#2992)
|
||||||
- Minor: Added autocompletion in /whispers for Twitch emotes, Global Bttv/Ffz emotes and emojis. (#2999)
|
- Minor: Added autocompletion in /whispers for Twitch emotes, Global Bttv/Ffz emotes and emojis. (#2999)
|
||||||
- Bugfix: Fixed "smiley" emotes being unable to be "Tabbed" with autocompletion, introduced in v2.3.3. (#3010)
|
- Bugfix: Fixed "smiley" emotes being unable to be "Tabbed" with autocompletion, introduced in v2.3.3. (#3010)
|
||||||
- Dev: Ubuntu packages are now available (#2936)
|
- Dev: Ubuntu packages are now available (#2936)
|
||||||
|
|
|
@ -35,7 +35,8 @@ void IvrApi::getSubage(QString userName, QString channelName,
|
||||||
|
|
||||||
void IvrApi::getBulkEmoteSets(QString emoteSetList,
|
void IvrApi::getBulkEmoteSets(QString emoteSetList,
|
||||||
ResultCallback<QJsonArray> successCallback,
|
ResultCallback<QJsonArray> successCallback,
|
||||||
IvrFailureCallback failureCallback)
|
IvrFailureCallback failureCallback,
|
||||||
|
std::function<void()> finallyCallback)
|
||||||
{
|
{
|
||||||
QUrlQuery urlQuery;
|
QUrlQuery urlQuery;
|
||||||
urlQuery.addQueryItem("set_id", emoteSetList);
|
urlQuery.addQueryItem("set_id", emoteSetList);
|
||||||
|
@ -54,6 +55,7 @@ void IvrApi::getBulkEmoteSets(QString emoteSetList,
|
||||||
<< QString(result.getData());
|
<< QString(result.getData());
|
||||||
failureCallback();
|
failureCallback();
|
||||||
})
|
})
|
||||||
|
.finally(std::move(finallyCallback))
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,10 +81,11 @@ public:
|
||||||
ResultCallback<IvrSubage> resultCallback,
|
ResultCallback<IvrSubage> resultCallback,
|
||||||
IvrFailureCallback failureCallback);
|
IvrFailureCallback failureCallback);
|
||||||
|
|
||||||
// https://api.ivr.fi/docs#tag/Twitch/paths/~1twitch~1emoteset/get
|
// https://api.ivr.fi/v2/docs/static/index.html#/Twitch/get_twitch_emotes_sets
|
||||||
void getBulkEmoteSets(QString emoteSetList,
|
void getBulkEmoteSets(QString emoteSetList,
|
||||||
ResultCallback<QJsonArray> successCallback,
|
ResultCallback<QJsonArray> successCallback,
|
||||||
IvrFailureCallback failureCallback);
|
IvrFailureCallback failureCallback,
|
||||||
|
std::function<void()> finallyCallback);
|
||||||
|
|
||||||
static void initialize();
|
static void initialize();
|
||||||
|
|
||||||
|
|
|
@ -497,7 +497,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
||||||
|
|
||||||
if (emoteSetsChanged)
|
if (emoteSetsChanged)
|
||||||
{
|
{
|
||||||
currentUser->loadUserstateEmotes();
|
currentUser->loadUserstateEmotes([] {});
|
||||||
}
|
}
|
||||||
|
|
||||||
QString channelName;
|
QString channelName;
|
||||||
|
@ -512,6 +512,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checking if currentUser is a VIP or staff member
|
||||||
QVariant _badges = message->tag("badges");
|
QVariant _badges = message->tag("badges");
|
||||||
if (_badges.isValid())
|
if (_badges.isValid())
|
||||||
{
|
{
|
||||||
|
@ -524,6 +525,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checking if currentUser is a moderator
|
||||||
QVariant _mod = message->tag("mod");
|
QVariant _mod = message->tag("mod");
|
||||||
if (_mod.isValid())
|
if (_mod.isValid())
|
||||||
{
|
{
|
||||||
|
@ -535,6 +537,24 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will emit only once and right after user logs in to IRC - reset emote data and reload emotes
|
||||||
|
void IrcMessageHandler::handleGlobalUserStateMessage(
|
||||||
|
Communi::IrcMessage *message)
|
||||||
|
{
|
||||||
|
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||||
|
|
||||||
|
// set received emote-sets, this time used to initially load emotes
|
||||||
|
// NOTE: this should always return true unless we reconnect
|
||||||
|
auto emoteSetsChanged = currentUser->setUserstateEmoteSets(
|
||||||
|
message->tag("emote-sets").toString().split(","));
|
||||||
|
|
||||||
|
// We should always attempt to reload emotes even on reconnections where
|
||||||
|
// emoteSetsChanged, since we want to trigger emote reloads when
|
||||||
|
// "currentUserChanged" signal is emitted
|
||||||
|
qCDebug(chatterinoTwitch) << emoteSetsChanged << message->toData();
|
||||||
|
currentUser->loadEmotes();
|
||||||
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
MessageParseArgs args;
|
MessageParseArgs args;
|
||||||
|
|
|
@ -30,6 +30,7 @@ public:
|
||||||
void handleClearChatMessage(Communi::IrcMessage *message);
|
void handleClearChatMessage(Communi::IrcMessage *message);
|
||||||
void handleClearMessageMessage(Communi::IrcMessage *message);
|
void handleClearMessageMessage(Communi::IrcMessage *message);
|
||||||
void handleUserStateMessage(Communi::IrcMessage *message);
|
void handleUserStateMessage(Communi::IrcMessage *message);
|
||||||
|
void handleGlobalUserStateMessage(Communi::IrcMessage *message);
|
||||||
void handleWhisperMessage(Communi::IrcMessage *message);
|
void handleWhisperMessage(Communi::IrcMessage *message);
|
||||||
|
|
||||||
// parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
|
// parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
|
||||||
|
|
|
@ -21,6 +21,35 @@
|
||||||
#include "util/QStringHash.hpp"
|
#include "util/QStringHash.hpp"
|
||||||
#include "util/RapidjsonHelpers.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 {
|
namespace chatterino {
|
||||||
TwitchAccount::TwitchAccount(const QString &username, const QString &oauthToken,
|
TwitchAccount::TwitchAccount(const QString &username, const QString &oauthToken,
|
||||||
const QString &oauthClient, const QString &userID)
|
const QString &oauthClient, const QString &userID)
|
||||||
|
@ -116,7 +145,7 @@ void TwitchAccount::loadBlocks()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[] {
|
[] {
|
||||||
qDebug() << "Fetching blocks failed!";
|
qCWarning(chatterinoTwitch) << "Fetching blocks failed!";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,76 +225,24 @@ void TwitchAccount::loadEmotes()
|
||||||
|
|
||||||
if (this->getOAuthClient().isEmpty() || this->getOAuthToken().isEmpty())
|
if (this->getOAuthClient().isEmpty() || this->getOAuthToken().isEmpty())
|
||||||
{
|
{
|
||||||
qCDebug(chatterinoTwitch) << "Missing Client ID and/or OAuth token";
|
qCDebug(chatterinoTwitch)
|
||||||
return;
|
<< "Aborted loadEmotes due to missing Client ID and/or OAuth token";
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// Clearing emote data
|
|
||||||
auto emoteData = this->emotes_.access();
|
auto emoteData = this->emotes_.access();
|
||||||
emoteData->emoteSets.clear();
|
emoteData->emoteSets.clear();
|
||||||
emoteData->emotes.clear();
|
emoteData->emotes.clear();
|
||||||
|
qCDebug(chatterinoTwitch) << "Cleared emotes!";
|
||||||
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(),
|
// TODO(zneix): Once Helix adds Get User Emotes we could remove this hacky solution
|
||||||
[](const TwitchEmote &l, const TwitchEmote &r) {
|
// For now, this is necessary as Kraken's equivalent doesn't return all emotes
|
||||||
return l.name.string < r.name.string;
|
// See: https://twitch.uservoice.com/forums/310213-developers/suggestions/43599900
|
||||||
});
|
this->loadUserstateEmotes([=] {
|
||||||
emoteData->emoteSets.emplace_back(emoteSet);
|
// Fill up emoteData with emote sets that were returned in a Kraken call, but aren't present in emoteData.
|
||||||
}
|
this->loadKrakenEmotes();
|
||||||
}
|
|
||||||
// Getting userstate emotes from Ivr
|
|
||||||
this->loadUserstateEmotes();
|
|
||||||
},
|
|
||||||
[] {
|
|
||||||
// kraken request failed
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,10 +261,11 @@ bool TwitchAccount::setUserstateEmoteSets(QStringList newEmoteSets)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchAccount::loadUserstateEmotes()
|
void TwitchAccount::loadUserstateEmotes(std::function<void()> callback)
|
||||||
{
|
{
|
||||||
if (this->userstateEmoteSets_.isEmpty())
|
if (this->userstateEmoteSets_.isEmpty())
|
||||||
{
|
{
|
||||||
|
callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +281,7 @@ void TwitchAccount::loadUserstateEmotes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter out emote sets from userstate message, which are not in fetched emote set list
|
// 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))
|
if (!krakenEmoteSetKeys.contains(emoteSetKey))
|
||||||
{
|
{
|
||||||
|
@ -314,54 +292,50 @@ void TwitchAccount::loadUserstateEmotes()
|
||||||
// return if there are no new emote sets
|
// return if there are no new emote sets
|
||||||
if (newEmoteSetKeys.isEmpty())
|
if (newEmoteSetKeys.isEmpty())
|
||||||
{
|
{
|
||||||
|
callback();
|
||||||
return;
|
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
|
// 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(
|
getIvr()->getBulkEmoteSets(
|
||||||
batch.join(","),
|
batches.at(i).join(","),
|
||||||
[this](QJsonArray emoteSetArray) {
|
[this](QJsonArray emoteSetArray) {
|
||||||
auto emoteData = this->emotes_.access();
|
auto emoteData = this->emotes_.access();
|
||||||
auto localEmoteData = this->localEmotes_.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;
|
// check if the emoteset is already in emoteData
|
||||||
name.detach();
|
auto isAlreadyFetched =
|
||||||
name[0] = name[0].toUpper();
|
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;
|
emoteSet->channelName = ivrEmoteSet.login;
|
||||||
newUserEmoteSet->channelName = ivrEmoteSet.login;
|
emoteSet->text = ivrEmoteSet.displayName;
|
||||||
|
|
||||||
for (const auto &emoteObj : ivrEmoteSet.emotes)
|
for (const auto &emoteObj : ivrEmoteSet.emotes)
|
||||||
{
|
{
|
||||||
|
@ -371,8 +345,7 @@ void TwitchAccount::loadUserstateEmotes()
|
||||||
auto code = EmoteName{
|
auto code = EmoteName{
|
||||||
TwitchEmotes::cleanUpEmoteCode(ivrEmote.code)};
|
TwitchEmotes::cleanUpEmoteCode(ivrEmote.code)};
|
||||||
|
|
||||||
newUserEmoteSet->emotes.push_back(
|
emoteSet->emotes.push_back(TwitchEmote{id, code});
|
||||||
TwitchEmote{id, code});
|
|
||||||
|
|
||||||
auto emote =
|
auto emote =
|
||||||
getApp()->emotes->twitch.getOrCreateEmote(id, code);
|
getApp()->emotes->twitch.getOrCreateEmote(id, code);
|
||||||
|
@ -380,7 +353,7 @@ void TwitchAccount::loadUserstateEmotes()
|
||||||
// Follower emotes can be only used in their origin channel
|
// Follower emotes can be only used in their origin channel
|
||||||
if (ivrEmote.emoteType == "FOLLOWER")
|
if (ivrEmote.emoteType == "FOLLOWER")
|
||||||
{
|
{
|
||||||
newUserEmoteSet->local = true;
|
emoteSet->local = true;
|
||||||
|
|
||||||
// EmoteMap for target channel wasn't initialized yet, doing it now
|
// EmoteMap for target channel wasn't initialized yet, doing it now
|
||||||
if (localEmoteData->find(ivrEmoteSet.channelId) ==
|
if (localEmoteData->find(ivrEmoteSet.channelId) ==
|
||||||
|
@ -398,16 +371,25 @@ void TwitchAccount::loadUserstateEmotes()
|
||||||
emoteData->emotes.emplace(code, emote);
|
emoteData->emotes.emplace(code, emote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::sort(newUserEmoteSet->emotes.begin(),
|
std::sort(emoteSet->emotes.begin(), emoteSet->emotes.end(),
|
||||||
newUserEmoteSet->emotes.end(),
|
|
||||||
[](const TwitchEmote &l, const TwitchEmote &r) {
|
[](const TwitchEmote &l, const TwitchEmote &r) {
|
||||||
return l.name.string < r.name.string;
|
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
|
// 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)
|
void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||||
{
|
{
|
||||||
if (!emoteSet)
|
if (!emoteSet)
|
||||||
|
@ -546,7 +601,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||||
if (emoteSetData.ownerId.isEmpty() ||
|
if (emoteSetData.ownerId.isEmpty() ||
|
||||||
emoteSetData.setId != emoteSet->key)
|
emoteSetData.setId != emoteSet->key)
|
||||||
{
|
{
|
||||||
qCWarning(chatterinoTwitch)
|
qCDebug(chatterinoTwitch)
|
||||||
<< QString("Failed to fetch emoteSetData for %1, assuming "
|
<< QString("Failed to fetch emoteSetData for %1, assuming "
|
||||||
"Twitch is the owner")
|
"Twitch is the owner")
|
||||||
.arg(emoteSet->key);
|
.arg(emoteSet->key);
|
||||||
|
|
|
@ -115,7 +115,7 @@ public:
|
||||||
void loadEmotes();
|
void loadEmotes();
|
||||||
// loadUserstateEmotes loads emote sets that are part of the USERSTATE emote-sets key
|
// loadUserstateEmotes loads emote sets that are part of the USERSTATE emote-sets key
|
||||||
// this function makes sure not to load emote sets that have already been loaded
|
// this function makes sure not to load emote sets that have already been loaded
|
||||||
void loadUserstateEmotes();
|
void loadUserstateEmotes(std::function<void()> callback);
|
||||||
// setUserStateEmoteSets sets the emote sets that were parsed from the USERSTATE emote-sets key
|
// setUserStateEmoteSets sets the emote sets that were parsed from the USERSTATE emote-sets key
|
||||||
// Returns true if the newly inserted emote sets differ from the ones previously saved
|
// Returns true if the newly inserted emote sets differ from the ones previously saved
|
||||||
[[nodiscard]] bool setUserstateEmoteSets(QStringList newEmoteSets);
|
[[nodiscard]] bool setUserstateEmoteSets(QStringList newEmoteSets);
|
||||||
|
@ -128,6 +128,7 @@ public:
|
||||||
void autoModDeny(const QString msgID, ChannelPtr channel);
|
void autoModDeny(const QString msgID, ChannelPtr channel);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void loadKrakenEmotes();
|
||||||
void loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet);
|
void loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet);
|
||||||
|
|
||||||
QString oauthClient_;
|
QString oauthClient_;
|
||||||
|
|
|
@ -60,6 +60,19 @@ void TwitchIrcServer::initializeConnection(IrcConnection *connection,
|
||||||
|
|
||||||
qCDebug(chatterinoTwitch) << "logging in as" << account->getUserName();
|
qCDebug(chatterinoTwitch) << "logging in as" << account->getUserName();
|
||||||
|
|
||||||
|
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags
|
||||||
|
// twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands
|
||||||
|
// twitch.tv/membership enables the JOIN/PART/NAMES commands. See https://dev.twitch.tv/docs/irc/membership
|
||||||
|
// This is enabled so we receive USERSTATE messages when joining channels / typing messages, along with the other command capabilities
|
||||||
|
QStringList caps{"twitch.tv/tags", "twitch.tv/commands"};
|
||||||
|
if (type != ConnectionType::Write)
|
||||||
|
{
|
||||||
|
caps.push_back("twitch.tv/membership");
|
||||||
|
}
|
||||||
|
|
||||||
|
connection->network()->setSkipCapabilityValidation(true);
|
||||||
|
connection->network()->setRequestedCapabilities(caps);
|
||||||
|
|
||||||
QString username = account->getUserName();
|
QString username = account->getUserName();
|
||||||
QString oauthToken = account->getOAuthToken();
|
QString oauthToken = account->getOAuthToken();
|
||||||
|
|
||||||
|
@ -169,6 +182,10 @@ void TwitchIrcServer::readConnectionMessageReceived(
|
||||||
"Twitch Servers requested us to reconnect, reconnecting");
|
"Twitch Servers requested us to reconnect, reconnecting");
|
||||||
this->connect();
|
this->connect();
|
||||||
}
|
}
|
||||||
|
else if (command == "GLOBALUSERSTATE")
|
||||||
|
{
|
||||||
|
handler.handleGlobalUserStateMessage(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchIrcServer::writeConnectionMessageReceived(
|
void TwitchIrcServer::writeConnectionMessageReceived(
|
||||||
|
@ -219,28 +236,6 @@ void TwitchIrcServer::writeConnectionMessageReceived(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
|
|
||||||
{
|
|
||||||
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
|
|
||||||
// twitch.tv/membership enables the JOIN/PART/MODE/NAMES commands. See https://dev.twitch.tv/docs/irc/membership/
|
|
||||||
// twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/
|
|
||||||
// This is enabled here so we receive USERSTATE messages when joining channels
|
|
||||||
connection->sendRaw(
|
|
||||||
"CAP REQ :twitch.tv/tags twitch.tv/membership twitch.tv/commands");
|
|
||||||
|
|
||||||
AbstractIrcServer::onReadConnected(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TwitchIrcServer::onWriteConnected(IrcConnection *connection)
|
|
||||||
{
|
|
||||||
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
|
|
||||||
// twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/
|
|
||||||
// This is enabled here so we receive USERSTATE messages when typing messages, along with the other command capabilities
|
|
||||||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
|
|
||||||
|
|
||||||
AbstractIrcServer::onWriteConnected(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
|
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
|
||||||
const QString &channelName)
|
const QString &channelName)
|
||||||
{
|
{
|
||||||
|
|
|
@ -56,9 +56,6 @@ protected:
|
||||||
virtual void writeConnectionMessageReceived(
|
virtual void writeConnectionMessageReceived(
|
||||||
Communi::IrcMessage *message) override;
|
Communi::IrcMessage *message) override;
|
||||||
|
|
||||||
virtual void onReadConnected(IrcConnection *connection) override;
|
|
||||||
virtual void onWriteConnected(IrcConnection *connection) override;
|
|
||||||
|
|
||||||
virtual std::shared_ptr<Channel> getCustomChannel(
|
virtual std::shared_ptr<Channel> getCustomChannel(
|
||||||
const QString &channelname) override;
|
const QString &channelname) override;
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,6 @@ Emotes::Emotes()
|
||||||
|
|
||||||
void Emotes::initialize(Settings &settings, Paths &paths)
|
void Emotes::initialize(Settings &settings, Paths &paths)
|
||||||
{
|
{
|
||||||
getApp()->accounts->twitch.currentUserChanged.connect([] {
|
|
||||||
getApp()->accounts->twitch.getCurrent()->loadEmotes();
|
|
||||||
});
|
|
||||||
|
|
||||||
this->emojis.load();
|
this->emojis.load();
|
||||||
|
|
||||||
this->gifTimer.initialize();
|
this->gifTimer.initialize();
|
||||||
|
|
Loading…
Reference in a new issue