matchMuteWords function
List<MuteWordMatch>
matchMuteWords({
- required List<
MutedWord> mutedWords, - required String text,
- List<
RichtextFacet> ? facets, - List<
String> ? outlineTags, - List<
String> ? languages, - ProfileViewBasic? actor,
Checks if the given text matches any of the muted words, returning the list of matches. An empty list means no matches were found.
Implementation
List<MuteWordMatch> matchMuteWords({
required List<MutedWord> mutedWords,
required String text,
List<RichtextFacet>? facets,
List<String>? outlineTags,
List<String>? languages,
ProfileViewBasic? actor,
}) {
final exception = _kLanguageExceptions.contains(languages?.firstOrNull ?? '');
final tags = <String>[
if (outlineTags != null) ...outlineTags.map((e) => e.toLowerCase()),
if (facets != null)
...facets.expand(
(e) => e.features
.whereType<URichtextFacetFeaturesRichtextFacetTag>()
.map((tag) => tag.data.tag.toLowerCase()),
),
];
final matches = <MuteWordMatch>[];
outer:
for (final mute in mutedWords) {
final mutedWord = mute.value.toLowerCase();
final postText = text.toLowerCase();
// expired, ignore
if (mute.expiresAt != null && mute.expiresAt!.isBefore(DateTime.now())) {
continue;
}
if (mute.actorTarget?.knownValue ==
KnownMutedWordActorTarget.excludeFollowing &&
(actor?.viewer?.following != null)) {
continue;
}
// `content` applies to tags as well
if (tags.contains(mutedWord)) {
matches.add(MuteWordMatch(word: mute, predicate: mute.value));
continue;
}
// rest of the checks are for `content` only
if (!mute.targets.contains(
const MutedWordTarget.knownValue(data: KnownMutedWordTarget.content),
)) {
continue;
}
// single character or other exception, has to use contains
if ((mutedWord.characters.length == 1 || exception) &&
postText.contains(mutedWord)) {
matches.add(MuteWordMatch(word: mute, predicate: mute.value));
continue;
}
// too long
if (mutedWord.length > postText.length) continue;
// exact match
if (mutedWord == postText) {
matches.add(MuteWordMatch(word: mute, predicate: mute.value));
continue;
}
// any muted phrase with space or punctuation
if (_whitespacePunctuationRegex.hasMatch(mutedWord) &&
postText.contains(mutedWord)) {
matches.add(MuteWordMatch(word: mute, predicate: mute.value));
continue;
}
// check individual character groups
for (final word in postText.split(_wordBoundaryRegex)) {
if (word == mutedWord) {
matches.add(MuteWordMatch(word: mute, predicate: word));
continue outer;
}
// compare word without leading/trailing punctuation, but allow internal
// punctuation (such as `s@ssy`)
final wordTrimmedPunctuation = word.replaceAll(
_leadingTrailingPunctuationRegex,
'',
);
if (mutedWord == wordTrimmedPunctuation) {
matches.add(MuteWordMatch(word: mute, predicate: word));
continue outer;
}
if (mutedWord.length > wordTrimmedPunctuation.length) continue;
if (_punctuationRegex.hasMatch(wordTrimmedPunctuation)) {
// Exit case for any punctuation within the predicate that we _do_
// allow e.g. `and/or` should not match `Andor`.
if (_slashRegex.hasMatch(wordTrimmedPunctuation)) {
continue outer;
}
final spacedWord = wordTrimmedPunctuation.replaceAll(
_punctuationRegex,
' ',
);
if (spacedWord == mutedWord) {
matches.add(MuteWordMatch(word: mute, predicate: word));
continue outer;
}
final contiguousWord = spacedWord.replaceAll(_spaceRegex, '');
if (contiguousWord == mutedWord) {
matches.add(MuteWordMatch(word: mute, predicate: word));
continue outer;
}
final wordParts = wordTrimmedPunctuation.split(_punctuationRegex);
for (final wordPart in wordParts) {
if (wordPart == mutedWord) {
matches.add(MuteWordMatch(word: mute, predicate: word));
continue outer;
}
}
}
}
}
return matches;
}