Clean up Twitch badge appending code

This commit is contained in:
Rasmus Karlsson 2019-12-01 13:32:41 +01:00
parent c00f97ac53
commit bcc53c9aa7
6 changed files with 151 additions and 189 deletions

View file

@ -173,6 +173,7 @@ SOURCES += \
src/providers/twitch/TwitchAccount.cpp \ src/providers/twitch/TwitchAccount.cpp \
src/providers/twitch/TwitchAccountManager.cpp \ src/providers/twitch/TwitchAccountManager.cpp \
src/providers/twitch/TwitchApi.cpp \ src/providers/twitch/TwitchApi.cpp \
src/providers/twitch/TwitchBadge.cpp \
src/providers/twitch/TwitchBadges.cpp \ src/providers/twitch/TwitchBadges.cpp \
src/providers/twitch/TwitchChannel.cpp \ src/providers/twitch/TwitchChannel.cpp \
src/providers/twitch/TwitchEmotes.cpp \ src/providers/twitch/TwitchEmotes.cpp \
@ -372,6 +373,7 @@ HEADERS += \
src/providers/twitch/TwitchAccount.hpp \ src/providers/twitch/TwitchAccount.hpp \
src/providers/twitch/TwitchAccountManager.hpp \ src/providers/twitch/TwitchAccountManager.hpp \
src/providers/twitch/TwitchApi.hpp \ src/providers/twitch/TwitchApi.hpp \
src/providers/twitch/TwitchBadge.hpp \
src/providers/twitch/TwitchBadges.hpp \ src/providers/twitch/TwitchBadges.hpp \
src/providers/twitch/TwitchChannel.hpp \ src/providers/twitch/TwitchChannel.hpp \
src/providers/twitch/TwitchCommon.hpp \ src/providers/twitch/TwitchCommon.hpp \

View file

@ -0,0 +1,31 @@
#include "providers/twitch/TwitchBadge.hpp"
#include <QSet>
namespace chatterino {
// set of badge IDs that should be given specific flags.
// vanity flag is left out on purpose as it is our default flag
const QSet<QString> globalAuthority{"staff", "admin", "global_mod"};
const QSet<QString> channelAuthority{"moderator", "vip", "broadcaster"};
const QSet<QString> subBadges{"subscriber", "founder"};
Badge::Badge(QString key, QString value)
: key_(std::move(key))
, value_(std::move(value))
{
if (globalAuthority.contains(this->key_))
{
this->flag_ = MessageElementFlag::BadgeGlobalAuthority;
}
else if (channelAuthority.contains(this->key_))
{
this->flag_ = MessageElementFlag::BadgeChannelAuthority;
}
else if (subBadges.contains(this->key_))
{
this->flag_ = MessageElementFlag::BadgeSubscription;
}
}
} // namespace chatterino

View file

@ -0,0 +1,21 @@
#pragma once
#include "messages/MessageElement.hpp"
#include <QString>
namespace chatterino {
class Badge
{
public:
Badge(QString key, QString value);
QString key_; // e.g. bits
QString value_; // e.g. 100
QString extraValue_{}; // e.g. 5 (the number of months subscribed)
MessageElementFlag flag_{
MessageElementFlag::BadgeVanity}; // badge slot it takes up
};
} // namespace chatterino

View file

@ -65,6 +65,59 @@ QColor getRandomColor(const QVariant &userId)
namespace chatterino { namespace chatterino {
namespace {
QStringList parseTagList(const QVariantMap &tags, const QString &key)
{
auto iterator = tags.find(key);
if (iterator == tags.end())
return QStringList{};
return iterator.value().toString().split(
',', QString::SplitBehavior::SkipEmptyParts);
}
std::map<QString, QString> parseBadgeInfos(const QVariantMap &tags)
{
std::map<QString, QString> badgeInfos;
for (QString badgeInfo : parseTagList(tags, "badge-info"))
{
QStringList parts = badgeInfo.split('/');
if (parts.size() != 2)
{
log("Skipping badge-info because it split weird: {}",
badgeInfo);
continue;
}
badgeInfos.emplace(parts[0], parts[1]);
}
return badgeInfos;
}
std::vector<Badge> parseBadges(const QVariantMap &tags)
{
std::vector<Badge> badges;
for (QString badge : parseTagList(tags, "badges"))
{
QStringList parts = badge.split('/');
if (parts.size() != 2)
{
log("Skipping badge because it split weird: {}", badge);
continue;
}
badges.emplace_back(parts[0], parts[1]);
}
return badges;
}
} // namespace
TwitchMessageBuilder::TwitchMessageBuilder( TwitchMessageBuilder::TwitchMessageBuilder(
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage, Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
const MessageParseArgs &_args) const MessageParseArgs &_args)
@ -1120,7 +1173,24 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
return Failure; return Failure;
} }
// fourtf: this is ugly boost::optional<EmotePtr> TwitchMessageBuilder::getTwitchBadge(
const Badge &badge)
{
if (auto channelBadge =
this->twitchChannel->twitchBadge(badge.key_, badge.value_))
{
return channelBadge;
}
if (auto globalBadge = this->twitchChannel->globalTwitchBadges().badge(
badge.key_, badge.value_))
{
return globalBadge;
}
return boost::none;
}
void TwitchMessageBuilder::appendTwitchBadges() void TwitchMessageBuilder::appendTwitchBadges()
{ {
if (this->twitchChannel == nullptr) if (this->twitchChannel == nullptr)
@ -1128,66 +1198,25 @@ void TwitchMessageBuilder::appendTwitchBadges()
return; return;
} }
auto iterator = this->tags.find("badges"); auto badgeInfos = parseBadgeInfos(this->tags);
if (iterator == this->tags.end()) auto badges = parseBadges(this->tags);
return;
for (QString badge : iterator.value().toString().split(',')) for (const auto &badge : badges)
{ {
if (badge.startsWith("bits/")) auto badgeEmote = this->getTwitchBadge(badge);
if (!badgeEmote)
{ {
QString cheerAmount = badge.mid(5); log("No channel/global variant found {}", badge.key_);
QString tooltip = QString("Twitch cheer ") + cheerAmount; continue;
}
auto tooltip = (*badgeEmote)->tooltip.string;
// Try to fetch channel-specific bit badge if (badge.key_ == "bits")
try
{
if (twitchChannel)
if (const auto &_badge = this->twitchChannel->twitchBadge(
"bits", cheerAmount))
{
this->emplace<BadgeElement>(
_badge.get(), MessageElementFlag::BadgeVanity)
->setTooltip(tooltip);
continue;
}
}
catch (const std::out_of_range &)
{
// Channel does not contain a special bit badge for this version
}
// Use default bit badge
if (auto _badge = this->twitchChannel->globalTwitchBadges().badge(
"bits", cheerAmount))
{
this->emplace<BadgeElement>(_badge.get(),
MessageElementFlag::BadgeVanity)
->setTooltip(tooltip);
}
}
else if (badge == "staff/1")
{ {
this->emplace<ImageElement>( const auto &cheerAmount = badge.value_;
Image::fromPixmap(getResources().twitch.staff), tooltip = QString("Twitch cheer %0").arg(cheerAmount);
MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Staff");
} }
else if (badge == "admin/1") else if (badge.key_ == "moderator")
{
this->emplace<ImageElement>(
Image::fromPixmap(getResources().twitch.admin),
MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Admin");
}
else if (badge == "global_mod/1")
{
this->emplace<ImageElement>(
Image::fromPixmap(getResources().twitch.globalmod),
MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Global Moderator");
}
else if (badge == "moderator/1")
{ {
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge()) if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
{ {
@ -1195,149 +1224,24 @@ void TwitchMessageBuilder::appendTwitchBadges()
customModBadge.get(), customModBadge.get(),
MessageElementFlag::BadgeChannelAuthority) MessageElementFlag::BadgeChannelAuthority)
->setTooltip((*customModBadge)->tooltip.string); ->setTooltip((*customModBadge)->tooltip.string);
// early out, since we have to add a custom badge element here
continue; continue;
} }
this->emplace<ImageElement>(
Image::fromPixmap(getResources().twitch.moderator),
MessageElementFlag::BadgeChannelAuthority)
->setTooltip("Moderator");
} }
else if (badge == "vip/1") else if (badge.flag_ == MessageElementFlag::BadgeSubscription)
{ {
this->emplace<ImageElement>( auto badgeInfoIt = badgeInfos.find(badge.key_);
Image::fromPixmap(getResources().twitch.vip), if (badgeInfoIt != badgeInfos.end())
MessageElementFlag::BadgeChannelAuthority)
->setTooltip("VIP");
}
else if (badge == "broadcaster/1")
{
this->emplace<ImageElement>(
Image::fromPixmap(getResources().twitch.broadcaster),
MessageElementFlag::BadgeChannelAuthority)
->setTooltip("Broadcaster");
}
else if (badge == "turbo/1")
{
this->emplace<ImageElement>(
Image::fromPixmap(getResources().twitch.turbo),
MessageElementFlag::BadgeVanity)
->setTooltip("Twitch Turbo");
}
else if (badge == "premium/1")
{
this->emplace<ImageElement>(
Image::fromPixmap(getResources().twitch.prime),
MessageElementFlag::BadgeVanity)
->setTooltip("Twitch Prime");
}
else if (badge.startsWith("partner/"))
{
int index = badge.midRef(8).toInt();
switch (index)
{ {
case 1: { const auto &subMonths = badgeInfoIt->second;
this->emplace<ImageElement>( tooltip += QString(" (%0 months)").arg(subMonths);
Image::fromPixmap(getResources().twitch.verified,
0.25),
MessageElementFlag::BadgeVanity)
->setTooltip("Verified");
}
break;
default: {
printf("[TwitchMessageBuilder] Unhandled partner badge "
"index: %d\n",
index);
}
break;
} }
} }
else if (badge.startsWith("founder/"))
{
if (auto badgeEmote =
this->twitchChannel->globalTwitchBadges().badge("founder",
"0"))
{
auto badgeInfo = this->tags.find("badge-info");
if (badgeInfo != this->tags.end() &&
badgeInfo.value().toString().split(',')[0].startsWith(
"founder/"))
{
auto subMonths =
badgeInfo.value().toString().split(',')[0].mid(8);
this->emplace<BadgeElement>(
badgeEmote.get(),
MessageElementFlag::BadgeSubscription)
->setTooltip(QString((*badgeEmote)->tooltip.string) +
" (" + subMonths + " months)");
}
else
{
this->emplace<BadgeElement>(
badgeEmote.get(),
MessageElementFlag::BadgeSubscription)
->setTooltip((*badgeEmote)->tooltip.string);
}
}
}
else if (badge.startsWith("subscriber/"))
{
if (auto badgeEmote = this->twitchChannel->twitchBadge(
"subscriber", badge.mid(11)))
{
auto badgeInfo = this->tags.find("badge-info");
if (badgeInfo != this->tags.end() &&
badgeInfo.value().toString().split(',')[0].startsWith(
"subscriber/"))
{
auto subMonths =
badgeInfo.value().toString().split(',')[0].mid(11);
this->emplace<BadgeElement>(
badgeEmote.get(),
MessageElementFlag::BadgeSubscription)
->setTooltip(QString((*badgeEmote)->tooltip.string) +
" (" + subMonths + " months)");
}
else
{
this->emplace<BadgeElement>(
badgeEmote.get(),
MessageElementFlag::BadgeSubscription)
->setTooltip((*badgeEmote)->tooltip.string);
}
continue;
}
// use default subscriber badge if custom one not found this->emplace<BadgeElement>(badgeEmote.get(), badge.flag_)
this->emplace<ImageElement>( ->setTooltip(tooltip);
Image::fromPixmap(getResources().twitch.subscriber, 0.25),
MessageElementFlag::BadgeSubscription)
->setTooltip("Twitch Subscriber");
}
else
{
auto splits = badge.split('/');
if (splits.size() != 2)
continue;
if (auto badgeEmote =
this->twitchChannel->twitchBadge(splits[0], splits[1]))
{
this->emplace<BadgeElement>(badgeEmote.get(),
MessageElementFlag::BadgeVanity)
->setTooltip((*badgeEmote)->tooltip.string);
continue;
}
if (auto _badge = this->twitchChannel->globalTwitchBadges().badge(
splits[0], splits[1]))
{
this->emplace<BadgeElement>(_badge.get(),
MessageElementFlag::BadgeVanity)
->setTooltip((*_badge)->tooltip.string);
continue;
}
}
} }
} // namespace chatterino }
void TwitchMessageBuilder::appendChatterinoBadges() void TwitchMessageBuilder::appendChatterinoBadges()
{ {
@ -1373,4 +1277,5 @@ Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
} }
return Success; return Success;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -3,6 +3,7 @@
#include "common/Aliases.hpp" #include "common/Aliases.hpp"
#include "common/Outcome.hpp" #include "common/Outcome.hpp"
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include "providers/twitch/TwitchBadge.hpp"
#include <IrcMessage> #include <IrcMessage>
#include <QString> #include <QString>
@ -60,6 +61,7 @@ private:
// parseHighlights only updates the visual state of the message, but leaves the playing of alerts and sounds to the triggerHighlights function // parseHighlights only updates the visual state of the message, but leaves the playing of alerts and sounds to the triggerHighlights function
void parseHighlights(); void parseHighlights();
boost::optional<EmotePtr> getTwitchBadge(const Badge &badge);
void appendTwitchEmote( void appendTwitchEmote(
const QString &emote, const QString &emote,
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec, std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec,

View file

@ -230,6 +230,7 @@ void Window::addDebugStuff()
// display name renders strangely // display name renders strangely
miscMessages.emplace_back(R"(@badges=;color=#00AD2B;display-name=Iamme420\s;emotes=;id=d47a1e4b-a3c6-4b9e-9bf1-51b8f3dbc76e;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1529670347537;turbo=0;user-id=56422869;user-type= :iamme420!iamme420@iamme420.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)"); miscMessages.emplace_back(R"(@badges=;color=#00AD2B;display-name=Iamme420\s;emotes=;id=d47a1e4b-a3c6-4b9e-9bf1-51b8f3dbc76e;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1529670347537;turbo=0;user-id=56422869;user-type= :iamme420!iamme420@iamme420.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)");
miscMessages.emplace_back(R"(@badge-info=founder/47;badges=moderator/1,founder/0,premium/1;color=#00FF80;display-name=gempir;emotes=;flags=;id=d4514490-202e-43cb-b429-ef01a9d9c2fe;mod=1;room-id=11148817;subscriber=0;tmi-sent-ts=1575198233854;turbo=0;user-id=77829817;user-type=mod :gempir!gempir@gempir.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)");
// clang-format on // clang-format on
createWindowShortcut(this, "F6", [=] { createWindowShortcut(this, "F6", [=] {