From d5b372f7a92e3892addb306fc6b62b169e2bfc41 Mon Sep 17 00:00:00 2001 From: Marie Date: Fri, 4 Oct 2024 02:31:22 +0200 Subject: [PATCH 01/21] upd&merge: Merge Cherrypick/MisskeyIO's external url popup, delete old popup warning and modify script to handle undefined domains --- locales/en-US.yml | 8 +- locales/index.d.ts | 38 ++++- locales/ja-JP.yml | 8 ++ .../1711008460816-external-website-warn.js | 16 +++ .../src/core/entities/MetaEntityService.ts | 1 + packages/backend/src/models/Meta.ts | 8 ++ .../backend/src/models/json-schema/meta.ts | 8 ++ .../src/server/api/endpoints/admin/meta.ts | 9 ++ .../server/api/endpoints/admin/update-meta.ts | 9 ++ packages/frontend/src/components/MkLink.vue | 14 +- .../src/components/MkUrlWarningDialog.vue | 131 ++++++++++++++++++ .../frontend/src/pages/admin/moderation.vue | 9 ++ packages/frontend/src/plugin.ts | 13 +- .../src/scripts/warning-external-website.ts | 48 +++++++ packages/frontend/src/store.ts | 4 + packages/misskey-js/src/autogen/types.ts | 3 + 16 files changed, 298 insertions(+), 29 deletions(-) create mode 100644 packages/backend/migration/1711008460816-external-website-warn.js create mode 100644 packages/frontend/src/components/MkUrlWarningDialog.vue create mode 100644 packages/frontend/src/scripts/warning-external-website.ts diff --git a/locales/en-US.yml b/locales/en-US.yml index 221ade4028..541917677c 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -61,6 +61,9 @@ copyNoteId: "Copy note ID" copyFileId: "Copy file ID" copyFolderId: "Copy folder ID" copyProfileUrl: "Copy profile URL" +trustedLinkUrlPatterns: "Link to external site warning exclusion list" +trustedLinkUrlPatternsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition. Using surrounding keywords with slashes will turn them into a regular expression. If you write only the domain name, it will be a backward match." +open: "Open" searchUser: "Search for a user" searchThisUsersNotes: "Search this user’s notes" reply: "Reply" @@ -291,7 +294,6 @@ removeAreYouSure: "Are you sure that you want to remove \"{x}\"?" deleteAreYouSure: "Are you sure that you want to delete \"{x}\"?" resetAreYouSure: "Really reset?" areYouSure: "Are you sure?" -confirmRemoteUrl: "Are you sure that you want to go to \"{x}\"?" saved: "Saved" messaging: "Chat" upload: "Upload" @@ -2827,3 +2829,7 @@ _contextMenu: app: "Application" appWithShift: "Application with shift key" native: "Native" +_externalNavigationWarning: + title: "Navigate to an external site" + description: "Leave {host} and go to an external site" + trustThisDomain: "Trust this domain on this device in the future" diff --git a/locales/index.d.ts b/locales/index.d.ts index f93ef14325..a7f24eea04 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -260,6 +260,18 @@ export interface Locale extends ILocale { * プロフィールURLをコピー */ "copyProfileUrl": string; + /** + * 外部サイトへのリンク警告 除外リスト + */ + "trustedLinkUrlPatterns": string; + /** + * スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。ドメイン名だけ書くと後方一致になります。 + */ + "trustedLinkUrlPatternsDescription": string; + /** + * 開く + */ + "open": string; /** * ユーザーを検索 */ @@ -3128,6 +3140,10 @@ export interface Locale extends ILocale { * 返信にサーバー情報を表示する */ "showTickerOnReplies": string; + /** + * 猫の話し方を無効にする + */ + "disableCatSpeak": string; /** * 検索MFMの検索エンジン */ @@ -4429,10 +4445,6 @@ export interface Locale extends ILocale { * 連合なしにする */ "disableFederationOk": string; - /** - * 猫の話し方を無効にする - */ - "disableCatSpeak": string; /** * 現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。 */ @@ -5777,7 +5789,7 @@ export interface Locale extends ILocale { */ "social": string; /** - * バッッブルタイムラインでは、管理者が選択した接続サーバーからのメモを表示できます。 + * バブルタイムラインでは、管理者が選択した接続サーバーからの投稿を表示できます。 */ "bubble": string; /** @@ -9139,7 +9151,7 @@ export interface Locale extends ILocale { */ "global": string; /** - * バッッブル + * バブル */ "bubble": string; }; @@ -10913,6 +10925,20 @@ export interface Locale extends ILocale { */ "native": string; }; + "_externalNavigationWarning": { + /** + * 外部サイトに移動します + */ + "title": string; + /** + * {host}を離れて外部サイトに移動します + */ + "description": ParameterizedString<"host">; + /** + * このデバイスで今後このドメインを信頼する + */ + "trustThisDomain": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 70acc3adf4..49cd717465 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -61,6 +61,9 @@ copyNoteId: "ノートIDをコピー" copyFileId: "ファイルIDをコピー" copyFolderId: "フォルダーIDをコピー" copyProfileUrl: "プロフィールURLをコピー" +trustedLinkUrlPatterns: "外部サイトへのリンク警告 除外リスト" +trustedLinkUrlPatternsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。ドメイン名だけ書くと後方一致になります。" +open: "開く" searchUser: "ユーザーを検索" searchThisUsersNotes: "ユーザーのノートを検索" reply: "返信" @@ -2902,3 +2905,8 @@ _contextMenu: app: "アプリケーション" appWithShift: "Shiftキーでアプリケーション" native: "ブラウザのUI" + +_externalNavigationWarning: + title: "外部サイトに移動します" + description: "{host}を離れて外部サイトに移動します" + trustThisDomain: "このデバイスで今後このドメインを信頼する" diff --git a/packages/backend/migration/1711008460816-external-website-warn.js b/packages/backend/migration/1711008460816-external-website-warn.js new file mode 100644 index 0000000000..d36639459b --- /dev/null +++ b/packages/backend/migration/1711008460816-external-website-warn.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ExternalWebsiteWarn1711008460816 { + name = 'ExternalWebsiteWarn1711008460816' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "trustedLinkUrlPatterns" character varying(3072) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "trustedLinkUrlPatterns"`); + } +} \ No newline at end of file diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index afeefc9033..fa4ddc0bd6 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -120,6 +120,7 @@ export class MetaEntityService { imageUrl: ad.imageUrl, dayOfWeek: ad.dayOfWeek, })), + trustedLinkUrlPatterns: instance.trustedLinkUrlPatterns, notesPerOneAd: instance.notesPerOneAd, enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 29e1dd032a..0e244931d9 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -674,4 +674,12 @@ export class MiMeta { nullable: true, }) public urlPreviewUserAgent: string | null; + + @Column('varchar', { + length: 3072, + array: true, + default: '{}', + comment: 'An array of URL strings or regex that can be used to omit warnings about redirects to external sites. Separate them with spaces to specify AND, and enclose them with slashes to specify regular expressions. Each item is regarded as an OR.', + }) + public trustedLinkUrlPatterns: string[]; } diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 74b6cfe883..8915436b9e 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -273,6 +273,14 @@ export const packedMetaLiteSchema = { optional: false, nullable: false, default: 'local', }, + trustedLinkUrlPatterns: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 5a69fbf679..395da384ab 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -526,6 +526,14 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + trustedLinkUrlPatterns: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, }, }, } as const; @@ -669,6 +677,7 @@ export default class extends Endpoint { // eslint- urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength, urlPreviewUserAgent: instance.urlPreviewUserAgent, urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl, + trustedLinkUrlPatterns: instance.trustedLinkUrlPatterns, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index c56dd053d3..cbde554428 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -176,6 +176,11 @@ export const paramDef = { urlPreviewRequireContentLength: { type: 'boolean' }, urlPreviewUserAgent: { type: 'string', nullable: true }, urlPreviewSummaryProxyUrl: { type: 'string', nullable: true }, + trustedLinkUrlPatterns: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, }, required: [], } as const; @@ -665,6 +670,10 @@ export default class extends Endpoint { // eslint- set.urlPreviewSummaryProxyUrl = value === '' ? null : value; } + if (Array.isArray(ps.trustedLinkUrlPatterns)) { + set.trustedLinkUrlPatterns = ps.trustedLinkUrlPatterns.filter(Boolean); + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index d2819f9f4c..b04edd1150 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only :is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target" :behavior="props.navigationBehavior" :title="url" - @click.prevent="self ? true : promptConfirm()" + @click.prevent="self ? true : warningExternalWebsite(url)" @click.stop > @@ -23,7 +23,7 @@ import { useTooltip } from '@/scripts/use-tooltip.js'; import * as os from '@/os.js'; import { isEnabledUrlPreview } from '@/instance.js'; import { MkABehavior } from '@/components/global/MkA.vue'; -import { i18n } from '@/i18n.js'; +import { warningExternalWebsite } from '@/scripts/warning-external-website.js'; const props = withDefaults(defineProps<{ url: string; @@ -49,16 +49,6 @@ if (isEnabledUrlPreview.value) { }); }); } - -async function promptConfirm() { - const { canceled } = await os.confirm({ - type: 'question', - text: i18n.tsx.confirmRemoteUrl({ x: props.url }), - plain: true, - }); - if (canceled) return; - window.open(props.url, '_blank', 'nofollow noopener popup=false'); -} diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index 6297b9a182..0a5b06a969 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -50,6 +50,12 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + @@ -105,6 +111,7 @@ const bubbleTimeline = ref(''); const tosUrl = ref(null); const privacyPolicyUrl = ref(null); const inquiryUrl = ref(null); +const trustedLinkUrlPatterns = ref(''); async function init() { const meta = await misskeyApi('admin/meta'); @@ -120,6 +127,7 @@ async function init() { bubbleTimeline.value = meta.bubbleInstances.join('\n'); bubbleTimelineEnabled.value = meta.policies.btlAvailable; inquiryUrl.value = meta.inquiryUrl; + trustedLinkUrlPatterns.value = meta.trustedLinkUrlPatterns.join('\n'); } function save() { @@ -135,6 +143,7 @@ function save() { hiddenTags: hiddenTags.value.split('\n'), preservedUsernames: preservedUsernames.value.split('\n'), bubbleInstances: bubbleTimeline.value.split('\n'), + trustedLinkUrlPatterns: trustedLinkUrlPatterns.value.split('\n'), }).then(() => { fetchInstance(true); }); diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts index 9640c988eb..c0034d414c 100644 --- a/packages/frontend/src/plugin.ts +++ b/packages/frontend/src/plugin.ts @@ -9,6 +9,7 @@ import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { Plugin, noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions, pageViewInterruptors } from '@/store.js'; +import { warningExternalWebsite } from '@/scripts/warning-external-website.js'; const parser = new Parser(); const pluginContexts = new Map(); @@ -92,16 +93,8 @@ function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Record { - (async () => { - utils.assertString(url); - const { canceled } = await os.confirm({ - type: 'question', - text: i18n.tsx.confirmRemoteUrl({ x: url.value }), - plain: true, - }); - if (canceled) return; - window.open(url.value, '_blank', 'noopener'); - })(); + utils.assertString(url); + warningExternalWebsite(url.value); }), 'Plugin:config': values.OBJ(config), }; diff --git a/packages/frontend/src/scripts/warning-external-website.ts b/packages/frontend/src/scripts/warning-external-website.ts new file mode 100644 index 0000000000..c0050112ce --- /dev/null +++ b/packages/frontend/src/scripts/warning-external-website.ts @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { instance } from '@/instance.js'; +import { defaultStore } from '@/store.js'; +import * as os from '@/os.js'; +import MkUrlWarningDialog from '@/components/MkUrlWarningDialog.vue'; + +const extractDomain = /^(https?:\/\/|\/\/)?([^@/\s]+@)?(www\.)?([^:/\s]+)/i; +const isRegExp = /^\/(.+)\/(.*)$/; + +export async function warningExternalWebsite(url: string) { + const domain = extractDomain.exec(url)?.[4]; + + if (!domain) return false; + + const isTrustedByInstance = instance.trustedLinkUrlPatterns.some(expression => { + const r = isRegExp.exec(expression); + + if (r) { + return new RegExp(r[1], r[2]).test(url); + } else if (expression.includes(' ')) return expression.split(' ').every(keyword => url.includes(keyword)); + else return domain.endsWith(expression); + }); + + const isTrustedByUser = defaultStore.reactiveState.trustedDomains.value.includes(domain); + + if (!isTrustedByInstance && !isTrustedByUser) { + const confirm = await new Promise<{ canceled: boolean }>(resolve => { + const { dispose } = os.popup(MkUrlWarningDialog, { + url, + }, { + done: result => { + resolve(result ? result : { canceled: true }); + }, + closed: () => dispose(), + }); + }); + + if (confirm.canceled) return false; + + window.open(url, '_blank', 'nofollow noopener popup=false'); + } + + return true; +} diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 036e43a4b6..ab5fbf0dd1 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -165,6 +165,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'account', default: 'public' as 'public' | 'home' | 'followers', }, + trustedDomains: { + where: 'account', + default: [] as string[], + }, menu: { where: 'deviceAccount', diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 0e83bdfcca..65cf76affd 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -5098,6 +5098,7 @@ export type components = { * @enum {string} */ noteSearchableScope: 'local' | 'global'; + trustedLinkUrlPatterns: string[]; }; MetaDetailedOnly: { features?: { @@ -5294,6 +5295,7 @@ export type operations = { urlPreviewRequireContentLength: boolean; urlPreviewUserAgent: string | null; urlPreviewSummaryProxyUrl: string | null; + trustedLinkUrlPatterns: string[]; }; }; }; @@ -9815,6 +9817,7 @@ export type operations = { urlPreviewRequireContentLength?: boolean; urlPreviewUserAgent?: string | null; urlPreviewSummaryProxyUrl?: string | null; + trustedLinkUrlPatterns?: string[] | null; }; }; }; From b0568e2baa3aa0660d78c062ab3dc5b9d0f7738d Mon Sep 17 00:00:00 2001 From: Marie Date: Fri, 4 Oct 2024 02:33:02 +0200 Subject: [PATCH 02/21] chore: remove unused locale --- locales/index.d.ts | 4 ---- locales/ja-JP.yml | 1 - 2 files changed, 5 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index a7f24eea04..a923ef72f9 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1192,10 +1192,6 @@ export interface Locale extends ILocale { * よろしいですか? */ "areYouSure": string; - /** - * 「{x}」を開きますか? - */ - "confirmRemoteUrl": ParameterizedString<"x">; /** * 保存しました */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 49cd717465..1083bfabcf 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -294,7 +294,6 @@ removeAreYouSure: "「{x}」を削除しますか?" deleteAreYouSure: "「{x}」を削除しますか?" resetAreYouSure: "リセットしますか?" areYouSure: "よろしいですか?" -confirmRemoteUrl: "「{x}」を開きますか?" saved: "保存しました" messaging: "チャット" upload: "アップロード" From 67d739cf7186bdac712e350cafa6b7a39511d8e1 Mon Sep 17 00:00:00 2001 From: Marie Date: Fri, 4 Oct 2024 02:34:01 +0200 Subject: [PATCH 03/21] upd: add script and check to MkUrl --- packages/frontend/src/components/global/MkUrl.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue index 15595ba515..d3bddb017f 100644 --- a/packages/frontend/src/components/global/MkUrl.vue +++ b/packages/frontend/src/components/global/MkUrl.vue @@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only :is="self ? 'MkA' : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel ?? 'nofollow noopener'" :target="target" :behavior="props.navigationBehavior" @contextmenu.stop="() => {}" + @click.prevent="self ? true : warningExternalWebsite(url)" @click.stop > - {{ i18n.ts.deleteAllFilesConfirm }} - {{ i18n.ts.severAllFollowRelations }} - {{ i18n.ts._delivery.stop }} +
+ {{ i18n.ts.deleteAllFiles }} + {{ i18n.ts.severAllFollowRelations }} + {{ i18n.ts._delivery.stop }} +
{{ i18n.ts._delivery.resume }} {{ i18n.ts.blockThisInstance }} {{ i18n.ts.silenceThisInstance }} @@ -277,7 +279,8 @@ async function deleteAllFiles(): void { type: 'danger', text: i18n.ts.deleteAllFilesConfirm, }); - if (!confirm) return; + if (confirm.canceled) return; + if (!instance.value) throw new Error('No instance?'); await misskeyApi('admin/federation/delete-all-files', { host: instance.value.host, @@ -292,9 +295,10 @@ async function severAllFollowRelations(): void { const confirm = await os.confirm({ type: 'danger', - text: i18n.ts.severAllFollowRelationsConfirm, + text: `${i18n.ts.severAllFollowRelationsConfirm} This will break ${instance.value.followingCount} following and ${instance.value.followersCount} follower relations on ${meta.value.name}.`, }); - if (!confirm) return; + if (confirm.canceled) return; + await misskeyApi('admin/federation/remove-all-following', { host: instance.value.host, }); From 996d0794764332b511419e06e8c75df1cc93907b Mon Sep 17 00:00:00 2001 From: PrivateGER Date: Sat, 5 Oct 2024 19:58:58 +0200 Subject: [PATCH 08/21] Make remove-all-following remove follows in both directions --- .../admin/federation/remove-all-following.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index 7f84e7694d..601c898f52 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -37,9 +37,14 @@ export default class extends Endpoint { // eslint- private queueService: QueueService, ) { super(meta, paramDef, async (ps, me) => { - const followings = await this.followingsRepository.findBy({ - followerHost: ps.host, - }); + const followings = await this.followingsRepository.findBy([ + { + followeeHost: ps.host, + }, + { + followerHost: ps.host, + }, + ]); const pairs = await Promise.all(followings.map(f => Promise.all([ this.usersRepository.findOneByOrFail({ id: f.followerId }), From 5cd44433a0c4b18f24172c31a528e12c767c4fc9 Mon Sep 17 00:00:00 2001 From: Evan Paterakis Date: Sat, 5 Oct 2024 21:10:33 +0300 Subject: [PATCH 09/21] Set visitor dashboard's menu button z-index to 50 --- packages/frontend/src/components/MkVisitorDashboard.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index b154f7a5b3..ff2e27aaf8 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -142,6 +142,7 @@ function showMenu(ev: MouseEvent) { height: 32px; border-radius: var(--radius-sm); font-size: 18px; + z-index: 50; } .mainFg { From b23d650a153ad2e150350fe2c808896ae8a2e3f9 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Sat, 5 Oct 2024 20:42:31 -0400 Subject: [PATCH 10/21] warn when a domain is hard-blocked by a base domain --- locales/en-US.yml | 2 ++ locales/index.d.ts | 20 +++++++++++++------ locales/ja-JP.yml | 2 ++ packages/frontend/src/pages/instance-info.vue | 20 +++++++++++++++++-- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index 221ade4028..4321c9b5fe 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -263,6 +263,8 @@ noCustomEmojis: "There are no emoji" noJobs: "There are no jobs" federating: "Federating" blocked: "Blocked" +blockedByBase: "This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s)." +silencedByBase: "This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s)." suspended: "Suspended" all: "All" subscribing: "Subscribing" diff --git a/locales/index.d.ts b/locales/index.d.ts index f93ef14325..530c5008e3 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1068,6 +1068,14 @@ export interface Locale extends ILocale { * ブロック中 */ "blocked": string; + /** + * This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s). + */ + "blockedByBase": string; + /** + * This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s). + */ + "silencedByBase": string; /** * 配信停止 */ @@ -3128,6 +3136,10 @@ export interface Locale extends ILocale { * 返信にサーバー情報を表示する */ "showTickerOnReplies": string; + /** + * 猫の話し方を無効にする + */ + "disableCatSpeak": string; /** * 検索MFMの検索エンジン */ @@ -4429,10 +4441,6 @@ export interface Locale extends ILocale { * 連合なしにする */ "disableFederationOk": string; - /** - * 猫の話し方を無効にする - */ - "disableCatSpeak": string; /** * 現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。 */ @@ -5777,7 +5785,7 @@ export interface Locale extends ILocale { */ "social": string; /** - * バッッブルタイムラインでは、管理者が選択した接続サーバーからのメモを表示できます。 + * バブルタイムラインでは、管理者が選択した接続サーバーからの投稿を表示できます。 */ "bubble": string; /** @@ -9139,7 +9147,7 @@ export interface Locale extends ILocale { */ "global": string; /** - * バッッブル + * バブル */ "bubble": string; }; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 70acc3adf4..ab70767925 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -263,6 +263,8 @@ noCustomEmojis: "絵文字はありません" noJobs: "ジョブはありません" federating: "連合中" blocked: "ブロック中" +blockedByBase: "This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s)." +silencedByBase: "This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s)." suspended: "配信停止" all: "全て" subscribing: "購読中" diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 4ff26197d8..6848a0715a 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -45,8 +45,10 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._delivery.stop }} {{ i18n.ts._delivery.resume }} - {{ i18n.ts.blockThisInstance }} - {{ i18n.ts.silenceThisInstance }} + {{ i18n.ts.blockedByBase }} + {{ i18n.ts.blockThisInstance }} + {{ i18n.ts.silenedByBase }} + {{ i18n.ts.silenceThisInstance }} Mark as NSFW {{ i18n.ts.mediaSilenceThisInstance }} Refresh metadata @@ -174,6 +176,20 @@ const isMediaSilenced = ref(false); const faviconUrl = ref(null); const moderationNote = ref(''); +const baseDomains = computed(() => { + const domains: string[] = []; + + const parts = props.host.toLowerCase().split('.'); + for (let s = 1; s < parts.length; s++) { + const domain = parts.slice(s).join('.'); + domains.push(domain); + } + + return domains; +}); +const isBaseBlocked = computed(() => meta.value && baseDomains.value.some(d => meta.value?.blockedHosts.includes(d))); +const isBaseSilenced = computed(() => meta.value && meta.value.silencedHosts && baseDomains.value.some(d => meta.value?.silencedHosts?.includes(d))); + const usersPagination = { endpoint: iAmModerator ? 'admin/show-users' : 'users' as const, limit: 10, From 4da0d4be7171f11e3a30ade275b764b8335e40e0 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Sat, 5 Oct 2024 22:01:55 -0400 Subject: [PATCH 11/21] add option to reject reports from an instance --- locales/en-US.yml | 3 +++ locales/index.d.ts | 16 ++++++++++++++++ locales/ja-JP.yml | 3 +++ .../1728177700920-add-reject-reports.js | 16 ++++++++++++++++ .../src/core/activitypub/ApInboxService.ts | 9 +++++++++ packages/backend/src/models/Instance.ts | 7 ++++++- .../admin/federation/update-instance.ts | 10 ++++++++++ packages/backend/src/types.ts | 10 ++++++++++ .../frontend/src/pages/admin/modlog.ModLog.vue | 4 ++++ packages/frontend/src/pages/instance-info.vue | 11 +++++++++++ 10 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 packages/backend/migration/1728177700920-add-reject-reports.js diff --git a/locales/en-US.yml b/locales/en-US.yml index 221ade4028..b4d08b73ee 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -223,6 +223,7 @@ stopActivityDelivery: "Stop sending activities" blockThisInstance: "Block this instance" silenceThisInstance: "Silence this instance" mediaSilenceThisInstance: "Silence media from this instance" +rejectReports: "Reject reports from this instance" operations: "Operations" software: "Software" version: "Version" @@ -2572,6 +2573,8 @@ _moderationLogTypes: resetPassword: "Password reset" suspendRemoteInstance: "Remote instance suspended" unsuspendRemoteInstance: "Remote instance unsuspended" + rejectRemoteInstanceReports: "Rejected reports from remote instance" + acceptRemoteInstanceReports: "Accepted reports from remote instance" updateRemoteInstanceNote: "Moderation note updated for remote instance." markSensitiveDriveFile: "File marked as sensitive" unmarkSensitiveDriveFile: "File unmarked as sensitive" diff --git a/locales/index.d.ts b/locales/index.d.ts index f93ef14325..c796174ac8 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -908,6 +908,10 @@ export interface Locale extends ILocale { * サーバーをメディアサイレンス */ "mediaSilenceThisInstance": string; + /** + * Reject reports from this instance + */ + "rejectReports": string; /** * 操作 */ @@ -3128,6 +3132,10 @@ export interface Locale extends ILocale { * 返信にサーバー情報を表示する */ "showTickerOnReplies": string; + /** + * 猫の話し方を無効にする + */ + "disableCatSpeak": string; /** * 検索MFMの検索エンジン */ @@ -9972,6 +9980,14 @@ export interface Locale extends ILocale { * リモートサーバーを再開 */ "unsuspendRemoteInstance": string; + /** + * Rejected reports from remote instance + */ + "rejectRemoteInstanceReports": string; + /** + * Accepted reports from remote instance + */ + "acceptRemoteInstanceReports": string; /** * リモートサーバーのモデレーションノート更新 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 70acc3adf4..36e74e8167 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -223,6 +223,7 @@ stopActivityDelivery: "アクティビティの配送を停止" blockThisInstance: "このサーバーをブロック" silenceThisInstance: "サーバーをサイレンス" mediaSilenceThisInstance: "サーバーをメディアサイレンス" +rejectReports: "Reject reports from this instance" operations: "操作" software: "ソフトウェア" version: "バージョン" @@ -2640,6 +2641,8 @@ _moderationLogTypes: resetPassword: "パスワードをリセット" suspendRemoteInstance: "リモートサーバーを停止" unsuspendRemoteInstance: "リモートサーバーを再開" + rejectRemoteInstanceReports: "Rejected reports from remote instance" + acceptRemoteInstanceReports: "Accepted reports from remote instance" updateRemoteInstanceNote: "リモートサーバーのモデレーションノート更新" markSensitiveDriveFile: "ファイルをセンシティブ付与" unmarkSensitiveDriveFile: "ファイルをセンシティブ解除" diff --git a/packages/backend/migration/1728177700920-add-reject-reports.js b/packages/backend/migration/1728177700920-add-reject-reports.js new file mode 100644 index 0000000000..ed5f6bc559 --- /dev/null +++ b/packages/backend/migration/1728177700920-add-reject-reports.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AddRejectReports1728177700920 { + name = 'AddRejectReports1728177700920' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" ADD "rejectReports" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "rejectReports"`); + } +} diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 6a28cbad15..66947714b9 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -39,6 +39,8 @@ import { ApPersonService } from './models/ApPersonService.js'; import { ApQuestionService } from './models/ApQuestionService.js'; import type { Resolver } from './ApResolverService.js'; import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js'; +import * as Bull from 'bullmq'; +import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; @Injectable() export class ApInboxService { @@ -83,6 +85,7 @@ export class ApInboxService { private apQuestionService: ApQuestionService, private queueService: QueueService, private globalEventService: GlobalEventService, + private federatedInstanceService: FederatedInstanceService, ) { this.logger = this.apLoggerService.logger; } @@ -530,6 +533,12 @@ export class ApInboxService { @bindThis private async flag(actor: MiRemoteUser, activity: IFlag): Promise { + // Make sure the source instance is allowed to send reports. + const instance = await this.federatedInstanceService.fetch(actor.host); + if (instance.rejectReports) { + throw new Bull.UnrecoverableError(`Rejecting report from instance: ${actor.host}`); + } + // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する const uris = getApIds(activity.object); diff --git a/packages/backend/src/models/Instance.ts b/packages/backend/src/models/Instance.ts index dd625f95d3..ba93190c57 100644 --- a/packages/backend/src/models/Instance.ts +++ b/packages/backend/src/models/Instance.ts @@ -158,7 +158,12 @@ export class MiInstance { default: false, }) public isNSFW: boolean; - + + @Column('boolean', { + default: false, + }) + public rejectReports: boolean; + @Column('varchar', { length: 16384, default: '', }) diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 8b142910a6..0d62417299 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -25,6 +25,7 @@ export const paramDef = { host: { type: 'string' }, isSuspended: { type: 'boolean' }, isNSFW: { type: 'boolean' }, + rejectReports: { type: 'boolean' }, moderationNote: { type: 'string' }, }, required: ['host'], @@ -57,6 +58,7 @@ export default class extends Endpoint { // eslint- await this.federatedInstanceService.update(instance.id, { suspensionState, isNSFW: ps.isNSFW, + rejectReports: ps.rejectReports, moderationNote: ps.moderationNote, }); @@ -74,6 +76,14 @@ export default class extends Endpoint { // eslint- } } + if (ps.rejectReports != null && instance.rejectReports !== ps.rejectReports) { + const message = ps.rejectReports ? 'rejectRemoteInstanceReports' : 'acceptRemoteInstanceReports'; + this.moderationLogService.log(me, message, { + id: instance.id, + host: instance.host, + }); + } + if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) { this.moderationLogService.log(me, 'updateRemoteInstanceNote', { id: instance.id, diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index d83d414096..b3c3275b24 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -79,6 +79,8 @@ export const moderationLogTypes = [ 'resetPassword', 'suspendRemoteInstance', 'unsuspendRemoteInstance', + 'rejectRemoteInstanceReports', + 'acceptRemoteInstanceReports', 'updateRemoteInstanceNote', 'markSensitiveDriveFile', 'unmarkSensitiveDriveFile', @@ -235,6 +237,14 @@ export type ModerationLogPayloads = { id: string; host: string; }; + rejectRemoteInstanceReports: { + id: string; + host: string; + }; + acceptRemoteInstanceReports: { + id: string; + host: string; + }; updateRemoteInstanceNote: { id: string; host: string; diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index f6f276de53..63e04e5bdd 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -23,6 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only 'markSensitiveDriveFile', 'resetPassword', 'suspendRemoteInstance', + 'rejectRemoteInstanceReports', + 'acceptRemoteInstanceReports', ].includes(log.type), [$style.logRed]: [ 'suspend', @@ -61,6 +63,8 @@ SPDX-License-Identifier: AGPL-3.0-only : @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }} : {{ log.info.host }} : {{ log.info.host }} + : {{ log.info.host }} + : {{ log.info.host }} : {{ log.info.announcement.title }} : {{ log.info.before.title }} : {{ log.info.announcement.title }} diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 4ff26197d8..f5cc37a490 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -48,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.blockThisInstance }} {{ i18n.ts.silenceThisInstance }} Mark as NSFW + {{ i18n.ts.rejectReports }} {{ i18n.ts.mediaSilenceThisInstance }} Refresh metadata @@ -170,6 +171,7 @@ const suspensionState = ref<'none' | 'manuallySuspended' | 'goneSuspended' | 'au const isBlocked = ref(false); const isSilenced = ref(false); const isNSFW = ref(false); +const rejectReports = ref(false); const isMediaSilenced = ref(false); const faviconUrl = ref(null); const moderationNote = ref(''); @@ -200,6 +202,7 @@ async function fetch(): Promise { isBlocked.value = instance.value?.isBlocked ?? false; isSilenced.value = instance.value?.isSilenced ?? false; isNSFW.value = instance.value?.isNSFW ?? false; + rejectReports.value = instance.value?.rejectReports ?? false; isMediaSilenced.value = instance.value?.isMediaSilenced ?? false; faviconUrl.value = getProxiedImageUrlNullable(instance.value?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.value?.iconUrl, 'preview'); moderationNote.value = instance.value?.moderationNote ?? ''; @@ -260,6 +263,14 @@ async function toggleNSFW(): Promise { }); } +async function toggleRejectReports(): Promise { + if (!instance.value) throw new Error('No instance?'); + await misskeyApi('admin/federation/update-instance', { + host: instance.value.host, + rejectReports: rejectReports.value, + }); +} + function refreshMetadata(): void { if (!instance.value) throw new Error('No instance?'); misskeyApi('admin/federation/refresh-remote-instance-metadata', { From 61124699da77991af70720838d4b8dec95c09e94 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Sat, 5 Oct 2024 22:21:00 -0400 Subject: [PATCH 12/21] fix lint errors --- packages/backend/src/core/activitypub/ApInboxService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 66947714b9..bce67a458f 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; +import * as Bull from 'bullmq'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; @@ -29,6 +30,7 @@ import { bindThis } from '@/decorators.js'; import type { MiRemoteUser } from '@/models/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AbuseReportService } from '@/core/AbuseReportService.js'; +import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { ApNoteService } from './models/ApNoteService.js'; import { ApLoggerService } from './ApLoggerService.js'; @@ -39,8 +41,6 @@ import { ApPersonService } from './models/ApPersonService.js'; import { ApQuestionService } from './models/ApQuestionService.js'; import type { Resolver } from './ApResolverService.js'; import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js'; -import * as Bull from 'bullmq'; -import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; @Injectable() export class ApInboxService { From 7dd296017b835771f9c8e9a39da1e8348938f7f2 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Sat, 5 Oct 2024 22:43:34 -0400 Subject: [PATCH 13/21] fix modlog and translations for "Mark as NSFW" --- locales/en-US.yml | 3 +++ locales/index.d.ts | 12 ++++++++++++ locales/ja-JP.yml | 3 +++ .../endpoints/admin/federation/update-instance.ts | 8 ++++++++ packages/backend/src/types.ts | 10 ++++++++++ packages/frontend/src/pages/admin/modlog.ModLog.vue | 4 ++++ packages/frontend/src/pages/instance-info.vue | 2 +- 7 files changed, 41 insertions(+), 1 deletion(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index b4d08b73ee..dfd236b9c5 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -149,6 +149,7 @@ renoteUnmute: "Unmute Boosts" block: "Block" unblock: "Unblock" markAsNSFW: "Mark all media from user as NSFW" +markInstanceAsNSFW: "Mark as NSFW" suspend: "Suspend" unsuspend: "Unsuspend" blockConfirm: "Are you sure that you want to block this account?" @@ -2573,6 +2574,8 @@ _moderationLogTypes: resetPassword: "Password reset" suspendRemoteInstance: "Remote instance suspended" unsuspendRemoteInstance: "Remote instance unsuspended" + setRemoteInstanceNSFW: "Set remote instance as NSFW" + unsetRemoteInstanceNSFW: "Set remote instance as NSFW" rejectRemoteInstanceReports: "Rejected reports from remote instance" acceptRemoteInstanceReports: "Accepted reports from remote instance" updateRemoteInstanceNote: "Moderation note updated for remote instance." diff --git a/locales/index.d.ts b/locales/index.d.ts index c796174ac8..fb8d9339fc 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -612,6 +612,10 @@ export interface Locale extends ILocale { * ユーザーのすべてのメディアをNSFWとしてマークする */ "markAsNSFW": string; + /** + * Mark as NSFW + */ + "markInstanceAsNSFW": string; /** * 凍結 */ @@ -9980,6 +9984,14 @@ export interface Locale extends ILocale { * リモートサーバーを再開 */ "unsuspendRemoteInstance": string; + /** + * Set remote instance as NSFW + */ + "setRemoteInstanceNSFW": string; + /** + * Set remote instance as NSFW + */ + "unsetRemoteInstanceNSFW": string; /** * Rejected reports from remote instance */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 36e74e8167..d86b7a73d1 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -149,6 +149,7 @@ renoteUnmute: "ブーストのミュートを解除" block: "ブロック" unblock: "ブロック解除" markAsNSFW: "ユーザーのすべてのメディアをNSFWとしてマークする" +markInstanceAsNSFW: "Mark as NSFW" suspend: "凍結" unsuspend: "解凍" blockConfirm: "ブロックしますか?" @@ -2641,6 +2642,8 @@ _moderationLogTypes: resetPassword: "パスワードをリセット" suspendRemoteInstance: "リモートサーバーを停止" unsuspendRemoteInstance: "リモートサーバーを再開" + setRemoteInstanceNSFW: "Set remote instance as NSFW" + unsetRemoteInstanceNSFW: "Set remote instance as NSFW" rejectRemoteInstanceReports: "Rejected reports from remote instance" acceptRemoteInstanceReports: "Accepted reports from remote instance" updateRemoteInstanceNote: "リモートサーバーのモデレーションノート更新" diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 0d62417299..daf19c4435 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -76,6 +76,14 @@ export default class extends Endpoint { // eslint- } } + if (ps.isNSFW != null && instance.isNSFW !== ps.isNSFW) { + const message = ps.rejectReports ? 'setRemoteInstanceNSFW' : 'unsetRemoteInstanceNSFW'; + this.moderationLogService.log(me, message, { + id: instance.id, + host: instance.host, + }); + } + if (ps.rejectReports != null && instance.rejectReports !== ps.rejectReports) { const message = ps.rejectReports ? 'rejectRemoteInstanceReports' : 'acceptRemoteInstanceReports'; this.moderationLogService.log(me, message, { diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index b3c3275b24..d64d72c07f 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -77,6 +77,8 @@ export const moderationLogTypes = [ 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'resetPassword', + 'setRemoteInstanceNSFW', + 'unsetRemoteInstanceNSFW', 'suspendRemoteInstance', 'unsuspendRemoteInstance', 'rejectRemoteInstanceReports', @@ -229,6 +231,14 @@ export type ModerationLogPayloads = { userUsername: string; userHost: string | null; }; + setRemoteInstanceNSFW: { + id: string; + host: string; + }; + unsetRemoteInstanceNSFW: { + id: string; + host: string; + }; suspendRemoteInstance: { id: string; host: string; diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index 63e04e5bdd..9fe804b2bd 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -23,6 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only 'markSensitiveDriveFile', 'resetPassword', 'suspendRemoteInstance', + 'setRemoteInstanceNSFW', + 'unsetRemoteInstanceNSFW', 'rejectRemoteInstanceReports', 'acceptRemoteInstanceReports', ].includes(log.type), @@ -63,6 +65,8 @@ SPDX-License-Identifier: AGPL-3.0-only : @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }} : {{ log.info.host }} : {{ log.info.host }} + : {{ log.info.host }} + : {{ log.info.host }} : {{ log.info.host }} : {{ log.info.host }} : {{ log.info.announcement.title }} diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index f5cc37a490..b5803ab3f1 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._delivery.resume }} {{ i18n.ts.blockThisInstance }} {{ i18n.ts.silenceThisInstance }} - Mark as NSFW + {{ i18n.ts.markInstanceAsNSFW }} {{ i18n.ts.rejectReports }} {{ i18n.ts.mediaSilenceThisInstance }} Refresh metadata From 2bc9ce93b8760b128cd86e4c69ce727fc1f15524 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Sun, 6 Oct 2024 10:47:55 -0400 Subject: [PATCH 14/21] add missing import --- packages/frontend/src/pages/instance-info.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 6848a0715a..94c0f4c3a5 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -158,6 +158,7 @@ import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; import { dateString } from '@/filters/date.js'; import MkTextarea from '@/components/MkTextarea.vue'; +import MkInfo from '@/components/MkInfo.vue'; const props = defineProps<{ host: string; From 008ac896723a3d1f8ad281c3c61ca954cfff4a92 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Sun, 6 Oct 2024 10:58:06 -0400 Subject: [PATCH 15/21] fix translation string --- packages/frontend/src/pages/instance-info.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 94c0f4c3a5..1a90abba12 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._delivery.resume }} {{ i18n.ts.blockedByBase }} {{ i18n.ts.blockThisInstance }} - {{ i18n.ts.silenedByBase }} + {{ i18n.ts.silencedByBase }} {{ i18n.ts.silenceThisInstance }} Mark as NSFW {{ i18n.ts.mediaSilenceThisInstance }} From 605c22b55e9cead59e780de58f3fb5673c961f29 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Sun, 6 Oct 2024 10:58:27 -0400 Subject: [PATCH 16/21] add lockout for isMediaSilenced --- packages/frontend/src/pages/instance-info.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 1a90abba12..929c22d114 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -50,7 +50,8 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.silencedByBase }} {{ i18n.ts.silenceThisInstance }} Mark as NSFW - {{ i18n.ts.mediaSilenceThisInstance }} + {{ i18n.ts.mediaSilencedByBase }} + {{ i18n.ts.mediaSilenceThisInstance }} Refresh metadata @@ -190,6 +191,7 @@ const baseDomains = computed(() => { }); const isBaseBlocked = computed(() => meta.value && baseDomains.value.some(d => meta.value?.blockedHosts.includes(d))); const isBaseSilenced = computed(() => meta.value && meta.value.silencedHosts && baseDomains.value.some(d => meta.value?.silencedHosts?.includes(d))); +const isBaseMediaSilenced = computed(() => meta.value && meta.value.mediaSilencedHosts && baseDomains.value.some(d => meta.value?.mediaSilencedHosts.includes(d))); const usersPagination = { endpoint: iAmModerator ? 'admin/show-users' : 'users' as const, From eec4a5082d3adf0164c4d320ac444c35bc43e381 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Sun, 6 Oct 2024 11:03:28 -0400 Subject: [PATCH 17/21] fix OpenAPI definition for admin/meta.silencedHosts --- packages/backend/src/server/api/endpoints/admin/meta.ts | 2 +- packages/frontend/src/pages/instance-info.vue | 4 ++-- packages/misskey-js/src/autogen/types.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 5a69fbf679..dbfa31024a 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -128,7 +128,7 @@ export const meta = { }, silencedHosts: { type: 'array', - optional: true, + optional: false, nullable: false, items: { type: 'string', diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 929c22d114..821cd599eb 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -190,8 +190,8 @@ const baseDomains = computed(() => { return domains; }); const isBaseBlocked = computed(() => meta.value && baseDomains.value.some(d => meta.value?.blockedHosts.includes(d))); -const isBaseSilenced = computed(() => meta.value && meta.value.silencedHosts && baseDomains.value.some(d => meta.value?.silencedHosts?.includes(d))); -const isBaseMediaSilenced = computed(() => meta.value && meta.value.mediaSilencedHosts && baseDomains.value.some(d => meta.value?.mediaSilencedHosts.includes(d))); +const isBaseSilenced = computed(() => meta.value && baseDomains.value.some(d => meta.value?.silencedHosts.includes(d))); +const isBaseMediaSilenced = computed(() => meta.value && baseDomains.value.some(d => meta.value?.mediaSilencedHosts.includes(d))); const usersPagination = { endpoint: iAmModerator ? 'admin/show-users' : 'users' as const, diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 0e83bdfcca..e3bf828982 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -5199,7 +5199,7 @@ export type operations = { enableEmail: boolean; enableServiceWorker: boolean; translatorAvailable: boolean; - silencedHosts?: string[]; + silencedHosts: string[]; mediaSilencedHosts: string[]; pinnedUsers: string[]; hiddenTags: string[]; From b9e89edbc7cde6a62360fd15eaadf4f7309e2ba6 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Sun, 6 Oct 2024 11:16:18 -0400 Subject: [PATCH 18/21] add missing translation --- locales/en-US.yml | 1 + locales/index.d.ts | 4 ++++ locales/ja-JP.yml | 1 + 3 files changed, 6 insertions(+) diff --git a/locales/en-US.yml b/locales/en-US.yml index 4b949b705a..7e24a62022 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -267,6 +267,7 @@ federating: "Federating" blocked: "Blocked" blockedByBase: "This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s)." silencedByBase: "This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s)." +mediaSilencedByBase: "This host's media is silenced implicitly because a base domain's media is silenced. To un-silence this host, first un-silence the base domain(s)." suspended: "Suspended" all: "All" subscribing: "Subscribing" diff --git a/locales/index.d.ts b/locales/index.d.ts index c1001125a0..9a02dbc1e2 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1084,6 +1084,10 @@ export interface Locale extends ILocale { * This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s). */ "silencedByBase": string; + /** + * This host's media is silenced implicitly because a base domain's media is silenced. To un-silence this host, first un-silence the base domain(s). + */ + "mediaSilencedByBase": string; /** * 配信停止 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d6a8faf1e7..c422172eb9 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -267,6 +267,7 @@ federating: "連合中" blocked: "ブロック中" blockedByBase: "This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s)." silencedByBase: "This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s)." +mediaSilencedByBase: "This host's media is silenced implicitly because a base domain's media is silenced. To un-silence this host, first un-silence the base domain(s)." suspended: "配信停止" all: "全て" subscribing: "購読中" From 939f07fa5514bff9df9ccbb6bff201668398e84f Mon Sep 17 00:00:00 2001 From: Marie Date: Sun, 6 Oct 2024 19:08:32 +0200 Subject: [PATCH 19/21] upd: fix returns --- packages/frontend/src/scripts/warning-external-website.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/scripts/warning-external-website.ts b/packages/frontend/src/scripts/warning-external-website.ts index 277656c651..5ef003cb01 100644 --- a/packages/frontend/src/scripts/warning-external-website.ts +++ b/packages/frontend/src/scripts/warning-external-website.ts @@ -43,9 +43,9 @@ export async function warningExternalWebsite(url: string) { }); if (confirm.canceled) return false; - - window.open(url, '_blank', 'nofollow noopener popup=false'); + + return window.open(url, '_blank', 'nofollow noopener popup=false'); } - return true; + return window.open(url, '_blank', 'nofollow noopener popup=false'); } From 941a200f35d8b6ce6e8e9ea1c8207311b99f0f83 Mon Sep 17 00:00:00 2001 From: PrivateGER Date: Sun, 6 Oct 2024 19:53:10 +0200 Subject: [PATCH 20/21] Move button into button group --- packages/frontend/src/pages/instance-info.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 6f3fc34dd1..54ccd8a66b 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -47,8 +47,8 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.deleteAllFiles }} {{ i18n.ts.severAllFollowRelations }} {{ i18n.ts._delivery.stop }} + {{ i18n.ts._delivery.resume }} - {{ i18n.ts._delivery.resume }} {{ i18n.ts.blockThisInstance }} {{ i18n.ts.silenceThisInstance }} Mark as NSFW From f0287954839992c0b02f0db072fd9e93ae7695ac Mon Sep 17 00:00:00 2001 From: PrivateGER Date: Sun, 6 Oct 2024 20:31:41 +0200 Subject: [PATCH 21/21] Move text into translation files --- locales/en-US.yml | 5 ++++- locales/index.d.ts | 12 ++++++++++-- locales/ja-JP.yml | 4 +++- packages/frontend/src/pages/instance-info.vue | 10 +++++++--- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index eebda35a5e..9e111fa680 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -621,6 +621,7 @@ unsetUserBanner: "Unset banner" unsetUserBannerConfirm: "Are you sure you want to unset the banner?" deleteAllFiles: "Delete all files" deleteAllFilesConfirm: "Are you sure that you want to delete all files?" +deleteAllFilesQueued: "Deletion of all files queued" removeAllFollowing: "Unfollow all followed users" removeAllFollowingDescription: "Executing this unfollows all accounts from {host}. Please run this if the instance e.g. no longer exists." userSuspended: "This user has been suspended." @@ -1331,7 +1332,9 @@ sensitiveMediaRevealConfirm: "This media might be sensitive. Are you sure you wa createdLists: "Created lists" createdAntennas: "Created antennas" severAllFollowRelations: "Break following relationships" -severAllFollowRelationsConfirm: "Really break all follow relationships? This is irreversible!" +severAllFollowRelationsConfirm: "Really break all follow relationships? This is irreversible! This will break {followingCount} following and {followersCount} follower relations on {instanceName}!" +severAllFollowRelationsQueued: "Severing all follow relations with {host} queued." + _delivery: status: "Delivery status" stop: "Suspend delivery" diff --git a/locales/index.d.ts b/locales/index.d.ts index 517a7bf87d..b3d56d31b3 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2500,6 +2500,10 @@ export interface Locale extends ILocale { * すべてのファイルを削除しますか? */ "deleteAllFilesConfirm": string; + /** + * キューに入れられたすべてのファイルの削除 + */ + "deleteAllFilesQueued": string; /** * フォローを全解除 */ @@ -5342,9 +5346,13 @@ export interface Locale extends ILocale { */ "severAllFollowRelations": string; /** - * 本当にすべての関係を断ち切りたいのですか?これは不可逆的だ。 + * すべての人間関係を壊す?これは不可逆です!これは{instanceName}の{followingCount}フォローと{followersCount}フォロワーの関係を壊す! */ - "severAllFollowRelationsConfirm": string; + "severAllFollowRelationsConfirm": ParameterizedString<"instanceName" | "followingCount" | "followersCount">; + /** + * キューに入れられた{host}とのすべてのフォロー関係を切断する。 + */ + "severAllFollowRelationsQueued": ParameterizedString<"host">; "_delivery": { /** * 配信状態 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5aec622638..b37641cc4f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -621,6 +621,7 @@ unsetUserBanner: "バナーを解除" unsetUserBannerConfirm: "バナーを解除しますか?" deleteAllFiles: "すべてのファイルを削除" deleteAllFilesConfirm: "すべてのファイルを削除しますか?" +deleteAllFilesQueued: "キューに入れられたすべてのファイルの削除" removeAllFollowing: "フォローを全解除" removeAllFollowingDescription: "{host}からのフォローをすべて解除します。そのサーバーがもう存在しなくなった場合などに実行してください。" userSuspended: "このユーザーは凍結されています。" @@ -1331,7 +1332,8 @@ sensitiveMediaRevealConfirm: "センシティブなメディアです。表示 createdLists: "作成したリスト" createdAntennas: "作成したアンテナ" severAllFollowRelations: "以下の関係をすべて断ち切る" -severAllFollowRelationsConfirm: "本当にすべての関係を断ち切りたいのですか?これは不可逆的だ。" +severAllFollowRelationsConfirm: "すべての人間関係を壊す?これは不可逆です!これは{instanceName}の{followingCount}フォローと{followersCount}フォロワーの関係を壊す!" +severAllFollowRelationsQueued: "キューに入れられた{host}とのすべてのフォロー関係を切断する。" _delivery: status: "配信状態" diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 54ccd8a66b..28ad2da0a7 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -286,7 +286,7 @@ async function deleteAllFiles(): void { host: instance.value.host, }); await os.alert({ - text: 'Deletion of all files queued', + text: i18n.ts.deleteAllFilesQueued, }); } @@ -295,7 +295,11 @@ async function severAllFollowRelations(): void { const confirm = await os.confirm({ type: 'danger', - text: `${i18n.ts.severAllFollowRelationsConfirm} This will break ${instance.value.followingCount} following and ${instance.value.followersCount} follower relations on ${meta.value.name}.`, + text: i18n.tsx.severAllFollowRelationsConfirm({ + instanceName: meta.value.shortName ?? meta.value.name, + followingCount: instance.value.followingCount, + followersCount: instance.value.followersCount, + }), }); if (confirm.canceled) return; @@ -303,7 +307,7 @@ async function severAllFollowRelations(): void { host: instance.value.host, }); await os.alert({ - text: 'Severing all follow relations queued', + text: i18n.tsx.severAllFollowRelationsQueued({ host: instance.value.host }), }); }