mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
feat: Show FrankerFaceZ channel badges (#5119)
This commit is contained in:
parent
2815c7b67d
commit
101dc82ea0
|
@ -16,6 +16,7 @@
|
|||
- Minor: All sound capabilities can now be disabled by setting your "Sound backend" setting to "Null" and restarting Chatterino. (#4978)
|
||||
- Minor: Add an option to use new experimental smarter emote completion. (#4987)
|
||||
- Minor: Add `--safe-mode` command line option that can be used for troubleshooting when Chatterino is misbehaving or is misconfigured. It disables hiding the settings button & prevents plugins from loading. (#4985)
|
||||
- Minor: Added support for FrankerFaceZ channel badges. These can be configured at https://www.frankerfacez.com/channel/mine - right now only supporting bot badges for your chat bots. (#5119)
|
||||
- Minor: Updated the flatpakref link included with nightly builds to point to up-to-date flathub-beta builds. (#5008)
|
||||
- Minor: Add a new completion API for experimental plugins feature. (#5000, #5047)
|
||||
- Minor: Re-enabled _Restart on crash_ option on Windows. (#5012)
|
||||
|
|
|
@ -42,8 +42,9 @@ std::vector<FfzBadges::Badge> FfzBadges::getUserBadges(const UserId &id)
|
|||
return badges;
|
||||
}
|
||||
|
||||
std::optional<FfzBadges::Badge> FfzBadges::getBadge(const int badgeID)
|
||||
std::optional<FfzBadges::Badge> FfzBadges::getBadge(const int badgeID) const
|
||||
{
|
||||
this->tgBadges.guard();
|
||||
auto it = this->badges.find(badgeID);
|
||||
if (it != this->badges.end())
|
||||
{
|
||||
|
@ -62,6 +63,7 @@ void FfzBadges::load()
|
|||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
auto jsonRoot = result.parseJson();
|
||||
this->tgBadges.guard();
|
||||
for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray())
|
||||
{
|
||||
auto jsonBadge = jsonBadge_.toObject();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "common/Aliases.hpp"
|
||||
#include "common/Singleton.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
#include "util/ThreadGuard.hpp"
|
||||
|
||||
#include <QColor>
|
||||
|
||||
|
@ -30,10 +31,9 @@ public:
|
|||
};
|
||||
|
||||
std::vector<Badge> getUserBadges(const UserId &id);
|
||||
std::optional<Badge> getBadge(int badgeID) const;
|
||||
|
||||
private:
|
||||
std::optional<Badge> getBadge(int badgeID);
|
||||
|
||||
void load();
|
||||
|
||||
std::shared_mutex mutex_;
|
||||
|
@ -43,6 +43,7 @@ private:
|
|||
|
||||
// badges points a badge ID to the information about the badge
|
||||
std::unordered_map<int, Badge> badges;
|
||||
ThreadGuard tgBadges;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -169,6 +169,33 @@ EmoteMap ffz::detail::parseChannelEmotes(const QJsonObject &jsonRoot)
|
|||
return emotes;
|
||||
}
|
||||
|
||||
FfzChannelBadgeMap ffz::detail::parseChannelBadges(const QJsonObject &badgeRoot)
|
||||
{
|
||||
FfzChannelBadgeMap channelBadges;
|
||||
|
||||
for (auto it = badgeRoot.begin(); it != badgeRoot.end(); ++it)
|
||||
{
|
||||
const auto badgeID = it.key().toInt();
|
||||
const auto &jsonUserIDs = it.value().toArray();
|
||||
for (const auto &jsonUserID : jsonUserIDs)
|
||||
{
|
||||
// NOTE: The Twitch User IDs come through as ints right now, the code below
|
||||
// tries to parse them as strings first since that's how we treat them anyway.
|
||||
if (jsonUserID.isString())
|
||||
{
|
||||
channelBadges[jsonUserID.toString()].emplace_back(badgeID);
|
||||
}
|
||||
else
|
||||
{
|
||||
channelBadges[QString::number(jsonUserID.toInt())].emplace_back(
|
||||
badgeID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return channelBadges;
|
||||
}
|
||||
|
||||
FfzEmotes::FfzEmotes()
|
||||
: global_(std::make_shared<EmoteMap>())
|
||||
{
|
||||
|
@ -220,6 +247,7 @@ void FfzEmotes::loadChannel(
|
|||
std::function<void(EmoteMap &&)> emoteCallback,
|
||||
std::function<void(std::optional<EmotePtr>)> modBadgeCallback,
|
||||
std::function<void(std::optional<EmotePtr>)> vipBadgeCallback,
|
||||
std::function<void(FfzChannelBadgeMap &&)> channelBadgesCallback,
|
||||
bool manualRefresh)
|
||||
{
|
||||
qCDebug(LOG) << "Reload FFZ Channel Emotes for channel" << channelID;
|
||||
|
@ -229,8 +257,9 @@ void FfzEmotes::loadChannel(
|
|||
.timeout(20000)
|
||||
.onSuccess([emoteCallback = std::move(emoteCallback),
|
||||
modBadgeCallback = std::move(modBadgeCallback),
|
||||
vipBadgeCallback = std::move(vipBadgeCallback), channel,
|
||||
manualRefresh](const auto &result) {
|
||||
vipBadgeCallback = std::move(vipBadgeCallback),
|
||||
channelBadgesCallback = std::move(channelBadgesCallback),
|
||||
channel, manualRefresh](const auto &result) {
|
||||
const auto json = result.parseJson();
|
||||
|
||||
auto emoteMap = parseChannelEmotes(json);
|
||||
|
@ -238,12 +267,15 @@ void FfzEmotes::loadChannel(
|
|||
json["room"]["mod_urls"].toObject(), "Moderator");
|
||||
auto vipBadge = parseAuthorityBadge(
|
||||
json["room"]["vip_badge"].toObject(), "VIP");
|
||||
auto channelBadges =
|
||||
parseChannelBadges(json["room"]["user_badge_ids"].toObject());
|
||||
|
||||
bool hasEmotes = !emoteMap.empty();
|
||||
|
||||
emoteCallback(std::move(emoteMap));
|
||||
modBadgeCallback(std::move(modBadge));
|
||||
vipBadgeCallback(std::move(vipBadge));
|
||||
channelBadgesCallback(std::move(channelBadges));
|
||||
if (auto shared = channel.lock(); manualRefresh)
|
||||
{
|
||||
if (hasEmotes)
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Atomic.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <memory>
|
||||
|
@ -15,10 +17,19 @@ using EmotePtr = std::shared_ptr<const Emote>;
|
|||
class EmoteMap;
|
||||
class Channel;
|
||||
|
||||
/// Maps a Twitch User ID to a list of badge IDs
|
||||
using FfzChannelBadgeMap =
|
||||
boost::unordered::unordered_flat_map<QString, std::vector<int>>;
|
||||
|
||||
namespace ffz::detail {
|
||||
|
||||
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot);
|
||||
|
||||
/**
|
||||
* Parse the `user_badge_ids` into a map of User IDs -> Badge IDs
|
||||
*/
|
||||
FfzChannelBadgeMap parseChannelBadges(const QJsonObject &badgeRoot);
|
||||
|
||||
} // namespace ffz::detail
|
||||
|
||||
class FfzEmotes final
|
||||
|
@ -35,6 +46,7 @@ public:
|
|||
std::function<void(EmoteMap &&)> emoteCallback,
|
||||
std::function<void(std::optional<EmotePtr>)> modBadgeCallback,
|
||||
std::function<void(std::optional<EmotePtr>)> vipBadgeCallback,
|
||||
std::function<void(FfzChannelBadgeMap &&)> channelBadgesCallback,
|
||||
bool manualRefresh);
|
||||
|
||||
private:
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "providers/bttv/BttvEmotes.hpp"
|
||||
#include "providers/bttv/BttvLiveUpdates.hpp"
|
||||
#include "providers/bttv/liveupdates/BttvLiveUpdateMessages.hpp"
|
||||
#include "providers/ffz/FfzBadges.hpp"
|
||||
#include "providers/ffz/FfzEmotes.hpp"
|
||||
#include "providers/recentmessages/Api.hpp"
|
||||
#include "providers/seventv/eventapi/Dispatch.hpp"
|
||||
|
@ -333,6 +334,14 @@ void TwitchChannel::refreshFFZChannelEmotes(bool manualRefresh)
|
|||
std::forward<decltype(vipBadge)>(vipBadge));
|
||||
}
|
||||
},
|
||||
[this, weak = weakOf<Channel>(this)](auto &&channelBadges) {
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
this->tgFfzChannelBadges_.guard();
|
||||
this->ffzChannelBadges_ =
|
||||
std::forward<decltype(channelBadges)>(channelBadges);
|
||||
}
|
||||
},
|
||||
manualRefresh);
|
||||
}
|
||||
|
||||
|
@ -1707,6 +1716,33 @@ std::optional<EmotePtr> TwitchChannel::twitchBadge(const QString &set,
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<FfzBadges::Badge> TwitchChannel::ffzChannelBadges(
|
||||
const QString &userID) const
|
||||
{
|
||||
this->tgFfzChannelBadges_.guard();
|
||||
|
||||
auto it = this->ffzChannelBadges_.find(userID);
|
||||
if (it == this->ffzChannelBadges_.end())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<FfzBadges::Badge> badges;
|
||||
|
||||
const auto *ffzBadges = getIApp()->getFfzBadges();
|
||||
|
||||
for (const auto &badgeID : it->second)
|
||||
{
|
||||
auto badge = ffzBadges->getBadge(badgeID);
|
||||
if (badge.has_value())
|
||||
{
|
||||
badges.emplace_back(*badge);
|
||||
}
|
||||
}
|
||||
|
||||
return badges;
|
||||
}
|
||||
|
||||
std::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
|
||||
{
|
||||
return this->ffzCustomModBadge_.get();
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
#include "common/ChannelChatters.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "common/UniqueAccess.hpp"
|
||||
#include "providers/ffz/FfzBadges.hpp"
|
||||
#include "providers/ffz/FfzEmotes.hpp"
|
||||
#include "providers/twitch/TwitchEmotes.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
#include "util/ThreadGuard.hpp"
|
||||
|
||||
#include <boost/circular_buffer/space_optimized.hpp>
|
||||
#include <boost/signals2.hpp>
|
||||
|
@ -200,6 +203,10 @@ public:
|
|||
std::optional<EmotePtr> ffzCustomVipBadge() const;
|
||||
std::optional<EmotePtr> twitchBadge(const QString &set,
|
||||
const QString &version) const;
|
||||
/**
|
||||
* Returns a list of channel-specific FrankerFaceZ badges for the given user
|
||||
*/
|
||||
std::vector<FfzBadges::Badge> ffzChannelBadges(const QString &userID) const;
|
||||
|
||||
// Cheers
|
||||
std::optional<CheerEmote> cheerEmote(const QString &string);
|
||||
|
@ -393,6 +400,9 @@ protected:
|
|||
Atomic<std::optional<EmotePtr>> ffzCustomModBadge_;
|
||||
Atomic<std::optional<EmotePtr>> ffzCustomVipBadge_;
|
||||
|
||||
FfzChannelBadgeMap ffzChannelBadges_;
|
||||
ThreadGuard tgFfzChannelBadges_;
|
||||
|
||||
private:
|
||||
// Badges
|
||||
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
|
||||
|
|
|
@ -1447,6 +1447,18 @@ void TwitchMessageBuilder::appendFfzBadges()
|
|||
this->emplace<FfzBadgeElement>(
|
||||
badge.emote, MessageElementFlag::BadgeFfz, badge.color);
|
||||
}
|
||||
|
||||
if (this->twitchChannel == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &badge :
|
||||
this->twitchChannel->ffzChannelBadges(this->userId_))
|
||||
{
|
||||
this->emplace<FfzBadgeElement>(
|
||||
badge.emote, MessageElementFlag::BadgeFfz, badge.color);
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::appendSeventvBadges()
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/container_hash/hash_fwd.hpp>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace boost {
|
||||
|
||||
template <>
|
||||
struct hash<QString> {
|
||||
std::size_t operator()(QString const &s) const
|
||||
{
|
||||
return qHash(s);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace boost
|
||||
|
||||
namespace std {
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
|
|
|
@ -10,11 +10,11 @@ namespace chatterino {
|
|||
// Debug-class which asserts if guard of the same object has been called from different threads
|
||||
struct ThreadGuard {
|
||||
#ifndef NDEBUG
|
||||
std::mutex mutex;
|
||||
std::optional<std::thread::id> threadID;
|
||||
mutable std::mutex mutex;
|
||||
mutable std::optional<std::thread::id> threadID;
|
||||
#endif
|
||||
|
||||
inline void guard()
|
||||
inline void guard() const
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
std::unique_lock lock(this->mutex);
|
||||
|
|
Loading…
Reference in a new issue