Add support FrankerFaceZ badges. (#2101)

On startup, we poll https://api.frankerfacez.com/v1/badges/ids and store the mappings of user IDs to badges for the remainder of the applications lifetime.
This commit is contained in:
Mm2PL 2020-10-25 09:36:00 +00:00 committed by GitHub
parent ec94869480
commit 3ee08b9ffd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 222 additions and 36 deletions

View file

@ -2,6 +2,7 @@
## Unversioned ## Unversioned
- Minor: Added support for FrankerFaceZ badges. (#2101, part of #1658)
- Minor: Added a navigation list to the settings and reordered them. - 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 "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) - 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)

View file

@ -170,6 +170,7 @@ SOURCES += \
src/providers/chatterino/ChatterinoBadges.cpp \ src/providers/chatterino/ChatterinoBadges.cpp \
src/providers/colors/ColorProvider.cpp \ src/providers/colors/ColorProvider.cpp \
src/providers/emoji/Emojis.cpp \ src/providers/emoji/Emojis.cpp \
src/providers/ffz/FfzBadges.cpp \
src/providers/ffz/FfzEmotes.cpp \ src/providers/ffz/FfzEmotes.cpp \
src/providers/irc/AbstractIrcServer.cpp \ src/providers/irc/AbstractIrcServer.cpp \
src/providers/irc/Irc2.cpp \ src/providers/irc/Irc2.cpp \
@ -396,6 +397,7 @@ HEADERS += \
src/providers/chatterino/ChatterinoBadges.hpp \ src/providers/chatterino/ChatterinoBadges.hpp \
src/providers/colors/ColorProvider.hpp \ src/providers/colors/ColorProvider.hpp \
src/providers/emoji/Emojis.hpp \ src/providers/emoji/Emojis.hpp \
src/providers/ffz/FfzBadges.hpp \
src/providers/ffz/FfzEmotes.hpp \ src/providers/ffz/FfzEmotes.hpp \
src/providers/irc/AbstractIrcServer.hpp \ src/providers/irc/AbstractIrcServer.hpp \
src/providers/irc/Irc2.hpp \ src/providers/irc/Irc2.hpp \

View file

@ -11,6 +11,7 @@
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include "providers/bttv/BttvEmotes.hpp" #include "providers/bttv/BttvEmotes.hpp"
#include "providers/chatterino/ChatterinoBadges.hpp" #include "providers/chatterino/ChatterinoBadges.hpp"
#include "providers/ffz/FfzBadges.hpp"
#include "providers/ffz/FfzEmotes.hpp" #include "providers/ffz/FfzEmotes.hpp"
#include "providers/irc/Irc2.hpp" #include "providers/irc/Irc2.hpp"
#include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/PubsubClient.hpp"
@ -55,6 +56,7 @@ Application::Application(Settings &_settings, Paths &_paths)
, notifications(&this->emplace<NotificationController>()) , notifications(&this->emplace<NotificationController>())
, twitch2(&this->emplace<TwitchIrcServer>()) , twitch2(&this->emplace<TwitchIrcServer>())
, chatterinoBadges(&this->emplace<ChatterinoBadges>()) , chatterinoBadges(&this->emplace<ChatterinoBadges>())
, ffzBadges(&this->emplace<FfzBadges>())
, logging(&this->emplace<Logging>()) , logging(&this->emplace<Logging>())
{ {
this->instance = this; this->instance = this;

View file

@ -26,6 +26,7 @@ class Settings;
class Fonts; class Fonts;
class Toasts; class Toasts;
class ChatterinoBadges; class ChatterinoBadges;
class FfzBadges;
class Application class Application
{ {
@ -57,6 +58,7 @@ public:
NotificationController *const notifications{}; NotificationController *const notifications{};
TwitchIrcServer *const twitch2{}; TwitchIrcServer *const twitch2{};
ChatterinoBadges *const chatterinoBadges{}; ChatterinoBadges *const chatterinoBadges{};
FfzBadges *const ffzBadges{};
/*[[deprecated]]*/ Logging *const logging{}; /*[[deprecated]]*/ Logging *const logging{};

View file

@ -253,6 +253,24 @@ MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
return element; 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 // TEXT
TextElement::TextElement(const QString &text, MessageElementFlags flags, TextElement::TextElement(const QString &text, MessageElementFlags flags,
const MessageColor &color, FontStyle style) const MessageColor &color, FontStyle style)

View file

@ -26,91 +26,99 @@ using ImagePtr = std::shared_ptr<Image>;
struct Emote; struct Emote;
using EmotePtr = std::shared_ptr<const Emote>; using EmotePtr = std::shared_ptr<const Emote>;
enum class MessageElementFlag { enum class MessageElementFlag : int64_t {
None = 0, None = 0LL,
Misc = (1 << 0), Misc = (1LL << 0),
Text = (1 << 1), Text = (1LL << 1),
Username = (1 << 2), Username = (1LL << 2),
Timestamp = (1 << 3), Timestamp = (1LL << 3),
TwitchEmoteImage = (1 << 4), TwitchEmoteImage = (1LL << 4),
TwitchEmoteText = (1 << 5), TwitchEmoteText = (1LL << 5),
TwitchEmote = TwitchEmoteImage | TwitchEmoteText, TwitchEmote = TwitchEmoteImage | TwitchEmoteText,
BttvEmoteImage = (1 << 6), BttvEmoteImage = (1LL << 6),
BttvEmoteText = (1 << 7), BttvEmoteText = (1LL << 7),
BttvEmote = BttvEmoteImage | BttvEmoteText, BttvEmote = BttvEmoteImage | BttvEmoteText,
ChannelPointReward = (1 << 8), ChannelPointReward = (1LL << 8),
ChannelPointRewardImage = ChannelPointReward | TwitchEmoteImage, ChannelPointRewardImage = ChannelPointReward | TwitchEmoteImage,
FfzEmoteImage = (1 << 10), FfzEmoteImage = (1LL << 10),
FfzEmoteText = (1 << 11), FfzEmoteText = (1LL << 11),
FfzEmote = FfzEmoteImage | FfzEmoteText, FfzEmote = FfzEmoteImage | FfzEmoteText,
EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage, EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage,
EmoteText = TwitchEmoteText | BttvEmoteText | FfzEmoteText, EmoteText = TwitchEmoteText | BttvEmoteText | FfzEmoteText,
BitsStatic = (1 << 12), BitsStatic = (1LL << 12),
BitsAnimated = (1 << 13), BitsAnimated = (1LL << 13),
// Slot 1: Twitch // Slot 1: Twitch
// - Staff badge // - Staff badge
// - Admin badge // - Admin badge
// - Global Moderator badge // - Global Moderator badge
BadgeGlobalAuthority = (1 << 14), BadgeGlobalAuthority = (1LL << 14),
// Slot 2: Twitch // Slot 2: Twitch
// - Moderator badge // - Moderator badge
// - Broadcaster badge // - Broadcaster badge
BadgeChannelAuthority = (1 << 15), BadgeChannelAuthority = (1LL << 15),
// Slot 3: Twitch // Slot 3: Twitch
// - Subscription badges // - Subscription badges
BadgeSubscription = (1 << 16), BadgeSubscription = (1LL << 16),
// Slot 4: Twitch // Slot 4: Twitch
// - Turbo badge // - Turbo badge
// - Prime badge // - Prime badge
// - Bit badges // - Bit badges
// - Game badges // - Game badges
BadgeVanity = (1 << 17), BadgeVanity = (1LL << 17),
// Slot 5: Chatterino // Slot 5: Chatterino
// - Chatterino developer badge // - Chatterino developer badge
// - Chatterino donator badge // - Chatterino donator badge
// - Chatterino top 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 | 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), EmojiImage = (1LL << 23),
EmojiText = (1 << 24), EmojiText = (1LL << 24),
EmojiAll = EmojiImage | EmojiText, EmojiAll = EmojiImage | EmojiText,
AlwaysShow = (1 << 25), AlwaysShow = (1LL << 25),
// used in the ChannelView class to make the collapse buttons visible if // used in the ChannelView class to make the collapse buttons visible if
// needed // needed
Collapsed = (1 << 26), Collapsed = (1LL << 26),
// used for dynamic bold usernames // used for dynamic bold usernames
BoldUsername = (1 << 27), BoldUsername = (1LL << 27),
NonBoldUsername = (1 << 28), NonBoldUsername = (1LL << 28),
// for links // for links
LowercaseLink = (1 << 29), LowercaseLink = (1LL << 29),
OriginalLink = (1 << 30), OriginalLink = (1LL << 30),
// ZeroWidthEmotes are emotes that are supposed to overlay over any pre-existing emotes // ZeroWidthEmotes are emotes that are supposed to overlay over any pre-existing emotes
// e.g. BTTV's SoSnowy during christmas season // 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 | Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage |
BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text | BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text |
@ -267,6 +275,18 @@ protected:
const QSize &size) override; 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 // contains a text, formated depending on the preferences
class TimestampElement : public MessageElement class TimestampElement : public MessageElement
{ {

View file

@ -17,7 +17,7 @@ struct Selection;
struct MessageLayoutContainer; struct MessageLayoutContainer;
class MessageLayoutElement; class MessageLayoutElement;
enum class MessageElementFlag; enum class MessageElementFlag : int64_t;
using MessageElementFlags = FlagsEnum<MessageElementFlag>; using MessageElementFlags = FlagsEnum<MessageElementFlag>;
enum class MessageLayoutFlag : uint8_t { enum class MessageLayoutFlag : uint8_t {

View file

@ -0,0 +1,89 @@
#include "FfzBadges.hpp"
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QThread>
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "messages/Emote.hpp"
#include <QUrl>
#include <map>
namespace chatterino {
void FfzBadges::initialize(Settings &settings, Paths &paths)
{
this->loadFfzBadges();
}
boost::optional<EmotePtr> 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<QColor> 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<const Emote>(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

View file

@ -0,0 +1,32 @@
#pragma once
#include <boost/optional.hpp>
#include <common/Singleton.hpp>
#include "common/Aliases.hpp"
#include <map>
#include <vector>
namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class FfzBadges : public Singleton
{
public:
virtual void initialize(Settings &settings, Paths &paths) override;
FfzBadges() = default;
boost::optional<EmotePtr> getBadge(const UserId &id);
boost::optional<QColor> getBadgeColor(const UserId &id);
private:
void loadFfzBadges();
std::map<QString, int> badgeMap;
std::vector<EmotePtr> badges;
std::map<int, QColor> colorMap;
};
} // namespace chatterino

View file

@ -6,6 +6,7 @@
#include "controllers/ignores/IgnorePhrase.hpp" #include "controllers/ignores/IgnorePhrase.hpp"
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "providers/chatterino/ChatterinoBadges.hpp" #include "providers/chatterino/ChatterinoBadges.hpp"
#include "providers/ffz/FfzBadges.hpp"
#include "providers/twitch/TwitchBadges.hpp" #include "providers/twitch/TwitchBadges.hpp"
#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchCommon.hpp"
@ -255,6 +256,7 @@ MessagePtr TwitchMessageBuilder::build()
this->appendTwitchBadges(); this->appendTwitchBadges();
this->appendChatterinoBadges(); this->appendChatterinoBadges();
this->appendFfzBadges();
this->appendUsername(); 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<FfzBadgeElement>(*badge, MessageElementFlag::BadgeFfz,
color.get());
}
}
}
Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string) Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
{ {
if (this->bitsLeft == 0) if (this->bitsLeft == 0)

View file

@ -75,6 +75,7 @@ private:
void appendTwitchBadges(); void appendTwitchBadges();
void appendChatterinoBadges(); void appendChatterinoBadges();
void appendFfzBadges();
Outcome tryParseCheermote(const QString &string); Outcome tryParseCheermote(const QString &string);
QString roomID_; QString roomID_;

View file

@ -127,6 +127,7 @@ public:
true}; true};
BoolSetting showBadgesVanity = {"/appearance/badges/vanity", true}; BoolSetting showBadgesVanity = {"/appearance/badges/vanity", true};
BoolSetting showBadgesChatterino = {"/appearance/badges/chatterino", true}; BoolSetting showBadgesChatterino = {"/appearance/badges/chatterino", true};
BoolSetting showBadgesFfz = {"/appearance/badges/ffz", true};
/// Behaviour /// Behaviour
BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages",

View file

@ -93,6 +93,7 @@ WindowManager::WindowManager()
this->wordFlagsListener_.addSetting(settings->showBadgesSubscription); this->wordFlagsListener_.addSetting(settings->showBadgesSubscription);
this->wordFlagsListener_.addSetting(settings->showBadgesVanity); this->wordFlagsListener_.addSetting(settings->showBadgesVanity);
this->wordFlagsListener_.addSetting(settings->showBadgesChatterino); this->wordFlagsListener_.addSetting(settings->showBadgesChatterino);
this->wordFlagsListener_.addSetting(settings->showBadgesFfz);
this->wordFlagsListener_.addSetting(settings->enableEmoteImages); this->wordFlagsListener_.addSetting(settings->enableEmoteImages);
this->wordFlagsListener_.addSetting(settings->boldUsernames); this->wordFlagsListener_.addSetting(settings->boldUsernames);
this->wordFlagsListener_.addSetting(settings->lowercaseDomains); this->wordFlagsListener_.addSetting(settings->lowercaseDomains);
@ -156,6 +157,7 @@ void WindowManager::updateWordTypeMask()
flags.set(settings->showBadgesVanity ? MEF::BadgeVanity : MEF::None); flags.set(settings->showBadgesVanity ? MEF::BadgeVanity : MEF::None);
flags.set(settings->showBadgesChatterino ? MEF::BadgeChatterino flags.set(settings->showBadgesChatterino ? MEF::BadgeChatterino
: MEF::None); : MEF::None);
flags.set(settings->showBadgesFfz ? MEF::BadgeFfz : MEF::None);
// username // username
flags.set(MEF::Username); flags.set(MEF::Username);

View file

@ -14,7 +14,7 @@ class Paths;
class Window; class Window;
class SplitContainer; class SplitContainer;
enum class MessageElementFlag; enum class MessageElementFlag : int64_t;
using MessageElementFlags = FlagsEnum<MessageElementFlag>; using MessageElementFlags = FlagsEnum<MessageElementFlag>;
enum class WindowType; enum class WindowType;

View file

@ -32,7 +32,7 @@ using MessageFlags = FlagsEnum<MessageFlag>;
class MessageLayout; class MessageLayout;
using MessageLayoutPtr = std::shared_ptr<MessageLayout>; using MessageLayoutPtr = std::shared_ptr<MessageLayout>;
enum class MessageElementFlag; enum class MessageElementFlag : int64_t;
using MessageElementFlags = FlagsEnum<MessageElementFlag>; using MessageElementFlags = FlagsEnum<MessageElementFlag>;
class Scrollbar; class Scrollbar;

View file

@ -496,6 +496,8 @@ void GeneralPage::initLayout(GeneralPageView &layout)
layout.addCheckbox("Vanity (prime, bits, subgifter)", layout.addCheckbox("Vanity (prime, bits, subgifter)",
getSettings()->showBadgesVanity); getSettings()->showBadgesVanity);
layout.addCheckbox("Chatterino", getSettings()->showBadgesChatterino); layout.addCheckbox("Chatterino", getSettings()->showBadgesChatterino);
layout.addCheckbox("FrankerFaceZ (Bot, FFZ Supporter, FFZ Developer)",
getSettings()->showBadgesFfz);
layout.addSubtitle("Miscellaneous"); layout.addSubtitle("Miscellaneous");