Skip to content

Commit

Permalink
Corrected the way we parse comma-separated "list tags" in PRIVMSGs (C…
Browse files Browse the repository at this point in the history
…hatterino#3771)

tl;dr: we now split by slash only its first occurrence instead of every occurrence.
  • Loading branch information
zneix authored May 28, 2022
1 parent 6ef3ecc commit 7d0023c
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 102 deletions.
20 changes: 6 additions & 14 deletions src/controllers/highlights/HighlightBadge.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "HighlightBadge.hpp"

#include "messages/SharedMessageBuilder.hpp"
#include "singletons/Resources.hpp"

namespace chatterino {
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/messages/Message.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "common/FlagsEnum.hpp"
#include "providers/twitch/TwitchBadge.hpp"
#include "util/QStringHash.hpp"
#include "widgets/helper/ScrollbarHighlight.hpp"

#include <QTime>
Expand Down Expand Up @@ -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;
Expand Down
70 changes: 41 additions & 29 deletions src/messages/SharedMessageBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions src/messages/SharedMessageBuilder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -32,6 +33,11 @@ class SharedMessageBuilder : public MessageBuilder
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();

Expand Down
5 changes: 5 additions & 0 deletions src/providers/twitch/TwitchBadge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 7 additions & 3 deletions src/providers/twitch/TwitchBadge.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
81 changes: 26 additions & 55 deletions src/providers/twitch/TwitchMessageBuilder.cpp
Original file line number Diff line number Diff line change
Expand 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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1033,15 +985,34 @@ 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)
{
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)
{
Expand Down Expand Up @@ -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)")
Expand All @@ -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

Expand Down
4 changes: 4 additions & 0 deletions src/providers/twitch/TwitchMessageBuilder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ class TwitchMessageBuilder : public SharedMessageBuilder
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;
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ set(test_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/UtilTwitch.cpp
${CMAKE_CURRENT_LIST_DIR}/src/IrcHelpers.cpp
${CMAKE_CURRENT_LIST_DIR}/src/TwitchPubSubClient.cpp
${CMAKE_CURRENT_LIST_DIR}/src/TwitchMessageBuilder.cpp
# Add your new file above this line!
)

Expand Down
Loading

0 comments on commit 7d0023c

Please sign in to comment.