diff --git a/chatterino.pro b/chatterino.pro index bf6c4915..b272506c 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 a0c82418..ef19d370 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 1cd30d81..24af6a52 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 ca71f7a5..3e134554 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 94bdfcb1..297ac425 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 a41442c1..00000000 --- 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 859cb1a1..00000000 --- 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 e87abbae..d2f08d82 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 97f3b6e0..379d3aed 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 e59c265a..d582d7a7 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;