From 72d59b459ab04206c83c3ce61a096122589472e8 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Thu, 3 Oct 2024 00:45:49 -0400 Subject: [PATCH] respect word mutes --- locales/en-US.yml | 1 + locales/index.d.ts | 4 + locales/ja-JP.yml | 1 + .../src/components/FollowingFeedEntry.vue | 17 +++- .../frontend/src/pages/following-feed.vue | 79 ++++++++++++++++++- .../frontend/src/scripts/check-word-mute.ts | 2 +- 6 files changed, 98 insertions(+), 6 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index 9c5237b3b7..049c347111 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -696,6 +696,7 @@ regexpError: "Regular Expression error" regexpErrorDescription: "An error occurred in the regular expression on line {line} of your {tab} word mutes:" instanceMute: "Instance Mutes" userSaysSomething: "{name} said something" +postFiltered: "post is hidden by a filter" makeActive: "Activate" display: "Display" copy: "Copy" diff --git a/locales/index.d.ts b/locales/index.d.ts index bfa29e6e44..10a846cd42 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2800,6 +2800,10 @@ export interface Locale extends ILocale { * {name}が何かを言いました */ "userSaysSomething": ParameterizedString<"name">; + /** + * post is hidden by a filter + */ + "postFiltered": string; /** * アクティブにする */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 75866f2596..25a2a7e825 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -696,6 +696,7 @@ regexpError: "正規表現エラー" regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:" instanceMute: "サーバーミュート" userSaysSomething: "{name}が何かを言いました" +postFiltered: "post is hidden by a filter" makeActive: "アクティブにする" display: "表示" copy: "コピー" diff --git a/packages/frontend/src/components/FollowingFeedEntry.vue b/packages/frontend/src/components/FollowingFeedEntry.vue index 7f5abaa9cc..8fa5e014d8 100644 --- a/packages/frontend/src/components/FollowingFeedEntry.vue +++ b/packages/frontend/src/components/FollowingFeedEntry.vue @@ -18,7 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
({{ i18n.ts.postFiltered }})
+
@@ -29,10 +30,14 @@ import * as Misskey from 'misskey-js'; import { getNoteSummary } from '@/scripts/get-note-summary.js'; import { userPage } from '@/filters/user.js'; import { notePage } from '@/filters/note.js'; +import { i18n } from '@/i18n.js'; -defineProps<{ - note: Misskey.entities.Note -}>(); +withDefaults(defineProps<{ + note: Misskey.entities.Note, + isMuted: boolean +}>(), { + isMuted: false, +}); defineEmits<{ (event: 'select', user: Misskey.entities.UserLite): void @@ -98,6 +103,10 @@ defineEmits<{ height: 2.5em; } +.muted { + font-style: italic; +} + @container (max-width: 600px) { .root { padding: 16px; diff --git a/packages/frontend/src/pages/following-feed.vue b/packages/frontend/src/pages/following-feed.vue index c47880bb58..5335191246 100644 --- a/packages/frontend/src/pages/following-feed.vue +++ b/packages/frontend/src/pages/following-feed.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -68,7 +68,9 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { useRouter } from '@/router/supplier.js'; import * as os from '@/os.js'; import MkPageHeader from '@/components/global/MkPageHeader.vue'; +import { $i } from '@/account.js'; import MkLoading from '@/components/global/MkLoading.vue'; +import { getNoteText } from '@/scripts/check-word-mute.js'; const props = withDefaults(defineProps<{ initialTab?: FollowingFeedTab, @@ -158,6 +160,81 @@ async function onChangeTab(): Promise { await showUserNotes(''); } +const softMutePatterns = ref(buildMutePatterns($i?.mutedWords)); +const hardMutePatterns = ref(buildMutePatterns($i?.hardMutedWords)); + +function buildMutePatterns(mutedWords: (string | string[])[] | undefined): RegExp[] { + if (!mutedWords || mutedWords.length < 1) { + return []; + } + + // flags -> pattern[] + const patternMap = new Map>(); + for (const mute of mutedWords) { + let flags: string; + let patterns: string[]; + + if (!mute) { + continue; + } else if (Array.isArray(mute)) { + patterns = mute; + flags = 'i'; + } else { + const match = mute.match(/^\/(.+)\/(.*)$/); + if (!match) { + continue; + } else { + patterns = [match[1]]; + flags = match[2]; + } + } + + let flagPatterns = patternMap.get(flags); + if (!flagPatterns) { + flagPatterns = new Set(); + patternMap.set(flags, flagPatterns); + } + + for (const pattern of patterns) { + flagPatterns.add(pattern); + } + } + + return Array + .from(patternMap) + .map(([flag, patterns]) => { + const pattern = Array.from(patterns).map(p => `(${p})`).join('|'); + return new RegExp(pattern, flag); + }); +} + +// Adapted from MkNote.ts +function isSoftMuted(note: Misskey.entities.Note): boolean { + return isMuted(note, softMutePatterns.value); +} + +function isHardMuted(note: Misskey.entities.Note): boolean { + return isMuted(note, hardMutePatterns.value); +} + +function isMuted(note: Misskey.entities.Note, mutes: RegExp[]): boolean { + if (mutes.length < 1) return false; + + return checkMute(note, mutes) + || checkMute(note.reply, mutes) + || checkMute(note.renote, mutes); +} + +// Adapted from check-word-mute.ts +function checkMute(note: Misskey.entities.Note | undefined | null, mutes: RegExp[]): boolean { + if (!note) { + return false; + } + + const noteText = getNoteText(note); + return mutes.some(p => p.test(noteText)); +} + const latestNotesPaging = shallowRef>(); const latestNotesPagination: Paging<'notes/following'> = { diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts index e65c327ffe..45af7374e8 100644 --- a/packages/frontend/src/scripts/check-word-mute.ts +++ b/packages/frontend/src/scripts/check-word-mute.ts @@ -43,7 +43,7 @@ export function checkWordMute(note: Note, me: MeDetailed | null | undefined, mut return false; } -function getNoteText(note: Note): string { +export function getNoteText(note: Note): string { const textParts: string[] = []; if (note.cw) textParts.push(note.cw);