From 075e5a1c7ae2a78033187c7a9601852f4be14637 Mon Sep 17 00:00:00 2001 From: naskya Date: Thu, 4 May 2023 13:17:37 +0900 Subject: [PATCH 01/20] Refactor hard word mutes --- packages/backend/src/misc/check-word-mute.ts | 94 +++++++++---------- .../src/server/api/stream/channels/antenna.ts | 7 ++ .../src/server/api/stream/channels/channel.ts | 7 ++ .../api/stream/channels/global-timeline.ts | 2 +- .../api/stream/channels/home-timeline.ts | 2 +- .../api/stream/channels/hybrid-timeline.ts | 2 +- .../api/stream/channels/local-timeline.ts | 2 +- .../stream/channels/recommended-timeline.ts | 2 +- .../server/api/stream/channels/user-list.ts | 7 ++ packages/backend/src/services/note/create.ts | 2 +- 10 files changed, 72 insertions(+), 55 deletions(-) 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, From cdd876ba03852c318f8599215a4860854d7a82ea Mon Sep 17 00:00:00 2001 From: naskya Date: Thu, 4 May 2023 14:13:13 +0900 Subject: [PATCH 02/20] Refactor soft word mutes --- locales/en-US.yml | 3 + locales/ja-JP.yml | 3 + packages/client/src/components/MkNote.vue | 16 +++- .../client/src/components/MkNoteDetailed.vue | 16 +++- packages/client/src/components/MkNoteSub.vue | 40 +++++++++ .../client/src/scripts/check-word-mute.ts | 84 ++++++++++++------- 6 files changed, 128 insertions(+), 34 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index 4acb0fc5b1..89c0a08e66 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -663,6 +663,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/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue index 7e3fd6be55..67eab29c73 100644 --- a/packages/client/src/components/MkNote.vue +++ b/packages/client/src/components/MkNote.vue @@ -198,7 +198,7 @@
- +