diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d943e66a55..f3edae44ec59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Unreleased ### General +- 未知のリモートユーザーによる通知が発生するノートをブロックする機能の改善 (#206) ### Client diff --git a/packages/backend/migration/1719663422807-NirilaBlockMentionsFromUnfamiliarRemoteUsers.js b/packages/backend/migration/1719663422807-NirilaBlockMentionsFromUnfamiliarRemoteUsers.js new file mode 100644 index 000000000000..b89a50b69add --- /dev/null +++ b/packages/backend/migration/1719663422807-NirilaBlockMentionsFromUnfamiliarRemoteUsers.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: anatawa12 + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class NirilaBlockMentionsFromUnfamiliarRemoteUsers1719663422807 { + name = 'NirilaBlockMentionsFromUnfamiliarRemoteUsers1719663422807' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "nirilaBlockMentionsFromUnfamiliarRemoteUsers" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "nirilaBlockMentionsFromUnfamiliarRemoteUsers"`); + } +} diff --git a/packages/backend/migration/1719664479666-NirilaAllowedUnfamiliarRemoteUserIds.js b/packages/backend/migration/1719664479666-NirilaAllowedUnfamiliarRemoteUserIds.js new file mode 100644 index 000000000000..18f9c2e9fa2f --- /dev/null +++ b/packages/backend/migration/1719664479666-NirilaAllowedUnfamiliarRemoteUserIds.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: anatawa12 + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class NirilaAllowedUnfamiliarRemoteUserIds1719664479666 { + name = 'NirilaAllowedUnfamiliarRemoteUserIds1719664479666' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "nirilaAllowedUnfamiliarRemoteUserIds" character varying(32) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "nirilaAllowedUnfamiliarRemoteUserIds"`); + } +} diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index b2b35f3af97f..70f4f7392bf9 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -103,7 +103,6 @@ type Source = { maxWebImageSize?: number; withRepliesInHomeTL?: boolean; withRepliesInUserList?: boolean; - blockMentionsFromUnfamiliarRemoteUsers?: boolean; } }; @@ -191,7 +190,6 @@ export type Config = { maxWebImageSize?: number; withRepliesInHomeTL?: boolean, withRepliesInUserList: boolean, - blockMentionsFromUnfamiliarRemoteUsers: boolean; } }; @@ -237,7 +235,6 @@ export function loadConfig(): Config { // to avoid merge conflict in the future, this is at top nirila: Object.assign({ withRepliesInUserList: true, - blockMentionsFromUnfamiliarRemoteUsers: false, }, config.nirila ?? {}), version, publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl, diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index c497567e5883..21e965b41478 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -377,7 +377,23 @@ export class NoteCreateService implements OnApplicationShutdown { || (data.visibility === 'specified' && data.visibleUsers?.some(u => u.host === null)) || data.reply?.userHost === null || (this.isRenote(data) && this.isQuote(data) && data.renote?.userHost === null) || false; - if (this.config.nirila.blockMentionsFromUnfamiliarRemoteUsers && user.host !== null && willCauseNotification) { + const isAllowedToCreateNotification = () => { + const targetUserIds: string[] = [ + ...mentionedUsers.filter(x => x.host == null).map(x => x.id), + ...(data.visibility === 'specified' && data.visibleUsers != null ? data.visibleUsers.filter(x => x.host == null).map(x => x.id) : []), + ...(data.reply != null && data.reply.userHost == null ? [data.reply.userId] : []), + ...(this.isRenote(data) && this.isQuote(data) && data.renote.userHost === null ? [data.renote.userId] : []), + ]; + const allowedIds = new Set(meta.nirilaAllowedUnfamiliarRemoteUserIds); + for (const targetUserId of targetUserIds) { + if (!allowedIds.has(targetUserId)) { + return false; + } + } + return true; + }; + + if (meta.nirilaBlockMentionsFromUnfamiliarRemoteUsers && user.host !== null && willCauseNotification && !isAllowedToCreateNotification()) { const userEntity = await this.usersRepository.findOneBy({ id: user.id }); if ((userEntity?.followersCount ?? 0) === 0) { this.logger.error('Request rejected because user has no local followers', { user: user.id, note: data }); diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index e7e050a02c8f..18a41123ea5d 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -625,4 +625,16 @@ export class MiMeta { nullable: true, }) public urlPreviewUserAgent: string | null; + + @Column('boolean', { + default: false, + }) + public nirilaBlockMentionsFromUnfamiliarRemoteUsers: boolean; + + @Column('varchar', { + length: 32, + array: true, + default: '{}', + }) + public nirilaAllowedUnfamiliarRemoteUserIds: string[]; } diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 327353d2b7e2..32bef35c965b 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -481,6 +481,20 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + nirilaBlockMentionsFromUnfamiliarRemoteUsers: { + type: 'boolean', + optional: false, nullable: false, + }, + nirilaAllowedUnfamiliarRemoteUserIds: { + type: 'array', + optional: false, + nullable: false, + items: { + type: 'string', + optional: false, + nullable: false, + }, + }, }, }, } as const; @@ -615,6 +629,8 @@ export default class extends Endpoint { // eslint- urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength, urlPreviewUserAgent: instance.urlPreviewUserAgent, urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl, + nirilaBlockMentionsFromUnfamiliarRemoteUsers: instance.nirilaBlockMentionsFromUnfamiliarRemoteUsers, + nirilaAllowedUnfamiliarRemoteUserIds: instance.nirilaAllowedUnfamiliarRemoteUserIds, }; }); } 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 c53990802711..9f2acc97978a 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -161,6 +161,15 @@ export const paramDef = { urlPreviewRequireContentLength: { type: 'boolean' }, urlPreviewUserAgent: { type: 'string', nullable: true }, urlPreviewSummaryProxyUrl: { type: 'string', nullable: true }, + nirilaBlockMentionsFromUnfamiliarRemoteUsers: { type: 'boolean', nullable: false }, + nirilaAllowedUnfamiliarRemoteUserIds: { + type: 'array', + nullable: false, + items: { + type: 'string', + nullable: false, + }, + } }, required: [], } as const; @@ -622,6 +631,14 @@ export default class extends Endpoint { // eslint- set.urlPreviewSummaryProxyUrl = value === '' ? null : value; } + if (ps.nirilaBlockMentionsFromUnfamiliarRemoteUsers !== undefined) { + set.nirilaBlockMentionsFromUnfamiliarRemoteUsers = ps.nirilaBlockMentionsFromUnfamiliarRemoteUsers; + } + + if (ps.nirilaAllowedUnfamiliarRemoteUserIds !== undefined) { + set.nirilaAllowedUnfamiliarRemoteUserIds = ps.nirilaAllowedUnfamiliarRemoteUserIds; + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index a75799696d8f..fa1fd6f8ce56 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -55,6 +55,15 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + + + @@ -93,6 +102,8 @@ const preservedUsernames = ref(''); const tosUrl = ref(null); const privacyPolicyUrl = ref(null); const inquiryUrl = ref(null); +const nirilaBlockMentionsFromUnfamiliarRemoteUsers = ref(false); +const nirilaAllowedUnfamiliarRemoteUserIds = ref(''); async function init() { const meta = await misskeyApi('admin/meta'); @@ -105,6 +116,8 @@ async function init() { tosUrl.value = meta.tosUrl; privacyPolicyUrl.value = meta.privacyPolicyUrl; inquiryUrl.value = meta.inquiryUrl; + nirilaBlockMentionsFromUnfamiliarRemoteUsers.value = meta.nirilaBlockMentionsFromUnfamiliarRemoteUsers; + nirilaAllowedUnfamiliarRemoteUserIds.value = meta.nirilaAllowedUnfamiliarRemoteUserIds.join('\n'); } function save() { @@ -118,6 +131,9 @@ function save() { prohibitedWords: prohibitedWords.value.split('\n'), hiddenTags: hiddenTags.value.split('\n'), preservedUsernames: preservedUsernames.value.split('\n'), + nirilaBlockMentionsFromUnfamiliarRemoteUsers: nirilaBlockMentionsFromUnfamiliarRemoteUsers.value, + nirilaAllowedUnfamiliarRemoteUserIds: nirilaAllowedUnfamiliarRemoteUserIds.value === '' + ? [] : nirilaAllowedUnfamiliarRemoteUserIds.value.split('\n'), }).then(() => { fetchInstance(true); }); diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 033d8dc1fa4e..b247fdcd6ebd 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -5100,6 +5100,8 @@ export type operations = { urlPreviewRequireContentLength: boolean; urlPreviewUserAgent: string | null; urlPreviewSummaryProxyUrl: string | null; + nirilaBlockMentionsFromUnfamiliarRemoteUsers: boolean; + nirilaAllowedUnfamiliarRemoteUserIds: string[]; }; }; }; @@ -9110,6 +9112,8 @@ export type operations = { urlPreviewRequireContentLength?: boolean; urlPreviewUserAgent?: string | null; urlPreviewSummaryProxyUrl?: string | null; + nirilaBlockMentionsFromUnfamiliarRemoteUsers?: boolean; + nirilaAllowedUnfamiliarRemoteUserIds?: string[]; }; }; };