Harden emote parsing (#3885)
This commit is contained in:
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ¤tTwitchEmoteIt)
|
||||
const std::vector<TwitchEmoteOccurrence> &twitchEmotes,
|
||||
std::vector<TwitchEmoteOccurrence>::const_iterator ¤tTwitchEmoteIt)
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -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,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
|
||||
|
||||
Reference in New Issue
Block a user