feat: Add 7TV Emotes and Badges (#4002)
Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- Major: Added support for Twitch's Chat Replies. [Wiki Page](https://wiki.chatterino.com/Features/#message-replies) (#3722, #3989, #4041, #4047, #4055)
|
- Major: Added support for Twitch's Chat Replies. [Wiki Page](https://wiki.chatterino.com/Features/#message-replies) (#3722, #3989, #4041, #4047, #4055)
|
||||||
- Major: Added multi-channel searching to search dialog via keyboard shortcut. (Ctrl+Shift+F by default) (#3694, #3875)
|
- Major: Added multi-channel searching to search dialog via keyboard shortcut. (Ctrl+Shift+F by default) (#3694, #3875)
|
||||||
|
- Major: Added support for emotes and badges from [7TV](https://7tv.app). [Wiki Page](https://wiki.chatterino.com/Third_party_services/#7tv) (#4002)
|
||||||
- Minor: Added highlights for `Elevated Messages`. (#4016)
|
- Minor: Added highlights for `Elevated Messages`. (#4016)
|
||||||
- Minor: Removed total views from the usercard, as Twitch no longer updates the number. (#3792)
|
- Minor: Removed total views from the usercard, as Twitch no longer updates the number. (#3792)
|
||||||
- Minor: Load missing messages from Recent Messages API upon reconnecting (#3878, #3932)
|
- Minor: Load missing messages from Recent Messages API upon reconnecting (#3878, #3932)
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
#include "providers/ffz/FfzBadges.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/seventv/SeventvBadges.hpp"
|
||||||
|
#include "providers/seventv/SeventvEmotes.hpp"
|
||||||
#include "providers/twitch/PubSubManager.hpp"
|
#include "providers/twitch/PubSubManager.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
@@ -72,6 +74,7 @@ Application::Application(Settings &_settings, Paths &_paths)
|
|||||||
, twitch(&this->emplace<TwitchIrcServer>())
|
, twitch(&this->emplace<TwitchIrcServer>())
|
||||||
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
||||||
, ffzBadges(&this->emplace<FfzBadges>())
|
, ffzBadges(&this->emplace<FfzBadges>())
|
||||||
|
, seventvBadges(&this->emplace<SeventvBadges>())
|
||||||
, logging(&this->emplace<Logging>())
|
, logging(&this->emplace<Logging>())
|
||||||
{
|
{
|
||||||
this->instance = this;
|
this->instance = this;
|
||||||
@@ -199,6 +202,16 @@ int Application::run(QApplication &qtApp)
|
|||||||
this->twitch->reloadAllFFZChannelEmotes();
|
this->twitch->reloadAllFFZChannelEmotes();
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
|
getSettings()->enableSevenTVGlobalEmotes.connect(
|
||||||
|
[this] {
|
||||||
|
this->twitch->reloadSevenTVGlobalEmotes();
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
getSettings()->enableSevenTVChannelEmotes.connect(
|
||||||
|
[this] {
|
||||||
|
this->twitch->reloadAllSevenTVChannelEmotes();
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
|
||||||
return qtApp.exec();
|
return qtApp.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class Fonts;
|
|||||||
class Toasts;
|
class Toasts;
|
||||||
class ChatterinoBadges;
|
class ChatterinoBadges;
|
||||||
class FfzBadges;
|
class FfzBadges;
|
||||||
|
class SeventvBadges;
|
||||||
|
|
||||||
class IApplication
|
class IApplication
|
||||||
{
|
{
|
||||||
@@ -86,6 +87,7 @@ public:
|
|||||||
TwitchIrcServer *const twitch{};
|
TwitchIrcServer *const twitch{};
|
||||||
ChatterinoBadges *const chatterinoBadges{};
|
ChatterinoBadges *const chatterinoBadges{};
|
||||||
FfzBadges *const ffzBadges{};
|
FfzBadges *const ffzBadges{};
|
||||||
|
SeventvBadges *const seventvBadges{};
|
||||||
|
|
||||||
/*[[deprecated]]*/ Logging *const logging{};
|
/*[[deprecated]]*/ Logging *const logging{};
|
||||||
|
|
||||||
|
|||||||
@@ -214,6 +214,11 @@ set(SOURCE_FILES
|
|||||||
providers/irc/IrcServer.cpp
|
providers/irc/IrcServer.cpp
|
||||||
providers/irc/IrcServer.hpp
|
providers/irc/IrcServer.hpp
|
||||||
|
|
||||||
|
providers/seventv/SeventvBadges.cpp
|
||||||
|
providers/seventv/SeventvBadges.hpp
|
||||||
|
providers/seventv/SeventvEmotes.cpp
|
||||||
|
providers/seventv/SeventvEmotes.hpp
|
||||||
|
|
||||||
providers/twitch/ChannelPointReward.cpp
|
providers/twitch/ChannelPointReward.cpp
|
||||||
providers/twitch/ChannelPointReward.hpp
|
providers/twitch/ChannelPointReward.hpp
|
||||||
providers/twitch/IrcMessageHandler.cpp
|
providers/twitch/IrcMessageHandler.cpp
|
||||||
|
|||||||
@@ -141,6 +141,11 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 7TV Global
|
||||||
|
for (auto &emote : *getApp()->twitch->getSeventvEmotes().globalEmotes())
|
||||||
|
{
|
||||||
|
addString(emote.first.string, TaggedString::Type::SeventvGlobalEmote);
|
||||||
|
}
|
||||||
// Bttv Global
|
// Bttv Global
|
||||||
for (auto &emote : *getApp()->twitch->getBttvEmotes().emotes())
|
for (auto &emote : *getApp()->twitch->getBttvEmotes().emotes())
|
||||||
{
|
{
|
||||||
@@ -198,6 +203,11 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 7TV Channel
|
||||||
|
for (auto &emote : *tc->seventvEmotes())
|
||||||
|
{
|
||||||
|
addString(emote.first.string, TaggedString::Type::SeventvChannelEmote);
|
||||||
|
}
|
||||||
// Bttv Channel
|
// Bttv Channel
|
||||||
for (auto &emote : *tc->bttvEmotes())
|
for (auto &emote : *tc->bttvEmotes())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class CompletionModel : public QAbstractListModel
|
|||||||
FFZChannelEmote,
|
FFZChannelEmote,
|
||||||
BTTVGlobalEmote,
|
BTTVGlobalEmote,
|
||||||
BTTVChannelEmote,
|
BTTVChannelEmote,
|
||||||
|
SeventvGlobalEmote,
|
||||||
|
SeventvChannelEmote,
|
||||||
TwitchGlobalEmote,
|
TwitchGlobalEmote,
|
||||||
TwitchLocalEmote,
|
TwitchLocalEmote,
|
||||||
TwitchSubscriberEmote,
|
TwitchSubscriberEmote,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Q_LOGGING_CATEGORY(chatterinoPubSub, "chatterino.pubsub", logThreshold);
|
|||||||
Q_LOGGING_CATEGORY(chatterinoRecentMessages, "chatterino.recentmessages",
|
Q_LOGGING_CATEGORY(chatterinoRecentMessages, "chatterino.recentmessages",
|
||||||
logThreshold);
|
logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoSettings, "chatterino.settings", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoSettings, "chatterino.settings", logThreshold);
|
||||||
|
Q_LOGGING_CATEGORY(chatterinoSeventv, "chatterino.seventv", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoStreamerMode, "chatterino.streamermode",
|
Q_LOGGING_CATEGORY(chatterinoStreamerMode, "chatterino.streamermode",
|
||||||
logThreshold);
|
logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoStreamlink, "chatterino.streamlink", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoStreamlink, "chatterino.streamlink", logThreshold);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Q_DECLARE_LOGGING_CATEGORY(chatterinoNuulsuploader);
|
|||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoPubSub);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoPubSub);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoRecentMessages);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoRecentMessages);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoSettings);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoSettings);
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoSeventv);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoStreamerMode);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoStreamerMode);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoStreamlink);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoStreamlink);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoTokenizer);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoTokenizer);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ struct Emote {
|
|||||||
ImageSet images;
|
ImageSet images;
|
||||||
Tooltip tooltip;
|
Tooltip tooltip;
|
||||||
Url homePage;
|
Url homePage;
|
||||||
|
bool zeroWidth;
|
||||||
|
|
||||||
// FOURTF: no solution yet, to be refactored later
|
// FOURTF: no solution yet, to be refactored later
|
||||||
const QString &getCopyString() const
|
const QString &getCopyString() const
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ enum class MessageElementFlag : int64_t {
|
|||||||
TwitchEmoteImage = (1LL << 4),
|
TwitchEmoteImage = (1LL << 4),
|
||||||
TwitchEmoteText = (1LL << 5),
|
TwitchEmoteText = (1LL << 5),
|
||||||
TwitchEmote = TwitchEmoteImage | TwitchEmoteText,
|
TwitchEmote = TwitchEmoteImage | TwitchEmoteText,
|
||||||
|
|
||||||
BttvEmoteImage = (1LL << 6),
|
BttvEmoteImage = (1LL << 6),
|
||||||
BttvEmoteText = (1LL << 7),
|
BttvEmoteText = (1LL << 7),
|
||||||
BttvEmote = BttvEmoteImage | BttvEmoteText,
|
BttvEmote = BttvEmoteImage | BttvEmoteText,
|
||||||
@@ -47,8 +48,15 @@ enum class MessageElementFlag : int64_t {
|
|||||||
FfzEmoteImage = (1LL << 9),
|
FfzEmoteImage = (1LL << 9),
|
||||||
FfzEmoteText = (1LL << 10),
|
FfzEmoteText = (1LL << 10),
|
||||||
FfzEmote = FfzEmoteImage | FfzEmoteText,
|
FfzEmote = FfzEmoteImage | FfzEmoteText,
|
||||||
EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage,
|
|
||||||
EmoteText = TwitchEmoteText | BttvEmoteText | FfzEmoteText,
|
SevenTVEmoteImage = (1LL << 34),
|
||||||
|
SevenTVEmoteText = (1LL << 35),
|
||||||
|
SevenTVEmote = SevenTVEmoteImage | SevenTVEmoteText,
|
||||||
|
|
||||||
|
EmoteImages =
|
||||||
|
TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage | SevenTVEmoteImage,
|
||||||
|
EmoteText =
|
||||||
|
TwitchEmoteText | BttvEmoteText | FfzEmoteText | SevenTVEmoteText,
|
||||||
|
|
||||||
BitsStatic = (1LL << 11),
|
BitsStatic = (1LL << 11),
|
||||||
BitsAnimated = (1LL << 12),
|
BitsAnimated = (1LL << 12),
|
||||||
@@ -89,6 +97,15 @@ enum class MessageElementFlag : int64_t {
|
|||||||
// - Chatterino gnome badge
|
// - Chatterino gnome badge
|
||||||
BadgeChatterino = (1LL << 18),
|
BadgeChatterino = (1LL << 18),
|
||||||
|
|
||||||
|
// Slot 7: 7TV
|
||||||
|
// - 7TV Admin
|
||||||
|
// - 7TV Dungeon Mistress
|
||||||
|
// - 7TV Moderator
|
||||||
|
// - 7TV Subscriber
|
||||||
|
// - 7TV Translator
|
||||||
|
// - 7TV Contributor
|
||||||
|
BadgeSevenTV = (1LL << 36),
|
||||||
|
|
||||||
// Slot 7: FrankerFaceZ
|
// Slot 7: FrankerFaceZ
|
||||||
// - FFZ developer badge
|
// - FFZ developer badge
|
||||||
// - FFZ bot badge
|
// - FFZ bot badge
|
||||||
@@ -96,7 +113,8 @@ enum class MessageElementFlag : int64_t {
|
|||||||
BadgeFfz = (1LL << 19),
|
BadgeFfz = (1LL << 19),
|
||||||
|
|
||||||
Badges = BadgeGlobalAuthority | BadgePredictions | BadgeChannelAuthority |
|
Badges = BadgeGlobalAuthority | BadgePredictions | BadgeChannelAuthority |
|
||||||
BadgeSubscription | BadgeVanity | BadgeChatterino | BadgeFfz,
|
BadgeSubscription | BadgeVanity | BadgeChatterino | BadgeSevenTV |
|
||||||
|
BadgeFfz,
|
||||||
|
|
||||||
ChannelName = (1LL << 20),
|
ChannelName = (1LL << 20),
|
||||||
|
|
||||||
@@ -123,7 +141,7 @@ enum class MessageElementFlag : int64_t {
|
|||||||
OriginalLink = (1LL << 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 or 7TV's RainTime
|
||||||
ZeroWidthEmote = (1LL << 31),
|
ZeroWidthEmote = (1LL << 31),
|
||||||
|
|
||||||
// for elements of the message reply
|
// for elements of the message reply
|
||||||
@@ -132,9 +150,12 @@ enum class MessageElementFlag : int64_t {
|
|||||||
// for the reply button element
|
// for the reply button element
|
||||||
ReplyButton = (1LL << 33),
|
ReplyButton = (1LL << 33),
|
||||||
|
|
||||||
|
// (1LL << 34) through (1LL << 36) are occupied by
|
||||||
|
// SevenTVEmoteImage, SevenTVEmoteText, and BadgeSevenTV,
|
||||||
|
|
||||||
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage |
|
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage |
|
||||||
BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text |
|
BttvEmoteImage | SevenTVEmoteImage | TwitchEmoteImage |
|
||||||
AlwaysShow,
|
BitsAmount | Text | AlwaysShow,
|
||||||
};
|
};
|
||||||
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
|
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#include "providers/seventv/SeventvBadges.hpp"
|
||||||
|
|
||||||
|
#include "common/NetworkRequest.hpp"
|
||||||
|
#include "common/Outcome.hpp"
|
||||||
|
#include "messages/Emote.hpp"
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
void SeventvBadges::initialize(Settings & /*settings*/, Paths & /*paths*/)
|
||||||
|
{
|
||||||
|
this->loadSeventvBadges();
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<EmotePtr> SeventvBadges::getBadge(const UserId &id)
|
||||||
|
{
|
||||||
|
std::shared_lock lock(this->mutex_);
|
||||||
|
|
||||||
|
auto it = this->badgeMap_.find(id.string);
|
||||||
|
if (it != this->badgeMap_.end())
|
||||||
|
{
|
||||||
|
return this->emotes_[it->second];
|
||||||
|
}
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SeventvBadges::loadSeventvBadges()
|
||||||
|
{
|
||||||
|
// Cosmetics will work differently in v3, until this is ready
|
||||||
|
// we'll use this endpoint.
|
||||||
|
static QUrl url("https://7tv.io/v2/cosmetics");
|
||||||
|
|
||||||
|
static QUrlQuery urlQuery;
|
||||||
|
// valid user_identifier values: "object_id", "twitch_id", "login"
|
||||||
|
urlQuery.addQueryItem("user_identifier", "twitch_id");
|
||||||
|
|
||||||
|
url.setQuery(urlQuery);
|
||||||
|
|
||||||
|
NetworkRequest(url)
|
||||||
|
.onSuccess([this](const NetworkResult &result) -> Outcome {
|
||||||
|
auto root = result.parseJson();
|
||||||
|
|
||||||
|
std::shared_lock lock(this->mutex_);
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (const auto &jsonBadge : root.value("badges").toArray())
|
||||||
|
{
|
||||||
|
auto badge = jsonBadge.toObject();
|
||||||
|
auto urls = badge.value("urls").toArray();
|
||||||
|
auto emote =
|
||||||
|
Emote{EmoteName{},
|
||||||
|
ImageSet{Url{urls.at(0).toArray().at(1).toString()},
|
||||||
|
Url{urls.at(1).toArray().at(1).toString()},
|
||||||
|
Url{urls.at(2).toArray().at(1).toString()}},
|
||||||
|
Tooltip{badge.value("tooltip").toString()}, Url{}};
|
||||||
|
|
||||||
|
this->emotes_.push_back(
|
||||||
|
std::make_shared<const Emote>(std::move(emote)));
|
||||||
|
|
||||||
|
for (const auto &user : badge.value("users").toArray())
|
||||||
|
{
|
||||||
|
this->badgeMap_[user.toString()] = index;
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Aliases.hpp"
|
||||||
|
#include "util/QStringHash.hpp"
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include <common/Singleton.hpp>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
struct Emote;
|
||||||
|
using EmotePtr = std::shared_ptr<const Emote>;
|
||||||
|
|
||||||
|
class SeventvBadges : public Singleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void initialize(Settings &settings, Paths &paths) override;
|
||||||
|
|
||||||
|
boost::optional<EmotePtr> getBadge(const UserId &id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void loadSeventvBadges();
|
||||||
|
|
||||||
|
// Mutex for both `badgeMap_` and `emotes_`
|
||||||
|
std::shared_mutex mutex_;
|
||||||
|
|
||||||
|
std::unordered_map<QString, int> badgeMap_;
|
||||||
|
std::vector<EmotePtr> emotes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
#include "providers/seventv/SeventvEmotes.hpp"
|
||||||
|
|
||||||
|
#include "common/Common.hpp"
|
||||||
|
#include "common/NetworkRequest.hpp"
|
||||||
|
#include "common/QLogging.hpp"
|
||||||
|
#include "messages/Emote.hpp"
|
||||||
|
#include "messages/Image.hpp"
|
||||||
|
#include "messages/ImageSet.hpp"
|
||||||
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QThread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* # References
|
||||||
|
*
|
||||||
|
* - EmoteSet: https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote-set.model.go#L8-L18
|
||||||
|
* - ActiveEmote: https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote-set.model.go#L20-L27
|
||||||
|
* - EmotePartial (emoteData): https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote.model.go#L24-L34
|
||||||
|
* - ImageHost: https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/model.go#L36-L39
|
||||||
|
* - ImageFile: https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/model.go#L41-L48
|
||||||
|
*/
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
|
||||||
|
// These declarations won't throw an exception.
|
||||||
|
const QString CHANNEL_HAS_NO_EMOTES("This channel has no 7TV channel emotes.");
|
||||||
|
const QString EMOTE_LINK_FORMAT("https://7tv.app/emotes/%1");
|
||||||
|
|
||||||
|
// TODO(nerix): add links to documentation (7tv.io)
|
||||||
|
const QString API_URL_USER("https://7tv.io/v3/users/twitch/%1");
|
||||||
|
const QString API_URL_GLOBAL_EMOTE_SET("https://7tv.io/v3/emote-sets/global");
|
||||||
|
|
||||||
|
struct CreateEmoteResult {
|
||||||
|
Emote emote;
|
||||||
|
EmoteId id;
|
||||||
|
EmoteName name;
|
||||||
|
bool hasImages;
|
||||||
|
};
|
||||||
|
|
||||||
|
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
|
||||||
|
{
|
||||||
|
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
|
||||||
|
static std::mutex mutex;
|
||||||
|
|
||||||
|
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This decides whether an emote should be displayed
|
||||||
|
* as zero-width
|
||||||
|
*/
|
||||||
|
bool isZeroWidthActive(const QJsonObject &activeEmote)
|
||||||
|
{
|
||||||
|
auto flags = SeventvActiveEmoteFlags(
|
||||||
|
SeventvActiveEmoteFlag(activeEmote.value("flags").toInt()));
|
||||||
|
return flags.has(SeventvActiveEmoteFlag::ZeroWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is only an indicator if an emote should be added
|
||||||
|
* as zero-width or not. The user can still overwrite this.
|
||||||
|
*/
|
||||||
|
bool isZeroWidthRecommended(const QJsonObject &emoteData)
|
||||||
|
{
|
||||||
|
auto flags =
|
||||||
|
SeventvEmoteFlags(SeventvEmoteFlag(emoteData.value("flags").toInt()));
|
||||||
|
return flags.has(SeventvEmoteFlag::ZeroWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageSet makeImageSet(const QJsonObject &emoteData)
|
||||||
|
{
|
||||||
|
auto host = emoteData["host"].toObject();
|
||||||
|
// "//cdn.7tv[...]"
|
||||||
|
auto baseUrl = host["url"].toString();
|
||||||
|
auto files = host["files"].toArray();
|
||||||
|
|
||||||
|
// TODO: emit four images
|
||||||
|
std::array<ImagePtr, 3> sizes;
|
||||||
|
double baseWidth = 0.0;
|
||||||
|
int nextSize = 0;
|
||||||
|
|
||||||
|
for (auto fileItem : files)
|
||||||
|
{
|
||||||
|
if (nextSize >= sizes.size())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file = fileItem.toObject();
|
||||||
|
if (file["format"].toString() != "WEBP")
|
||||||
|
{
|
||||||
|
continue; // We only use webp
|
||||||
|
}
|
||||||
|
|
||||||
|
double width = file["width"].toDouble();
|
||||||
|
double scale = 1.0; // in relation to first image
|
||||||
|
if (baseWidth > 0.0)
|
||||||
|
{
|
||||||
|
scale = baseWidth / width;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// => this is the first image
|
||||||
|
baseWidth = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto image = Image::fromUrl(
|
||||||
|
{QString("https:%1/%2").arg(baseUrl, file["name"].toString())},
|
||||||
|
scale);
|
||||||
|
|
||||||
|
sizes.at(nextSize) = image;
|
||||||
|
nextSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextSize < sizes.size())
|
||||||
|
{
|
||||||
|
// this should be really rare
|
||||||
|
// this means we didn't get all sizes of an emote
|
||||||
|
if (nextSize == 0)
|
||||||
|
{
|
||||||
|
qCDebug(chatterinoSeventv)
|
||||||
|
<< "Got file list without any eligible files";
|
||||||
|
// When this emote is typed, chatterino will crash.
|
||||||
|
return ImageSet{};
|
||||||
|
}
|
||||||
|
for (; nextSize < sizes.size(); nextSize++)
|
||||||
|
{
|
||||||
|
sizes.at(nextSize) = Image::getEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImageSet{sizes[0], sizes[1], sizes[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
Tooltip createTooltip(const QString &name, const QString &author, bool isGlobal)
|
||||||
|
{
|
||||||
|
return Tooltip{QString("%1<br>%2 7TV Emote<br>By: %3")
|
||||||
|
.arg(name, isGlobal ? "Global" : "Channel",
|
||||||
|
author.isEmpty() ? "<deleted>" : author)};
|
||||||
|
}
|
||||||
|
|
||||||
|
Tooltip createAliasedTooltip(const QString &name, const QString &baseName,
|
||||||
|
const QString &author, bool isGlobal)
|
||||||
|
{
|
||||||
|
return Tooltip{QString("%1<br>Alias to %2<br>%3 7TV Emote<br>By: %4")
|
||||||
|
.arg(name, baseName, isGlobal ? "Global" : "Channel",
|
||||||
|
author.isEmpty() ? "<deleted>" : author)};
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateEmoteResult createEmote(const QJsonObject &activeEmote,
|
||||||
|
const QJsonObject &emoteData, bool isGlobal)
|
||||||
|
{
|
||||||
|
auto emoteId = EmoteId{activeEmote["id"].toString()};
|
||||||
|
auto emoteName = EmoteName{activeEmote["name"].toString()};
|
||||||
|
auto author =
|
||||||
|
EmoteAuthor{emoteData["owner"].toObject()["display_name"].toString()};
|
||||||
|
auto baseEmoteName = emoteData["name"].toString();
|
||||||
|
bool zeroWidth = isZeroWidthActive(activeEmote);
|
||||||
|
bool aliasedName = emoteName.string != baseEmoteName;
|
||||||
|
auto tooltip =
|
||||||
|
aliasedName ? createAliasedTooltip(emoteName.string, baseEmoteName,
|
||||||
|
author.string, isGlobal)
|
||||||
|
: createTooltip(emoteName.string, author.string, isGlobal);
|
||||||
|
auto imageSet = makeImageSet(emoteData);
|
||||||
|
|
||||||
|
auto emote = Emote({emoteName, imageSet, tooltip,
|
||||||
|
Url{EMOTE_LINK_FORMAT.arg(emoteId.string)}, zeroWidth});
|
||||||
|
|
||||||
|
return {emote, emoteId, emoteName, !emote.images.getImage1()->isEmpty()};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkEmoteVisibility(const QJsonObject &emoteData)
|
||||||
|
{
|
||||||
|
if (!emoteData["listed"].toBool() &&
|
||||||
|
!getSettings()->showUnlistedSevenTVEmotes)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto flags =
|
||||||
|
SeventvEmoteFlags(SeventvEmoteFlag(emoteData["flags"].toInt()));
|
||||||
|
return !flags.has(SeventvEmoteFlag::ContentTwitchDisallowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmoteMap parseEmotes(const QJsonArray &emoteSetEmotes, bool isGlobal)
|
||||||
|
{
|
||||||
|
auto emotes = EmoteMap();
|
||||||
|
|
||||||
|
for (const auto &activeEmoteJson : emoteSetEmotes)
|
||||||
|
{
|
||||||
|
auto activeEmote = activeEmoteJson.toObject();
|
||||||
|
auto emoteData = activeEmote["data"].toObject();
|
||||||
|
|
||||||
|
if (emoteData.empty() || !checkEmoteVisibility(emoteData))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = createEmote(activeEmote, emoteData, isGlobal);
|
||||||
|
if (!result.hasImages)
|
||||||
|
{
|
||||||
|
// this shouldn't happen but if it does, it will crash,
|
||||||
|
// so we don't add the emote
|
||||||
|
qCDebug(chatterinoSeventv)
|
||||||
|
<< "Emote without images:" << activeEmote;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto ptr = cachedOrMake(std::move(result.emote), result.id);
|
||||||
|
emotes[result.name] = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return emotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
SeventvEmotes::SeventvEmotes()
|
||||||
|
: global_(std::make_shared<EmoteMap>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const EmoteMap> SeventvEmotes::globalEmotes() const
|
||||||
|
{
|
||||||
|
return this->global_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<EmotePtr> SeventvEmotes::globalEmote(
|
||||||
|
const EmoteName &name) const
|
||||||
|
{
|
||||||
|
auto emotes = this->global_.get();
|
||||||
|
auto it = emotes->find(name);
|
||||||
|
|
||||||
|
if (it == emotes->end())
|
||||||
|
{
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SeventvEmotes::loadGlobalEmotes()
|
||||||
|
{
|
||||||
|
if (!Settings::instance().enableSevenTVGlobalEmotes)
|
||||||
|
{
|
||||||
|
this->global_.set(EMPTY_EMOTE_MAP);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(chatterinoSeventv) << "Loading 7TV Global Emotes";
|
||||||
|
|
||||||
|
NetworkRequest(API_URL_GLOBAL_EMOTE_SET, NetworkRequestType::Get)
|
||||||
|
.timeout(30000)
|
||||||
|
.onSuccess([this](const NetworkResult &result) -> Outcome {
|
||||||
|
QJsonArray parsedEmotes = result.parseJson()["emotes"].toArray();
|
||||||
|
|
||||||
|
auto emoteMap = parseEmotes(parsedEmotes, true);
|
||||||
|
qCDebug(chatterinoSeventv)
|
||||||
|
<< "Loaded" << emoteMap.size() << "7TV Global Emotes";
|
||||||
|
this->global_.set(std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([](const NetworkResult &result) {
|
||||||
|
qCWarning(chatterinoSeventv)
|
||||||
|
<< "Couldn't load 7TV global emotes" << result.getData();
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SeventvEmotes::loadChannelEmotes(const std::weak_ptr<Channel> &channel,
|
||||||
|
const QString &channelId,
|
||||||
|
std::function<void(EmoteMap &&)> callback,
|
||||||
|
bool manualRefresh)
|
||||||
|
{
|
||||||
|
qCDebug(chatterinoSeventv)
|
||||||
|
<< "Reloading 7TV Channel Emotes" << channelId << manualRefresh;
|
||||||
|
|
||||||
|
NetworkRequest(API_URL_USER.arg(channelId), NetworkRequestType::Get)
|
||||||
|
.timeout(20000)
|
||||||
|
.onSuccess([callback = std::move(callback), channel, channelId,
|
||||||
|
manualRefresh](const NetworkResult &result) -> Outcome {
|
||||||
|
auto json = result.parseJson();
|
||||||
|
auto emoteSet = json["emote_set"].toObject();
|
||||||
|
auto parsedEmotes = emoteSet["emotes"].toArray();
|
||||||
|
|
||||||
|
auto emoteMap = parseEmotes(parsedEmotes, false);
|
||||||
|
bool hasEmotes = !emoteMap.empty();
|
||||||
|
|
||||||
|
qCDebug(chatterinoSeventv)
|
||||||
|
<< "Loaded" << emoteMap.size() << "7TV Channel Emotes for"
|
||||||
|
<< channelId << "manual refresh:" << manualRefresh;
|
||||||
|
|
||||||
|
if (hasEmotes)
|
||||||
|
{
|
||||||
|
callback(std::move(emoteMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shared = channel.lock();
|
||||||
|
if (!shared)
|
||||||
|
{
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manualRefresh)
|
||||||
|
{
|
||||||
|
if (hasEmotes)
|
||||||
|
{
|
||||||
|
shared->addMessage(
|
||||||
|
makeSystemMessage("7TV channel emotes reloaded."));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shared->addMessage(
|
||||||
|
makeSystemMessage(CHANNEL_HAS_NO_EMOTES));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError(
|
||||||
|
[channelId, channel, manualRefresh](const NetworkResult &result) {
|
||||||
|
auto shared = channel.lock();
|
||||||
|
if (!shared)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.status() == 404)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoSeventv)
|
||||||
|
<< "Error occurred fetching 7TV emotes: "
|
||||||
|
<< result.parseJson();
|
||||||
|
if (manualRefresh)
|
||||||
|
{
|
||||||
|
shared->addMessage(
|
||||||
|
makeSystemMessage(CHANNEL_HAS_NO_EMOTES));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (result.status() == NetworkResult::timedoutStatus)
|
||||||
|
{
|
||||||
|
// TODO: Auto retry in case of a timeout, with a delay
|
||||||
|
qCWarning(chatterinoSeventv)
|
||||||
|
<< "Fetching 7TV emotes for channel" << channelId
|
||||||
|
<< "failed due to timeout";
|
||||||
|
shared->addMessage(makeSystemMessage(
|
||||||
|
"Failed to fetch 7TV channel emotes. (timed out)"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoSeventv)
|
||||||
|
<< "Error fetching 7TV emotes for channel" << channelId
|
||||||
|
<< ", error" << result.status();
|
||||||
|
shared->addMessage(
|
||||||
|
makeSystemMessage("Failed to fetch 7TV channel "
|
||||||
|
"emotes. (unknown error)"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "boost/optional.hpp"
|
||||||
|
#include "common/Aliases.hpp"
|
||||||
|
#include "common/Atomic.hpp"
|
||||||
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
// https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote-set.model.go#L29-L36
|
||||||
|
enum class SeventvActiveEmoteFlag : int64_t {
|
||||||
|
None = 0LL,
|
||||||
|
|
||||||
|
// Emote is zero-width
|
||||||
|
ZeroWidth = (1LL << 0),
|
||||||
|
|
||||||
|
// Overrides Twitch Global emotes with the same name
|
||||||
|
OverrideTwitchGlobal = (1 << 16),
|
||||||
|
// Overrides Twitch Subscriber emotes with the same name
|
||||||
|
OverrideTwitchSubscriber = (1 << 17),
|
||||||
|
// Overrides BetterTTV emotes with the same name
|
||||||
|
OverrideBetterTTV = (1 << 18),
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote.model.go#L57-L70
|
||||||
|
enum class SeventvEmoteFlag : int64_t {
|
||||||
|
None = 0LL,
|
||||||
|
// The emote is private and can only be accessed by its owner, editors and moderators
|
||||||
|
Private = 1 << 0,
|
||||||
|
// The emote was verified to be an original creation by the uploader
|
||||||
|
Authentic = (1LL << 1),
|
||||||
|
// The emote is recommended to be enabled as Zero-Width
|
||||||
|
ZeroWidth = (1LL << 8),
|
||||||
|
|
||||||
|
// Content Flags
|
||||||
|
|
||||||
|
// Sexually Suggesive
|
||||||
|
ContentSexual = (1LL << 16),
|
||||||
|
// Rapid flashing
|
||||||
|
ContentEpilepsy = (1LL << 17),
|
||||||
|
// Edgy or distasteful, may be offensive to some users
|
||||||
|
ContentEdgy = (1 << 18),
|
||||||
|
// Not allowed specifically on the Twitch platform
|
||||||
|
ContentTwitchDisallowed = (1LL << 24),
|
||||||
|
};
|
||||||
|
|
||||||
|
using SeventvActiveEmoteFlags = FlagsEnum<SeventvActiveEmoteFlag>;
|
||||||
|
using SeventvEmoteFlags = FlagsEnum<SeventvEmoteFlag>;
|
||||||
|
|
||||||
|
struct Emote;
|
||||||
|
using EmotePtr = std::shared_ptr<const Emote>;
|
||||||
|
class EmoteMap;
|
||||||
|
|
||||||
|
class SeventvEmotes final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SeventvEmotes();
|
||||||
|
|
||||||
|
std::shared_ptr<const EmoteMap> globalEmotes() const;
|
||||||
|
boost::optional<EmotePtr> globalEmote(const EmoteName &name) const;
|
||||||
|
void loadGlobalEmotes();
|
||||||
|
static void loadChannelEmotes(const std::weak_ptr<Channel> &channel,
|
||||||
|
const QString &channelId,
|
||||||
|
std::function<void(EmoteMap &&)> callback,
|
||||||
|
bool manualRefresh);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "providers/RecentMessagesApi.hpp"
|
#include "providers/RecentMessagesApi.hpp"
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/bttv/LoadBttvChannelEmote.hpp"
|
#include "providers/bttv/LoadBttvChannelEmote.hpp"
|
||||||
|
#include "providers/seventv/SeventvEmotes.hpp"
|
||||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||||
#include "providers/twitch/PubSubManager.hpp"
|
#include "providers/twitch/PubSubManager.hpp"
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
@@ -83,6 +84,7 @@ TwitchChannel::TwitchChannel(const QString &name)
|
|||||||
name)
|
name)
|
||||||
, bttvEmotes_(std::make_shared<EmoteMap>())
|
, bttvEmotes_(std::make_shared<EmoteMap>())
|
||||||
, ffzEmotes_(std::make_shared<EmoteMap>())
|
, ffzEmotes_(std::make_shared<EmoteMap>())
|
||||||
|
, seventvEmotes_(std::make_shared<EmoteMap>())
|
||||||
, mod_(false)
|
, mod_(false)
|
||||||
{
|
{
|
||||||
qCDebug(chatterinoTwitch) << "[TwitchChannel" << name << "] Opened";
|
qCDebug(chatterinoTwitch) << "[TwitchChannel" << name << "] Opened";
|
||||||
@@ -107,6 +109,7 @@ TwitchChannel::TwitchChannel(const QString &name)
|
|||||||
this->refreshCheerEmotes();
|
this->refreshCheerEmotes();
|
||||||
this->refreshFFZChannelEmotes(false);
|
this->refreshFFZChannelEmotes(false);
|
||||||
this->refreshBTTVChannelEmotes(false);
|
this->refreshBTTVChannelEmotes(false);
|
||||||
|
this->refreshSevenTVChannelEmotes(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
this->connected.connect([this]() {
|
this->connected.connect([this]() {
|
||||||
@@ -243,6 +246,26 @@ void TwitchChannel::refreshFFZChannelEmotes(bool manualRefresh)
|
|||||||
manualRefresh);
|
manualRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TwitchChannel::refreshSevenTVChannelEmotes(bool manualRefresh)
|
||||||
|
{
|
||||||
|
if (!Settings::instance().enableSevenTVChannelEmotes)
|
||||||
|
{
|
||||||
|
this->seventvEmotes_.set(EMPTY_EMOTE_MAP);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SeventvEmotes::loadChannelEmotes(
|
||||||
|
weakOf<Channel>(this), this->roomId(),
|
||||||
|
[this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
||||||
|
if (auto shared = weak.lock())
|
||||||
|
{
|
||||||
|
this->seventvEmotes_.set(std::make_shared<EmoteMap>(
|
||||||
|
std::forward<decltype(emoteMap)>(emoteMap)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
manualRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
void TwitchChannel::addChannelPointReward(const ChannelPointReward &reward)
|
void TwitchChannel::addChannelPointReward(const ChannelPointReward &reward)
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
@@ -553,6 +576,19 @@ boost::optional<EmotePtr> TwitchChannel::ffzEmote(const EmoteName &name) const
|
|||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::optional<EmotePtr> TwitchChannel::seventvEmote(
|
||||||
|
const EmoteName &name) const
|
||||||
|
{
|
||||||
|
auto emotes = this->seventvEmotes_.get();
|
||||||
|
auto it = emotes->find(name);
|
||||||
|
|
||||||
|
if (it == emotes->end())
|
||||||
|
{
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<const EmoteMap> TwitchChannel::bttvEmotes() const
|
std::shared_ptr<const EmoteMap> TwitchChannel::bttvEmotes() const
|
||||||
{
|
{
|
||||||
return this->bttvEmotes_.get();
|
return this->bttvEmotes_.get();
|
||||||
@@ -563,6 +599,11 @@ std::shared_ptr<const EmoteMap> TwitchChannel::ffzEmotes() const
|
|||||||
return this->ffzEmotes_.get();
|
return this->ffzEmotes_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const EmoteMap> TwitchChannel::seventvEmotes() const
|
||||||
|
{
|
||||||
|
return this->seventvEmotes_.get();
|
||||||
|
}
|
||||||
|
|
||||||
const QString &TwitchChannel::subscriptionUrl()
|
const QString &TwitchChannel::subscriptionUrl()
|
||||||
{
|
{
|
||||||
return this->subscriptionUrl_;
|
return this->subscriptionUrl_;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class EmoteMap;
|
|||||||
class TwitchBadges;
|
class TwitchBadges;
|
||||||
class FfzEmotes;
|
class FfzEmotes;
|
||||||
class BttvEmotes;
|
class BttvEmotes;
|
||||||
|
class SeventvEmotes;
|
||||||
|
|
||||||
class TwitchIrcServer;
|
class TwitchIrcServer;
|
||||||
|
|
||||||
@@ -109,11 +110,14 @@ public:
|
|||||||
// Emotes
|
// Emotes
|
||||||
boost::optional<EmotePtr> bttvEmote(const EmoteName &name) const;
|
boost::optional<EmotePtr> bttvEmote(const EmoteName &name) const;
|
||||||
boost::optional<EmotePtr> ffzEmote(const EmoteName &name) const;
|
boost::optional<EmotePtr> ffzEmote(const EmoteName &name) const;
|
||||||
|
boost::optional<EmotePtr> seventvEmote(const EmoteName &name) const;
|
||||||
std::shared_ptr<const EmoteMap> bttvEmotes() const;
|
std::shared_ptr<const EmoteMap> bttvEmotes() const;
|
||||||
std::shared_ptr<const EmoteMap> ffzEmotes() const;
|
std::shared_ptr<const EmoteMap> ffzEmotes() const;
|
||||||
|
std::shared_ptr<const EmoteMap> seventvEmotes() const;
|
||||||
|
|
||||||
virtual void refreshBTTVChannelEmotes(bool manualRefresh);
|
virtual void refreshBTTVChannelEmotes(bool manualRefresh);
|
||||||
virtual void refreshFFZChannelEmotes(bool manualRefresh);
|
virtual void refreshFFZChannelEmotes(bool manualRefresh);
|
||||||
|
virtual void refreshSevenTVChannelEmotes(bool manualRefresh);
|
||||||
|
|
||||||
// Badges
|
// Badges
|
||||||
boost::optional<EmotePtr> ffzCustomModBadge() const;
|
boost::optional<EmotePtr> ffzCustomModBadge() const;
|
||||||
@@ -196,6 +200,7 @@ private:
|
|||||||
protected:
|
protected:
|
||||||
Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
|
Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
|
||||||
Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
|
Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
|
||||||
|
Atomic<std::shared_ptr<const EmoteMap>> seventvEmotes_;
|
||||||
Atomic<boost::optional<EmotePtr>> ffzCustomModBadge_;
|
Atomic<boost::optional<EmotePtr>> ffzCustomModBadge_;
|
||||||
Atomic<boost::optional<EmotePtr>> ffzCustomVipBadge_;
|
Atomic<boost::optional<EmotePtr>> ffzCustomVipBadge_;
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ void TwitchIrcServer::initialize(Settings &settings, Paths &paths)
|
|||||||
|
|
||||||
this->reloadBTTVGlobalEmotes();
|
this->reloadBTTVGlobalEmotes();
|
||||||
this->reloadFFZGlobalEmotes();
|
this->reloadFFZGlobalEmotes();
|
||||||
|
this->reloadSevenTVGlobalEmotes();
|
||||||
|
|
||||||
/* Refresh all twitch channel's live status in bulk every 30 seconds after starting chatterino */
|
/* Refresh all twitch channel's live status in bulk every 30 seconds after starting chatterino */
|
||||||
QObject::connect(&this->bulkLiveStatusTimer_, &QTimer::timeout, [=] {
|
QObject::connect(&this->bulkLiveStatusTimer_, &QTimer::timeout, [=] {
|
||||||
@@ -467,6 +468,10 @@ const FfzEmotes &TwitchIrcServer::getFfzEmotes() const
|
|||||||
{
|
{
|
||||||
return this->ffz;
|
return this->ffz;
|
||||||
}
|
}
|
||||||
|
const SeventvEmotes &TwitchIrcServer::getSeventvEmotes() const
|
||||||
|
{
|
||||||
|
return this->seventv_;
|
||||||
|
}
|
||||||
|
|
||||||
void TwitchIrcServer::reloadBTTVGlobalEmotes()
|
void TwitchIrcServer::reloadBTTVGlobalEmotes()
|
||||||
{
|
{
|
||||||
@@ -497,4 +502,19 @@ void TwitchIrcServer::reloadAllFFZChannelEmotes()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TwitchIrcServer::reloadSevenTVGlobalEmotes()
|
||||||
|
{
|
||||||
|
this->seventv_.loadGlobalEmotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchIrcServer::reloadAllSevenTVChannelEmotes()
|
||||||
|
{
|
||||||
|
this->forEachChannel([](const auto &chan) {
|
||||||
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
||||||
|
{
|
||||||
|
channel->refreshSevenTVChannelEmotes(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/ffz/FfzEmotes.hpp"
|
#include "providers/ffz/FfzEmotes.hpp"
|
||||||
#include "providers/irc/AbstractIrcServer.hpp"
|
#include "providers/irc/AbstractIrcServer.hpp"
|
||||||
|
#include "providers/seventv/SeventvEmotes.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -37,6 +38,8 @@ public:
|
|||||||
void reloadAllBTTVChannelEmotes();
|
void reloadAllBTTVChannelEmotes();
|
||||||
void reloadFFZGlobalEmotes();
|
void reloadFFZGlobalEmotes();
|
||||||
void reloadAllFFZChannelEmotes();
|
void reloadAllFFZChannelEmotes();
|
||||||
|
void reloadSevenTVGlobalEmotes();
|
||||||
|
void reloadAllSevenTVChannelEmotes();
|
||||||
|
|
||||||
Atomic<QString> lastUserThatWhisperedMe;
|
Atomic<QString> lastUserThatWhisperedMe;
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ public:
|
|||||||
|
|
||||||
const BttvEmotes &getBttvEmotes() const;
|
const BttvEmotes &getBttvEmotes() const;
|
||||||
const FfzEmotes &getFfzEmotes() const;
|
const FfzEmotes &getFfzEmotes() const;
|
||||||
|
const SeventvEmotes &getSeventvEmotes() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void initializeConnection(IrcConnection *connection,
|
virtual void initializeConnection(IrcConnection *connection,
|
||||||
@@ -85,6 +89,7 @@ private:
|
|||||||
|
|
||||||
BttvEmotes bttv;
|
BttvEmotes bttv;
|
||||||
FfzEmotes ffz;
|
FfzEmotes ffz;
|
||||||
|
SeventvEmotes seventv_;
|
||||||
QTimer bulkLiveStatusTimer_;
|
QTimer bulkLiveStatusTimer_;
|
||||||
|
|
||||||
pajlada::Signals::SignalHolder signalHolder_;
|
pajlada::Signals::SignalHolder signalHolder_;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#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/ffz/FfzBadges.hpp"
|
||||||
|
#include "providers/seventv/SeventvBadges.hpp"
|
||||||
#include "providers/twitch/TwitchBadge.hpp"
|
#include "providers/twitch/TwitchBadge.hpp"
|
||||||
#include "providers/twitch/TwitchBadges.hpp"
|
#include "providers/twitch/TwitchBadges.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
@@ -280,6 +281,7 @@ MessagePtr TwitchMessageBuilder::build()
|
|||||||
|
|
||||||
this->appendChatterinoBadges();
|
this->appendChatterinoBadges();
|
||||||
this->appendFfzBadges();
|
this->appendFfzBadges();
|
||||||
|
this->appendSeventvBadges();
|
||||||
|
|
||||||
this->appendUsername();
|
this->appendUsername();
|
||||||
|
|
||||||
@@ -1028,6 +1030,7 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
|||||||
|
|
||||||
const auto &globalBttvEmotes = app->twitch->getBttvEmotes();
|
const auto &globalBttvEmotes = app->twitch->getBttvEmotes();
|
||||||
const auto &globalFfzEmotes = app->twitch->getFfzEmotes();
|
const auto &globalFfzEmotes = app->twitch->getFfzEmotes();
|
||||||
|
const auto &globalSeventvEmotes = app->twitch->getSeventvEmotes();
|
||||||
|
|
||||||
auto flags = MessageElementFlags();
|
auto flags = MessageElementFlags();
|
||||||
auto emote = boost::optional<EmotePtr>{};
|
auto emote = boost::optional<EmotePtr>{};
|
||||||
@@ -1035,8 +1038,10 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
|||||||
// Emote order:
|
// Emote order:
|
||||||
// - FrankerFaceZ Channel
|
// - FrankerFaceZ Channel
|
||||||
// - BetterTTV Channel
|
// - BetterTTV Channel
|
||||||
|
// - 7TV Channel
|
||||||
// - FrankerFaceZ Global
|
// - FrankerFaceZ Global
|
||||||
// - BetterTTV Global
|
// - BetterTTV Global
|
||||||
|
// - 7TV Global
|
||||||
if (this->twitchChannel && (emote = this->twitchChannel->ffzEmote(name)))
|
if (this->twitchChannel && (emote = this->twitchChannel->ffzEmote(name)))
|
||||||
{
|
{
|
||||||
flags = MessageElementFlag::FfzEmote;
|
flags = MessageElementFlag::FfzEmote;
|
||||||
@@ -1046,6 +1051,15 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
|||||||
{
|
{
|
||||||
flags = MessageElementFlag::BttvEmote;
|
flags = MessageElementFlag::BttvEmote;
|
||||||
}
|
}
|
||||||
|
else if (this->twitchChannel != nullptr &&
|
||||||
|
(emote = this->twitchChannel->seventvEmote(name)))
|
||||||
|
{
|
||||||
|
flags = MessageElementFlag::SevenTVEmote;
|
||||||
|
if (emote.value()->zeroWidth)
|
||||||
|
{
|
||||||
|
flags.set(MessageElementFlag::ZeroWidthEmote);
|
||||||
|
}
|
||||||
|
}
|
||||||
else if ((emote = globalFfzEmotes.emote(name)))
|
else if ((emote = globalFfzEmotes.emote(name)))
|
||||||
{
|
{
|
||||||
flags = MessageElementFlag::FfzEmote;
|
flags = MessageElementFlag::FfzEmote;
|
||||||
@@ -1059,6 +1073,14 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
|||||||
flags.set(MessageElementFlag::ZeroWidthEmote);
|
flags.set(MessageElementFlag::ZeroWidthEmote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ((emote = globalSeventvEmotes.globalEmote(name)))
|
||||||
|
{
|
||||||
|
flags = MessageElementFlag::SevenTVEmote;
|
||||||
|
if (emote.value()->zeroWidth)
|
||||||
|
{
|
||||||
|
flags.set(MessageElementFlag::ZeroWidthEmote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (emote)
|
if (emote)
|
||||||
{
|
{
|
||||||
@@ -1217,6 +1239,14 @@ void TwitchMessageBuilder::appendFfzBadges()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TwitchMessageBuilder::appendSeventvBadges()
|
||||||
|
{
|
||||||
|
if (auto badge = getApp()->seventvBadges->getBadge({this->userId_}))
|
||||||
|
{
|
||||||
|
this->emplace<BadgeElement>(*badge, MessageElementFlag::BadgeSevenTV);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
|
Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
|
||||||
{
|
{
|
||||||
if (this->bitsLeft == 0)
|
if (this->bitsLeft == 0)
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ private:
|
|||||||
void appendTwitchBadges();
|
void appendTwitchBadges();
|
||||||
void appendChatterinoBadges();
|
void appendChatterinoBadges();
|
||||||
void appendFfzBadges();
|
void appendFfzBadges();
|
||||||
|
void appendSeventvBadges();
|
||||||
Outcome tryParseCheermote(const QString &string);
|
Outcome tryParseCheermote(const QString &string);
|
||||||
|
|
||||||
bool shouldAddModerationElements() const;
|
bool shouldAddModerationElements() const;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/emoji/Emojis.hpp"
|
#include "providers/emoji/Emojis.hpp"
|
||||||
#include "providers/ffz/FfzEmotes.hpp"
|
#include "providers/ffz/FfzEmotes.hpp"
|
||||||
|
#include "providers/seventv/SeventvEmotes.hpp"
|
||||||
#include "providers/twitch/TwitchEmotes.hpp"
|
#include "providers/twitch/TwitchEmotes.hpp"
|
||||||
#include "singletons/helper/GifTimer.hpp"
|
#include "singletons/helper/GifTimer.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ public:
|
|||||||
"/appearance/badges/useCustomFfzModeratorBadges", true};
|
"/appearance/badges/useCustomFfzModeratorBadges", true};
|
||||||
BoolSetting useCustomFfzVipBadges = {
|
BoolSetting useCustomFfzVipBadges = {
|
||||||
"/appearance/badges/useCustomFfzVipBadges", true};
|
"/appearance/badges/useCustomFfzVipBadges", true};
|
||||||
|
BoolSetting showBadgesSevenTV = {"/appearance/badges/seventv", true};
|
||||||
|
|
||||||
/// Behaviour
|
/// Behaviour
|
||||||
BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages",
|
BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages",
|
||||||
@@ -209,6 +210,8 @@ public:
|
|||||||
BoolSetting enableEmoteImages = {"/emotes/enableEmoteImages", true};
|
BoolSetting enableEmoteImages = {"/emotes/enableEmoteImages", true};
|
||||||
BoolSetting animateEmotes = {"/emotes/enableGifAnimations", true};
|
BoolSetting animateEmotes = {"/emotes/enableGifAnimations", true};
|
||||||
FloatSetting emoteScale = {"/emotes/scale", 1.f};
|
FloatSetting emoteScale = {"/emotes/scale", 1.f};
|
||||||
|
BoolSetting showUnlistedSevenTVEmotes = {
|
||||||
|
"/emotes/showUnlistedSevenTVEmotes", false};
|
||||||
|
|
||||||
QStringSetting emojiSet = {"/emotes/emojiSet", "Twitter"};
|
QStringSetting emojiSet = {"/emotes/emojiSet", "Twitter"};
|
||||||
|
|
||||||
@@ -220,6 +223,8 @@ public:
|
|||||||
BoolSetting enableBTTVChannelEmotes = {"/emotes/bttv/channel", true};
|
BoolSetting enableBTTVChannelEmotes = {"/emotes/bttv/channel", true};
|
||||||
BoolSetting enableFFZGlobalEmotes = {"/emotes/ffz/global", true};
|
BoolSetting enableFFZGlobalEmotes = {"/emotes/ffz/global", true};
|
||||||
BoolSetting enableFFZChannelEmotes = {"/emotes/ffz/channel", true};
|
BoolSetting enableFFZChannelEmotes = {"/emotes/ffz/channel", true};
|
||||||
|
BoolSetting enableSevenTVGlobalEmotes = {"/emotes/seventv/global", true};
|
||||||
|
BoolSetting enableSevenTVChannelEmotes = {"/emotes/seventv/channel", true};
|
||||||
|
|
||||||
/// Links
|
/// Links
|
||||||
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};
|
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ WindowManager::WindowManager()
|
|||||||
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->showBadgesFfz);
|
||||||
|
this->wordFlagsListener_.addSetting(settings->showBadgesSevenTV);
|
||||||
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);
|
||||||
@@ -179,6 +180,7 @@ void WindowManager::updateWordTypeMask()
|
|||||||
flags.set(settings->showBadgesChatterino ? MEF::BadgeChatterino
|
flags.set(settings->showBadgesChatterino ? MEF::BadgeChatterino
|
||||||
: MEF::None);
|
: MEF::None);
|
||||||
flags.set(settings->showBadgesFfz ? MEF::BadgeFfz : MEF::None);
|
flags.set(settings->showBadgesFfz ? MEF::BadgeFfz : MEF::None);
|
||||||
|
flags.set(settings->showBadgesSevenTV ? MEF::BadgeSevenTV : MEF::None);
|
||||||
|
|
||||||
// username
|
// username
|
||||||
flags.set(MEF::Username);
|
flags.set(MEF::Username);
|
||||||
|
|||||||
@@ -359,6 +359,12 @@ void EmotePopup::loadChannel(ChannelPtr channel)
|
|||||||
addEmotes(*globalChannel, *getApp()->twitch->getFfzEmotes().emotes(),
|
addEmotes(*globalChannel, *getApp()->twitch->getFfzEmotes().emotes(),
|
||||||
"FrankerFaceZ", MessageElementFlag::FfzEmote);
|
"FrankerFaceZ", MessageElementFlag::FfzEmote);
|
||||||
}
|
}
|
||||||
|
if (Settings::instance().enableSevenTVGlobalEmotes)
|
||||||
|
{
|
||||||
|
addEmotes(*globalChannel,
|
||||||
|
*getApp()->twitch->getSeventvEmotes().globalEmotes(), "7TV",
|
||||||
|
MessageElementFlag::SevenTVEmote);
|
||||||
|
}
|
||||||
|
|
||||||
// channel
|
// channel
|
||||||
if (Settings::instance().enableBTTVChannelEmotes)
|
if (Settings::instance().enableBTTVChannelEmotes)
|
||||||
@@ -371,6 +377,11 @@ void EmotePopup::loadChannel(ChannelPtr channel)
|
|||||||
addEmotes(*channelChannel, *this->twitchChannel_->ffzEmotes(),
|
addEmotes(*channelChannel, *this->twitchChannel_->ffzEmotes(),
|
||||||
"FrankerFaceZ", MessageElementFlag::FfzEmote);
|
"FrankerFaceZ", MessageElementFlag::FfzEmote);
|
||||||
}
|
}
|
||||||
|
if (Settings::instance().enableSevenTVChannelEmotes)
|
||||||
|
{
|
||||||
|
addEmotes(*channelChannel, *this->twitchChannel_->seventvEmotes(),
|
||||||
|
"7TV", MessageElementFlag::SevenTVEmote);
|
||||||
|
}
|
||||||
|
|
||||||
this->globalEmotesView_->setChannel(globalChannel);
|
this->globalEmotesView_->setChannel(globalChannel);
|
||||||
this->subEmotesView_->setChannel(subChannel);
|
this->subEmotesView_->setChannel(subChannel);
|
||||||
@@ -429,6 +440,8 @@ void EmotePopup::filterTwitchEmotes(std::shared_ptr<Channel> searchChannel,
|
|||||||
searchText, getApp()->twitch->getBttvEmotes().emotes());
|
searchText, getApp()->twitch->getBttvEmotes().emotes());
|
||||||
auto ffzGlobalEmotes = this->filterEmoteMap(
|
auto ffzGlobalEmotes = this->filterEmoteMap(
|
||||||
searchText, getApp()->twitch->getFfzEmotes().emotes());
|
searchText, getApp()->twitch->getFfzEmotes().emotes());
|
||||||
|
auto *seventvGlobalEmotes = this->filterEmoteMap(
|
||||||
|
searchText, getApp()->twitch->getSeventvEmotes().globalEmotes());
|
||||||
|
|
||||||
// twitch
|
// twitch
|
||||||
addEmoteSets(twitchGlobalEmotes, *searchChannel, *searchChannel,
|
addEmoteSets(twitchGlobalEmotes, *searchChannel, *searchChannel,
|
||||||
@@ -451,6 +464,9 @@ void EmotePopup::filterTwitchEmotes(std::shared_ptr<Channel> searchChannel,
|
|||||||
this->filterEmoteMap(searchText, this->twitchChannel_->bttvEmotes());
|
this->filterEmoteMap(searchText, this->twitchChannel_->bttvEmotes());
|
||||||
auto ffzChannelEmotes =
|
auto ffzChannelEmotes =
|
||||||
this->filterEmoteMap(searchText, this->twitchChannel_->ffzEmotes());
|
this->filterEmoteMap(searchText, this->twitchChannel_->ffzEmotes());
|
||||||
|
auto *seventvChannelEmotes =
|
||||||
|
this->filterEmoteMap(searchText, this->twitchChannel_->seventvEmotes());
|
||||||
|
|
||||||
// channel
|
// channel
|
||||||
if (bttvChannelEmotes->size() > 0)
|
if (bttvChannelEmotes->size() > 0)
|
||||||
addEmotes(*searchChannel, *bttvChannelEmotes, "BetterTTV (Channel)",
|
addEmotes(*searchChannel, *bttvChannelEmotes, "BetterTTV (Channel)",
|
||||||
@@ -458,6 +474,11 @@ void EmotePopup::filterTwitchEmotes(std::shared_ptr<Channel> searchChannel,
|
|||||||
if (ffzChannelEmotes->size() > 0)
|
if (ffzChannelEmotes->size() > 0)
|
||||||
addEmotes(*searchChannel, *ffzChannelEmotes, "FrankerFaceZ (Channel)",
|
addEmotes(*searchChannel, *ffzChannelEmotes, "FrankerFaceZ (Channel)",
|
||||||
MessageElementFlag::FfzEmote);
|
MessageElementFlag::FfzEmote);
|
||||||
|
if (!seventvChannelEmotes->empty())
|
||||||
|
{
|
||||||
|
addEmotes(*searchChannel, *seventvChannelEmotes, "SevenTV (Channel)",
|
||||||
|
MessageElementFlag::SevenTVEmote);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmotePopup::filterEmotes(const QString &searchText)
|
void EmotePopup::filterEmotes(const QString &searchText)
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ namespace {
|
|||||||
{
|
{
|
||||||
addPageLink("FFZ");
|
addPageLink("FFZ");
|
||||||
}
|
}
|
||||||
|
else if (creatorFlags.has(MessageElementFlag::SevenTVEmote))
|
||||||
|
{
|
||||||
|
addPageLink("7TV");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current function: https://www.desmos.com/calculator/vdyamchjwh
|
// Current function: https://www.desmos.com/calculator/vdyamchjwh
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
#include "controllers/hotkeys/HotkeyCategory.hpp"
|
#include "controllers/hotkeys/HotkeyCategory.hpp"
|
||||||
#include "controllers/hotkeys/HotkeyController.hpp"
|
#include "controllers/hotkeys/HotkeyController.hpp"
|
||||||
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Fonts.hpp"
|
#include "singletons/Fonts.hpp"
|
||||||
#include "singletons/NativeMessaging.hpp"
|
#include "singletons/NativeMessaging.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
@@ -340,6 +342,22 @@ void GeneralPage::initLayout(GeneralPageView &layout)
|
|||||||
|
|
||||||
layout.addCheckbox("Remove spaces between emotes",
|
layout.addCheckbox("Remove spaces between emotes",
|
||||||
s.removeSpacesBetweenEmotes);
|
s.removeSpacesBetweenEmotes);
|
||||||
|
layout.addCheckbox("Show unlisted 7TV emotes", s.showUnlistedSevenTVEmotes);
|
||||||
|
s.showUnlistedSevenTVEmotes.connect(
|
||||||
|
[]() {
|
||||||
|
getApp()->twitch->forEachChannelAndSpecialChannels(
|
||||||
|
[](const auto &c) {
|
||||||
|
if (c->isTwitchChannel())
|
||||||
|
{
|
||||||
|
auto *channel = dynamic_cast<TwitchChannel *>(c.get());
|
||||||
|
if (channel != nullptr)
|
||||||
|
{
|
||||||
|
channel->refreshSevenTVChannelEmotes(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
false);
|
||||||
layout.addDropdown<int>(
|
layout.addDropdown<int>(
|
||||||
"Show info on hover", {"Don't show", "Always show", "Hold shift"},
|
"Show info on hover", {"Don't show", "Always show", "Hold shift"},
|
||||||
s.emotesTooltipPreview,
|
s.emotesTooltipPreview,
|
||||||
@@ -362,6 +380,8 @@ void GeneralPage::initLayout(GeneralPageView &layout)
|
|||||||
layout.addCheckbox("Show BTTV channel emotes", s.enableBTTVChannelEmotes);
|
layout.addCheckbox("Show BTTV channel emotes", s.enableBTTVChannelEmotes);
|
||||||
layout.addCheckbox("Show FFZ global emotes", s.enableFFZGlobalEmotes);
|
layout.addCheckbox("Show FFZ global emotes", s.enableFFZGlobalEmotes);
|
||||||
layout.addCheckbox("Show FFZ channel emotes", s.enableFFZChannelEmotes);
|
layout.addCheckbox("Show FFZ channel emotes", s.enableFFZChannelEmotes);
|
||||||
|
layout.addCheckbox("Show 7TV global emotes", s.enableSevenTVGlobalEmotes);
|
||||||
|
layout.addCheckbox("Show 7TV channel emotes", s.enableSevenTVChannelEmotes);
|
||||||
|
|
||||||
layout.addTitle("Streamer Mode");
|
layout.addTitle("Streamer Mode");
|
||||||
layout.addDescription(
|
layout.addDescription(
|
||||||
@@ -631,6 +651,7 @@ void GeneralPage::initLayout(GeneralPageView &layout)
|
|||||||
layout.addCheckbox("Chatterino", s.showBadgesChatterino);
|
layout.addCheckbox("Chatterino", s.showBadgesChatterino);
|
||||||
layout.addCheckbox("FrankerFaceZ (Bot, FFZ Supporter, FFZ Developer)",
|
layout.addCheckbox("FrankerFaceZ (Bot, FFZ Supporter, FFZ Developer)",
|
||||||
s.showBadgesFfz);
|
s.showBadgesFfz);
|
||||||
|
layout.addCheckbox("7TV", s.showBadgesSevenTV);
|
||||||
layout.addSeperator();
|
layout.addSeperator();
|
||||||
layout.addCheckbox("Use custom FrankerFaceZ moderator badges",
|
layout.addCheckbox("Use custom FrankerFaceZ moderator badges",
|
||||||
s.useCustomFfzModeratorBadges);
|
s.useCustomFfzModeratorBadges);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/ffz/FfzEmotes.hpp"
|
#include "providers/ffz/FfzEmotes.hpp"
|
||||||
|
#include "providers/seventv/SeventvEmotes.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
@@ -99,17 +100,25 @@ void InputCompletionPopup::updateEmotes(const QString &text, ChannelPtr channel)
|
|||||||
|
|
||||||
if (tc)
|
if (tc)
|
||||||
{
|
{
|
||||||
// TODO extract "Channel BetterTTV" text into a #define.
|
// TODO extract "Channel {BetterTTV,7TV,FrankerFaceZ}" text into a #define.
|
||||||
if (auto bttv = tc->bttvEmotes())
|
if (auto bttv = tc->bttvEmotes())
|
||||||
addEmotes(emotes, *bttv, text, "Channel BetterTTV");
|
addEmotes(emotes, *bttv, text, "Channel BetterTTV");
|
||||||
if (auto ffz = tc->ffzEmotes())
|
if (auto ffz = tc->ffzEmotes())
|
||||||
addEmotes(emotes, *ffz, text, "Channel FrankerFaceZ");
|
addEmotes(emotes, *ffz, text, "Channel FrankerFaceZ");
|
||||||
|
if (auto seventv = tc->seventvEmotes())
|
||||||
|
{
|
||||||
|
addEmotes(emotes, *seventv, text, "Channel 7TV");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto bttvG = getApp()->twitch->getBttvEmotes().emotes())
|
if (auto bttvG = getApp()->twitch->getBttvEmotes().emotes())
|
||||||
addEmotes(emotes, *bttvG, text, "Global BetterTTV");
|
addEmotes(emotes, *bttvG, text, "Global BetterTTV");
|
||||||
if (auto ffzG = getApp()->twitch->getFfzEmotes().emotes())
|
if (auto ffzG = getApp()->twitch->getFfzEmotes().emotes())
|
||||||
addEmotes(emotes, *ffzG, text, "Global FrankerFaceZ");
|
addEmotes(emotes, *ffzG, text, "Global FrankerFaceZ");
|
||||||
|
if (auto seventvG = getApp()->twitch->getSeventvEmotes().globalEmotes())
|
||||||
|
{
|
||||||
|
addEmotes(emotes, *seventvG, text, "Global 7TV");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addEmojis(emotes, getApp()->emotes->emojis.emojis, text);
|
addEmojis(emotes, getApp()->emotes->emojis.emojis, text);
|
||||||
|
|||||||
@@ -1198,6 +1198,7 @@ void Split::reloadChannelAndSubscriberEmotes()
|
|||||||
{
|
{
|
||||||
twitchChannel->refreshBTTVChannelEmotes(true);
|
twitchChannel->refreshBTTVChannelEmotes(true);
|
||||||
twitchChannel->refreshFFZChannelEmotes(true);
|
twitchChannel->refreshFFZChannelEmotes(true);
|
||||||
|
twitchChannel->refreshSevenTVChannelEmotes(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -999,6 +999,7 @@ void SplitHeader::reloadChannelEmotes()
|
|||||||
{
|
{
|
||||||
twitchChannel->refreshFFZChannelEmotes(true);
|
twitchChannel->refreshFFZChannelEmotes(true);
|
||||||
twitchChannel->refreshBTTVChannelEmotes(true);
|
twitchChannel->refreshBTTVChannelEmotes(true);
|
||||||
|
twitchChannel->refreshSevenTVChannelEmotes(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user