diff --git a/CHANGELOG.md b/CHANGELOG.md index dfcf3aa5..2d9f7def 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 9605f490..00f9bf68 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -846,28 +846,25 @@ void TwitchMessageBuilder::appendUsername() void TwitchMessageBuilder::runIgnoreReplaces( std::vector &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 v(it, twitchEmotes.end()); + std::vector 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; @@ -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()) @@ -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(startIndex + pos), + static_cast(startIndex + pos + + emote.first.string.length()), emote.second, 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(from + length), + static_cast(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(from + match.capturedStart()); + emote.end = static_cast(from + match.capturedEnd()); + twitchEmotes.push_back(std::move(emote)); + } + } + + addReplEmotes(phrase, midExtendedRef, wordStart); + }; + for (const auto &phrase : *phrases) { if (phrase.isBlock()) @@ -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) + replaceMessageAt(phrase, from, match.capturedLength(), + phrase.getReplace()); + from += phrase.getReplace().length(); + iterations++; + if (iterations >= 128) { - if (this->originalMessage_[pos1 - 1] == ' ') - { - break; - } - --pos1; + this->originalMessage_ = + u"Too many replacements - check your ignores!"_s; + return; } - 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; - 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); - - from += replacesize; - } + replaceMessageAt(phrase, from, pattern.length(), + phrase.getReplace()); + from += phrase.getReplace().length(); } } }