From 26f89194324da565aafd5943284d83fed9dcde5f Mon Sep 17 00:00:00 2001 From: Hazel Koehler Date: Thu, 2 May 2024 20:31:34 -0400 Subject: [PATCH] feat: check polls and media for muted keywords --- .../frontend/src/scripts/check-word-mute.ts | 77 ++++++++++++------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts index 67e896b4b9..6f3c6c40de 100644 --- a/packages/frontend/src/scripts/check-word-mute.ts +++ b/packages/frontend/src/scripts/check-word-mute.ts @@ -3,40 +3,63 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export function checkWordMute(note: Record, me: Record | null | undefined, mutedWords: Array): boolean { +import type { Note, MeDetailed } from "misskey-js/entities.js"; + +// TODO: this implementation is horribly inefficient. +// Each regex is validated (using a regex, ironically), transformed, and then parsed - for each note being checked. +// These regex objects should be cached somewhere. + +export function checkWordMute(note: Note, me: MeDetailed | null | undefined, mutedWords: Array): boolean { // 自分自身 if (me && (note.userId === me.id)) return false; - if (mutedWords.length > 0) { - const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim(); + if (mutedWords.length < 1) return false; - if (text === '') return false; + const text = getNoteText(note); + if (text === '') return false; - const matched = mutedWords.some(filter => { - if (Array.isArray(filter)) { - // Clean up - const filteredFilter = filter.filter(keyword => keyword !== ''); - if (filteredFilter.length === 0) return false; + return mutedWords.some(filter => { + if (Array.isArray(filter)) { + // Clean up + const filteredFilter = filter.filter(keyword => keyword !== ''); + if (filteredFilter.length === 0) return false; - return filteredFilter.every(keyword => text.includes(keyword)); - } else { - // represents RegExp - const regexp = filter.match(/^\/(.+)\/(.*)$/); + return filteredFilter.every(keyword => text.includes(keyword)); + } else { + // represents RegExp + const regexp = filter.match(/^\/(.+)\/(.*)$/); + // This should never happen due to input sanitisation. + if (!regexp) return false; + + try { + return new RegExp(regexp[1], regexp[2]).test(text); + } catch (err) { // This should never happen due to input sanitisation. - if (!regexp) return false; - - try { - return new RegExp(regexp[1], regexp[2]).test(text); - } catch (err) { - // This should never happen due to input sanitisation. - return false; - } + return false; } - }); - - if (matched) return true; - } - - return false; + } + }); +} + +function getNoteText(note: Note): string { + const textParts: string[] = []; + + if (note.cw) + textParts.push(note.cw); + + if (note.text) + textParts.push(note.text); + + if (note.files) + for (const file of note.files) + if (file.comment) + textParts.push(file.comment); + + if (note.poll) + for (const choice of note.poll.choices) + if (choice.text) + textParts.push(choice.text); + + return textParts.join('\n').trim(); }