Harden emote parsing (#3885)

This commit is contained in:
pajlada
2022-11-05 11:04:35 +01:00
committed by GitHub
parent f00f766eeb
commit aac9ea53d0
14 changed files with 1163 additions and 104 deletions
+5
View File
@@ -216,6 +216,11 @@ int Application::run(QApplication &qtApp)
return qtApp.exec();
}
IEmotes *Application::getEmotes()
{
return this->emotes;
}
void Application::save()
{
for (auto &singleton : this->singletons_)
+3 -5
View File
@@ -24,6 +24,7 @@ class Logging;
class Paths;
class AccountManager;
class Emotes;
class IEmotes;
class Settings;
class Fonts;
class Toasts;
@@ -41,7 +42,7 @@ public:
virtual Theme *getThemes() = 0;
virtual Fonts *getFonts() = 0;
virtual Emotes *getEmotes() = 0;
virtual IEmotes *getEmotes() = 0;
virtual AccountController *getAccounts() = 0;
virtual HotkeyController *getHotkeys() = 0;
virtual WindowManager *getWindows() = 0;
@@ -99,10 +100,7 @@ public:
{
return this->fonts;
}
Emotes *getEmotes() override
{
return this->emotes;
}
IEmotes *getEmotes() override;
AccountController *getAccounts() override
{
return this->accounts;
+15
View File
@@ -542,6 +542,21 @@ source_group(TREE ${CMAKE_SOURCE_DIR} FILES ${SOURCE_FILES})
add_library(${LIBRARY_PROJECT} OBJECT ${SOURCE_FILES})
if (CHATTERINO_GENERATE_COVERAGE)
include(CodeCoverage)
append_coverage_compiler_flags_to_target(${LIBRARY_PROJECT})
target_link_libraries(${LIBRARY_PROJECT} PUBLIC gcov)
message(STATUS "project source dir: ${PROJECT_SOURCE_DIR}/src")
setup_target_for_coverage_lcov(
NAME coverage
EXECUTABLE ./bin/chatterino-test
BASE_DIRECTORY ${PROJECT_SOURCE_DIR}/src
EXCLUDE "/usr/include/*"
EXCLUDE "build-*/*"
EXCLUDE "lib/*"
)
endif ()
target_link_libraries(${LIBRARY_PROJECT}
PUBLIC
Qt${MAJOR_QT_VERSION}::Core
+6 -2
View File
@@ -38,10 +38,10 @@ public:
SignalVector(std::function<bool(const T &, const T &)> &&compare)
: SignalVector()
{
itemCompare_ = std::move(compare);
this->itemCompare_ = std::move(compare);
}
virtual bool isSorted() const
bool isSorted() const
{
return bool(this->itemCompare_);
}
@@ -76,9 +76,13 @@ public:
else
{
if (index == -1)
{
index = this->items_.size();
}
else
{
assert(index >= 0 && index <= this->items_.size());
}
this->items_.insert(this->items_.begin() + index, item);
}
-7
View File
@@ -1,17 +1,10 @@
#include "providers/twitch/TwitchEmotes.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Benchmark.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace chatterino {
TwitchEmotes::TwitchEmotes()
{
}
QString TwitchEmotes::cleanUpEmoteCode(const QString &dirtyEmoteCode)
{
auto cleanCode = dirtyEmoteCode;
+13 -3
View File
@@ -33,13 +33,23 @@ struct CheerEmoteSet {
std::vector<CheerEmote> cheerEmotes;
};
class TwitchEmotes
class ITwitchEmotes
{
public:
virtual ~ITwitchEmotes() = default;
virtual EmotePtr getOrCreateEmote(const EmoteId &id,
const EmoteName &name) = 0;
};
class TwitchEmotes : public ITwitchEmotes
{
public:
static QString cleanUpEmoteCode(const QString &dirtyEmoteCode);
TwitchEmotes();
TwitchEmotes() = default;
EmotePtr getOrCreateEmote(const EmoteId &id, const EmoteName &name);
EmotePtr getOrCreateEmote(const EmoteId &id,
const EmoteName &name) override;
private:
Url getEmoteLink(const EmoteId &id, const QString &emoteScale);
+110 -78
View File
@@ -107,6 +107,77 @@ namespace {
return usernameText;
}
void appendTwitchEmoteOccurrences(const QString &emote,
std::vector<TwitchEmoteOccurrence> &vec,
const std::vector<int> &correctPositions,
const QString &originalMessage,
int messageOffset)
{
auto *app = getIApp();
if (!emote.contains(':'))
{
return;
}
auto parameters = emote.split(':');
if (parameters.length() < 2)
{
return;
}
auto id = EmoteId{parameters.at(0)};
auto occurrences = parameters.at(1).split(',');
for (const QString &occurrence : occurrences)
{
auto coords = occurrence.split('-');
if (coords.length() < 2)
{
return;
}
auto from = coords.at(0).toUInt() - messageOffset;
auto to = coords.at(1).toUInt() - messageOffset;
auto maxPositions = correctPositions.size();
if (from > to || to >= maxPositions)
{
// Emote coords are out of range
qCDebug(chatterinoTwitch)
<< "Emote coords" << from << "-" << to
<< "are out of range (" << maxPositions << ")";
return;
}
auto start = correctPositions[from];
auto end = correctPositions[to];
if (start > end || start < 0 || end > originalMessage.length())
{
// Emote coords are out of range from the modified character positions
qCDebug(chatterinoTwitch) << "Emote coords" << from << "-" << to
<< "are out of range after offsets ("
<< originalMessage.length() << ")";
return;
}
auto name = EmoteName{originalMessage.mid(start, end - start + 1)};
TwitchEmoteOccurrence emoteOccurrence{
start,
end,
app->getEmotes()->getTwitchEmotes()->getOrCreateEmote(id, name),
name,
};
if (emoteOccurrence.ptr == nullptr)
{
qCDebug(chatterinoTwitch)
<< "nullptr" << emoteOccurrence.name.string;
}
vec.push_back(std::move(emoteOccurrence));
}
}
} // namespace
TwitchMessageBuilder::TwitchMessageBuilder(
@@ -304,25 +375,8 @@ MessagePtr TwitchMessageBuilder::build()
}
// Twitch emotes
std::vector<TwitchEmoteOccurence> twitchEmotes;
iterator = this->tags.find("emotes");
if (iterator != this->tags.end())
{
QStringList emoteString = iterator.value().toString().split('/');
std::vector<int> correctPositions;
for (int i = 0; i < this->originalMessage_.size(); ++i)
{
if (!this->originalMessage_.at(i).isLowSurrogate())
{
correctPositions.push_back(i);
}
}
for (QString emote : emoteString)
{
this->appendTwitchEmote(emote, twitchEmotes, correctPositions);
}
}
auto twitchEmotes = TwitchMessageBuilder::parseTwitchEmotes(
this->tags, this->originalMessage_, this->messageOffset_);
// This runs through all ignored phrases and runs its replacements on this->originalMessage_
this->runIgnoreReplaces(twitchEmotes);
@@ -379,8 +433,8 @@ MessagePtr TwitchMessageBuilder::build()
bool doesWordContainATwitchEmote(
int cursor, const QString &word,
const std::vector<TwitchEmoteOccurence> &twitchEmotes,
std::vector<TwitchEmoteOccurence>::const_iterator &currentTwitchEmoteIt)
const std::vector<TwitchEmoteOccurrence> &twitchEmotes,
std::vector<TwitchEmoteOccurrence>::const_iterator &currentTwitchEmoteIt)
{
if (currentTwitchEmoteIt == twitchEmotes.end())
{
@@ -404,7 +458,7 @@ bool doesWordContainATwitchEmote(
void TwitchMessageBuilder::addWords(
const QStringList &words,
const std::vector<TwitchEmoteOccurence> &twitchEmotes)
const std::vector<TwitchEmoteOccurrence> &twitchEmotes)
{
// cursor currently indicates what character index we're currently operating in the full list of words
int cursor = 0;
@@ -762,7 +816,7 @@ void TwitchMessageBuilder::appendUsername()
}
void TwitchMessageBuilder::runIgnoreReplaces(
std::vector<TwitchEmoteOccurence> &twitchEmotes)
std::vector<TwitchEmoteOccurrence> &twitchEmotes)
{
auto phrases = getCSettings().ignoredMessages.readOnly();
auto removeEmotesInRange = [](int pos, int len,
@@ -780,7 +834,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
<< "remem nullptr" << (*copy).name.string;
}
}
std::vector<TwitchEmoteOccurence> v(it, twitchEmotes.end());
std::vector<TwitchEmoteOccurrence> v(it, twitchEmotes.end());
twitchEmotes.erase(it, twitchEmotes.end());
return v;
};
@@ -818,7 +872,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
qCDebug(chatterinoTwitch)
<< "emote null" << emote.first.string;
}
twitchEmotes.push_back(TwitchEmoteOccurence{
twitchEmotes.push_back(TwitchEmoteOccurrence{
startIndex + pos,
startIndex + pos + emote.first.string.length(),
emote.second,
@@ -980,59 +1034,6 @@ void TwitchMessageBuilder::runIgnoreReplaces(
}
}
void TwitchMessageBuilder::appendTwitchEmote(
const QString &emote, std::vector<TwitchEmoteOccurence> &vec,
std::vector<int> &correctPositions)
{
auto app = getApp();
if (!emote.contains(':'))
{
return;
}
auto parameters = emote.split(':');
if (parameters.length() < 2)
{
return;
}
auto id = EmoteId{parameters.at(0)};
auto occurences = parameters.at(1).split(',');
for (QString occurence : occurences)
{
auto coords = occurence.split('-');
if (coords.length() < 2)
{
return;
}
auto start =
correctPositions[coords.at(0).toUInt() - this->messageOffset_];
auto end =
correctPositions[coords.at(1).toUInt() - this->messageOffset_];
if (start >= end || start < 0 || end > this->originalMessage_.length())
{
return;
}
auto name =
EmoteName{this->originalMessage_.mid(start, end - start + 1)};
TwitchEmoteOccurence emoteOccurence{
start, end, app->emotes->twitch.getOrCreateEmote(id, name), name};
if (emoteOccurence.ptr == nullptr)
{
qCDebug(chatterinoTwitch)
<< "nullptr" << emoteOccurence.name.string;
}
vec.push_back(std::move(emoteOccurence));
}
}
Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
{
auto *app = getApp();
@@ -1137,6 +1138,37 @@ std::unordered_map<QString, QString> TwitchMessageBuilder::parseBadgeInfoTag(
return infoMap;
}
std::vector<TwitchEmoteOccurrence> TwitchMessageBuilder::parseTwitchEmotes(
const QVariantMap &tags, const QString &originalMessage, int messageOffset)
{
// Twitch emotes
std::vector<TwitchEmoteOccurrence> twitchEmotes;
auto emotesTag = tags.find("emotes");
if (emotesTag == tags.end())
{
return twitchEmotes;
}
QStringList emoteString = emotesTag.value().toString().split('/');
std::vector<int> correctPositions;
for (int i = 0; i < originalMessage.size(); ++i)
{
if (!originalMessage.at(i).isLowSurrogate())
{
correctPositions.push_back(i);
}
}
for (const QString &emote : emoteString)
{
appendTwitchEmoteOccurrences(emote, twitchEmotes, correctPositions,
originalMessage, messageOffset);
}
return twitchEmotes;
}
void TwitchMessageBuilder::appendTwitchBadges()
{
if (this->twitchChannel == nullptr)
+13 -6
View File
@@ -20,11 +20,17 @@ using EmotePtr = std::shared_ptr<const Emote>;
class Channel;
class TwitchChannel;
struct TwitchEmoteOccurence {
struct TwitchEmoteOccurrence {
int start;
int end;
EmotePtr ptr;
EmoteName name;
bool operator==(const TwitchEmoteOccurrence &other) const
{
return std::tie(this->start, this->end, this->ptr, this->name) ==
std::tie(other.start, other.end, other.ptr, other.name);
}
};
class TwitchMessageBuilder : public SharedMessageBuilder
@@ -76,6 +82,10 @@ public:
static std::unordered_map<QString, QString> parseBadgeInfoTag(
const QVariantMap &tags);
static std::vector<TwitchEmoteOccurrence> parseTwitchEmotes(
const QVariantMap &tags, const QString &originalMessage,
int messageOffset);
private:
void parseUsernameColor() override;
void parseUsername() override;
@@ -83,16 +93,13 @@ private:
void parseRoomID();
void appendUsername();
void runIgnoreReplaces(std::vector<TwitchEmoteOccurence> &twitchEmotes);
void runIgnoreReplaces(std::vector<TwitchEmoteOccurrence> &twitchEmotes);
boost::optional<EmotePtr> getTwitchBadge(const Badge &badge);
void appendTwitchEmote(const QString &emote,
std::vector<TwitchEmoteOccurence> &vec,
std::vector<int> &correctPositions);
Outcome tryAppendEmote(const EmoteName &name) override;
void addWords(const QStringList &words,
const std::vector<TwitchEmoteOccurence> &twitchEmotes);
const std::vector<TwitchEmoteOccurrence> &twitchEmotes);
void addTextOrEmoji(EmotePtr emote) override;
void addTextOrEmoji(const QString &value) override;
+14 -1
View File
@@ -14,7 +14,15 @@ namespace chatterino {
class Settings;
class Paths;
class Emotes final : public Singleton
class IEmotes
{
public:
virtual ~IEmotes() = default;
virtual ITwitchEmotes *getTwitchEmotes() = 0;
};
class Emotes final : public IEmotes, public Singleton
{
public:
Emotes();
@@ -23,6 +31,11 @@ public:
bool isIgnoredEmote(const QString &emote);
ITwitchEmotes *getTwitchEmotes() final
{
return &this->twitch;
}
TwitchEmotes twitch;
Emojis emojis;
@@ -7,4 +7,4 @@ namespace chatterino {
using QuickSwitcherModel = GenericListModel;
}
} // namespace chatterino