Skip to content

Commit

Permalink
perf(frontend): ahocorasickを使ったワードミュート
Browse files Browse the repository at this point in the history
  • Loading branch information
tai-cha committed Feb 2, 2025
1 parent 9230ee5 commit 1d2f2ed
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 58 deletions.
1 change: 1 addition & 0 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*",
"misskey-reversi": "workspace:*",
"modern-ahocorasick": "2.0.2",
"photoswipe": "5.4.4",
"punycode.js": "2.3.1",
"rollup": "4.26.0",
Expand Down
68 changes: 40 additions & 28 deletions packages/frontend/src/scripts/check-word-mute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,53 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as Misskey from 'misskey-js';

export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): Array<string | string[]> | false {
// 自分自身
import * as AhoCorasick from 'modern-ahocorasick';
export function checkWordMute(
note: Misskey.entities.Note,
me: Misskey.entities.UserLite | null | undefined,
mutedWords: Array<string | string[]>,
): Array<string | string[]> | false {
// 自分自身の投稿は対象外
if (me && (note.userId === me.id)) return false;
if (mutedWords.length <= 0) return false;

if (mutedWords.length > 0) {
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();

if (text === '') return false;
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
if (text === '') return false;

const matched = mutedWords.filter(filter => {
if (Array.isArray(filter)) {
// Clean up
const filteredFilter = filter.filter(keyword => keyword !== '');
if (filteredFilter.length === 0) return false;
const normalTexts: string[] = [];
const andTexts: string[][] = [];
const regexTexts: Array<{ originaL: string; regex: RegExp }> = [];

return filteredFilter.every(keyword => text.includes(keyword));
for (const filter of mutedWords) {
if (Array.isArray(filter)) {
if (filter.length === 1) {
normalTexts.push(filter[0]);
} else {
// represents RegExp
const regexp = filter.match(/^\/(.+)\/(.*)$/);
andTexts.push(filter);
}
} else if (filter.startsWith('/') && filter.endsWith('/')) {
const regExp = filter.match(/^\/(.+)\/(.*)$/);
if (!regExp) continue;
try {
regexTexts.push({ originaL: filter, regex: new RegExp(filter.slice(1, -1)) });
} catch {
// 無効な正規表現はスキップ
}
} else {
normalTexts.push(filter);
}
}
// normal wordmute with AhoCorasick
const ac = new AhoCorasick.default(normalTexts);
const normalMatches = ac.search(text);

// This should never happen due to input sanitisation.
if (!regexp) return false;
// andTexts
const andMatches = andTexts.filter(texts => texts.filter(keyword => keyword !== '').every(keyword => text.includes(keyword)));

try {
return new RegExp(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
}
}
});
// RegExp
const regexMatches = regexTexts.filter(({ regex }) => regex.test(text));

if (matched.length > 0) return matched;
}
const matched: Array<string | string[]> = normalMatches.map(match => match[1]).concat(andMatches, regexMatches.map(({ originaL }) => originaL));

return false;
return matched.length > 0 ? matched : false;
}
Loading

0 comments on commit 1d2f2ed

Please sign in to comment.