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
- 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)

View file

@ -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 \

View file

@ -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<NotificationController>())
, twitch2(&this->emplace<TwitchIrcServer>())
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
, ffzBadges(&this->emplace<FfzBadges>())
, logging(&this->emplace<Logging>())
{
this->instance = this;

View file

@ -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{};

View file

@ -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)

View file

@ -26,91 +26,99 @@ using ImagePtr = std::shared_ptr<Image>;
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
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
{

View file

@ -17,7 +17,7 @@ struct Selection;
struct MessageLayoutContainer;
class MessageLayoutElement;
enum class MessageElementFlag;
enum class MessageElementFlag : int64_t;
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
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 "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<FfzBadgeElement>(*badge, MessageElementFlag::BadgeFfz,
color.get());
}
}
}
Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
{
if (this->bitsLeft == 0)

View file

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

View file

@ -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",

View file

@ -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);

View file

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

View file

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

View file

@ -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");