mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Load mod badge information from the FFZ API instead of assuming the URL.
This lets us load all sizes of the emote if they are available. Channel with all versions of the mod badge: https://api.frankerfacez.com/v1/room/pajlada Channel with only one version of the mod badge: https://api.frankerfacez.com/v1/room/apa420 Channel with no mod badge: https://api.frankerfacez.com/v1/room/forsen
This commit is contained in:
parent
4ec10e720c
commit
256a65a12e
|
@ -127,7 +127,6 @@ SOURCES += \
|
||||||
src/providers/chatterino/ChatterinoBadges.cpp \
|
src/providers/chatterino/ChatterinoBadges.cpp \
|
||||||
src/providers/emoji/Emojis.cpp \
|
src/providers/emoji/Emojis.cpp \
|
||||||
src/providers/ffz/FfzEmotes.cpp \
|
src/providers/ffz/FfzEmotes.cpp \
|
||||||
src/providers/ffz/FfzModBadge.cpp \
|
|
||||||
src/providers/irc/AbstractIrcServer.cpp \
|
src/providers/irc/AbstractIrcServer.cpp \
|
||||||
src/providers/irc/IrcAccount.cpp \
|
src/providers/irc/IrcAccount.cpp \
|
||||||
src/providers/irc/IrcChannel2.cpp \
|
src/providers/irc/IrcChannel2.cpp \
|
||||||
|
@ -293,7 +292,6 @@ HEADERS += \
|
||||||
src/providers/chatterino/ChatterinoBadges.hpp \
|
src/providers/chatterino/ChatterinoBadges.hpp \
|
||||||
src/providers/emoji/Emojis.hpp \
|
src/providers/emoji/Emojis.hpp \
|
||||||
src/providers/ffz/FfzEmotes.hpp \
|
src/providers/ffz/FfzEmotes.hpp \
|
||||||
src/providers/ffz/FfzModBadge.hpp \
|
|
||||||
src/providers/irc/AbstractIrcServer.hpp \
|
src/providers/irc/AbstractIrcServer.hpp \
|
||||||
src/providers/irc/IrcAccount.hpp \
|
src/providers/irc/IrcAccount.hpp \
|
||||||
src/providers/irc/IrcChannel2.hpp \
|
src/providers/irc/IrcChannel2.hpp \
|
||||||
|
|
|
@ -349,6 +349,9 @@ void Image::actuallyLoad()
|
||||||
if (!shared)
|
if (!shared)
|
||||||
return Failure;
|
return Failure;
|
||||||
|
|
||||||
|
if (shared->customOnSuccess_)
|
||||||
|
return shared->customOnSuccess_(shared, result);
|
||||||
|
|
||||||
auto data = result.getData();
|
auto data = result.getData();
|
||||||
|
|
||||||
// const cast since we are only reading from it
|
// const cast since we are only reading from it
|
||||||
|
@ -393,4 +396,11 @@ bool Image::operator!=(const Image &other) const
|
||||||
return !this->operator==(other);
|
return !this->operator==(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImagePtr Image::setCustomOnSuccess(
|
||||||
|
std::function<Outcome(ImagePtr, NetworkResult)> customOnSuccess)
|
||||||
|
{
|
||||||
|
this->customOnSuccess_ = customOnSuccess;
|
||||||
|
return shared_from_this();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
|
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
|
#include "common/NetworkResult.hpp"
|
||||||
#include "common/NullablePtr.hpp"
|
#include "common/NullablePtr.hpp"
|
||||||
|
#include "common/Outcome.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
@ -66,13 +68,18 @@ public:
|
||||||
bool operator==(const Image &image) const;
|
bool operator==(const Image &image) const;
|
||||||
bool operator!=(const Image &image) const;
|
bool operator!=(const Image &image) const;
|
||||||
|
|
||||||
|
ImagePtr setCustomOnSuccess(
|
||||||
|
std::function<Outcome(ImagePtr, NetworkResult)> onSuccess);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Image();
|
Image();
|
||||||
Image(const Url &url, qreal scale);
|
Image(const Url &url, qreal scale);
|
||||||
Image(qreal scale);
|
Image(qreal scale);
|
||||||
|
|
||||||
|
public:
|
||||||
void setPixmap(const QPixmap &pixmap);
|
void setPixmap(const QPixmap &pixmap);
|
||||||
|
|
||||||
|
private:
|
||||||
void actuallyLoad();
|
void actuallyLoad();
|
||||||
|
|
||||||
Url url_{};
|
Url url_{};
|
||||||
|
@ -81,5 +88,6 @@ private:
|
||||||
bool shouldLoad_{false};
|
bool shouldLoad_{false};
|
||||||
std::unique_ptr<detail::Frames> frames_{};
|
std::unique_ptr<detail::Frames> frames_{};
|
||||||
QObject object_{};
|
QObject object_{};
|
||||||
|
std::function<Outcome(ImagePtr, NetworkResult)> customOnSuccess_{};
|
||||||
};
|
};
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
#include "providers/ffz/FfzEmotes.hpp"
|
#include "providers/ffz/FfzEmotes.hpp"
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QImageReader>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
|
@ -10,6 +15,30 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace {
|
namespace {
|
||||||
|
Outcome addModBadgeBackground(ImagePtr image, NetworkResult result)
|
||||||
|
{
|
||||||
|
auto data = result.getData();
|
||||||
|
|
||||||
|
QBuffer buffer(const_cast<QByteArray *>(&data));
|
||||||
|
buffer.open(QIODevice::ReadOnly);
|
||||||
|
QImageReader reader(&buffer);
|
||||||
|
if (reader.imageCount() == 0)
|
||||||
|
return Failure;
|
||||||
|
|
||||||
|
QPixmap badgeOverlay = QPixmap::fromImageReader(&reader);
|
||||||
|
QPixmap badgePixmap(badgeOverlay.width(), badgeOverlay.height());
|
||||||
|
|
||||||
|
// the default mod badge green color
|
||||||
|
badgePixmap.fill(QColor("#34AE0A"));
|
||||||
|
QPainter painter(&badgePixmap);
|
||||||
|
QRectF rect(0, 0, badgeOverlay.width(), badgeOverlay.height());
|
||||||
|
painter.drawPixmap(rect, badgeOverlay, rect);
|
||||||
|
|
||||||
|
image->setPixmap(badgePixmap);
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
|
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
|
||||||
{
|
{
|
||||||
auto emote = urls.value(emoteScale);
|
auto emote = urls.value(emoteScale);
|
||||||
|
@ -79,8 +108,42 @@ namespace {
|
||||||
|
|
||||||
return {Success, std::move(emotes)};
|
return {Success, std::move(emotes)};
|
||||||
}
|
}
|
||||||
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
|
std::tuple<Outcome, EmoteMap, boost::optional<EmotePtr>>
|
||||||
|
parseChannelResponse(const QJsonObject &jsonRoot)
|
||||||
{
|
{
|
||||||
|
boost::optional<EmotePtr> modBadge;
|
||||||
|
|
||||||
|
// Parse mod badge
|
||||||
|
auto room = jsonRoot.value("room").toObject();
|
||||||
|
auto modUrls = room.value("mod_urls").toObject();
|
||||||
|
if (!modUrls.isEmpty())
|
||||||
|
{
|
||||||
|
auto modBadge1x = getEmoteLink(modUrls, "1");
|
||||||
|
auto modBadge2x = getEmoteLink(modUrls, "2");
|
||||||
|
auto modBadge3x = getEmoteLink(modUrls, "4");
|
||||||
|
|
||||||
|
auto modBadgeImageSet = ImageSet{
|
||||||
|
Image::fromUrl(modBadge1x, 1)
|
||||||
|
->setCustomOnSuccess(addModBadgeBackground),
|
||||||
|
modBadge2x.string.isEmpty()
|
||||||
|
? Image::getEmpty()
|
||||||
|
: Image::fromUrl(modBadge2x, 0.5)
|
||||||
|
->setCustomOnSuccess(addModBadgeBackground),
|
||||||
|
modBadge3x.string.isEmpty()
|
||||||
|
? Image::getEmpty()
|
||||||
|
: Image::fromUrl(modBadge3x, 0.25)
|
||||||
|
->setCustomOnSuccess(addModBadgeBackground),
|
||||||
|
};
|
||||||
|
|
||||||
|
modBadge = std::make_shared<Emote>(Emote{
|
||||||
|
{""},
|
||||||
|
modBadgeImageSet,
|
||||||
|
Tooltip{"Twitch Channel Moderator"},
|
||||||
|
modBadge1x,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse emotes
|
||||||
auto jsonSets = jsonRoot.value("sets").toObject();
|
auto jsonSets = jsonRoot.value("sets").toObject();
|
||||||
auto emotes = EmoteMap();
|
auto emotes = EmoteMap();
|
||||||
|
|
||||||
|
@ -110,7 +173,7 @@ namespace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {Success, std::move(emotes)};
|
return {Success, std::move(emotes), modBadge};
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -151,19 +214,29 @@ void FfzEmotes::loadEmotes()
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FfzEmotes::loadChannel(const QString &channelId,
|
void FfzEmotes::loadChannel(
|
||||||
std::function<void(EmoteMap &&)> callback)
|
const QString &channelId, std::function<void(EmoteMap &&)> emoteCallback,
|
||||||
|
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback)
|
||||||
{
|
{
|
||||||
log("[FFZEmotes] Reload FFZ Channel Emotes for channel {}\n", channelId);
|
log("[FFZEmotes] Reload FFZ Channel Emotes for channel {}\n", channelId);
|
||||||
|
|
||||||
NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId)
|
NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId)
|
||||||
|
|
||||||
.timeout(20000)
|
.timeout(20000)
|
||||||
.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
|
.onSuccess([emoteCallback = std::move(emoteCallback),
|
||||||
auto pair = parseChannelEmotes(result.parseJson());
|
modBadgeCallback =
|
||||||
if (pair.first)
|
std::move(modBadgeCallback)](auto result) -> Outcome {
|
||||||
callback(std::move(pair.second));
|
auto [success, emoteMap, modBadge] =
|
||||||
return pair.first;
|
parseChannelResponse(result.parseJson());
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
emoteCallback(std::move(emoteMap));
|
||||||
|
modBadgeCallback(std::move(modBadge));
|
||||||
|
|
||||||
|
return success;
|
||||||
})
|
})
|
||||||
.onError([channelId](int result) {
|
.onError([channelId](int result) {
|
||||||
if (result == 203)
|
if (result == 203)
|
||||||
|
|
|
@ -22,8 +22,10 @@ public:
|
||||||
std::shared_ptr<const EmoteMap> emotes() const;
|
std::shared_ptr<const EmoteMap> emotes() const;
|
||||||
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
||||||
void loadEmotes();
|
void loadEmotes();
|
||||||
static void loadChannel(const QString &channelId,
|
static void loadChannel(
|
||||||
std::function<void(EmoteMap &&)> callback);
|
const QString &channelId,
|
||||||
|
std::function<void(EmoteMap &&)> emoteCallback,
|
||||||
|
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
#include "FfzModBadge.hpp"
|
|
||||||
|
|
||||||
#include <QBuffer>
|
|
||||||
#include <QImageReader>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include "common/NetworkRequest.hpp"
|
|
||||||
#include "common/Outcome.hpp"
|
|
||||||
#include "messages/Emote.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
FfzModBadge::FfzModBadge(const QString &channelName)
|
|
||||||
: channelName_(channelName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void FfzModBadge::loadCustomModBadge()
|
|
||||||
{
|
|
||||||
static QString partialUrl("https://cdn.frankerfacez.com/room-badge/mod/");
|
|
||||||
|
|
||||||
QString url = partialUrl + channelName_ + "/1";
|
|
||||||
NetworkRequest(url)
|
|
||||||
|
|
||||||
.onSuccess([this, url](auto result) -> Outcome {
|
|
||||||
auto data = result.getData();
|
|
||||||
|
|
||||||
QBuffer buffer(const_cast<QByteArray *>(&data));
|
|
||||||
buffer.open(QIODevice::ReadOnly);
|
|
||||||
QImageReader reader(&buffer);
|
|
||||||
if (reader.imageCount() == 0)
|
|
||||||
return Failure;
|
|
||||||
|
|
||||||
QPixmap badgeOverlay = QPixmap::fromImageReader(&reader);
|
|
||||||
QPixmap badgePixmap(18, 18);
|
|
||||||
|
|
||||||
// the default mod badge green color
|
|
||||||
badgePixmap.fill(QColor("#34AE0A"));
|
|
||||||
QPainter painter(&badgePixmap);
|
|
||||||
QRectF rect(0, 0, 18, 18);
|
|
||||||
painter.drawPixmap(rect, badgeOverlay, rect);
|
|
||||||
|
|
||||||
auto emote = Emote{{""},
|
|
||||||
ImageSet{Image::fromPixmap(badgePixmap)},
|
|
||||||
Tooltip{"Twitch Channel Moderator"},
|
|
||||||
Url{url}};
|
|
||||||
|
|
||||||
this->badge_ = std::make_shared<Emote>(emote);
|
|
||||||
// getBadge.execute();
|
|
||||||
|
|
||||||
return Success;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
EmotePtr FfzModBadge::badge() const
|
|
||||||
{
|
|
||||||
return this->badge_;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -1,25 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <boost/optional.hpp>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
struct Emote;
|
|
||||||
using EmotePtr = std::shared_ptr<const Emote>;
|
|
||||||
|
|
||||||
class FfzModBadge
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FfzModBadge(const QString &channelName);
|
|
||||||
|
|
||||||
void loadCustomModBadge();
|
|
||||||
|
|
||||||
EmotePtr badge() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
const QString channelName_;
|
|
||||||
EmotePtr badge_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -48,8 +48,14 @@ void ChatroomChannel::refreshFFZChannelEmotes()
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FfzEmotes::loadChannel(this->chatroomOwnerId, [this](auto &&emoteMap) {
|
FfzEmotes::loadChannel(
|
||||||
this->ffzEmotes_.set(std::make_shared<EmoteMap>(std::move(emoteMap)));
|
this->chatroomOwnerId,
|
||||||
|
[this](auto &&emoteMap) {
|
||||||
|
this->ffzEmotes_.set(
|
||||||
|
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||||
|
},
|
||||||
|
[this](auto &&modBadge) {
|
||||||
|
this->ffzCustomModBadge_ = std::move(modBadge);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,6 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||||
, globalFfz_(ffz)
|
, globalFfz_(ffz)
|
||||||
, bttvEmotes_(std::make_shared<EmoteMap>())
|
, bttvEmotes_(std::make_shared<EmoteMap>())
|
||||||
, ffzEmotes_(std::make_shared<EmoteMap>())
|
, ffzEmotes_(std::make_shared<EmoteMap>())
|
||||||
, ffzCustomModBadge_(name)
|
|
||||||
, mod_(false)
|
, mod_(false)
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:{}] Opened", name);
|
log("[TwitchChannel:{}] Opened", name);
|
||||||
|
@ -138,7 +137,6 @@ void TwitchChannel::initialize()
|
||||||
{
|
{
|
||||||
this->refreshChatters();
|
this->refreshChatters();
|
||||||
this->refreshBadges();
|
this->refreshBadges();
|
||||||
this->ffzCustomModBadge_.loadCustomModBadge();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TwitchChannel::isEmpty() const
|
bool TwitchChannel::isEmpty() const
|
||||||
|
@ -164,10 +162,17 @@ void TwitchChannel::refreshBTTVChannelEmotes()
|
||||||
void TwitchChannel::refreshFFZChannelEmotes()
|
void TwitchChannel::refreshFFZChannelEmotes()
|
||||||
{
|
{
|
||||||
FfzEmotes::loadChannel(
|
FfzEmotes::loadChannel(
|
||||||
this->roomId(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
this->roomId(),
|
||||||
|
[this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
||||||
if (auto shared = weak.lock())
|
if (auto shared = weak.lock())
|
||||||
this->ffzEmotes_.set(
|
this->ffzEmotes_.set(
|
||||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
||||||
|
},
|
||||||
|
[this, weak = weakOf<Channel>(this)](auto &&modBadge) {
|
||||||
|
if (auto shared = weak.lock())
|
||||||
|
{
|
||||||
|
this->ffzCustomModBadge_ = std::move(modBadge);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -824,10 +829,7 @@ boost::optional<EmotePtr> TwitchChannel::twitchBadge(
|
||||||
|
|
||||||
boost::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
|
boost::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
|
||||||
{
|
{
|
||||||
if (auto badge = this->ffzCustomModBadge_.badge())
|
return this->ffzCustomModBadge_;
|
||||||
return badge;
|
|
||||||
|
|
||||||
return boost::none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "common/UniqueAccess.hpp"
|
#include "common/UniqueAccess.hpp"
|
||||||
#include "common/UsernameSet.hpp"
|
#include "common/UsernameSet.hpp"
|
||||||
#include "providers/ffz/FfzModBadge.hpp"
|
|
||||||
#include "providers/twitch/TwitchEmotes.hpp"
|
#include "providers/twitch/TwitchEmotes.hpp"
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
|
@ -148,13 +147,13 @@ protected:
|
||||||
FfzEmotes &globalFfz_;
|
FfzEmotes &globalFfz_;
|
||||||
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_;
|
||||||
|
boost::optional<EmotePtr> ffzCustomModBadge_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Badges
|
// Badges
|
||||||
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
|
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
|
||||||
badgeSets_; // "subscribers": { "0": ... "3": ... "6": ...
|
badgeSets_; // "subscribers": { "0": ... "3": ... "6": ...
|
||||||
UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_;
|
UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_;
|
||||||
FfzModBadge ffzCustomModBadge_;
|
|
||||||
|
|
||||||
bool mod_ = false;
|
bool mod_ = false;
|
||||||
bool vip_ = false;
|
bool vip_ = false;
|
||||||
|
|
Loading…
Reference in a new issue