Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Ignores and Replacements #4965

Merged
merged 11 commits into from
Nov 17, 2023
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
- Bugfix: Fixed a crash when clicking `More messages below` button in a usercard and closing it quickly. (#4933)
- Bugfix: Fixed thread popup window missing messages for nested threads. (#4923)
- Bugfix: Fixed an occasional crash for channel point redemptions with text input. (#4949)
- Bugfix: Fixed a freeze from a bad regex in _Ignores_. (#4965)
- Bugfix: Fixed some emotes not appearing when using _Ignores_. (#4965)
- Bugfix: Fixed lookahead/-behind not working in _Ignores_. (#4965)
- Dev: Change clang-format from v14 to v16. (#4929)
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)
- Dev: Temporarily disable High DPI scaling on Qt6 builds on Windows. (#4767)
Expand Down
242 changes: 96 additions & 146 deletions src/providers/twitch/TwitchMessageBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,28 +846,25 @@ void TwitchMessageBuilder::appendUsername()
void TwitchMessageBuilder::runIgnoreReplaces(
std::vector<TwitchEmoteOccurrence> &twitchEmotes)
{
using SizeType = QString::size_type;

auto phrases = getSettings()->ignoredMessages.readOnly();
auto removeEmotesInRange = [](int pos, int len,
auto &twitchEmotes) mutable {
auto removeEmotesInRange = [&twitchEmotes](SizeType pos, SizeType len) {
// all emotes outside the range come before `it`
// all emotes in the range start at `it`
auto it = std::partition(
twitchEmotes.begin(), twitchEmotes.end(),
[pos, len](const auto &item) {
// returns true for emotes outside the range
return !((item.start >= pos) && item.start < (pos + len));
});
for (auto copy = it; copy != twitchEmotes.end(); ++copy)
{
if ((*copy).ptr == nullptr)
{
qCDebug(chatterinoTwitch)
<< "remem nullptr" << (*copy).name.string;
}
}
std::vector<TwitchEmoteOccurrence> v(it, twitchEmotes.end());
std::vector<TwitchEmoteOccurrence> emotesInRange(it,
twitchEmotes.end());
twitchEmotes.erase(it, twitchEmotes.end());
return v;
return emotesInRange;
};

auto shiftIndicesAfter = [&twitchEmotes](int pos, int by) mutable {
auto shiftIndicesAfter = [&twitchEmotes](int pos, int by) {
for (auto &item : twitchEmotes)
{
auto &index = item.start;
Expand All @@ -881,14 +878,18 @@ void TwitchMessageBuilder::runIgnoreReplaces(

auto addReplEmotes = [&twitchEmotes](const IgnorePhrase &phrase,
const auto &midrepl,
int startIndex) mutable {
SizeType startIndex) {
if (!phrase.containsEmote())
{
return;
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
auto words = midrepl.tokenize(u' ');
#else
auto words = midrepl.split(' ');
int pos = 0;
#endif
SizeType pos = 0;
for (const auto &word : words)
{
for (const auto &emote : phrase.getEmotes())
Expand All @@ -901,8 +902,9 @@ void TwitchMessageBuilder::runIgnoreReplaces(
<< "emote null" << emote.first.string;
}
twitchEmotes.push_back(TwitchEmoteOccurrence{
startIndex + pos,
startIndex + pos + (int)emote.first.string.length(),
static_cast<int>(startIndex + pos),
static_cast<int>(startIndex + pos +
emote.first.string.length()),
emote.second,
emote.first,
});
Expand All @@ -912,6 +914,63 @@ void TwitchMessageBuilder::runIgnoreReplaces(
}
};

auto replaceMessageAt = [&](const IgnorePhrase &phrase, SizeType from,
SizeType length, const QString &replacement) {
auto removedEmotes = removeEmotesInRange(from, length);
this->originalMessage_.replace(from, length, replacement);
auto wordStart = from;
while (wordStart > 0)
{
if (this->originalMessage_[wordStart - 1] == ' ')
{
break;
}
--wordStart;
}
auto wordEnd = from + replacement.length();
while (wordEnd < this->originalMessage_.length())
{
if (this->originalMessage_[wordEnd] == ' ')
{
break;
}
++wordEnd;
}

shiftIndicesAfter(static_cast<int>(from + length),
static_cast<int>(replacement.length() - length));

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
auto midExtendedRef = QStringView{this->originalMessage_}.mid(
wordStart, wordEnd - wordStart);
#else
auto midExtendedRef =
this->originalMessage_.midRef(wordStart, wordEnd - wordStart);
#endif

for (auto &emote : removedEmotes)
{
if (emote.ptr == nullptr)
{
qCDebug(chatterinoTwitch)
<< "Invalid emote occurrence" << emote.name.string;
continue;
}
QRegularExpression emoteregex(
"\\b" + emote.name.string + "\\b",
QRegularExpression::UseUnicodePropertiesOption);
auto match = emoteregex.match(midExtendedRef);
if (match.hasMatch())
{
emote.start = static_cast<int>(from + match.capturedStart());
emote.end = static_cast<int>(from + match.capturedEnd());
twitchEmotes.push_back(std::move(emote));
}
}

addReplEmotes(phrase, midExtendedRef, wordStart);
};

for (const auto &phrase : *phrases)
{
if (phrase.isBlock())
Expand All @@ -930,144 +989,35 @@ void TwitchMessageBuilder::runIgnoreReplaces(
{
continue;
}

QRegularExpressionMatch match;
int from = 0;
size_t iterations = 0;
SizeType from = 0;
while ((from = this->originalMessage_.indexOf(regex, from,
&match)) != -1)
{
int len = match.capturedLength();
auto vret = removeEmotesInRange(from, len, twitchEmotes);
auto mid = this->originalMessage_.mid(from, len);
mid.replace(regex, phrase.getReplace());

int midsize = mid.size();
this->originalMessage_.replace(from, len, mid);
int pos1 = from;
while (pos1 > 0)
{
if (this->originalMessage_[pos1 - 1] == ' ')
{
break;
}
--pos1;
}
int pos2 = from + midsize;
while (pos2 < this->originalMessage_.length())
{
if (this->originalMessage_[pos2] == ' ')
{
break;
}
++pos2;
}

shiftIndicesAfter(from + len, midsize - len);

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
auto midExtendedRef =
QStringView{this->originalMessage_}.mid(pos1, pos2 - pos1);
#else
auto midExtendedRef =
this->originalMessage_.midRef(pos1, pos2 - pos1);
#endif

for (auto &tup : vret)
replaceMessageAt(phrase, from, match.capturedLength(),
phrase.getReplace());
from += phrase.getReplace().length();
iterations++;
if (iterations >= 128)
{
if (tup.ptr == nullptr)
{
qCDebug(chatterinoTwitch)
<< "v nullptr" << tup.name.string;
continue;
}
QRegularExpression emoteregex(
"\\b" + tup.name.string + "\\b",
QRegularExpression::UseUnicodePropertiesOption);
auto _match = emoteregex.match(midExtendedRef);
if (_match.hasMatch())
{
int last = _match.lastCapturedIndex();
for (int i = 0; i <= last; ++i)
{
tup.start = from + _match.capturedStart();
twitchEmotes.push_back(std::move(tup));
}
}
this->originalMessage_ =
u"Too many replacements - check your ignores!"_s;
return;
}

addReplEmotes(phrase, midExtendedRef, pos1);

from += midsize;
}
}
else
{
int from = 0;
while ((from = this->originalMessage_.indexOf(
pattern, from, phrase.caseSensitivity())) != -1)
{
int len = pattern.size();
auto vret = removeEmotesInRange(from, len, twitchEmotes);
auto replace = phrase.getReplace();

int replacesize = replace.size();
this->originalMessage_.replace(from, len, replace);

int pos1 = from;
while (pos1 > 0)
{
if (this->originalMessage_[pos1 - 1] == ' ')
{
break;
}
--pos1;
}
int pos2 = from + replacesize;
while (pos2 < this->originalMessage_.length())
{
if (this->originalMessage_[pos2] == ' ')
{
break;
}
++pos2;
}

shiftIndicesAfter(from + len, replacesize - len);

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
auto midExtendedRef =
QStringView{this->originalMessage_}.mid(pos1, pos2 - pos1);
#else
auto midExtendedRef =
this->originalMessage_.midRef(pos1, pos2 - pos1);
#endif

for (auto &tup : vret)
{
if (tup.ptr == nullptr)
{
qCDebug(chatterinoTwitch)
<< "v nullptr" << tup.name.string;
continue;
}
QRegularExpression emoteregex(
"\\b" + tup.name.string + "\\b",
QRegularExpression::UseUnicodePropertiesOption);
auto match = emoteregex.match(midExtendedRef);
if (match.hasMatch())
{
int last = match.lastCapturedIndex();
for (int i = 0; i <= last; ++i)
{
tup.start = from + match.capturedStart();
twitchEmotes.push_back(std::move(tup));
}
}
}

addReplEmotes(phrase, midExtendedRef, pos1);
continue;
}

from += replacesize;
}
SizeType from = 0;
while ((from = this->originalMessage_.indexOf(
pattern, from, phrase.caseSensitivity())) != -1)
{
replaceMessageAt(phrase, from, pattern.length(),
phrase.getReplace());
from += phrase.getReplace().length();
}
}
}
Expand Down
Loading