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: 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 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: 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: 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: Add a new completion API for experimental plugins feature. (#5000, #5047)
|
||||||
- Minor: Re-enabled _Restart on crash_ option on Windows. (#5012)
|
- 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;
|
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);
|
auto it = this->badges.find(badgeID);
|
||||||
if (it != this->badges.end())
|
if (it != this->badges.end())
|
||||||
{
|
{
|
||||||
|
@ -62,6 +63,7 @@ void FfzBadges::load()
|
||||||
std::unique_lock lock(this->mutex_);
|
std::unique_lock lock(this->mutex_);
|
||||||
|
|
||||||
auto jsonRoot = result.parseJson();
|
auto jsonRoot = result.parseJson();
|
||||||
|
this->tgBadges.guard();
|
||||||
for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray())
|
for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray())
|
||||||
{
|
{
|
||||||
auto jsonBadge = jsonBadge_.toObject();
|
auto jsonBadge = jsonBadge_.toObject();
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
#include "common/Singleton.hpp"
|
#include "common/Singleton.hpp"
|
||||||
#include "util/QStringHash.hpp"
|
#include "util/QStringHash.hpp"
|
||||||
|
#include "util/ThreadGuard.hpp"
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
|
|
||||||
|
@ -30,10 +31,9 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Badge> getUserBadges(const UserId &id);
|
std::vector<Badge> getUserBadges(const UserId &id);
|
||||||
|
std::optional<Badge> getBadge(int badgeID) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<Badge> getBadge(int badgeID);
|
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
|
|
||||||
std::shared_mutex mutex_;
|
std::shared_mutex mutex_;
|
||||||
|
@ -43,6 +43,7 @@ private:
|
||||||
|
|
||||||
// badges points a badge ID to the information about the badge
|
// badges points a badge ID to the information about the badge
|
||||||
std::unordered_map<int, Badge> badges;
|
std::unordered_map<int, Badge> badges;
|
||||||
|
ThreadGuard tgBadges;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -169,6 +169,33 @@ EmoteMap ffz::detail::parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||||
return emotes;
|
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()
|
FfzEmotes::FfzEmotes()
|
||||||
: global_(std::make_shared<EmoteMap>())
|
: global_(std::make_shared<EmoteMap>())
|
||||||
{
|
{
|
||||||
|
@ -220,6 +247,7 @@ void FfzEmotes::loadChannel(
|
||||||
std::function<void(EmoteMap &&)> emoteCallback,
|
std::function<void(EmoteMap &&)> emoteCallback,
|
||||||
std::function<void(std::optional<EmotePtr>)> modBadgeCallback,
|
std::function<void(std::optional<EmotePtr>)> modBadgeCallback,
|
||||||
std::function<void(std::optional<EmotePtr>)> vipBadgeCallback,
|
std::function<void(std::optional<EmotePtr>)> vipBadgeCallback,
|
||||||
|
std::function<void(FfzChannelBadgeMap &&)> channelBadgesCallback,
|
||||||
bool manualRefresh)
|
bool manualRefresh)
|
||||||
{
|
{
|
||||||
qCDebug(LOG) << "Reload FFZ Channel Emotes for channel" << channelID;
|
qCDebug(LOG) << "Reload FFZ Channel Emotes for channel" << channelID;
|
||||||
|
@ -229,8 +257,9 @@ void FfzEmotes::loadChannel(
|
||||||
.timeout(20000)
|
.timeout(20000)
|
||||||
.onSuccess([emoteCallback = std::move(emoteCallback),
|
.onSuccess([emoteCallback = std::move(emoteCallback),
|
||||||
modBadgeCallback = std::move(modBadgeCallback),
|
modBadgeCallback = std::move(modBadgeCallback),
|
||||||
vipBadgeCallback = std::move(vipBadgeCallback), channel,
|
vipBadgeCallback = std::move(vipBadgeCallback),
|
||||||
manualRefresh](const auto &result) {
|
channelBadgesCallback = std::move(channelBadgesCallback),
|
||||||
|
channel, manualRefresh](const auto &result) {
|
||||||
const auto json = result.parseJson();
|
const auto json = result.parseJson();
|
||||||
|
|
||||||
auto emoteMap = parseChannelEmotes(json);
|
auto emoteMap = parseChannelEmotes(json);
|
||||||
|
@ -238,12 +267,15 @@ void FfzEmotes::loadChannel(
|
||||||
json["room"]["mod_urls"].toObject(), "Moderator");
|
json["room"]["mod_urls"].toObject(), "Moderator");
|
||||||
auto vipBadge = parseAuthorityBadge(
|
auto vipBadge = parseAuthorityBadge(
|
||||||
json["room"]["vip_badge"].toObject(), "VIP");
|
json["room"]["vip_badge"].toObject(), "VIP");
|
||||||
|
auto channelBadges =
|
||||||
|
parseChannelBadges(json["room"]["user_badge_ids"].toObject());
|
||||||
|
|
||||||
bool hasEmotes = !emoteMap.empty();
|
bool hasEmotes = !emoteMap.empty();
|
||||||
|
|
||||||
emoteCallback(std::move(emoteMap));
|
emoteCallback(std::move(emoteMap));
|
||||||
modBadgeCallback(std::move(modBadge));
|
modBadgeCallback(std::move(modBadge));
|
||||||
vipBadgeCallback(std::move(vipBadge));
|
vipBadgeCallback(std::move(vipBadge));
|
||||||
|
channelBadgesCallback(std::move(channelBadges));
|
||||||
if (auto shared = channel.lock(); manualRefresh)
|
if (auto shared = channel.lock(); manualRefresh)
|
||||||
{
|
{
|
||||||
if (hasEmotes)
|
if (hasEmotes)
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
#include "common/Atomic.hpp"
|
#include "common/Atomic.hpp"
|
||||||
|
#include "util/QStringHash.hpp"
|
||||||
|
|
||||||
|
#include <boost/unordered/unordered_flat_map.hpp>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -15,10 +17,19 @@ using EmotePtr = std::shared_ptr<const Emote>;
|
||||||
class EmoteMap;
|
class EmoteMap;
|
||||||
class Channel;
|
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 {
|
namespace ffz::detail {
|
||||||
|
|
||||||
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot);
|
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
|
} // namespace ffz::detail
|
||||||
|
|
||||||
class FfzEmotes final
|
class FfzEmotes final
|
||||||
|
@ -35,6 +46,7 @@ public:
|
||||||
std::function<void(EmoteMap &&)> emoteCallback,
|
std::function<void(EmoteMap &&)> emoteCallback,
|
||||||
std::function<void(std::optional<EmotePtr>)> modBadgeCallback,
|
std::function<void(std::optional<EmotePtr>)> modBadgeCallback,
|
||||||
std::function<void(std::optional<EmotePtr>)> vipBadgeCallback,
|
std::function<void(std::optional<EmotePtr>)> vipBadgeCallback,
|
||||||
|
std::function<void(FfzChannelBadgeMap &&)> channelBadgesCallback,
|
||||||
bool manualRefresh);
|
bool manualRefresh);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/bttv/BttvLiveUpdates.hpp"
|
#include "providers/bttv/BttvLiveUpdates.hpp"
|
||||||
#include "providers/bttv/liveupdates/BttvLiveUpdateMessages.hpp"
|
#include "providers/bttv/liveupdates/BttvLiveUpdateMessages.hpp"
|
||||||
|
#include "providers/ffz/FfzBadges.hpp"
|
||||||
#include "providers/ffz/FfzEmotes.hpp"
|
#include "providers/ffz/FfzEmotes.hpp"
|
||||||
#include "providers/recentmessages/Api.hpp"
|
#include "providers/recentmessages/Api.hpp"
|
||||||
#include "providers/seventv/eventapi/Dispatch.hpp"
|
#include "providers/seventv/eventapi/Dispatch.hpp"
|
||||||
|
@ -333,6 +334,14 @@ void TwitchChannel::refreshFFZChannelEmotes(bool manualRefresh)
|
||||||
std::forward<decltype(vipBadge)>(vipBadge));
|
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);
|
manualRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1707,6 +1716,33 @@ std::optional<EmotePtr> TwitchChannel::twitchBadge(const QString &set,
|
||||||
return std::nullopt;
|
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
|
std::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
|
||||||
{
|
{
|
||||||
return this->ffzCustomModBadge_.get();
|
return this->ffzCustomModBadge_.get();
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
#include "common/ChannelChatters.hpp"
|
#include "common/ChannelChatters.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "common/UniqueAccess.hpp"
|
#include "common/UniqueAccess.hpp"
|
||||||
|
#include "providers/ffz/FfzBadges.hpp"
|
||||||
|
#include "providers/ffz/FfzEmotes.hpp"
|
||||||
#include "providers/twitch/TwitchEmotes.hpp"
|
#include "providers/twitch/TwitchEmotes.hpp"
|
||||||
#include "util/QStringHash.hpp"
|
#include "util/QStringHash.hpp"
|
||||||
|
#include "util/ThreadGuard.hpp"
|
||||||
|
|
||||||
#include <boost/circular_buffer/space_optimized.hpp>
|
#include <boost/circular_buffer/space_optimized.hpp>
|
||||||
#include <boost/signals2.hpp>
|
#include <boost/signals2.hpp>
|
||||||
|
@ -200,6 +203,10 @@ public:
|
||||||
std::optional<EmotePtr> ffzCustomVipBadge() const;
|
std::optional<EmotePtr> ffzCustomVipBadge() const;
|
||||||
std::optional<EmotePtr> twitchBadge(const QString &set,
|
std::optional<EmotePtr> twitchBadge(const QString &set,
|
||||||
const QString &version) const;
|
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
|
// Cheers
|
||||||
std::optional<CheerEmote> cheerEmote(const QString &string);
|
std::optional<CheerEmote> cheerEmote(const QString &string);
|
||||||
|
@ -393,6 +400,9 @@ protected:
|
||||||
Atomic<std::optional<EmotePtr>> ffzCustomModBadge_;
|
Atomic<std::optional<EmotePtr>> ffzCustomModBadge_;
|
||||||
Atomic<std::optional<EmotePtr>> ffzCustomVipBadge_;
|
Atomic<std::optional<EmotePtr>> ffzCustomVipBadge_;
|
||||||
|
|
||||||
|
FfzChannelBadgeMap ffzChannelBadges_;
|
||||||
|
ThreadGuard tgFfzChannelBadges_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Badges
|
// Badges
|
||||||
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
|
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
|
||||||
|
|
|
@ -1447,6 +1447,18 @@ void TwitchMessageBuilder::appendFfzBadges()
|
||||||
this->emplace<FfzBadgeElement>(
|
this->emplace<FfzBadgeElement>(
|
||||||
badge.emote, MessageElementFlag::BadgeFfz, badge.color);
|
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()
|
void TwitchMessageBuilder::appendSeventvBadges()
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/container_hash/hash_fwd.hpp>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<QString> {
|
||||||
|
std::size_t operator()(QString const &s) const
|
||||||
|
{
|
||||||
|
return qHash(s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace boost
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
#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
|
// Debug-class which asserts if guard of the same object has been called from different threads
|
||||||
struct ThreadGuard {
|
struct ThreadGuard {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
std::mutex mutex;
|
mutable std::mutex mutex;
|
||||||
std::optional<std::thread::id> threadID;
|
mutable std::optional<std::thread::id> threadID;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline void guard()
|
inline void guard() const
|
||||||
{
|
{
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
std::unique_lock lock(this->mutex);
|
std::unique_lock lock(this->mutex);
|
||||||
|
|
Loading…
Reference in a new issue