diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts index 53193d851a..a411e46cc8 100644 --- a/packages/backend/src/misc/check-word-mute.ts +++ b/packages/backend/src/misc/check-word-mute.ts @@ -12,67 +12,63 @@ type UserLike = { id: User["id"]; }; -export type Muted = { - muted: boolean; - matched: string[]; -}; - -const NotMuted = { muted: false, matched: [] }; - -function escapeRegExp(x: string) { +function escapeRegExp(x: string): string { return x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string } +function checkWordMute(note: NoteLike): boolean { + if (note == null) return false; + + const text = ((note.cw ?? "") + " " + (note.text ?? "")).trim(); + if (text === "") return false; + + for (const mutePattern of mutedWords) { + let mute: RE2; + let matched: string[]; + if (Array.isArray(mutePattern)) { + matched = mutePattern.filter((keyword) => keyword !== ""); + + if (matched.length === 0) { + continue; + } + mute = new RE2( + `\\b${matched.map(escapeRegExp).join("\\b.*\\b")}\\b`, + "g", + ); + } else { + const regexp = mutePattern.match(/^\/(.+)\/(.*)$/); + // This should never happen due to input sanitisation. + if (!regexp) { + console.warn(`Found invalid regex in word mutes: ${mutePattern}`); + continue; + } + mute = new RE2(regexp[1], regexp[2]); + matched = [mutePattern]; + } + + try { + if (mute.test(text)) return true; + } catch (err) { + // This should never happen due to input sanitisation. + } + } + + return notMuted; +} + export async function getWordMute( note: NoteLike, me: UserLike | null | undefined, mutedWords: Array, -): Promise { +): Promise { // 自分自身 if (me && note.userId === me.id) { - return NotMuted; + return false; } if (mutedWords.length > 0) { - const text = ((note.cw ?? "") + "\n" + (note.text ?? "")).trim(); - - if (text === "") { - return NotMuted; - } - - for (const mutePattern of mutedWords) { - let mute: RE2; - let matched: string[]; - if (Array.isArray(mutePattern)) { - matched = mutePattern.filter((keyword) => keyword !== ""); - - if (matched.length === 0) { - continue; - } - mute = new RE2( - `\\b${matched.map(escapeRegExp).join("\\b.*\\b")}\\b`, - "g", - ); - } else { - const regexp = mutePattern.match(/^\/(.+)\/(.*)$/); - // This should never happen due to input sanitisation. - if (!regexp) { - console.warn(`Found invalid regex in word mutes: ${mutePattern}`); - continue; - } - mute = new RE2(regexp[1], regexp[2]); - matched = [mutePattern]; - } - - try { - if (mute.test(text)) { - return { muted: true, matched }; - } - } catch (err) { - // This should never happen due to input sanitisation. - } - } + return checkWordMute(note) || checkWordMute(reply) || checkWordMute(renote) } - return NotMuted; + return false; } diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index 050a8d1019..65e1eb9879 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -1,6 +1,7 @@ import Channel from "../channel.js"; import { Notes } from "@/models/index.js"; import { isUserRelated } from "@/misc/is-user-related.js"; +import { getWordMute } from "@/misc/check-word-mute.js"; import type { StreamMessages } from "../types.js"; import { IdentifiableError } from "@/misc/identifiable-error.js"; @@ -37,6 +38,12 @@ export default class extends Channel { if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return; + if ( + this.userProfile && + (await getWordMute(note, this.user, this.userProfile.mutedWords)) + ) + return; + this.connection.cacheNote(note); this.send("note", note); diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index d046579f42..3b242bb2a4 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -1,6 +1,7 @@ import Channel from "../channel.js"; import { Users } from "@/models/index.js"; import { isUserRelated } from "@/misc/is-user-related.js"; +import { getWordMute } from "@/misc/check-word-mute.js"; import type { User } from "@/models/entities/user.js"; import type { StreamMessages } from "../types.js"; import type { Packed } from "@/misc/schema.js"; @@ -39,6 +40,12 @@ export default class extends Channel { if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return; + if ( + this.userProfile && + (await getWordMute(note, this.user, this.userProfile.mutedWords)) + ) + return; + this.connection.cacheNote(note); this.send("note", note); diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index aa3844c7e3..fb5c458b41 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -66,7 +66,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordMute(note, this.user, this.userProfile.mutedWords)) ) return; diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index fa4a8a3901..70b2f387e1 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -64,7 +64,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordMute(note, this.user, this.userProfile.mutedWords)) ) return; diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 557bb96827..423fb461f3 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -81,7 +81,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordMute(note, this.user, this.userProfile.mutedWords)) ) return; diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index dc3aab8d7d..94901661b6 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -58,7 +58,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordMute(note, this.user, this.userProfile.mutedWords)) ) return; diff --git a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts index 6baec77442..7eee22c325 100644 --- a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts @@ -79,7 +79,7 @@ export default class extends Channel { // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordMute(note, this.user, this.userProfile.mutedWords)) ) return; diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 105c45955c..2183351e1a 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -2,6 +2,7 @@ import Channel from "../channel.js"; import { UserListJoinings, UserLists } from "@/models/index.js"; import type { User } from "@/models/entities/user.js"; import { isUserRelated } from "@/misc/is-user-related.js"; +import { getWordMute } from "@/misc/check-word-mute.js"; import type { Packed } from "@/misc/schema.js"; export default class extends Channel { @@ -59,6 +60,12 @@ export default class extends Channel { if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return; + if ( + this.userProfile && + (await getWordMute(note, this.user, this.userProfile.mutedWords)) + ) + return; + this.send("note", note); } diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index f1164c9c6e..7c9746b73a 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -356,7 +356,7 @@ export default async ( for (const u of us) { getWordMute(note, { id: u.userId }, u.mutedWords).then( (shouldMute) => { - if (shouldMute.muted) { + if (shouldMute) { MutedNotes.insert({ id: genId(), userId: u.userId,