refactor: Ignores and Replacements (#4965)

Fixes a freeze from a bad regex in _Ignores_
Fixes some emotes not appearing when using _Ignores_
Fixes lookahead/-behind not working in _Ignores_
This commit is contained in:
nerix 2023-11-17 17:39:45 +01:00 committed by GitHub
parent d9cdc88061
commit 3d9db1d528
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 99 additions and 146 deletions

View file

@ -35,6 +35,9 @@
- Bugfix: Fixed a crash when clicking `More messages below` button in a usercard and closing it quickly. (#4933) - 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 thread popup window missing messages for nested threads. (#4923)
- Bugfix: Fixed an occasional crash for channel point redemptions with text input. (#4949) - 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: Change clang-format from v14 to v16. (#4929)
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791) - Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)
- Dev: Temporarily disable High DPI scaling on Qt6 builds on Windows. (#4767) - Dev: Temporarily disable High DPI scaling on Qt6 builds on Windows. (#4767)

View file

@ -846,28 +846,25 @@ void TwitchMessageBuilder::appendUsername()
void TwitchMessageBuilder::runIgnoreReplaces( void TwitchMessageBuilder::runIgnoreReplaces(
std::vector<TwitchEmoteOccurrence> &twitchEmotes) std::vector<TwitchEmoteOccurrence> &twitchEmotes)
{ {
using SizeType = QString::size_type;
auto phrases = getSettings()->ignoredMessages.readOnly(); auto phrases = getSettings()->ignoredMessages.readOnly();
auto removeEmotesInRange = [](int pos, int len, auto removeEmotesInRange = [&twitchEmotes](SizeType pos, SizeType len) {
auto &twitchEmotes) mutable { // all emotes outside the range come before `it`
// all emotes in the range start at `it`
auto it = std::partition( auto it = std::partition(
twitchEmotes.begin(), twitchEmotes.end(), twitchEmotes.begin(), twitchEmotes.end(),
[pos, len](const auto &item) { [pos, len](const auto &item) {
// returns true for emotes outside the range
return !((item.start >= pos) && item.start < (pos + len)); return !((item.start >= pos) && item.start < (pos + len));
}); });
for (auto copy = it; copy != twitchEmotes.end(); ++copy) std::vector<TwitchEmoteOccurrence> emotesInRange(it,
{ twitchEmotes.end());
if ((*copy).ptr == nullptr)
{
qCDebug(chatterinoTwitch)
<< "remem nullptr" << (*copy).name.string;
}
}
std::vector<TwitchEmoteOccurrence> v(it, twitchEmotes.end());
twitchEmotes.erase(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) for (auto &item : twitchEmotes)
{ {
auto &index = item.start; auto &index = item.start;
@ -881,14 +878,18 @@ void TwitchMessageBuilder::runIgnoreReplaces(
auto addReplEmotes = [&twitchEmotes](const IgnorePhrase &phrase, auto addReplEmotes = [&twitchEmotes](const IgnorePhrase &phrase,
const auto &midrepl, const auto &midrepl,
int startIndex) mutable { SizeType startIndex) {
if (!phrase.containsEmote()) if (!phrase.containsEmote())
{ {
return; return;
} }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
auto words = midrepl.tokenize(u' ');
#else
auto words = midrepl.split(' '); auto words = midrepl.split(' ');
int pos = 0; #endif
SizeType pos = 0;
for (const auto &word : words) for (const auto &word : words)
{ {
for (const auto &emote : phrase.getEmotes()) for (const auto &emote : phrase.getEmotes())
@ -901,8 +902,9 @@ void TwitchMessageBuilder::runIgnoreReplaces(
<< "emote null" << emote.first.string; << "emote null" << emote.first.string;
} }
twitchEmotes.push_back(TwitchEmoteOccurrence{ twitchEmotes.push_back(TwitchEmoteOccurrence{
startIndex + pos, static_cast<int>(startIndex + pos),
startIndex + pos + (int)emote.first.string.length(), static_cast<int>(startIndex + pos +
emote.first.string.length()),
emote.second, emote.second,
emote.first, emote.first,
}); });
@ -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) for (const auto &phrase : *phrases)
{ {
if (phrase.isBlock()) if (phrase.isBlock())
@ -930,144 +989,35 @@ void TwitchMessageBuilder::runIgnoreReplaces(
{ {
continue; continue;
} }
QRegularExpressionMatch match; QRegularExpressionMatch match;
int from = 0; size_t iterations = 0;
SizeType from = 0;
while ((from = this->originalMessage_.indexOf(regex, from, while ((from = this->originalMessage_.indexOf(regex, from,
&match)) != -1) &match)) != -1)
{ {
int len = match.capturedLength(); replaceMessageAt(phrase, from, match.capturedLength(),
auto vret = removeEmotesInRange(from, len, twitchEmotes); phrase.getReplace());
auto mid = this->originalMessage_.mid(from, len); from += phrase.getReplace().length();
mid.replace(regex, phrase.getReplace()); iterations++;
if (iterations >= 128)
int midsize = mid.size();
this->originalMessage_.replace(from, len, mid);
int pos1 = from;
while (pos1 > 0)
{ {
if (this->originalMessage_[pos1 - 1] == ' ') this->originalMessage_ =
{ u"Too many replacements - check your ignores!"_s;
break; return;
}
--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)
{
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);
from += midsize;
} }
continue;
} }
else
SizeType from = 0;
while ((from = this->originalMessage_.indexOf(
pattern, from, phrase.caseSensitivity())) != -1)
{ {
int from = 0; replaceMessageAt(phrase, from, pattern.length(),
while ((from = this->originalMessage_.indexOf( phrase.getReplace());
pattern, from, phrase.caseSensitivity())) != -1) from += phrase.getReplace().length();
{
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);
from += replacesize;
}
} }
} }
} }