mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
badge texts wip
This commit is contained in:
parent
c4c62f2796
commit
7908e8855f
10 changed files with 154 additions and 80 deletions
|
@ -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()},
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,20 +182,13 @@ 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" &&
|
||||
getSettings()->useCustomFfzModeratorBadges)
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)},
|
||||
|
|
Loading…
Reference in a new issue