diff --git a/CHANGELOG.md b/CHANGELOG.md index daeabfe3..f653f7ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unversioned +- Minor: Added support for FrankerFaceZ badges. (#2101, part of #1658) - Minor: Added a navigation list to the settings and reordered them. - Major: Added "Channel Filters". See https://wiki.chatterino.com/Filters/ for how they work or how to configure them. (#1748, #2083) - Major: Added Streamer Mode configuration (under `Settings -> General`), where you can select which features of Chatterino should behave differently when you are in Streamer Mode. (#2001) diff --git a/chatterino.pro b/chatterino.pro index af25e7a3..dc8fe201 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -170,6 +170,7 @@ SOURCES += \ src/providers/chatterino/ChatterinoBadges.cpp \ src/providers/colors/ColorProvider.cpp \ src/providers/emoji/Emojis.cpp \ + src/providers/ffz/FfzBadges.cpp \ src/providers/ffz/FfzEmotes.cpp \ src/providers/irc/AbstractIrcServer.cpp \ src/providers/irc/Irc2.cpp \ @@ -396,6 +397,7 @@ HEADERS += \ src/providers/chatterino/ChatterinoBadges.hpp \ src/providers/colors/ColorProvider.hpp \ src/providers/emoji/Emojis.hpp \ + src/providers/ffz/FfzBadges.hpp \ src/providers/ffz/FfzEmotes.hpp \ src/providers/irc/AbstractIrcServer.hpp \ src/providers/irc/Irc2.hpp \ diff --git a/src/Application.cpp b/src/Application.cpp index cdfc469c..a44cbd05 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -11,6 +11,7 @@ #include "messages/MessageBuilder.hpp" #include "providers/bttv/BttvEmotes.hpp" #include "providers/chatterino/ChatterinoBadges.hpp" +#include "providers/ffz/FfzBadges.hpp" #include "providers/ffz/FfzEmotes.hpp" #include "providers/irc/Irc2.hpp" #include "providers/twitch/PubsubClient.hpp" @@ -55,6 +56,7 @@ Application::Application(Settings &_settings, Paths &_paths) , notifications(&this->emplace()) , twitch2(&this->emplace()) , chatterinoBadges(&this->emplace()) + , ffzBadges(&this->emplace()) , logging(&this->emplace()) { this->instance = this; diff --git a/src/Application.hpp b/src/Application.hpp index 0c0e54bc..4a900666 100644 --- a/src/Application.hpp +++ b/src/Application.hpp @@ -26,6 +26,7 @@ class Settings; class Fonts; class Toasts; class ChatterinoBadges; +class FfzBadges; class Application { @@ -57,6 +58,7 @@ public: NotificationController *const notifications{}; TwitchIrcServer *const twitch2{}; ChatterinoBadges *const chatterinoBadges{}; + FfzBadges *const ffzBadges{}; /*[[deprecated]]*/ Logging *const logging{}; diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp index e3318511..9c499a32 100644 --- a/src/messages/MessageElement.cpp +++ b/src/messages/MessageElement.cpp @@ -253,6 +253,24 @@ MessageLayoutElement *ModBadgeElement::makeImageLayoutElement( return element; } +// FFZ Badge +FfzBadgeElement::FfzBadgeElement(const EmotePtr &data, + MessageElementFlags flags_, QColor &color) + : BadgeElement(data, flags_) +{ + this->color = color; +} + +MessageLayoutElement *FfzBadgeElement::makeImageLayoutElement( + const ImagePtr &image, const QSize &size) +{ + auto element = + (new ImageWithBackgroundLayoutElement(*this, image, size, this->color)) + ->setLink(this->getLink()); + + return element; +} + // TEXT TextElement::TextElement(const QString &text, MessageElementFlags flags, const MessageColor &color, FontStyle style) diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp index 8da2bdf9..d3452dfd 100644 --- a/src/messages/MessageElement.hpp +++ b/src/messages/MessageElement.hpp @@ -26,91 +26,99 @@ using ImagePtr = std::shared_ptr; struct Emote; using EmotePtr = std::shared_ptr; -enum class MessageElementFlag { - None = 0, - Misc = (1 << 0), - Text = (1 << 1), +enum class MessageElementFlag : int64_t { + None = 0LL, + Misc = (1LL << 0), + Text = (1LL << 1), - Username = (1 << 2), - Timestamp = (1 << 3), + Username = (1LL << 2), + Timestamp = (1LL << 3), - TwitchEmoteImage = (1 << 4), - TwitchEmoteText = (1 << 5), + TwitchEmoteImage = (1LL << 4), + TwitchEmoteText = (1LL << 5), TwitchEmote = TwitchEmoteImage | TwitchEmoteText, - BttvEmoteImage = (1 << 6), - BttvEmoteText = (1 << 7), + BttvEmoteImage = (1LL << 6), + BttvEmoteText = (1LL << 7), BttvEmote = BttvEmoteImage | BttvEmoteText, - ChannelPointReward = (1 << 8), + ChannelPointReward = (1LL << 8), ChannelPointRewardImage = ChannelPointReward | TwitchEmoteImage, - FfzEmoteImage = (1 << 10), - FfzEmoteText = (1 << 11), + FfzEmoteImage = (1LL << 10), + FfzEmoteText = (1LL << 11), FfzEmote = FfzEmoteImage | FfzEmoteText, EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage, EmoteText = TwitchEmoteText | BttvEmoteText | FfzEmoteText, - BitsStatic = (1 << 12), - BitsAnimated = (1 << 13), + BitsStatic = (1LL << 12), + BitsAnimated = (1LL << 13), // Slot 1: Twitch // - Staff badge // - Admin badge // - Global Moderator badge - BadgeGlobalAuthority = (1 << 14), + BadgeGlobalAuthority = (1LL << 14), // Slot 2: Twitch // - Moderator badge // - Broadcaster badge - BadgeChannelAuthority = (1 << 15), + BadgeChannelAuthority = (1LL << 15), // Slot 3: Twitch // - Subscription badges - BadgeSubscription = (1 << 16), + BadgeSubscription = (1LL << 16), // Slot 4: Twitch // - Turbo badge // - Prime badge // - Bit badges // - Game badges - BadgeVanity = (1 << 17), + BadgeVanity = (1LL << 17), // Slot 5: Chatterino // - Chatterino developer badge // - Chatterino donator badge // - Chatterino top donator badge - BadgeChatterino = (1 << 18), + BadgeChatterino = (1LL << 18), + + // Slot 6: FrankerFaceZ + // - FFZ developer badge + // - FFZ bot badge + // - FFZ donator badge + BadgeFfz = (1LL << 32), Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription | - BadgeVanity | BadgeChatterino, + BadgeVanity | BadgeChatterino | BadgeFfz, - ChannelName = (1 << 19), + ChannelName = (1LL << 19), - BitsAmount = (1 << 20), + BitsAmount = (1LL << 20), - ModeratorTools = (1 << 21), + ModeratorTools = (1LL << 21), - EmojiImage = (1 << 23), - EmojiText = (1 << 24), + EmojiImage = (1LL << 23), + EmojiText = (1LL << 24), EmojiAll = EmojiImage | EmojiText, - AlwaysShow = (1 << 25), + AlwaysShow = (1LL << 25), // used in the ChannelView class to make the collapse buttons visible if // needed - Collapsed = (1 << 26), + Collapsed = (1LL << 26), // used for dynamic bold usernames - BoldUsername = (1 << 27), - NonBoldUsername = (1 << 28), + BoldUsername = (1LL << 27), + NonBoldUsername = (1LL << 28), // for links - LowercaseLink = (1 << 29), - OriginalLink = (1 << 30), + LowercaseLink = (1LL << 29), + OriginalLink = (1LL << 30), // ZeroWidthEmotes are emotes that are supposed to overlay over any pre-existing emotes // e.g. BTTV's SoSnowy during christmas season - ZeroWidthEmote = (1 << 31), + ZeroWidthEmote = (1LL << 31), + + // (1LL << 32) is used by BadgeFfz, it is next to BadgeChatterino Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage | BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text | @@ -267,6 +275,18 @@ protected: const QSize &size) override; }; +class FfzBadgeElement : public BadgeElement +{ +public: + FfzBadgeElement(const EmotePtr &data, MessageElementFlags flags_, + QColor &color); + +protected: + MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image, + const QSize &size) override; + QColor color; +}; + // contains a text, formated depending on the preferences class TimestampElement : public MessageElement { diff --git a/src/messages/layouts/MessageLayout.hpp b/src/messages/layouts/MessageLayout.hpp index 6048aca6..d097934d 100644 --- a/src/messages/layouts/MessageLayout.hpp +++ b/src/messages/layouts/MessageLayout.hpp @@ -17,7 +17,7 @@ struct Selection; struct MessageLayoutContainer; class MessageLayoutElement; -enum class MessageElementFlag; +enum class MessageElementFlag : int64_t; using MessageElementFlags = FlagsEnum; enum class MessageLayoutFlag : uint8_t { diff --git a/src/providers/ffz/FfzBadges.cpp b/src/providers/ffz/FfzBadges.cpp new file mode 100644 index 00000000..d6a5f842 --- /dev/null +++ b/src/providers/ffz/FfzBadges.cpp @@ -0,0 +1,89 @@ +#include "FfzBadges.hpp" + +#include +#include +#include +#include +#include "common/NetworkRequest.hpp" +#include "common/Outcome.hpp" +#include "messages/Emote.hpp" + +#include + +#include + +namespace chatterino { + +void FfzBadges::initialize(Settings &settings, Paths &paths) +{ + this->loadFfzBadges(); +} + +boost::optional FfzBadges::getBadge(const UserId &id) +{ + auto it = this->badgeMap.find(id.string); + if (it != this->badgeMap.end()) + { + return this->badges[it->second]; + } + return boost::none; +} +boost::optional FfzBadges::getBadgeColor(const UserId &id) +{ + auto badgeIt = this->badgeMap.find(id.string); + if (badgeIt != this->badgeMap.end()) + { + auto colorIt = this->colorMap.find(badgeIt->second); + if (colorIt != this->colorMap.end()) + { + return colorIt->second; + } + return boost::none; + } + return boost::none; +} + +void FfzBadges::loadFfzBadges() +{ + static QUrl url("https://api.frankerfacez.com/v1/badges/ids"); + + NetworkRequest(url) + .onSuccess([this](auto result) -> Outcome { + auto jsonRoot = result.parseJson(); + int index = 0; + for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray()) + { + auto jsonBadge = jsonBadge_.toObject(); + auto jsonUrls = jsonBadge.value("urls").toObject(); + + auto emote = Emote{ + EmoteName{}, + ImageSet{ + Url{QString("https:") + jsonUrls.value("1").toString()}, + Url{QString("https:") + jsonUrls.value("2").toString()}, + Url{QString("https:") + + jsonUrls.value("4").toString()}}, + Tooltip{jsonBadge.value("title").toString()}, Url{}}; + + this->badges.push_back( + std::make_shared(std::move(emote))); + this->colorMap[index] = + QColor(jsonBadge.value("color").toString()); + + auto badgeId = QString::number(jsonBadge.value("id").toInt()); + for (const auto &user : jsonRoot.value("users") + .toObject() + .value(badgeId) + .toArray()) + { + this->badgeMap[QString::number(user.toInt())] = index; + } + ++index; + } + + return Success; + }) + .execute(); +} + +} // namespace chatterino diff --git a/src/providers/ffz/FfzBadges.hpp b/src/providers/ffz/FfzBadges.hpp new file mode 100644 index 00000000..30a6857a --- /dev/null +++ b/src/providers/ffz/FfzBadges.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include "common/Aliases.hpp" + +#include +#include + +namespace chatterino { + +struct Emote; +using EmotePtr = std::shared_ptr; + +class FfzBadges : public Singleton +{ +public: + virtual void initialize(Settings &settings, Paths &paths) override; + FfzBadges() = default; + + boost::optional getBadge(const UserId &id); + boost::optional getBadgeColor(const UserId &id); + +private: + void loadFfzBadges(); + std::map badgeMap; + std::vector badges; + std::map colorMap; +}; + +} // namespace chatterino diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 722c6dd3..ee35c4ed 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -6,6 +6,7 @@ #include "controllers/ignores/IgnorePhrase.hpp" #include "messages/Message.hpp" #include "providers/chatterino/ChatterinoBadges.hpp" +#include "providers/ffz/FfzBadges.hpp" #include "providers/twitch/TwitchBadges.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchCommon.hpp" @@ -255,6 +256,7 @@ MessagePtr TwitchMessageBuilder::build() this->appendTwitchBadges(); this->appendChatterinoBadges(); + this->appendFfzBadges(); this->appendUsername(); @@ -1090,6 +1092,18 @@ void TwitchMessageBuilder::appendChatterinoBadges() } } +void TwitchMessageBuilder::appendFfzBadges() +{ + if (auto badge = getApp()->ffzBadges->getBadge({this->userId_})) + { + if (auto color = getApp()->ffzBadges->getBadgeColor({this->userId_})) + { + this->emplace(*badge, MessageElementFlag::BadgeFfz, + color.get()); + } + } +} + Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string) { if (this->bitsLeft == 0) diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index 5481832d..368b6529 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -75,6 +75,7 @@ private: void appendTwitchBadges(); void appendChatterinoBadges(); + void appendFfzBadges(); Outcome tryParseCheermote(const QString &string); QString roomID_; diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 60c30fd2..4d73fc5b 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -127,6 +127,7 @@ public: true}; BoolSetting showBadgesVanity = {"/appearance/badges/vanity", true}; BoolSetting showBadgesChatterino = {"/appearance/badges/chatterino", true}; + BoolSetting showBadgesFfz = {"/appearance/badges/ffz", true}; /// Behaviour BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp index adc9b8ed..07a4e7b0 100644 --- a/src/singletons/WindowManager.cpp +++ b/src/singletons/WindowManager.cpp @@ -93,6 +93,7 @@ WindowManager::WindowManager() this->wordFlagsListener_.addSetting(settings->showBadgesSubscription); this->wordFlagsListener_.addSetting(settings->showBadgesVanity); this->wordFlagsListener_.addSetting(settings->showBadgesChatterino); + this->wordFlagsListener_.addSetting(settings->showBadgesFfz); this->wordFlagsListener_.addSetting(settings->enableEmoteImages); this->wordFlagsListener_.addSetting(settings->boldUsernames); this->wordFlagsListener_.addSetting(settings->lowercaseDomains); @@ -156,6 +157,7 @@ void WindowManager::updateWordTypeMask() flags.set(settings->showBadgesVanity ? MEF::BadgeVanity : MEF::None); flags.set(settings->showBadgesChatterino ? MEF::BadgeChatterino : MEF::None); + flags.set(settings->showBadgesFfz ? MEF::BadgeFfz : MEF::None); // username flags.set(MEF::Username); diff --git a/src/singletons/WindowManager.hpp b/src/singletons/WindowManager.hpp index e6eb6371..c22896e3 100644 --- a/src/singletons/WindowManager.hpp +++ b/src/singletons/WindowManager.hpp @@ -14,7 +14,7 @@ class Paths; class Window; class SplitContainer; -enum class MessageElementFlag; +enum class MessageElementFlag : int64_t; using MessageElementFlags = FlagsEnum; enum class WindowType; diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 951a9d26..2585f4fa 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -32,7 +32,7 @@ using MessageFlags = FlagsEnum; class MessageLayout; using MessageLayoutPtr = std::shared_ptr; -enum class MessageElementFlag; +enum class MessageElementFlag : int64_t; using MessageElementFlags = FlagsEnum; class Scrollbar; diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index c0e15d3b..8c90609a 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -496,6 +496,8 @@ void GeneralPage::initLayout(GeneralPageView &layout) layout.addCheckbox("Vanity (prime, bits, subgifter)", getSettings()->showBadgesVanity); layout.addCheckbox("Chatterino", getSettings()->showBadgesChatterino); + layout.addCheckbox("FrankerFaceZ (Bot, FFZ Supporter, FFZ Developer)", + getSettings()->showBadgesFfz); layout.addSubtitle("Miscellaneous");