diff --git a/locales/en-US.yml b/locales/en-US.yml index 0b885c7afe..8da0af981f 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -668,6 +668,9 @@ regexpErrorDescription: "An error occurred in the regular expression on line {li instanceMute: "Instance Mutes" userSaysSomething: "{name} said something" userSaysSomethingReason: "{name} said {reason}" +userSaysSomethingReasonReply: "{name} replied to a post containing {reason}" +userSaysSomethingReasonRenote: "{name} boosted a post containing {reason}" +userSaysSomethingReasonQuote: "{name} quoted a post containing {reason}" makeActive: "Activate" display: "Display" copy: "Copy" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8ae43cdb9b..cb01fb564b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -619,6 +619,9 @@ regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表 instanceMute: "インスタンスミュート" userSaysSomething: "{name}が何かを言いました" userSaysSomethingReason: "{name}が{reason}と言いました" +userSaysSomethingReasonReply: "{name}が{reason}を含む投稿に返信しました" +userSaysSomethingReasonRenote: "{name}が{reason}を含む投稿をブーストしました" +userSaysSomethingReasonQuote: "{name}が{reason}を含む投稿を引用しました" makeActive: "アクティブにする" display: "表示" copy: "コピー" diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts index 53193d851a..8874a4a051 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) { - return x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string -} - -export async function getWordMute( +function checkWordMute( note: NoteLike, - me: UserLike | null | undefined, mutedWords: Array, -): Promise { - // 自分自身 - if (me && note.userId === me.id) { - return NotMuted; - } +): boolean { + if (note == null) return false; - if (mutedWords.length > 0) { - const text = ((note.cw ?? "") + "\n" + (note.text ?? "")).trim(); + const text = ((note.cw ?? "") + " " + (note.text ?? "")).trim(); + if (text === "") return false; - if (text === "") { - return NotMuted; - } + for (const mutePattern of mutedWords) { + if (Array.isArray(mutePattern)) { + // Clean up + const keywords = mutePattern.filter((keyword) => keyword !== ""); - for (const mutePattern of mutedWords) { - let mute: RE2; - let matched: string[]; - if (Array.isArray(mutePattern)) { - matched = mutePattern.filter((keyword) => keyword !== ""); + if ( + keywords.length > 0 && + keywords.every((keyword) => text.includes(keyword)) + ) + return true; + } else { + // represents RegExp + const regexp = mutePattern.match(/^\/(.+)\/(.*)$/); - 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]; + // This should never happen due to input sanitisation. + if (!regexp) { + console.warn(`Found invalid regex in word mutes: ${mutePattern}`); + continue; } try { - if (mute.test(text)) { - return { muted: true, matched }; - } + if (new RE2(regexp[1], regexp[2]).test(text)) return true; } catch (err) { // This should never happen due to input sanitisation. } } } - return NotMuted; + return false; +} + +export async function getWordHardMute( + note: NoteLike, + me: UserLike | null | undefined, + mutedWords: Array, +): Promise { + // 自分自身 + if (me && note.userId === me.id) { + return false; + } + + if (mutedWords.length > 0) { + return ( + checkWordMute(note, mutedWords) || + checkWordMute(note.reply, mutedWords) || + checkWordMute(note.renote, mutedWords) + ); + } + + return false; } diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index aa3c8528fb..75768ecca9 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -15,7 +15,7 @@ export default class extends Channel { constructor(id: string, connection: Channel["connection"]) { super(id, connection); - this.onNote = this.onNote.bind(this); + this.onNote = this.withPackedNote(this.onNote.bind(this)); this.emitTypers = this.emitTypers.bind(this); } 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..07ac380265 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -1,6 +1,6 @@ import Channel from "../channel.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import { getWordMute } from "@/misc/check-word-mute.js"; +import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import type { Packed } from "@/misc/schema.js"; @@ -63,10 +63,10 @@ export default class extends Channel { // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 - // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる + // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordHardMute(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 59501bafaf..dd36c60a1a 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -1,5 +1,5 @@ import Channel from "../channel.js"; -import { getWordMute } from "@/misc/check-word-mute.js"; +import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import type { Packed } from "@/misc/schema.js"; @@ -62,10 +62,10 @@ export default class extends Channel { // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 - // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる + // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordHardMute(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 2ac8b0fb79..d734f59df0 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -1,6 +1,6 @@ import Channel from "../channel.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import { getWordMute } from "@/misc/check-word-mute.js"; +import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import type { Packed } from "@/misc/schema.js"; @@ -79,10 +79,10 @@ export default class extends Channel { // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 - // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる + // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordHardMute(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..19bf43be22 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -1,6 +1,6 @@ import Channel from "../channel.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import { getWordMute } from "@/misc/check-word-mute.js"; +import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import type { Packed } from "@/misc/schema.js"; @@ -55,10 +55,10 @@ export default class extends Channel { // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 - // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる + // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordHardMute(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 37e29cd9a5..d2e60f3b59 100644 --- a/packages/backend/src/server/api/stream/channels/recommended-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/recommended-timeline.ts @@ -1,6 +1,6 @@ import Channel from "../channel.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import { getWordMute } from "@/misc/check-word-mute.js"; +import { getWordHardMute } from "@/misc/check-word-mute.js"; import { isUserRelated } from "@/misc/is-user-related.js"; import { isInstanceMuted } from "@/misc/is-instance-muted.js"; import type { Packed } from "@/misc/schema.js"; @@ -77,10 +77,10 @@ export default class extends Channel { // TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) // 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 - // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordMuteを呼んでいる + // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる if ( this.userProfile && - (await getWordMute(note, this.user, this.userProfile.mutedWords)).muted + (await getWordHardMute(note, this.user, this.userProfile.mutedWords)) ) return; diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 0c06b55cec..f2ccdca226 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -53,7 +53,7 @@ import { Poll } from "@/models/entities/poll.js"; import { createNotification } from "../create-notification.js"; import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; import { checkHitAntenna } from "@/misc/check-hit-antenna.js"; -import { getWordMute } from "@/misc/check-word-mute.js"; +import { getWordHardMute } from "@/misc/check-word-mute.js"; import { addNoteToAntenna } from "../add-note-to-antenna.js"; import { countSameRenotes } from "@/misc/count-same-renotes.js"; import { deliverToRelays } from "../relay.js"; @@ -355,9 +355,9 @@ export default async ( ) .then((us) => { for (const u of us) { - getWordMute(note, { id: u.userId }, u.mutedWords).then( + getWordHardMute(data, { id: u.userId }, u.mutedWords).then( (shouldMute) => { - if (shouldMute.muted) { + if (shouldMute) { MutedNotes.insert({ id: genId(), userId: u.userId, diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue index 6d617deba4..08bd27fc00 100644 --- a/packages/client/src/components/MkNote.vue +++ b/packages/client/src/components/MkNote.vue @@ -198,14 +198,14 @@
- +