perf: ノート毎にミュートワードを渡すのをやめる

This commit is contained in:
tai-cha 2025-02-04 22:51:09 +09:00
parent 9a71514b4c
commit cc1fdece65
No known key found for this signature in database
GPG Key ID: 1D5EE39F870DC283
5 changed files with 83 additions and 33 deletions

View File

@ -24,6 +24,7 @@ import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mainRouter } from '@/router/main.js';
import { type Keymap, makeHotkey } from '@/scripts/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
import { initMuteInfo } from '@/scripts/check-word-mute.js';
export async function mainBoot() {
const { isClientUpdated } = await common(() => {
@ -343,11 +344,14 @@ export async function mainBoot() {
}
}
initMuteInfo();
const main = markRaw(stream.useChannel('main', null, 'System'));
// 自分の情報が更新されたとき
main.on('meUpdated', i => {
updateAccountPartial(i);
initMuteInfo();
});
main.on('readAllNotifications', () => {

View File

@ -280,8 +280,8 @@ const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filte
const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
const collapsed = ref(appearNote.value.cw == null && isLong);
const isDeleted = ref(false);
const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true));
const muted = ref(checkMute(appearNote.value, 'soft'));
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, 'hard', true));
const showSoftWordMutedWord = computed(() => defaultStore.state.showSoftWordMutedWord);
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false);
@ -300,20 +300,18 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
}));
/* Overload FunctionLint
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): Array<string | string[]> | false | 'sensitiveMute';
function checkMute(noteToCheck: Misskey.entities.Note, type: 'soft' | 'hard', checkOnly: true): boolean;
function checkMute(noteToCheck: Misskey.entities.Note, type: 'soft' | 'hard', checkOnly: false): Array<string | string[]> | false | 'sensitiveMute';
*/
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): Array<string | string[]> | false | 'sensitiveMute' {
if (mutedWords != null) {
const result = checkWordMute(noteToCheck, $i, mutedWords);
if (Array.isArray(result)) return result;
function checkMute(noteToCheck: Misskey.entities.Note, type: 'soft' | 'hard', checkOnly = false): Array<string | string[]> | false | 'sensitiveMute' {
const result = checkWordMute(noteToCheck, $i, type);
if (Array.isArray(result)) return result;
const replyResult = noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords);
if (Array.isArray(replyResult)) return replyResult;
const replyResult = noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, type);
if (Array.isArray(replyResult)) return replyResult;
const renoteResult = noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords);
if (Array.isArray(renoteResult)) return renoteResult;
}
const renoteResult = noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, type);
if (Array.isArray(renoteResult)) return renoteResult;
if (checkOnly) return false;

View File

@ -296,7 +296,7 @@ const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const isDeleted = ref(false);
const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false);
const muted = ref($i ? checkWordMute(appearNote.value, $i, 'soft') : false);
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false);
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;

View File

@ -62,7 +62,7 @@ const props = withDefaults(defineProps<{
depth: 1,
});
const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
const muted = ref($i ? checkWordMute(props.note, $i, 'soft') : false);
const showContent = ref(false);
const replies = ref<Misskey.entities.Note[]>([]);

View File

@ -3,22 +3,27 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as AhoCorasick from 'modern-ahocorasick';
import { c } from 'node_modules/vite/dist/node/types.d-aGj9QkWt';
import type * 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 {
// 自分自身の投稿は対象外
if (me && (note.userId === me.id)) return false;
import { $i } from '@/account.js';
type WordMuteInfo = false | {
normals: string[];
and: string[][];
regex: Array<{ original: string; regex: RegExp }>;
ahoCorasick: AhoCorasick.default;
}
type GlobalMisskeyWordMute = {
soft: WordMuteInfo;
hard: WordMuteInfo;
};
function createWordMuteInfo(mutedWords: Array<string | string[]>) : WordMuteInfo {
if (mutedWords.length <= 0) return false;
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
if (text === '') return false;
const normalTexts: string[] = [];
const andTexts: string[][] = [];
const regexTexts: Array<{ originaL: string; regex: RegExp }> = [];
const regexTexts: Array<{ original: string; regex: RegExp }> = [];
for (const filter of mutedWords) {
if (Array.isArray(filter)) {
@ -31,7 +36,7 @@ export function checkWordMute(
const regExp = filter.match(/^\/(.+)\/(.*)$/);
if (!regExp) continue;
try {
regexTexts.push({ originaL: filter, regex: new RegExp(filter.slice(1, -1)) });
regexTexts.push({ original: filter, regex: new RegExp(filter.slice(1, -1)) });
} catch {
// 無効な正規表現はスキップ
}
@ -39,17 +44,60 @@ export function checkWordMute(
normalTexts.push(filter);
}
}
// normal wordmute with AhoCorasick
const ac = new AhoCorasick.default(normalTexts);
const normalMatches = ac.search(text);
return {
normals: normalTexts,
and: andTexts,
regex: regexTexts,
ahoCorasick: ac,
};
}
function setWordMuteInfo(mutedWords: Array<string | string[]>, hardMutedWords: Array<string | string[]>): void {
const soft = createWordMuteInfo(mutedWords);
const hard = createWordMuteInfo(hardMutedWords);
globalThis._misskeyWordMute = { soft, hard };
}
function getWordMuteInfo(): GlobalMisskeyWordMute | undefined {
if (!globalThis._misskeyWordMute) return undefined;
return globalThis._misskeyWordMute as unknown as GlobalMisskeyWordMute;
}
export function initMuteInfo(): void {
const mutedWords = $i?.mutedWords ?? [];
const hardMutedWords = $i?.hardMutedWords ?? [];
setWordMuteInfo(mutedWords, hardMutedWords);
}
export function checkWordMute(
note: Misskey.entities.Note,
me: Misskey.entities.UserLite | null | undefined,
type: 'soft' | 'hard',
): Array<string | string[]> | false {
// 自分自身の投稿は対象外
if (me && (note.userId === me.id)) return false;
const wordMuteInfo = getWordMuteInfo()?.[type];
if (wordMuteInfo == null || wordMuteInfo === false) return false;
const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
if (text === '') return false;
const normalMatches = wordMuteInfo.ahoCorasick.search(text);
// andTexts
const andMatches = andTexts.filter(texts => texts.filter(keyword => keyword !== '').every(keyword => text.includes(keyword)));
const andMatches = wordMuteInfo.and.filter(texts => texts.filter(keyword => keyword !== '').every(keyword => text.includes(keyword)));
// RegExp
const regexMatches = regexTexts.filter(({ regex }) => regex.test(text));
const regexMatches = wordMuteInfo.regex.filter(({ regex }) => regex.test(text));
const matched: Array<string | string[]> = normalMatches.map(match => match[1]).concat(andMatches, regexMatches.map(({ originaL }) => originaL));
const matched: Array<string | string[]> = normalMatches.map(match => match[1]).concat(andMatches, regexMatches.map(({ original }) => original));
return matched.length > 0 ? matched : false;
}