diff --git a/chatterino.pro b/chatterino.pro index 309ce1a11..7dea2bbd2 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -114,6 +114,7 @@ SOURCES += \ src/providers/twitch/twitchmessagebuilder.cpp \ src/providers/twitch/twitchserver.cpp \ src/providers/twitch/pubsub.cpp \ + src/providers/twitch/twitchemotes.cpp \ src/singletons/commandmanager.cpp \ src/singletons/emotemanager.cpp \ src/singletons/fontmanager.cpp \ @@ -241,6 +242,7 @@ HEADERS += \ src/providers/twitch/twitchmessagebuilder.hpp \ src/providers/twitch/twitchserver.hpp \ src/providers/twitch/pubsub.hpp \ + src/providers/twitch/twitchemotes.hpp \ src/singletons/commandmanager.hpp \ src/singletons/emotemanager.hpp \ src/singletons/fontmanager.hpp \ diff --git a/src/providers/twitch/twitchemotes.cpp b/src/providers/twitch/twitchemotes.cpp new file mode 100644 index 000000000..879424a7e --- /dev/null +++ b/src/providers/twitch/twitchemotes.cpp @@ -0,0 +1,114 @@ +#include "providers/twitch/twitchemotes.hpp" + +#include "debug/log.hpp" +#include "messages/image.hpp" +#include "util/urlfetch.hpp" + +#define TWITCH_EMOTE_TEMPLATE "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}" + +namespace chatterino { +namespace providers { +namespace twitch { + +namespace { + +QString getEmoteLink(long id, const QString &emoteScale) +{ + QString value = TWITCH_EMOTE_TEMPLATE; + + value.detach(); + + return value.replace("{id}", QString::number(id)).replace("{scale}", emoteScale); +} + +} // namespace + +// 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(long id, const QString &emoteName) +{ + QString _emoteName = emoteName; + _emoteName.replace("<", "<"); + _emoteName.replace(">", ">"); + + // clang-format off + static QMap emoteNameReplacements{ + {"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("}, {"\\<\\;3", "<3"}, + {"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"}, + {"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?\\)", ":)"}, + {"\\:-?D", ":D"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"}, + {"R-?\\)", "R)"}, {"B-?\\)", "B)"}, + }; + // clang-format on + + auto it = emoteNameReplacements.find(_emoteName); + if (it != emoteNameReplacements.end()) { + _emoteName = it.value(); + } + + return _twitchEmoteFromCache.getOrAdd(id, [&emoteName, &_emoteName, &id] { + util::EmoteData newEmoteData; + newEmoteData.image1x = new messages::Image(getEmoteLink(id, "1.0"), 1, emoteName, + _emoteName + "
Twitch Emote 1x"); + newEmoteData.image2x = new messages::Image(getEmoteLink(id, "2.0"), .5, emoteName, + _emoteName + "
Twitch Emote 2x"); + newEmoteData.image3x = new messages::Image(getEmoteLink(id, "3.0"), .25, emoteName, + _emoteName + "
Twitch Emote 3x"); + + return newEmoteData; + }); +} + +void TwitchEmotes::refresh(const std::shared_ptr &user) +{ + debug::Log("Loading Twitch emotes for user {}", user->getUserName()); + + const auto &roomID = user->getUserId(); + const auto &clientID = user->getOAuthClient(); + const auto &oauthToken = user->getOAuthToken(); + + if (clientID.isEmpty() || oauthToken.isEmpty()) { + debug::Log("Missing Client ID or OAuth token"); + return; + } + + TwitchAccountEmoteData &emoteData = this->emotes[roomID.toStdString()]; + + if (emoteData.filled) { + qDebug() << "Already loaded for room id " << roomID; + return; + } + + QString url("https://api.twitch.tv/kraken/users/" + roomID + "/emotes"); + + util::twitch::getAuthorized( + url, clientID, oauthToken, QThread::currentThread(), + [=, &emoteData](const QJsonObject &root) { + emoteData.emoteSets.clear(); + emoteData.emoteCodes.clear(); + + auto emoticonSets = root.value("emoticon_sets").toObject(); + for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) { + std::string emoteSetString = it.key().toStdString(); + QJsonArray emoteSetList = it.value().toArray(); + + for (QJsonValue emoteValue : emoteSetList) { + QJsonObject emoticon = emoteValue.toObject(); + std::string id = QString::number(emoticon["id"].toInt()).toStdString(); + std::string code = emoticon["code"].toString().toStdString(); + emoteData.emoteSets[emoteSetString].push_back({id, code}); + emoteData.emoteCodes.push_back(code); + + util::EmoteData emote = + this->getEmoteById(emoticon["id"].toInt(), emoticon["code"].toString()); + emoteData.emotes.insert(emoticon["code"].toString(), emote); + } + } + + emoteData.filled = true; + }); +} + +} // namespace twitch +} // namespace providers +} // namespace chatterino diff --git a/src/providers/twitch/twitchemotes.hpp b/src/providers/twitch/twitchemotes.hpp new file mode 100644 index 000000000..57f9595b4 --- /dev/null +++ b/src/providers/twitch/twitchemotes.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "providers/twitch/emotevalue.hpp" +#include "providers/twitch/twitchaccount.hpp" +#include "providers/twitch/twitchemotes.hpp" +#include "util/concurrentmap.hpp" +#include "util/emotemap.hpp" + +#include + +namespace chatterino { +namespace providers { +namespace twitch { + +class TwitchEmotes +{ +public: + util::EmoteData getEmoteById(long int id, const QString &emoteName); + + /// Twitch emotes + void refresh(const std::shared_ptr &user); + + struct TwitchAccountEmoteData { + struct TwitchEmote { + std::string id; + std::string code; + }; + + // emote set + std::map> emoteSets; + + std::vector emoteCodes; + + util::EmoteMap emotes; + + bool filled = false; + }; + + std::map emotes; + +private: + // emote code + util::ConcurrentMap _twitchEmotes; + + // emote id + util::ConcurrentMap _twitchEmoteFromCache; +}; + +} // namespace twitch +} // namespace providers +} // namespace chatterino diff --git a/src/providers/twitch/twitchmessagebuilder.cpp b/src/providers/twitch/twitchmessagebuilder.cpp index 986317043..d55aaba9b 100644 --- a/src/providers/twitch/twitchmessagebuilder.cpp +++ b/src/providers/twitch/twitchmessagebuilder.cpp @@ -516,8 +516,8 @@ void TwitchMessageBuilder::appendTwitchEmote(const Communi::IrcMessage *ircMessa QString name = this->originalMessage.mid(start, end - start + 1); - vec.push_back( - std::pair(start, app->emotes->getTwitchEmoteById(id, name))); + vec.push_back(std::pair( + start, app->emotes->twitch.getEmoteById(id, name))); } } diff --git a/src/singletons/emotemanager.cpp b/src/singletons/emotemanager.cpp index 7bcb634c6..eabf29dda 100644 --- a/src/singletons/emotemanager.cpp +++ b/src/singletons/emotemanager.cpp @@ -18,8 +18,6 @@ #include -#define TWITCH_EMOTE_TEMPLATE "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}" - using namespace chatterino::providers::twitch; using namespace chatterino::messages; @@ -28,15 +26,6 @@ namespace singletons { namespace { -QString GetTwitchEmoteLink(long id, const QString &emoteScale) -{ - QString value = TWITCH_EMOTE_TEMPLATE; - - value.detach(); - - return value.replace("{id}", QString::number(id)).replace("{scale}", emoteScale); -} - QString GetBTTVEmoteLink(QString urlTemplate, const QString &id, const QString &emoteScale) { urlTemplate.detach(); @@ -89,7 +78,7 @@ void EmoteManager::initialize() getApp()->accounts->twitch.currentUserChanged.connect([this] { auto currentUser = getApp()->accounts->twitch.getCurrent(); assert(currentUser); - this->refreshTwitchEmotes(currentUser); + this->twitch.refresh(currentUser); }); this->loadEmojis(); @@ -411,56 +400,6 @@ QString EmoteManager::replaceShortCodes(const QString &text) return ret; } -void EmoteManager::refreshTwitchEmotes(const std::shared_ptr &user) -{ - debug::Log("Loading Twitch emotes for user {}", user->getUserName()); - - const auto &roomID = user->getUserId(); - const auto &clientID = user->getOAuthClient(); - const auto &oauthToken = user->getOAuthToken(); - - if (clientID.isEmpty() || oauthToken.isEmpty()) { - debug::Log("Missing Client ID or OAuth token"); - return; - } - - TwitchAccountEmoteData &emoteData = this->twitchAccountEmotes[roomID.toStdString()]; - - if (emoteData.filled) { - qDebug() << "Already loaded for room id " << roomID; - return; - } - - QString url("https://api.twitch.tv/kraken/users/" + roomID + "/emotes"); - - util::twitch::getAuthorized( - url, clientID, oauthToken, QThread::currentThread(), - [=, &emoteData](const QJsonObject &root) { - emoteData.emoteSets.clear(); - emoteData.emoteCodes.clear(); - - auto emoticonSets = root.value("emoticon_sets").toObject(); - for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) { - std::string emoteSetString = it.key().toStdString(); - QJsonArray emoteSetList = it.value().toArray(); - - for (QJsonValue emoteValue : emoteSetList) { - QJsonObject emoticon = emoteValue.toObject(); - std::string id = QString::number(emoticon["id"].toInt()).toStdString(); - std::string code = emoticon["code"].toString().toStdString(); - emoteData.emoteSets[emoteSetString].push_back({id, code}); - emoteData.emoteCodes.push_back(code); - - util::EmoteData emote = - getTwitchEmoteById(emoticon["id"].toInt(), emoticon["code"].toString()); - emoteData.emotes.insert(emoticon["code"].toString(), emote); - } - } - - emoteData.filled = true; - }); -} - void EmoteManager::loadBTTVEmotes() { QString url("https://api.betterttv.net/2/emotes"); @@ -531,42 +470,6 @@ void EmoteManager::loadFFZEmotes() }); } -// id is used for lookup -// emoteName is used for giving a name to the emote in case it doesn't exist -util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteName) -{ - QString _emoteName = emoteName; - _emoteName.replace("<", "<"); - _emoteName.replace(">", ">"); - - // clang-format off - static QMap emoteNameReplacements{ - {"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("}, {"\\<\\;3", "<3"}, - {"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"}, - {"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?\\)", ":)"}, - {"\\:-?D", ":D"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"}, - {"R-?\\)", "R)"}, {"B-?\\)", "B)"}, - }; - // clang-format on - - auto it = emoteNameReplacements.find(_emoteName); - if (it != emoteNameReplacements.end()) { - _emoteName = it.value(); - } - - return _twitchEmoteFromCache.getOrAdd(id, [&emoteName, &_emoteName, &id] { - util::EmoteData newEmoteData; - newEmoteData.image1x = new Image(GetTwitchEmoteLink(id, "1.0"), 1, emoteName, - _emoteName + "
Twitch Emote 1x"); - newEmoteData.image2x = new Image(GetTwitchEmoteLink(id, "2.0"), .5, emoteName, - _emoteName + "
Twitch Emote 2x"); - newEmoteData.image3x = new Image(GetTwitchEmoteLink(id, "3.0"), .25, emoteName, - _emoteName + "
Twitch Emote 3x"); - - return newEmoteData; - }); -} - util::EmoteData EmoteManager::getCheerImage(long long amount, bool animated) { // TODO: fix this xD diff --git a/src/singletons/emotemanager.hpp b/src/singletons/emotemanager.hpp index a4d6806ff..53da37e1f 100644 --- a/src/singletons/emotemanager.hpp +++ b/src/singletons/emotemanager.hpp @@ -4,8 +4,7 @@ #include "emojis.hpp" #include "messages/image.hpp" -#include "providers/twitch/emotevalue.hpp" -#include "providers/twitch/twitchaccount.hpp" +#include "providers/twitch/twitchemotes.hpp" #include "signalvector.hpp" #include "util/concurrentmap.hpp" #include "util/emotemap.hpp" @@ -27,6 +26,8 @@ public: ~EmoteManager() = delete; + providers::twitch::TwitchEmotes twitch; + void initialize(); void reloadBTTVChannelEmotes(const QString &channelName, @@ -42,8 +43,6 @@ public: util::EmoteData getCheerImage(long long int amount, bool animated); - util::EmoteData getTwitchEmoteById(long int id, const QString &emoteName); - pajlada::Signals::NoArgSignal &getGifUpdateSignal(); // Bit badge/emotes? @@ -71,34 +70,6 @@ public: std::vector emojiShortCodes; - /// Twitch emotes - void refreshTwitchEmotes(const std::shared_ptr &user); - - struct TwitchAccountEmoteData { - struct TwitchEmote { - std::string id; - std::string code; - }; - - // emote set - std::map> emoteSets; - - std::vector emoteCodes; - - util::EmoteMap emotes; - - bool filled = false; - }; - - std::map twitchAccountEmotes; - -private: - // emote code - util::ConcurrentMap _twitchEmotes; - - // emote id - util::ConcurrentMap _twitchEmoteFromCache; - /// BTTV emotes util::EmoteMap bttvChannelEmotes; diff --git a/src/util/completionmodel.cpp b/src/util/completionmodel.cpp index 65bec3b12..1870f0cd0 100644 --- a/src/util/completionmodel.cpp +++ b/src/util/completionmodel.cpp @@ -24,7 +24,7 @@ void CompletionModel::refresh() // User-specific: Twitch Emotes // TODO: Fix this so it properly updates with the proper api. oauth token needs proper scope - for (const auto &m : app->emotes->twitchAccountEmotes) { + for (const auto &m : app->emotes->twitch.emotes) { for (const auto &emoteName : m.second.emoteCodes) { // XXX: No way to discern between a twitch global emote and sub emote right now this->addString(emoteName, TaggedString::Type::TwitchGlobalEmote); diff --git a/src/widgets/emotepopup.cpp b/src/widgets/emotepopup.cpp index 4449cc875..7c100e2af 100644 --- a/src/widgets/emotepopup.cpp +++ b/src/widgets/emotepopup.cpp @@ -88,7 +88,7 @@ void EmotePopup::loadChannel(ChannelPtr _channel) // fourtf: the entire emote manager needs to be refactored so there's no point in trying to // fix this pile of garbage - for (const auto &set : app->emotes->twitchAccountEmotes[userID.toStdString()].emoteSets) { + for (const auto &set : app->emotes->twitch.emotes[userID.toStdString()].emoteSets) { // TITLE messages::MessageBuilder builder1; @@ -107,8 +107,8 @@ void EmotePopup::loadChannel(ChannelPtr _channel) builder2.append((new EmoteElement(value, MessageElement::Flags::AlwaysShow)) ->setLink(Link(Link::InsertText, key))); }(QString::fromStdString(emote.code), - app->emotes->getTwitchEmoteById(QString::fromStdString(emote.id).toLong(), - QString::fromStdString(emote.code))); + app->emotes->twitch.getEmoteById(QString::fromStdString(emote.id).toLong(), + QString::fromStdString(emote.code))); } emoteChannel->addMessage(builder2.getMessage());