diff --git a/src/providers/twitch/twitchemotes.cpp b/src/providers/twitch/twitchemotes.cpp index b04e0c57a..a20945b2d 100644 --- a/src/providers/twitch/twitchemotes.cpp +++ b/src/providers/twitch/twitchemotes.cpp @@ -2,6 +2,8 @@ #include "debug/log.hpp" #include "messages/image.hpp" +#include "util/benchmark.hpp" +#include "util/rapidjson-helpers.hpp" #include "util/urlfetch.hpp" #define TWITCH_EMOTE_TEMPLATE "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}" @@ -41,8 +43,58 @@ QString cleanUpCode(const QString &dirtyEmoteCode) return dirtyEmoteCode; } +void loadSetData(std::shared_ptr emoteSet) +{ + debug::Log("Load twitch emote set data for {}", emoteSet->key); + util::NetworkRequest req("https://braize.pajlada.com/chatterino/twitchemotes/set/" + + emoteSet->key + "/"); + + req.setRequestType(util::NetworkRequest::GetRequest); + + req.onError([](int errorCode) -> bool { + debug::Log("Emote sets on ERROR {}", errorCode); + return true; + }); + + req.onSuccess([emoteSet](const rapidjson::Document &root) -> bool { + debug::Log("Emote sets on success"); + if (!root.IsObject()) { + return false; + } + + std::string emoteSetID; + QString channelName; + if (!rj::getSafe(root, "channel_name", channelName)) { + return false; + } + + emoteSet->channelName = channelName; + + return true; + }); + + req.execute(); +} + } // namespace +TwitchEmotes::TwitchEmotes() +{ + { + EmoteSet emoteSet; + emoteSet.key = "19194"; + emoteSet.text = "Twitch Prime Emotes"; + this->staticEmoteSets[emoteSet.key] = std::move(emoteSet); + } + + { + EmoteSet emoteSet; + emoteSet.key = "0"; + emoteSet.text = "Twitch Global Emotes"; + this->staticEmoteSets[emoteSet.key] = std::move(emoteSet); + } +} + // id is used for lookup // emoteName is used for giving a name to the emote in case it doesn't exist util::EmoteData TwitchEmotes::getEmoteById(const QString &id, const QString &emoteName) @@ -107,23 +159,26 @@ void TwitchEmotes::refresh(const std::shared_ptr &user) auto emoticonSets = root.value("emoticon_sets").toObject(); for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) { - EmoteSet emoteSet; + auto emoteSet = std::make_shared(); - emoteSet.key = it.key(); + emoteSet->key = it.key(); + + loadSetData(emoteSet); for (QJsonValue emoteValue : it.value().toArray()) { QJsonObject emoticon = emoteValue.toObject(); QString id = QString::number(emoticon["id"].toInt()); QString code = emoticon["code"].toString(); + auto cleanCode = cleanUpCode(code); - emoteSet.emotes.emplace_back(id, cleanCode); + emoteSet->emotes.emplace_back(id, cleanCode); emoteData.emoteCodes.push_back(cleanCode); util::EmoteData emote = this->getEmoteById(id, code); emoteData.emotes.insert(code, emote); } - emoteData.emoteSets.emplace_back(std::move(emoteSet)); + emoteData.emoteSets.emplace_back(emoteSet); } emoteData.filled = true; @@ -132,6 +187,64 @@ void TwitchEmotes::refresh(const std::shared_ptr &user) util::twitch::getAuthorized(url, clientID, oauthToken, QThread::currentThread(), loadEmotes); } +void TwitchEmotes::loadSetData(std::shared_ptr emoteSet) +{ + if (!emoteSet) { + debug::Log("null emote set sent"); + return; + } + + auto staticSetIt = this->staticEmoteSets.find(emoteSet->key); + if (staticSetIt != this->staticEmoteSets.end()) { + const auto &staticSet = staticSetIt->second; + emoteSet->channelName = staticSet.channelName; + emoteSet->text = staticSet.text; + return; + } + + debug::Log("Load twitch emote set data for {}..", emoteSet->key); + util::NetworkRequest req("https://braize.pajlada.com/chatterino/twitchemotes/set/" + + emoteSet->key + "/"); + + req.setRequestType(util::NetworkRequest::GetRequest); + + req.onError([](int errorCode) -> bool { + debug::Log("Emote sets on ERROR {}", errorCode); + return true; + }); + + req.onSuccess([emoteSet](const rapidjson::Document &root) -> bool { + if (!root.IsObject()) { + return false; + } + + std::string emoteSetID; + QString channelName; + QString type; + if (!rj::getSafe(root, "channel_name", channelName)) { + return false; + } + + if (!rj::getSafe(root, "type", type)) { + return false; + } + + debug::Log("Loaded twitch emote set data for {}!", emoteSet->key); + + if (type == "sub") { + emoteSet->text = QString("Twitch Subscriber Emote (%1)").arg(channelName); + } else { + emoteSet->text = QString("Twitch Account Emote (%1)").arg(channelName); + } + + emoteSet->channelName = channelName; + + return true; + }); + + req.execute(); +} + } // namespace twitch } // namespace providers } // namespace chatterino diff --git a/src/providers/twitch/twitchemotes.hpp b/src/providers/twitch/twitchemotes.hpp index f0aab695d..7060628e2 100644 --- a/src/providers/twitch/twitchemotes.hpp +++ b/src/providers/twitch/twitchemotes.hpp @@ -8,6 +8,8 @@ #include +#include + namespace chatterino { namespace providers { namespace twitch { @@ -15,6 +17,8 @@ namespace twitch { class TwitchEmotes { public: + TwitchEmotes(); + util::EmoteData getEmoteById(const QString &id, const QString &emoteName); /// Twitch emotes @@ -36,11 +40,15 @@ public: struct EmoteSet { QString key; + QString channelName; + QString text; std::vector emotes; }; + std::map staticEmoteSets; + struct TwitchAccountEmoteData { - std::vector emoteSets; + std::vector> emoteSets; std::vector emoteCodes; @@ -53,6 +61,8 @@ public: std::map emotes; private: + void loadSetData(std::shared_ptr emoteSet); + // emote code util::ConcurrentMap _twitchEmotes; diff --git a/src/util/networkrequest.hpp b/src/util/networkrequest.hpp index 991936da3..d4c0755cf 100644 --- a/src/util/networkrequest.hpp +++ b/src/util/networkrequest.hpp @@ -216,7 +216,7 @@ public: if (this->data.caller != nullptr) { QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller, - [ onFinished, data = this->data ](auto reply) mutable { + [onFinished, data = this->data](auto reply) mutable { if (reply->error() != QNetworkReply::NetworkError::NoError) { // TODO: We might want to call an onError callback here return; @@ -238,7 +238,7 @@ public: QObject::connect( &requester, &NetworkRequester::requestUrl, worker, - [ timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)} ]() { + [timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)}]() { QNetworkReply *reply = NetworkManager::NaM.get(data.request); if (timer != nullptr) { @@ -253,21 +253,21 @@ public: data.onReplyCreated(reply); } - QObject::connect(reply, &QNetworkReply::finished, worker, [ - data = std::move(data), worker, reply, onFinished = std::move(onFinished) - ]() mutable { - if (data.caller == nullptr) { - QByteArray bytes = reply->readAll(); - data.writeToCache(bytes); - onFinished(bytes); + QObject::connect(reply, &QNetworkReply::finished, worker, + [data = std::move(data), worker, reply, + onFinished = std::move(onFinished)]() mutable { + if (data.caller == nullptr) { + QByteArray bytes = reply->readAll(); + data.writeToCache(bytes); + onFinished(bytes); - reply->deleteLater(); - } else { - emit worker->doneUrl(reply); - } + reply->deleteLater(); + } else { + emit worker->doneUrl(reply); + } - delete worker; - }); + delete worker; + }); }); emit requester.requestUrl(); @@ -276,7 +276,7 @@ public: template void getJSON(FinishedCallback onFinished) { - this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes)->bool { + this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) -> bool { auto object = parseJSONFromData(bytes); onFinished(object); @@ -289,7 +289,7 @@ public: template void getJSON2(FinishedCallback onFinished) { - this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes)->bool { + this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) -> bool { auto object = parseJSONFromData2(bytes); onFinished(object); @@ -303,22 +303,19 @@ public: { switch (this->data.requestType) { case GetRequest: { - debug::Log("Call GET request!"); this->executeGet(); } break; case PutRequest: { - debug::Log("Call PUT request!"); this->executePut(); } break; case DeleteRequest: { - debug::Log("Call DELETE request!"); this->executeDelete(); } break; default: { - debug::Log("Unhandled request type {}", (int)this->data.requestType); + debug::Log("[Execute] Unhandled request type {}", (int)this->data.requestType); } break; } } @@ -370,8 +367,8 @@ private: worker->moveToThread(&NetworkManager::workerThread); if (this->data.caller != nullptr) { - QObject::connect(worker, &NetworkWorker::doneUrl, - this->data.caller, [data = this->data](auto reply) mutable { + QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller, + [data = this->data](auto reply) mutable { if (reply->error() != QNetworkReply::NetworkError::NoError) { if (data.onError) { data.onError(reply->error()); @@ -394,7 +391,7 @@ private: } QObject::connect(&requester, &NetworkRequester::requestUrl, worker, - [ timer, data = std::move(this->data), worker ]() { + [timer, data = std::move(this->data), worker]() { QNetworkReply *reply = nullptr; switch (data.requestType) { case GetRequest: { @@ -430,7 +427,7 @@ private: } QObject::connect(reply, &QNetworkReply::finished, worker, - [ data = std::move(data), worker, reply ]() mutable { + [data = std::move(data), worker, reply]() mutable { if (data.caller == nullptr) { QByteArray bytes = reply->readAll(); data.writeToCache(bytes); diff --git a/src/widgets/emotepopup.cpp b/src/widgets/emotepopup.cpp index 3a15a67ee..fae47033d 100644 --- a/src/widgets/emotepopup.cpp +++ b/src/widgets/emotepopup.cpp @@ -92,7 +92,18 @@ void EmotePopup::loadChannel(ChannelPtr _channel) // TITLE messages::MessageBuilder builder1; - builder1.append(new TextElement("Twitch Account Emotes", MessageElement::Text)); + QString setText; + if (set->text.isEmpty()) { + if (set->channelName.isEmpty()) { + setText = "Twitch Account Emotes"; + } else { + setText = "Twitch Account Emotes (" + set->channelName + ")"; + } + } else { + setText = set->text; + } + + builder1.append(new TextElement(setText, MessageElement::Text)); builder1.getMessage()->flags |= Message::Centered; emoteChannel->addMessage(builder1.getMessage()); @@ -102,7 +113,7 @@ void EmotePopup::loadChannel(ChannelPtr _channel) builder2.getMessage()->flags |= Message::Centered; builder2.getMessage()->flags |= Message::DisableCompactEmotes; - for (const auto &emote : set.emotes) { + for (const auto &emote : set->emotes) { [&](const QString &key, const util::EmoteData &value) { builder2.append((new EmoteElement(value, MessageElement::Flags::AlwaysShow)) ->setLink(Link(Link::InsertText, key)));