badge texts wip

This commit is contained in:
Rasmus Karlsson 2024-01-28 09:12:43 +01:00
parent c4c62f2796
commit 7908e8855f
10 changed files with 154 additions and 80 deletions

View file

@ -24,6 +24,7 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
* List of identifiers:
*
* author.badges
* author.badge_texts
* author.color
* author.name
* author.no_color
@ -56,11 +57,16 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
QStringList badges;
badges.reserve(m->badges.size());
QStringList badgeTexts;
badgeTexts.reserve(m->badges.size());
for (const auto &e : m->badges)
{
badges << e.key_;
badges << e.key;
badgeTexts << e.text;
}
qDebug() << "XXX: Badges:" << badges << badgeTexts;
bool watching = !watchingChannel->getName().isEmpty() &&
watchingChannel->getName().compare(
m->channelName, Qt::CaseInsensitive) == 0;
@ -81,6 +87,7 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
}
ContextMap vars = {
{"author.badges", std::move(badges)},
{"author.badge_texts", std::move(badgeTexts)},
{"author.color", m->usernameColor},
{"author.name", m->displayName},
{"author.no_color", !m->usernameColor.isValid()},

View file

@ -24,6 +24,7 @@ namespace chatterino::filters {
// i.e. if all the variables and operators being used have compatible types.
static const QMap<QString, Type> MESSAGE_TYPING_CONTEXT = {
{"author.badges", Type::StringList},
{"author.badge_texts", Type::StringList},
{"author.color", Type::Color},
{"author.name", Type::String},
{"author.no_color", Type::Bool},

View file

@ -10,6 +10,7 @@ namespace chatterino::filters {
static const QMap<QString, QString> validIdentifiersMap = {
{"author.badges", "author badges"},
{"author.badge_texts", "author badge texts"},
{"author.color", "author color"},
{"author.name", "author name"},
{"author.no_color", "author has no color?"},
@ -35,7 +36,8 @@ static const QMap<QString, QString> validIdentifiersMap = {
{"flags.restricted", "restricted message?"},
{"flags.monitored", "monitored message?"},
{"message.content", "message text"},
{"message.length", "message length"}};
{"message.length", "message length"},
};
// clang-format off
static const QRegularExpression tokenRegex(

View file

@ -98,11 +98,11 @@ bool HighlightBadge::compare(const QString &id, const Badge &badge) const
if (this->hasVersions_)
{
auto parts = SharedMessageBuilder::slashKeyValue(id);
return parts.first.compare(badge.key_, Qt::CaseInsensitive) == 0 &&
parts.second.compare(badge.value_, Qt::CaseInsensitive) == 0;
return parts.first.compare(badge.key, Qt::CaseInsensitive) == 0 &&
parts.second.compare(badge.value, Qt::CaseInsensitive) == 0;
}
return id.compare(badge.key_, Qt::CaseInsensitive) == 0;
return id.compare(badge.key, Qt::CaseInsensitive) == 0;
}
bool HighlightBadge::hasCustomSound() const

View file

@ -36,7 +36,7 @@ bool BadgePredicate::appliesToImpl(const Message &message)
{
for (const Badge &badge : message.badges)
{
if (badges_.contains(badge.key_, Qt::CaseInsensitive))
if (badges_.contains(badge.key, Qt::CaseInsensitive))
{
return true;
}

View file

@ -20,10 +20,10 @@ bool SubtierPredicate::appliesToImpl(const Message &message)
{
for (const Badge &badge : message.badges)
{
if (badge.key_ == "subscriber")
if (badge.key == "subscriber")
{
const auto &subTier =
badge.value_.length() > 3 ? badge.value_.at(0) : '1';
badge.value.length() > 3 ? badge.value.at(0) : '1';
return subtiers_.contains(subTier);
}

View file

@ -12,30 +12,30 @@ 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))
: key(std::move(key))
, value(std::move(value))
{
if (globalAuthority.contains(this->key_))
if (globalAuthority.contains(this->key))
{
this->flag_ = MessageElementFlag::BadgeGlobalAuthority;
this->flag = MessageElementFlag::BadgeGlobalAuthority;
}
else if (predictions.contains(this->key_))
else if (predictions.contains(this->key))
{
this->flag_ = MessageElementFlag::BadgePredictions;
this->flag = MessageElementFlag::BadgePredictions;
}
else if (channelAuthority.contains(this->key_))
else if (channelAuthority.contains(this->key))
{
this->flag_ = MessageElementFlag::BadgeChannelAuthority;
this->flag = MessageElementFlag::BadgeChannelAuthority;
}
else if (subBadges.contains(this->key_))
else if (subBadges.contains(this->key))
{
this->flag_ = MessageElementFlag::BadgeSubscription;
this->flag = MessageElementFlag::BadgeSubscription;
}
}
bool Badge::operator==(const Badge &other) const
{
return this->key_ == other.key_ && this->value_ == other.value_;
return this->key == other.key && this->value == other.value;
}
} // namespace chatterino

View file

@ -4,8 +4,14 @@
#include <QString>
#include <memory>
#include <optional>
namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class Badge
{
public:
@ -13,13 +19,42 @@ public:
bool operator==(const Badge &other) const;
// Class members are fetched from both "badges" and "badge-info" tags
// E.g.: "badges": "subscriber/18", "badge-info": "subscriber/22"
QString key_; // subscriber
QString value_; // 18
//QString info_; // 22 (should be parsed separetly into an std::unordered_map)
MessageElementFlag flag_{
MessageElementFlag::BadgeVanity}; // badge slot it takes up
/**
* The key of the badge (e.g. subscriber, moderator, chatter-cs-go-2022)
*/
QString key; // subscriber
//
/**
* The value of the badge (e.g. 96 for a subscriber badge, denoting that this should use the 96-month sub badge)
*/
QString value;
/**
* The text of the badge
* By default, the text is empty & will be filled in separately if text is found
* The text is what will be displayed in the badge's tooltip
*/
QString text;
/**
* The image of the badge
* Can be nullopt if the badge just doesn't have an image, or if no image has been found set it yet
*/
std::optional<EmotePtr> image{};
/**
* The badge slot this badge takes up
*/
MessageElementFlag flag{MessageElementFlag::BadgeVanity};
};
} // namespace chatterino
inline QDebug operator<<(QDebug debug, const chatterino::Badge &v)
{
QDebugStateSaver saver(debug);
debug.nospace() << "(key=" << v.key << ", value=" << v.value
<< ", text=" << v.text << ')';
return debug;
}

View file

@ -157,13 +157,13 @@ namespace {
const TwitchChannel *twitchChannel)
{
if (auto channelBadge =
twitchChannel->twitchBadge(badge.key_, badge.value_))
twitchChannel->twitchBadge(badge.key, badge.value_))
{
return channelBadge;
}
if (auto globalBadge =
TwitchBadges::instance()->badge(badge.key_, badge.value_))
TwitchBadges::instance()->badge(badge.key, badge.value_))
{
return globalBadge;
}
@ -182,19 +182,12 @@ namespace {
for (const auto &badge : badges)
{
auto badgeEmote = getTwitchBadge(badge, twitchChannel);
if (!badgeEmote)
if (!badge.image)
{
continue;
}
auto tooltip = (*badgeEmote)->tooltip.string;
if (badge.key_ == "bits")
{
const auto &cheerAmount = badge.value_;
tooltip = QString("Twitch cheer %0").arg(cheerAmount);
}
else if (badge.key_ == "moderator" &&
if (badge.key == "moderator" &&
getSettings()->useCustomFfzModeratorBadges)
{
if (auto customModBadge = twitchChannel->ffzCustomModBadge())
@ -203,13 +196,12 @@ namespace {
->emplace<ModBadgeElement>(
*customModBadge,
MessageElementFlag::BadgeChannelAuthority)
->setTooltip((*customModBadge)->tooltip.string);
->setTooltip(badge.text);
// early out, since we have to add a custom badge element here
continue;
}
}
else if (badge.key_ == "vip" &&
getSettings()->useCustomFfzVipBadges)
else if (badge.key == "vip" && getSettings()->useCustomFfzVipBadges)
{
if (auto customVipBadge = twitchChannel->ffzCustomVipBadge())
{
@ -217,49 +209,14 @@ namespace {
->emplace<VipBadgeElement>(
*customVipBadge,
MessageElementFlag::BadgeChannelAuthority)
->setTooltip((*customVipBadge)->tooltip.string);
->setTooltip(badge.text);
// early out, since we have to add a custom badge element here
continue;
}
}
else if (badge.flag_ == MessageElementFlag::BadgeSubscription)
{
auto badgeInfoIt = badgeInfos.find(badge.key_);
if (badgeInfoIt != badgeInfos.end())
{
// badge.value_ is 4 chars long if user is subbed on higher tier
// (tier + amount of months with leading zero if less than 100)
// e.g. 3054 - tier 3 4,5-year sub. 2108 - tier 2 9-year sub
const auto &subTier =
badge.value_.length() > 3 ? badge.value_.at(0) : '1';
const auto &subMonths = badgeInfoIt->second;
tooltip += QString(" (%1%2 months)")
.arg(subTier != '1'
? QString("Tier %1, ").arg(subTier)
: "")
.arg(subMonths);
}
}
else if (badge.flag_ == MessageElementFlag::BadgePredictions)
{
auto badgeInfoIt = badgeInfos.find(badge.key_);
if (badgeInfoIt != badgeInfos.end())
{
auto infoValue = badgeInfoIt->second;
auto predictionText =
infoValue
.replace(R"(\s)", " ") // standard IRC escapes
.replace(R"(\:)", ";")
.replace(R"(\\)", R"(\)")
.replace("", ","); // twitch's comma escape
// Careful, the first character is RIGHT LOW PARAPHRASE BRACKET or U+2E1D, which just looks like a comma
tooltip = QString("Predicted %1").arg(predictionText);
}
}
builder->emplace<BadgeElement>(*badgeEmote, badge.flag_)
->setTooltip(tooltip);
builder->emplace<BadgeElement>(*badge.image, badge.flag)
->setTooltip(badge.text);
}
builder->message().badges = badges;
@ -1285,6 +1242,62 @@ void TwitchMessageBuilder::appendTwitchBadges()
auto badgeInfos = TwitchMessageBuilder::parseBadgeInfoTag(this->tags);
auto badges = TwitchMessageBuilder::parseBadgeTag(this->tags);
for (auto &badge : badges)
{
auto oBadgeEmote = getTwitchBadge(badge, this->twitchChannel);
if (!oBadgeEmote.has_value())
{
continue;
}
const auto &badgeEmote = oBadgeEmote.value();
badge.image = badgeEmote;
if (badge.key == "bits")
{
const auto &cheerAmount = badge.value;
badge.text = QString("Twitch cheer %0").arg(cheerAmount);
}
else if (badge.flag == MessageElementFlag::BadgeSubscription)
{
auto badgeInfoIt = badgeInfos.find(badge.key);
if (badgeInfoIt != badgeInfos.end())
{
// badge.value_ is 4 chars long if user is subbed on higher tier
// (tier + amount of months with leading zero if less than 100)
// e.g. 3054 - tier 3 4,5-year sub. 2108 - tier 2 9-year sub
const auto &subTier =
badge.value.length() > 3 ? badge.value.at(0) : '1';
const auto &subMonths = badgeInfoIt->second;
badge.text =
QString("%0 (%1%2 months)")
.arg(badgeEmote->tooltip.string)
.arg(subTier != '1' ? QString("Tier %1, ").arg(subTier)
: "")
.arg(subMonths);
}
}
else if (badge.flag == MessageElementFlag::BadgePredictions)
{
auto badgeInfoIt = badgeInfos.find(badge.key);
if (badgeInfoIt != badgeInfos.end())
{
auto infoValue = badgeInfoIt->second;
auto predictionText =
infoValue
.replace(R"(\s)", " ") // standard IRC escapes
.replace(R"(\:)", ";")
.replace(R"(\\)", R"(\)")
.replace("", ","); // twitch's comma escape
// Careful, the first character is RIGHT LOW PARAPHRASE BRACKET or U+2E1D, which just looks like a comma
badge.text = QString("Predicted %1").arg(predictionText);
}
}
else
{
badge.text = badgeEmote->tooltip.string;
}
}
appendBadges(this, badges, badgeInfos, this->twitchChannel);
}

View file

@ -193,7 +193,19 @@ TEST(Filters, Evaluation)
{"author.subbed", QVariant(false)},
{"message.content", QVariant("hey there :) 2038-01-19 123 456")},
{"channel.name", QVariant("forsen")},
{"author.badges", QVariant(QStringList({"moderator", "staff"}))}};
{"author.badges",
QStringList{
"moderator",
"staff",
"premium",
}},
{"author.badge_texts",
QStringList{
"Moderator",
"Staff",
"Prime Gaming",
}},
};
// clang-format off
std::vector<TestCase> tests
@ -234,6 +246,10 @@ TEST(Filters, Evaluation)
{R".(!author.subbed).", QVariant(true)},
{R".(author.color == "#ff0000").", QVariant(true)},
{R".(channel.name == "forsen" && author.badges contains "moderator").", QVariant(true)},
{R".(author.badges contains "moderator").", QVariant(true)},
{R".(author.badge_texts contains "Moderator").", QVariant(true)},
{R".(author.badge_texts contains "Subscriber").", QVariant(false)},
{R".(author.badge_texts contains "Prime Gaming").", QVariant(true)},
{R".(message.content match {r"(\d\d\d\d)\-(\d\d)\-(\d\d)", 3}).", QVariant("19")},
{R".(message.content match r"HEY THERE").", QVariant(false)},
{R".(message.content match ri"HEY THERE").", QVariant(true)},