From 256a65a12e956ec70c51e22f1748bed64c572051 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 7 Sep 2019 13:48:30 +0200 Subject: [PATCH] Load mod badge information from the FFZ API instead of assuming the URL. This lets us load all sizes of the emote if they are available. Channel with all versions of the mod badge: https://api.frankerfacez.com/v1/room/pajlada Channel with only one version of the mod badge: https://api.frankerfacez.com/v1/room/apa420 Channel with no mod badge: https://api.frankerfacez.com/v1/room/forsen --- chatterino.pro | 2 - src/messages/Image.cpp | 10 +++ src/messages/Image.hpp | 8 +++ src/providers/ffz/FfzEmotes.cpp | 91 +++++++++++++++++++++--- src/providers/ffz/FfzEmotes.hpp | 6 +- src/providers/ffz/FfzModBadge.cpp | 63 ---------------- src/providers/ffz/FfzModBadge.hpp | 25 ------- src/providers/twitch/ChatroomChannel.cpp | 12 +++- src/providers/twitch/TwitchChannel.cpp | 16 +++-- src/providers/twitch/TwitchChannel.hpp | 3 +- 10 files changed, 123 insertions(+), 113 deletions(-) delete mode 100644 src/providers/ffz/FfzModBadge.cpp delete mode 100644 src/providers/ffz/FfzModBadge.hpp diff --git a/chatterino.pro b/chatterino.pro index bf6c49159..b272506c9 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -127,7 +127,6 @@ SOURCES += \ src/providers/chatterino/ChatterinoBadges.cpp \ src/providers/emoji/Emojis.cpp \ src/providers/ffz/FfzEmotes.cpp \ - src/providers/ffz/FfzModBadge.cpp \ src/providers/irc/AbstractIrcServer.cpp \ src/providers/irc/IrcAccount.cpp \ src/providers/irc/IrcChannel2.cpp \ @@ -293,7 +292,6 @@ HEADERS += \ src/providers/chatterino/ChatterinoBadges.hpp \ src/providers/emoji/Emojis.hpp \ src/providers/ffz/FfzEmotes.hpp \ - src/providers/ffz/FfzModBadge.hpp \ src/providers/irc/AbstractIrcServer.hpp \ src/providers/irc/IrcAccount.hpp \ src/providers/irc/IrcChannel2.hpp \ diff --git a/src/messages/Image.cpp b/src/messages/Image.cpp index a0c82418d..ef19d3702 100644 --- a/src/messages/Image.cpp +++ b/src/messages/Image.cpp @@ -349,6 +349,9 @@ void Image::actuallyLoad() if (!shared) return Failure; + if (shared->customOnSuccess_) + return shared->customOnSuccess_(shared, result); + auto data = result.getData(); // const cast since we are only reading from it @@ -393,4 +396,11 @@ bool Image::operator!=(const Image &other) const return !this->operator==(other); } +ImagePtr Image::setCustomOnSuccess( + std::function customOnSuccess) +{ + this->customOnSuccess_ = customOnSuccess; + return shared_from_this(); +} + } // namespace chatterino diff --git a/src/messages/Image.hpp b/src/messages/Image.hpp index 1cd30d81b..24af6a529 100644 --- a/src/messages/Image.hpp +++ b/src/messages/Image.hpp @@ -13,7 +13,9 @@ #include #include "common/Aliases.hpp" +#include "common/NetworkResult.hpp" #include "common/NullablePtr.hpp" +#include "common/Outcome.hpp" namespace chatterino { namespace detail { @@ -66,13 +68,18 @@ public: bool operator==(const Image &image) const; bool operator!=(const Image &image) const; + ImagePtr setCustomOnSuccess( + std::function onSuccess); + private: Image(); Image(const Url &url, qreal scale); Image(qreal scale); +public: void setPixmap(const QPixmap &pixmap); +private: void actuallyLoad(); Url url_{}; @@ -81,5 +88,6 @@ private: bool shouldLoad_{false}; std::unique_ptr frames_{}; QObject object_{}; + std::function customOnSuccess_{}; }; } // namespace chatterino diff --git a/src/providers/ffz/FfzEmotes.cpp b/src/providers/ffz/FfzEmotes.cpp index ca71f7a5b..3e1345545 100644 --- a/src/providers/ffz/FfzEmotes.cpp +++ b/src/providers/ffz/FfzEmotes.cpp @@ -1,6 +1,11 @@ #include "providers/ffz/FfzEmotes.hpp" +#include +#include #include +#include +#include +#include #include "common/NetworkRequest.hpp" #include "common/Outcome.hpp" @@ -10,6 +15,30 @@ namespace chatterino { namespace { + Outcome addModBadgeBackground(ImagePtr image, NetworkResult result) + { + auto data = result.getData(); + + QBuffer buffer(const_cast(&data)); + buffer.open(QIODevice::ReadOnly); + QImageReader reader(&buffer); + if (reader.imageCount() == 0) + return Failure; + + QPixmap badgeOverlay = QPixmap::fromImageReader(&reader); + QPixmap badgePixmap(badgeOverlay.width(), badgeOverlay.height()); + + // the default mod badge green color + badgePixmap.fill(QColor("#34AE0A")); + QPainter painter(&badgePixmap); + QRectF rect(0, 0, badgeOverlay.width(), badgeOverlay.height()); + painter.drawPixmap(rect, badgeOverlay, rect); + + image->setPixmap(badgePixmap); + + return Success; + } + Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale) { auto emote = urls.value(emoteScale); @@ -79,8 +108,42 @@ namespace { return {Success, std::move(emotes)}; } - std::pair parseChannelEmotes(const QJsonObject &jsonRoot) + std::tuple> + parseChannelResponse(const QJsonObject &jsonRoot) { + boost::optional modBadge; + + // Parse mod badge + auto room = jsonRoot.value("room").toObject(); + auto modUrls = room.value("mod_urls").toObject(); + if (!modUrls.isEmpty()) + { + auto modBadge1x = getEmoteLink(modUrls, "1"); + auto modBadge2x = getEmoteLink(modUrls, "2"); + auto modBadge3x = getEmoteLink(modUrls, "4"); + + auto modBadgeImageSet = ImageSet{ + Image::fromUrl(modBadge1x, 1) + ->setCustomOnSuccess(addModBadgeBackground), + modBadge2x.string.isEmpty() + ? Image::getEmpty() + : Image::fromUrl(modBadge2x, 0.5) + ->setCustomOnSuccess(addModBadgeBackground), + modBadge3x.string.isEmpty() + ? Image::getEmpty() + : Image::fromUrl(modBadge3x, 0.25) + ->setCustomOnSuccess(addModBadgeBackground), + }; + + modBadge = std::make_shared(Emote{ + {""}, + modBadgeImageSet, + Tooltip{"Twitch Channel Moderator"}, + modBadge1x, + }); + } + + // Parse emotes auto jsonSets = jsonRoot.value("sets").toObject(); auto emotes = EmoteMap(); @@ -110,7 +173,7 @@ namespace { } } - return {Success, std::move(emotes)}; + return {Success, std::move(emotes), modBadge}; } } // namespace @@ -151,19 +214,29 @@ void FfzEmotes::loadEmotes() .execute(); } -void FfzEmotes::loadChannel(const QString &channelId, - std::function callback) +void FfzEmotes::loadChannel( + const QString &channelId, std::function emoteCallback, + std::function)> modBadgeCallback) { log("[FFZEmotes] Reload FFZ Channel Emotes for channel {}\n", channelId); NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId) .timeout(20000) - .onSuccess([callback = std::move(callback)](auto result) -> Outcome { - auto pair = parseChannelEmotes(result.parseJson()); - if (pair.first) - callback(std::move(pair.second)); - return pair.first; + .onSuccess([emoteCallback = std::move(emoteCallback), + modBadgeCallback = + std::move(modBadgeCallback)](auto result) -> Outcome { + auto [success, emoteMap, modBadge] = + parseChannelResponse(result.parseJson()); + if (!success) + { + return success; + } + + emoteCallback(std::move(emoteMap)); + modBadgeCallback(std::move(modBadge)); + + return success; }) .onError([channelId](int result) { if (result == 203) diff --git a/src/providers/ffz/FfzEmotes.hpp b/src/providers/ffz/FfzEmotes.hpp index 94bdfcb17..297ac4257 100644 --- a/src/providers/ffz/FfzEmotes.hpp +++ b/src/providers/ffz/FfzEmotes.hpp @@ -22,8 +22,10 @@ public: std::shared_ptr emotes() const; boost::optional emote(const EmoteName &name) const; void loadEmotes(); - static void loadChannel(const QString &channelId, - std::function callback); + static void loadChannel( + const QString &channelId, + std::function emoteCallback, + std::function)> modBadgeCallback); private: Atomic> global_; diff --git a/src/providers/ffz/FfzModBadge.cpp b/src/providers/ffz/FfzModBadge.cpp deleted file mode 100644 index a41442c17..000000000 --- a/src/providers/ffz/FfzModBadge.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "FfzModBadge.hpp" - -#include -#include -#include -#include -#include - -#include "common/NetworkRequest.hpp" -#include "common/Outcome.hpp" -#include "messages/Emote.hpp" - -namespace chatterino { - -FfzModBadge::FfzModBadge(const QString &channelName) - : channelName_(channelName) -{ -} - -void FfzModBadge::loadCustomModBadge() -{ - static QString partialUrl("https://cdn.frankerfacez.com/room-badge/mod/"); - - QString url = partialUrl + channelName_ + "/1"; - NetworkRequest(url) - - .onSuccess([this, url](auto result) -> Outcome { - auto data = result.getData(); - - QBuffer buffer(const_cast(&data)); - buffer.open(QIODevice::ReadOnly); - QImageReader reader(&buffer); - if (reader.imageCount() == 0) - return Failure; - - QPixmap badgeOverlay = QPixmap::fromImageReader(&reader); - QPixmap badgePixmap(18, 18); - - // the default mod badge green color - badgePixmap.fill(QColor("#34AE0A")); - QPainter painter(&badgePixmap); - QRectF rect(0, 0, 18, 18); - painter.drawPixmap(rect, badgeOverlay, rect); - - auto emote = Emote{{""}, - ImageSet{Image::fromPixmap(badgePixmap)}, - Tooltip{"Twitch Channel Moderator"}, - Url{url}}; - - this->badge_ = std::make_shared(emote); - // getBadge.execute(); - - return Success; - }) - .execute(); -} - -EmotePtr FfzModBadge::badge() const -{ - return this->badge_; -} - -} // namespace chatterino diff --git a/src/providers/ffz/FfzModBadge.hpp b/src/providers/ffz/FfzModBadge.hpp deleted file mode 100644 index 859cb1a10..000000000 --- a/src/providers/ffz/FfzModBadge.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include - -namespace chatterino { - -struct Emote; -using EmotePtr = std::shared_ptr; - -class FfzModBadge -{ -public: - FfzModBadge(const QString &channelName); - - void loadCustomModBadge(); - - EmotePtr badge() const; - -private: - const QString channelName_; - EmotePtr badge_; -}; - -} // namespace chatterino diff --git a/src/providers/twitch/ChatroomChannel.cpp b/src/providers/twitch/ChatroomChannel.cpp index e87abbae4..d2f08d82c 100644 --- a/src/providers/twitch/ChatroomChannel.cpp +++ b/src/providers/twitch/ChatroomChannel.cpp @@ -48,9 +48,15 @@ void ChatroomChannel::refreshFFZChannelEmotes() { return; } - FfzEmotes::loadChannel(this->chatroomOwnerId, [this](auto &&emoteMap) { - this->ffzEmotes_.set(std::make_shared(std::move(emoteMap))); - }); + FfzEmotes::loadChannel( + this->chatroomOwnerId, + [this](auto &&emoteMap) { + this->ffzEmotes_.set( + std::make_shared(std::move(emoteMap))); + }, + [this](auto &&modBadge) { + this->ffzCustomModBadge_ = std::move(modBadge); + }); } const QString &ChatroomChannel::getDisplayName() const diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 97f3b6e06..379d3aed4 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -87,7 +87,6 @@ TwitchChannel::TwitchChannel(const QString &name, , globalFfz_(ffz) , bttvEmotes_(std::make_shared()) , ffzEmotes_(std::make_shared()) - , ffzCustomModBadge_(name) , mod_(false) { log("[TwitchChannel:{}] Opened", name); @@ -138,7 +137,6 @@ void TwitchChannel::initialize() { this->refreshChatters(); this->refreshBadges(); - this->ffzCustomModBadge_.loadCustomModBadge(); } bool TwitchChannel::isEmpty() const @@ -164,10 +162,17 @@ void TwitchChannel::refreshBTTVChannelEmotes() void TwitchChannel::refreshFFZChannelEmotes() { FfzEmotes::loadChannel( - this->roomId(), [this, weak = weakOf(this)](auto &&emoteMap) { + this->roomId(), + [this, weak = weakOf(this)](auto &&emoteMap) { if (auto shared = weak.lock()) this->ffzEmotes_.set( std::make_shared(std::move(emoteMap))); + }, + [this, weak = weakOf(this)](auto &&modBadge) { + if (auto shared = weak.lock()) + { + this->ffzCustomModBadge_ = std::move(modBadge); + } }); } @@ -824,10 +829,7 @@ boost::optional TwitchChannel::twitchBadge( boost::optional TwitchChannel::ffzCustomModBadge() const { - if (auto badge = this->ffzCustomModBadge_.badge()) - return badge; - - return boost::none; + return this->ffzCustomModBadge_; } } // namespace chatterino diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index e59c265ad..d582d7a79 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -6,7 +6,6 @@ #include "common/Outcome.hpp" #include "common/UniqueAccess.hpp" #include "common/UsernameSet.hpp" -#include "providers/ffz/FfzModBadge.hpp" #include "providers/twitch/TwitchEmotes.hpp" #include @@ -148,13 +147,13 @@ protected: FfzEmotes &globalFfz_; Atomic> bttvEmotes_; Atomic> ffzEmotes_; + boost::optional ffzCustomModBadge_; private: // Badges UniqueAccess>> badgeSets_; // "subscribers": { "0": ... "3": ... "6": ... UniqueAccess> cheerEmoteSets_; - FfzModBadge ffzCustomModBadge_; bool mod_ = false; bool vip_ = false;