Corrected the way we parse comma-separated "list tags" in PRIVMSGs (#3771)
tl;dr: we now split by slash only its first occurrence instead of every occurrence.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "HighlightBadge.hpp"
|
||||
|
||||
#include "messages/SharedMessageBuilder.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
@@ -86,21 +87,12 @@ bool HighlightBadge::compare(const QString &id, const Badge &badge) const
|
||||
{
|
||||
if (this->hasVersions_)
|
||||
{
|
||||
auto parts = id.split("/");
|
||||
if (parts.size() == 2)
|
||||
{
|
||||
return parts.at(0).compare(badge.key_, Qt::CaseInsensitive) == 0 &&
|
||||
parts.at(1).compare(badge.value_, Qt::CaseInsensitive) == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return parts.at(0).compare(badge.key_, Qt::CaseInsensitive) == 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return id.compare(badge.key_, Qt::CaseInsensitive) == 0;
|
||||
auto parts = SharedMessageBuilder::slashKeyValue(id);
|
||||
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;
|
||||
}
|
||||
|
||||
bool HighlightBadge::hasCustomSound() const
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "common/FlagsEnum.hpp"
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
#include "widgets/helper/ScrollbarHighlight.hpp"
|
||||
|
||||
#include <QTime>
|
||||
@@ -65,7 +66,7 @@ struct Message : boost::noncopyable {
|
||||
QColor usernameColor;
|
||||
QDateTime serverReceivedTime;
|
||||
std::vector<Badge> badges;
|
||||
std::map<QString, QString> badgeInfos;
|
||||
std::unordered_map<QString, QString> badgeInfos;
|
||||
std::shared_ptr<QColor> highlightColor;
|
||||
uint32_t count = 1;
|
||||
std::vector<std::unique_ptr<MessageElement>> elements;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "common/QLogging.hpp"
|
||||
#include "controllers/ignores/IgnoreController.hpp"
|
||||
#include "controllers/ignores/IgnorePhrase.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
@@ -36,33 +35,6 @@ 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(',', Qt::SkipEmptyParts);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
badges.emplace_back(parts[0], parts[1]);
|
||||
}
|
||||
|
||||
return badges;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SharedMessageBuilder::SharedMessageBuilder(
|
||||
@@ -103,6 +75,46 @@ void SharedMessageBuilder::parse()
|
||||
this->message().flags.set(MessageFlag::Collapsed);
|
||||
}
|
||||
|
||||
// "foo/bar/baz,tri/hard" can be a valid badge-info tag
|
||||
// In that case, valid map content should be 'split by slash' only once:
|
||||
// {"foo": "bar/baz", "tri": "hard"}
|
||||
std::pair<QString, QString> SharedMessageBuilder::slashKeyValue(
|
||||
const QString &kvStr)
|
||||
{
|
||||
return {
|
||||
// part before first slash (index 0 of section)
|
||||
kvStr.section('/', 0, 0),
|
||||
// part after first slash (index 1 of section)
|
||||
kvStr.section('/', 1, -1),
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<Badge> SharedMessageBuilder::parseBadgeTag(const QVariantMap &tags)
|
||||
{
|
||||
std::vector<Badge> b;
|
||||
|
||||
auto badgesIt = tags.constFind("badges");
|
||||
if (badgesIt == tags.end())
|
||||
{
|
||||
return b;
|
||||
}
|
||||
|
||||
auto badges = badgesIt.value().toString().split(',', Qt::SkipEmptyParts);
|
||||
|
||||
for (const QString &badge : badges)
|
||||
{
|
||||
if (!badge.contains('/'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto pair = SharedMessageBuilder::slashKeyValue(badge);
|
||||
b.emplace_back(Badge{pair.first, pair.second});
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
bool SharedMessageBuilder::isIgnored() const
|
||||
{
|
||||
return isIgnoredMessage({
|
||||
@@ -332,7 +344,7 @@ void SharedMessageBuilder::parseHighlights()
|
||||
}
|
||||
|
||||
// Highlight because of badge
|
||||
auto badges = parseBadges(this->tags);
|
||||
auto badges = this->parseBadgeTag(this->tags);
|
||||
auto badgeHighlights = getCSettings().highlightedBadges.readOnly();
|
||||
bool badgeHighlightSet = false;
|
||||
for (const HighlightBadge &highlight : *badgeHighlights)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "messages/MessageColor.hpp"
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
#include <QColor>
|
||||
@@ -32,6 +33,11 @@ public:
|
||||
virtual void triggerHighlights();
|
||||
virtual MessagePtr build() = 0;
|
||||
|
||||
static std::pair<QString, QString> slashKeyValue(const QString &kvStr);
|
||||
|
||||
// Parses "badges" tag which contains a comma separated list of key-value elements
|
||||
static std::vector<Badge> parseBadgeTag(const QVariantMap &tags);
|
||||
|
||||
protected:
|
||||
virtual void parse();
|
||||
|
||||
|
||||
@@ -33,4 +33,9 @@ Badge::Badge(QString key, QString value)
|
||||
}
|
||||
}
|
||||
|
||||
bool Badge::operator==(const Badge &other) const
|
||||
{
|
||||
return this->key_ == other.key_ && this->value_ == other.value_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -11,9 +11,13 @@ 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)
|
||||
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
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||
#include "providers/ffz/FfzBadges.hpp"
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
#include "providers/twitch/TwitchBadges.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
@@ -47,55 +48,6 @@ const QSet<QString> zeroWidthEmotes{
|
||||
|
||||
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(',', Qt::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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
badges.emplace_back(parts[0], parts[1]);
|
||||
}
|
||||
|
||||
return badges;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TwitchMessageBuilder::TwitchMessageBuilder(
|
||||
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
|
||||
const MessageParseArgs &_args)
|
||||
@@ -1033,6 +985,25 @@ boost::optional<EmotePtr> TwitchMessageBuilder::getTwitchBadge(
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
std::unordered_map<QString, QString> TwitchMessageBuilder::parseBadgeInfoTag(
|
||||
const QVariantMap &tags)
|
||||
{
|
||||
std::unordered_map<QString, QString> infoMap;
|
||||
|
||||
auto infoIt = tags.constFind("badge-info");
|
||||
if (infoIt == tags.end())
|
||||
return infoMap;
|
||||
|
||||
auto info = infoIt.value().toString().split(',', Qt::SkipEmptyParts);
|
||||
|
||||
for (const QString &badge : info)
|
||||
{
|
||||
infoMap.emplace(SharedMessageBuilder::slashKeyValue(badge));
|
||||
}
|
||||
|
||||
return infoMap;
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::appendTwitchBadges()
|
||||
{
|
||||
if (this->twitchChannel == nullptr)
|
||||
@@ -1040,8 +1011,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||
return;
|
||||
}
|
||||
|
||||
auto badgeInfos = parseBadgeInfos(this->tags);
|
||||
auto badges = parseBadges(this->tags);
|
||||
auto badgeInfos = TwitchMessageBuilder::parseBadgeInfoTag(this->tags);
|
||||
auto badges = this->parseBadgeTag(this->tags);
|
||||
|
||||
for (const auto &badge : badges)
|
||||
{
|
||||
@@ -1091,7 +1062,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||
// (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_.front() : '1';
|
||||
badge.value_.length() > 3 ? badge.value_.at(0) : '1';
|
||||
const auto &subMonths = badgeInfoIt->second;
|
||||
tooltip +=
|
||||
QString(" (%1%2 months)")
|
||||
@@ -1107,9 +1078,9 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||
{
|
||||
auto predictionText =
|
||||
badgeInfoIt->second
|
||||
.replace("\\s", " ") // standard IRC escapes
|
||||
.replace("\\:", ";")
|
||||
.replace("\\\\", "\\")
|
||||
.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
|
||||
|
||||
|
||||
@@ -68,6 +68,10 @@ public:
|
||||
Channel *channel,
|
||||
MessageBuilder *builder);
|
||||
|
||||
// Shares some common logic from SharedMessageBuilder::parseBadgeTag
|
||||
static std::unordered_map<QString, QString> parseBadgeInfoTag(
|
||||
const QVariantMap &tags);
|
||||
|
||||
private:
|
||||
void parseUsernameColor() override;
|
||||
void parseUsername() override;
|
||||
|
||||
Reference in New Issue
Block a user