From e06a6a1f1985ee75820d53c408315c4a51704489 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 15 Oct 2024 19:58:40 +0900 Subject: [PATCH 01/13] =?UTF-8?q?feat:=20=E3=83=87=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=AB=E3=83=88=E3=81=A7=E3=83=95=E3=82=A9=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=82=92?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 32 +++++++++++++++ locales/ja-JP.yml | 8 ++++ .../1728986848483-defaultFollowUsers.js | 18 ++++++++ packages/backend/src/core/SignupService.ts | 19 +++++++++ packages/backend/src/models/Meta.ts | 16 ++++++++ .../src/server/api/endpoints/admin/meta.ts | 16 ++++++++ .../server/api/endpoints/admin/update-meta.ts | 41 ++++++++++++++++++- .../server/api/endpoints/blocking/create.ts | 20 +++++++++ .../server/api/endpoints/following/delete.ts | 20 +++++++++ .../src/server/api/endpoints/mute/create.ts | 20 +++++++++ .../api/endpoints/renote-mute/create.ts | 22 +++++++++- .../src/components/MkFollowButton.vue | 8 ++++ .../frontend/src/pages/admin/moderation.vue | 34 +++++++++++++++ .../frontend/src/scripts/get-user-menu.ts | 15 +++++++ packages/misskey-js/src/autogen/types.ts | 4 ++ 15 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 packages/backend/migration/1728986848483-defaultFollowUsers.js diff --git a/locales/index.d.ts b/locales/index.d.ts index b5af5909a3..90d2987a7a 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5190,6 +5190,38 @@ export interface Locale extends ILocale { * 名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。 */ "yourNameContainsProhibitedWordsDescription": string; + /** + * デフォルトでフォローするユーザー + */ + "defaultFollowedUsers": string; + /** + * 今後アカウントが作成された際に自動でフォローされるユーザー(解除可能)を改行区切りで指定します。 + */ + "defaultFollowedUsersDescription": string; + /** + * 交流を断てないユーザー + */ + "permanentFollowedUsers": string; + /** + * 今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーを改行区切りで指定します。 + */ + "permanentFollowedUsersDescription": string; + /** + * 「デフォルトでフォローするユーザー」と「交流を絶てないユーザー」が重複しています。 + */ + "defaultFollowedUsersDuplicated": string; + /** + * サーバー管理者はこのユーザーをフォロー解除することを禁止しています。 + */ + "unfollowThisUserIsProhibited": string; + /** + * サーバー管理者はこのユーザーをブロックすることを禁止しています。 + */ + "blockThisUserIsProhibited": string; + /** + * サーバー管理者はこのユーザーをミュートすることを禁止しています。 + */ + "muteThisUserIsProhibited": string; "_abuseUserReport": { /** * 転送 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c448d4d50a..f6888d4333 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1293,6 +1293,14 @@ prohibitedWordsForNameOfUser: "禁止ワード(ユーザーの名前)" prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。" yourNameContainsProhibitedWords: "変更しようとした名前に禁止された文字列が含まれています" yourNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。" +defaultFollowedUsers: "デフォルトでフォローするユーザー" +defaultFollowedUsersDescription: "今後アカウントが作成された際に自動でフォローされるユーザー(解除可能)を改行区切りで指定します。" +permanentFollowedUsers: "交流を断てないユーザー" +permanentFollowedUsersDescription: "今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーを改行区切りで指定します。" +defaultFollowedUsersDuplicated: "「デフォルトでフォローするユーザー」と「交流を絶てないユーザー」が重複しています。" +unfollowThisUserIsProhibited: "サーバー管理者はこのユーザーをフォロー解除することを禁止しています。" +blockThisUserIsProhibited: "サーバー管理者はこのユーザーをブロックすることを禁止しています。" +muteThisUserIsProhibited: "サーバー管理者はこのユーザーをミュートすることを禁止しています。" _abuseUserReport: forward: "転送" diff --git a/packages/backend/migration/1728986848483-defaultFollowUsers.js b/packages/backend/migration/1728986848483-defaultFollowUsers.js new file mode 100644 index 0000000000..53eed5d558 --- /dev/null +++ b/packages/backend/migration/1728986848483-defaultFollowUsers.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class DefaultFollowUsers1728986848483 { + name = 'defaultFollowUsers1728986848483' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "defaultFollowedUsers" character varying(1024) array NOT NULL DEFAULT '{}'`); + await queryRunner.query(`ALTER TABLE "meta" ADD "permanentFollowedUsers" character varying(1024) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "permanentFollowedUsers"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "defaultFollowedUsers"`); + } +} diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 3865392b7f..832eb91038 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -21,6 +21,7 @@ import { bindThis } from '@/decorators.js'; import UsersChart from '@/core/chart/charts/users.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserService } from '@/core/UserService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; @Injectable() export class SignupService { @@ -39,6 +40,7 @@ export class SignupService { private utilityService: UtilityService, private userService: UserService, + private userFollowingService: UserFollowingService, private userEntityService: UserEntityService, private idService: IdService, private instanceActorService: InstanceActorService, @@ -151,6 +153,23 @@ export class SignupService { }); this.usersChart.update(account, true); + + //#region Default following + if ( + !isTheFirstUser && + (this.meta.defaultFollowedUsers.length > 0 || this.meta.permanentFollowedUsers.length > 0) + ) { + const userIdsToFollow = [ + ...this.meta.defaultFollowedUsers, + ...this.meta.permanentFollowedUsers, + ]; + + await Promise.allSettled(userIdsToFollow.map(async userId => { + await this.userFollowingService.follow(account, { id: userId }); + })); + } + //#endregion + this.userService.notifySystemWebhook(account, 'userCreated'); return { account, secret }; diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index ad5e31ad6f..8f331b0948 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -61,6 +61,22 @@ export class MiMeta { }) public pinnedUsers: string[]; + /** + * アカウント作成の段階でデフォルトでフォローしているユーザー(あとから解除可能) + */ + @Column('varchar', { + length: 1024, array: true, default: '{}', + }) + public defaultFollowedUsers: string[]; + + /** + * デフォルトでフォローしていて、フォロー解除・ブロック・ミュートができないユーザー + */ + @Column('varchar', { + length: 1024, array: true, default: '{}', + }) + public permanentFollowedUsers: string[]; + @Column('varchar', { length: 1024, array: true, default: '{}', }) diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 64e3cc33bd..30b11f8562 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -149,6 +149,20 @@ export const meta = { type: 'string', }, }, + defaultFollowedUsers: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + }, + }, + permanentFollowedUsers: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + }, + }, hiddenTags: { type: 'array', optional: false, nullable: false, @@ -591,6 +605,8 @@ export default class extends Endpoint { // eslint- cacheRemoteFiles: instance.cacheRemoteFiles, cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles, pinnedUsers: instance.pinnedUsers, + defaultFollowedUsers: instance.defaultFollowedUsers, + permanentFollowedUsers: instance.permanentFollowedUsers, hiddenTags: instance.hiddenTags, blockedHosts: instance.blockedHosts, silencedHosts: instance.silencedHosts, 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 38ef0d1de8..110d976e18 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -3,11 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; import type { MiMeta } from '@/models/Meta.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { MetaService } from '@/core/MetaService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['admin'], @@ -15,6 +17,14 @@ export const meta = { requireCredential: true, requireAdmin: true, kind: 'write:admin:meta', + + errors: { + followedUserDuplicated: { + message: 'Some items in "defaultFollowedUsers" and "permanentFollowedUsers" are duplicated.', + code: 'FOLLOWED_USER_DUPLICATED', + id: 'bcf088ec-fec5-42d0-8b9e-16d3b4797a4d', + }, + }, } as const; export const paramDef = { @@ -26,6 +36,16 @@ export const paramDef = { type: 'string', }, }, + defaultFollowedUsers: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, + permanentFollowedUsers: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, hiddenTags: { type: 'array', nullable: true, items: { type: 'string', @@ -192,6 +212,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + private metaService: MetaService, private moderationLogService: ModerationLogService, ) { @@ -206,6 +229,22 @@ export default class extends Endpoint { // eslint- set.pinnedUsers = ps.pinnedUsers.filter(Boolean); } + if (Array.isArray(ps.defaultFollowedUsers)) { + if (ps.defaultFollowedUsers.some(x => this.serverSettings.permanentFollowedUsers.includes(x) || ps.permanentFollowedUsers?.includes(x))) { + throw new ApiError(meta.errors.followedUserDuplicated); + } + + set.defaultFollowedUsers = ps.defaultFollowedUsers.filter(Boolean); + } + + if (Array.isArray(ps.permanentFollowedUsers)) { + if (ps.permanentFollowedUsers.some(x => this.serverSettings.defaultFollowedUsers.includes(x) || ps.defaultFollowedUsers?.includes(x))) { + throw new ApiError(meta.errors.followedUserDuplicated); + } + + set.permanentFollowedUsers = ps.permanentFollowedUsers.filter(Boolean); + } + if (Array.isArray(ps.hiddenTags)) { set.hiddenTags = ps.hiddenTags.filter(Boolean); } diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 5066215749..f4ae1fe53a 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -7,8 +7,10 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, BlockingsRepository } from '@/models/_.js'; +import type { MiMeta } from '@/models/Meta.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; +import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; @@ -43,6 +45,13 @@ export const meta = { code: 'ALREADY_BLOCKING', id: '787fed64-acb9-464a-82eb-afbd745b9614', }, + + cannotBlockDueToServerPolicy: { + message: 'You cannot block that user due to server policy.', + code: 'CANNOT_BLOCK_DUE_TO_SERVER_POLICY', + id: 'e2f04d25-0d94-4ac3-a4d8-ba401062741b', + httpStatusCode: 403, + }, }, res: { @@ -63,12 +72,16 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @Inject(DI.blockingsRepository) private blockingsRepository: BlockingsRepository, + private roleService: RoleService, private userEntityService: UserEntityService, private getterService: GetterService, private userBlockingService: UserBlockingService, @@ -99,6 +112,13 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyBlocking); } + if ( + this.serverSettings.permanentFollowedUsers.includes(blockee.id) && + !await this.roleService.isModerator(blocker) + ) { + throw new ApiError(meta.errors.cannotBlockDueToServerPolicy); + } + await this.userBlockingService.block(blocker, blockee); return await this.userEntityService.pack(blockee.id, blocker, { diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index ba146b6703..91917b5bd4 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -7,8 +7,10 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { FollowingsRepository } from '@/models/_.js'; +import type { MiMeta } from '@/models/Meta.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; @@ -43,6 +45,13 @@ export const meta = { code: 'NOT_FOLLOWING', id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09', }, + + cannotUnfollowDueToServerPolicy: { + message: 'You cannot unfollow that user due to server policy.', + code: 'CANNOT_UNFOLLOW_DUE_TO_SERVER_POLICY', + id: '19f25f61-0141-4683-99dc-217a88d633cb', + httpStatusCode: 403, + }, }, res: { @@ -63,9 +72,13 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, + private roleService: RoleService, private userEntityService: UserEntityService, private getterService: GetterService, private userFollowingService: UserFollowingService, @@ -96,6 +109,13 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.notFollowing); } + if ( + this.serverSettings.permanentFollowedUsers.includes(followee.id) && + !await this.roleService.isModerator(follower) + ) { + throw new ApiError(meta.errors.cannotUnfollowDueToServerPolicy); + } + await this.userFollowingService.unfollow(follower, followee); return await this.userEntityService.pack(followee.id, me); diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index e39c133b43..262e0c0e6c 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -7,9 +7,11 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MutingsRepository } from '@/models/_.js'; +import type { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; +import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -43,6 +45,13 @@ export const meta = { code: 'ALREADY_MUTING', id: '7e7359cb-160c-4956-b08f-4d1c653cd007', }, + + cannotMuteDueToServerPolicy: { + message: 'You cannot mute that user due to server policy.', + code: 'CANNOT_MUTE_DUE_TO_SERVER_POLICY', + id: '15273a89-374d-49fa-8df6-8bb3feeea455', + httpStatusCode: 403, + }, }, } as const; @@ -62,9 +71,13 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, + private roleService: RoleService, private getterService: GetterService, private userMutingService: UserMutingService, ) { @@ -94,6 +107,13 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyMuting); } + if ( + this.serverSettings.permanentFollowedUsers.includes(mutee.id) && + !await this.roleService.isModerator(muter) + ) { + throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); + } + if (ps.expiresAt && ps.expiresAt <= Date.now()) { return; } diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts index 84a1f010d4..0716c5b430 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts @@ -9,8 +9,10 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; -import { UserRenoteMutingService } from "@/core/UserRenoteMutingService.js"; +import { RoleService } from '@/core/RoleService.js'; +import { UserRenoteMutingService } from '@/core/UserRenoteMutingService.js'; import type { RenoteMutingsRepository } from '@/models/_.js'; +import type { MiMeta } from '@/models/Meta.js'; export const meta = { tags: ['account'], @@ -43,6 +45,13 @@ export const meta = { code: 'ALREADY_MUTING', id: 'ccfecbe4-1f1c-4fc2-8a3d-c3ffee61cb7b', }, + + cannotMuteDueToServerPolicy: { + message: 'You cannot mute that user due to server policy.', + code: 'CANNOT_MUTE_DUE_TO_SERVER_POLICY', + id: '15273a89-374d-49fa-8df6-8bb3feeea455', + httpStatusCode: 403, + }, }, } as const; @@ -57,9 +66,13 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, + private roleService: RoleService, private getterService: GetterService, private userRenoteMutingService: UserRenoteMutingService, ) { @@ -89,6 +102,13 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyMuting); } + if ( + this.serverSettings.permanentFollowedUsers.includes(mutee.id) && + !await this.roleService.isModerator(muter) + ) { + throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); + } + // Create mute await this.userRenoteMutingService.mute(muter, mutee); }); diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index ccea7cd453..66844ef372 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -95,6 +95,14 @@ async function onClick() { await misskeyApi('following/delete', { userId: props.user.id, + }).catch((err) => { + if (err.id === '19f25f61-0141-4683-99dc-217a88d633cb') { + os.alert({ + type: 'error', + title: i18n.ts.permissionDeniedError, + text: i18n.ts.unfollowThisUserProhibited, + }); + } }); } else { if (defaultStore.state.alwaysConfirmFollow) { diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index 5d8a581b2e..71e7093d5e 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -33,6 +33,23 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + +
+ + + + + + + + + {{ i18n.ts.save }} +
+
+ @@ -146,6 +163,8 @@ const prohibitedWords = ref(''); const prohibitedWordsForNameOfUser = ref(''); const hiddenTags = ref(''); const preservedUsernames = ref(''); +const defaultFollowedUsers = ref(''); +const permanentFollowedUsers = ref(''); const blockedHosts = ref(''); const silencedHosts = ref(''); const mediaSilencedHosts = ref(''); @@ -159,6 +178,8 @@ async function init() { prohibitedWordsForNameOfUser.value = meta.prohibitedWordsForNameOfUser.join('\n'); hiddenTags.value = meta.hiddenTags.join('\n'); preservedUsernames.value = meta.preservedUsernames.join('\n'); + defaultFollowedUsers.value = meta.defaultFollowedUsers.join('\n'); + permanentFollowedUsers.value = meta.permanentFollowedUsers.join('\n'); blockedHosts.value = meta.blockedHosts.join('\n'); silencedHosts.value = meta.silencedHosts?.join('\n') ?? ''; mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n'); @@ -188,6 +209,19 @@ function save_preservedUsernames() { }); } +function save_defaultUsers() { + os.apiWithDialog('admin/update-meta', { + defaultFollowedUsers: defaultFollowedUsers.value.split('\n'), + permanentFollowedUsers: permanentFollowedUsers.value.split('\n'), + }, undefined, { + 'bcf088ec-fec5-42d0-8b9e-16d3b4797a4d': { + text: i18n.ts.defaultFollowedUsersDuplicated, + } + }).then(() => { + fetchInstance(true); + }); +} + function save_sensitiveWords() { os.apiWithDialog('admin/update-meta', { sensitiveWords: sensitiveWords.value.split('\n'), diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index d15279d633..107495210b 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -60,6 +60,11 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter os.apiWithDialog('mute/create', { userId: user.id, expiresAt, + }, undefined, { + '15273a89-374d-49fa-8df6-8bb3feeea455': { + title: i18n.ts.permissionDeniedError, + text: i18n.ts.muteThisUserIsProhibited, + }, }).then(() => { user.isMuted = true; }); @@ -69,6 +74,11 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter async function toggleRenoteMute() { os.apiWithDialog(user.isRenoteMuted ? 'renote-mute/delete' : 'renote-mute/create', { userId: user.id, + }, undefined, { + '15273a89-374d-49fa-8df6-8bb3feeea455': { + title: i18n.ts.permissionDeniedError, + text: i18n.ts.muteThisUserIsProhibited, + }, }).then(() => { user.isRenoteMuted = !user.isRenoteMuted; }); @@ -79,6 +89,11 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter os.apiWithDialog(user.isBlocking ? 'blocking/delete' : 'blocking/create', { userId: user.id, + }, undefined, { + 'e2f04d25-0d94-4ac3-a4d8-ba401062741b': { + title: i18n.ts.permissionDeniedError, + text: i18n.ts.blockThisUserIsProhibited, + }, }).then(() => { user.isBlocking = !user.isBlocking; }); diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 698c08826a..cf532765c1 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -5120,6 +5120,8 @@ export type operations = { silencedHosts?: string[]; mediaSilencedHosts: string[]; pinnedUsers: string[]; + defaultFollowedUsers: string[]; + permanentFollowedUsers: string[]; hiddenTags: string[]; blockedHosts: string[]; sensitiveWords: string[]; @@ -9459,6 +9461,8 @@ export type operations = { 'application/json': { disableRegistration?: boolean | null; pinnedUsers?: string[] | null; + defaultFollowedUsers?: string[] | null; + permanentFollowedUsers?: string[] | null; hiddenTags?: string[] | null; blockedHosts?: string[] | null; sensitiveWords?: string[] | null; From 4a21edacdef36bfbeeeb44bdd576cb2ee9dba759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:23:32 +0900 Subject: [PATCH 02/13] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3dcb7bbd..ab0913ca19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## Unreleased ### General -- +- ユーザー登録時に自動で特定のユーザーをフォローすることができるように + - フォローさせるユーザーのフォロー解除・ミュート・ブロックができないように指定することもできます ### Client - From 52ee21b2ee0798d99dd120f10a7dd282092fe037 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:47:06 +0900 Subject: [PATCH 03/13] =?UTF-8?q?`forciblyFollowedUsers`=20=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 4 ++-- locales/ja-JP.yml | 4 ++-- packages/backend/src/core/SignupService.ts | 4 ++-- packages/backend/src/models/Meta.ts | 2 +- .../backend/src/server/api/endpoints/admin/meta.ts | 4 ++-- .../src/server/api/endpoints/admin/update-meta.ts | 12 ++++++------ .../src/server/api/endpoints/blocking/create.ts | 2 +- .../src/server/api/endpoints/following/delete.ts | 2 +- .../backend/src/server/api/endpoints/mute/create.ts | 2 +- .../src/server/api/endpoints/renote-mute/create.ts | 2 +- packages/frontend/src/pages/admin/moderation.vue | 12 ++++++------ packages/misskey-js/src/autogen/types.ts | 4 ++-- 12 files changed, 27 insertions(+), 27 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 90d2987a7a..9f422cb501 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5201,11 +5201,11 @@ export interface Locale extends ILocale { /** * 交流を断てないユーザー */ - "permanentFollowedUsers": string; + "forciblyFollowedUsers": string; /** * 今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーを改行区切りで指定します。 */ - "permanentFollowedUsersDescription": string; + "forciblyFollowedUsersDescription": string; /** * 「デフォルトでフォローするユーザー」と「交流を絶てないユーザー」が重複しています。 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f6888d4333..54671ec60a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1295,8 +1295,8 @@ yourNameContainsProhibitedWords: "変更しようとした名前に禁止され yourNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。" defaultFollowedUsers: "デフォルトでフォローするユーザー" defaultFollowedUsersDescription: "今後アカウントが作成された際に自動でフォローされるユーザー(解除可能)を改行区切りで指定します。" -permanentFollowedUsers: "交流を断てないユーザー" -permanentFollowedUsersDescription: "今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーを改行区切りで指定します。" +forciblyFollowedUsers: "交流を断てないユーザー" +forciblyFollowedUsersDescription: "今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーを改行区切りで指定します。" defaultFollowedUsersDuplicated: "「デフォルトでフォローするユーザー」と「交流を絶てないユーザー」が重複しています。" unfollowThisUserIsProhibited: "サーバー管理者はこのユーザーをフォロー解除することを禁止しています。" blockThisUserIsProhibited: "サーバー管理者はこのユーザーをブロックすることを禁止しています。" diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 832eb91038..79bc3ebd1c 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -157,11 +157,11 @@ export class SignupService { //#region Default following if ( !isTheFirstUser && - (this.meta.defaultFollowedUsers.length > 0 || this.meta.permanentFollowedUsers.length > 0) + (this.meta.defaultFollowedUsers.length > 0 || this.meta.forciblyFollowedUsers.length > 0) ) { const userIdsToFollow = [ ...this.meta.defaultFollowedUsers, - ...this.meta.permanentFollowedUsers, + ...this.meta.forciblyFollowedUsers, ]; await Promise.allSettled(userIdsToFollow.map(async userId => { diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 8f331b0948..e6fda2d5ff 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -75,7 +75,7 @@ export class MiMeta { @Column('varchar', { length: 1024, array: true, default: '{}', }) - public permanentFollowedUsers: string[]; + public forciblyFollowedUsers: string[]; @Column('varchar', { length: 1024, array: true, default: '{}', diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 30b11f8562..7c02fa0295 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -156,7 +156,7 @@ export const meta = { type: 'string', }, }, - permanentFollowedUsers: { + forciblyFollowedUsers: { type: 'array', optional: false, nullable: false, items: { @@ -606,7 +606,7 @@ export default class extends Endpoint { // eslint- cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles, pinnedUsers: instance.pinnedUsers, defaultFollowedUsers: instance.defaultFollowedUsers, - permanentFollowedUsers: instance.permanentFollowedUsers, + forciblyFollowedUsers: instance.forciblyFollowedUsers, hiddenTags: instance.hiddenTags, blockedHosts: instance.blockedHosts, silencedHosts: instance.silencedHosts, 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 110d976e18..a8f24eb76b 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -20,7 +20,7 @@ export const meta = { errors: { followedUserDuplicated: { - message: 'Some items in "defaultFollowedUsers" and "permanentFollowedUsers" are duplicated.', + message: 'Some items in "defaultFollowedUsers" and "forciblyFollowedUsers" are duplicated.', code: 'FOLLOWED_USER_DUPLICATED', id: 'bcf088ec-fec5-42d0-8b9e-16d3b4797a4d', }, @@ -41,7 +41,7 @@ export const paramDef = { type: 'string', }, }, - permanentFollowedUsers: { + forciblyFollowedUsers: { type: 'array', nullable: true, items: { type: 'string', }, @@ -230,19 +230,19 @@ export default class extends Endpoint { // eslint- } if (Array.isArray(ps.defaultFollowedUsers)) { - if (ps.defaultFollowedUsers.some(x => this.serverSettings.permanentFollowedUsers.includes(x) || ps.permanentFollowedUsers?.includes(x))) { + if (ps.defaultFollowedUsers.some(x => this.serverSettings.forciblyFollowedUsers.includes(x) || ps.forciblyFollowedUsers?.includes(x))) { throw new ApiError(meta.errors.followedUserDuplicated); } set.defaultFollowedUsers = ps.defaultFollowedUsers.filter(Boolean); } - if (Array.isArray(ps.permanentFollowedUsers)) { - if (ps.permanentFollowedUsers.some(x => this.serverSettings.defaultFollowedUsers.includes(x) || ps.defaultFollowedUsers?.includes(x))) { + if (Array.isArray(ps.forciblyFollowedUsers)) { + if (ps.forciblyFollowedUsers.some(x => this.serverSettings.defaultFollowedUsers.includes(x) || ps.defaultFollowedUsers?.includes(x))) { throw new ApiError(meta.errors.followedUserDuplicated); } - set.permanentFollowedUsers = ps.permanentFollowedUsers.filter(Boolean); + set.forciblyFollowedUsers = ps.forciblyFollowedUsers.filter(Boolean); } if (Array.isArray(ps.hiddenTags)) { diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index f4ae1fe53a..087140e4fa 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -113,7 +113,7 @@ export default class extends Endpoint { // eslint- } if ( - this.serverSettings.permanentFollowedUsers.includes(blockee.id) && + this.serverSettings.forciblyFollowedUsers.includes(blockee.id) && !await this.roleService.isModerator(blocker) ) { throw new ApiError(meta.errors.cannotBlockDueToServerPolicy); diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 91917b5bd4..1709fb8e4c 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -110,7 +110,7 @@ export default class extends Endpoint { // eslint- } if ( - this.serverSettings.permanentFollowedUsers.includes(followee.id) && + this.serverSettings.forciblyFollowedUsers.includes(followee.id) && !await this.roleService.isModerator(follower) ) { throw new ApiError(meta.errors.cannotUnfollowDueToServerPolicy); diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 262e0c0e6c..69762610fa 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -108,7 +108,7 @@ export default class extends Endpoint { // eslint- } if ( - this.serverSettings.permanentFollowedUsers.includes(mutee.id) && + this.serverSettings.forciblyFollowedUsers.includes(mutee.id) && !await this.roleService.isModerator(muter) ) { throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts index 0716c5b430..709f1b170f 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts @@ -103,7 +103,7 @@ export default class extends Endpoint { // eslint- } if ( - this.serverSettings.permanentFollowedUsers.includes(mutee.id) && + this.serverSettings.forciblyFollowedUsers.includes(mutee.id) && !await this.roleService.isModerator(muter) ) { throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index 71e7093d5e..505d282b32 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -42,9 +42,9 @@ SPDX-License-Identifier: AGPL-3.0-only - - - + + + {{ i18n.ts.save }} @@ -164,7 +164,7 @@ const prohibitedWordsForNameOfUser = ref(''); const hiddenTags = ref(''); const preservedUsernames = ref(''); const defaultFollowedUsers = ref(''); -const permanentFollowedUsers = ref(''); +const forciblyFollowedUsers = ref(''); const blockedHosts = ref(''); const silencedHosts = ref(''); const mediaSilencedHosts = ref(''); @@ -179,7 +179,7 @@ async function init() { hiddenTags.value = meta.hiddenTags.join('\n'); preservedUsernames.value = meta.preservedUsernames.join('\n'); defaultFollowedUsers.value = meta.defaultFollowedUsers.join('\n'); - permanentFollowedUsers.value = meta.permanentFollowedUsers.join('\n'); + forciblyFollowedUsers.value = meta.forciblyFollowedUsers.join('\n'); blockedHosts.value = meta.blockedHosts.join('\n'); silencedHosts.value = meta.silencedHosts?.join('\n') ?? ''; mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n'); @@ -212,7 +212,7 @@ function save_preservedUsernames() { function save_defaultUsers() { os.apiWithDialog('admin/update-meta', { defaultFollowedUsers: defaultFollowedUsers.value.split('\n'), - permanentFollowedUsers: permanentFollowedUsers.value.split('\n'), + forciblyFollowedUsers: forciblyFollowedUsers.value.split('\n'), }, undefined, { 'bcf088ec-fec5-42d0-8b9e-16d3b4797a4d': { text: i18n.ts.defaultFollowedUsersDuplicated, diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index cf532765c1..0bdb560be8 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -5121,7 +5121,7 @@ export type operations = { mediaSilencedHosts: string[]; pinnedUsers: string[]; defaultFollowedUsers: string[]; - permanentFollowedUsers: string[]; + forciblyFollowedUsers: string[]; hiddenTags: string[]; blockedHosts: string[]; sensitiveWords: string[]; @@ -9462,7 +9462,7 @@ export type operations = { disableRegistration?: boolean | null; pinnedUsers?: string[] | null; defaultFollowedUsers?: string[] | null; - permanentFollowedUsers?: string[] | null; + forciblyFollowedUsers?: string[] | null; hiddenTags?: string[] | null; blockedHosts?: string[] | null; sensitiveWords?: string[] | null; From 438abb6303e29caa0e6a0c482f2d7d023c78ac37 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:51:36 +0900 Subject: [PATCH 04/13] =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BCID?= =?UTF-8?q?=E3=82=92=E6=8C=87=E5=AE=9A=E3=81=99=E3=82=8B=E5=BF=85=E8=A6=81?= =?UTF-8?q?=E3=81=8C=E3=81=82=E3=82=8B=E6=97=A8=E3=82=92=E6=98=8E=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 8 ++++---- locales/ja-JP.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 9f422cb501..caa9c0b5cf 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5191,19 +5191,19 @@ export interface Locale extends ILocale { */ "yourNameContainsProhibitedWordsDescription": string; /** - * デフォルトでフォローするユーザー + * デフォルトでフォローするユーザー (ID) */ "defaultFollowedUsers": string; /** - * 今後アカウントが作成された際に自動でフォローされるユーザー(解除可能)を改行区切りで指定します。 + * 今後アカウントが作成された際に自動でフォローされるユーザー(解除可能)のユーザーIDを改行区切りで指定します。 */ "defaultFollowedUsersDescription": string; /** - * 交流を断てないユーザー + * 交流を断てないユーザー (ID) */ "forciblyFollowedUsers": string; /** - * 今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーを改行区切りで指定します。 + * 今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーのユーザーID改行区切りで指定します。 */ "forciblyFollowedUsersDescription": string; /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 54671ec60a..ec96e38ea1 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1293,10 +1293,10 @@ prohibitedWordsForNameOfUser: "禁止ワード(ユーザーの名前)" prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。" yourNameContainsProhibitedWords: "変更しようとした名前に禁止された文字列が含まれています" yourNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。" -defaultFollowedUsers: "デフォルトでフォローするユーザー" -defaultFollowedUsersDescription: "今後アカウントが作成された際に自動でフォローされるユーザー(解除可能)を改行区切りで指定します。" -forciblyFollowedUsers: "交流を断てないユーザー" -forciblyFollowedUsersDescription: "今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーを改行区切りで指定します。" +defaultFollowedUsers: "デフォルトでフォローするユーザー (ID)" +defaultFollowedUsersDescription: "今後アカウントが作成された際に自動でフォローされるユーザー(解除可能)のユーザーIDを改行区切りで指定します。" +forciblyFollowedUsers: "交流を断てないユーザー (ID)" +forciblyFollowedUsersDescription: "今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーのユーザーID改行区切りで指定します。" defaultFollowedUsersDuplicated: "「デフォルトでフォローするユーザー」と「交流を絶てないユーザー」が重複しています。" unfollowThisUserIsProhibited: "サーバー管理者はこのユーザーをフォロー解除することを禁止しています。" blockThisUserIsProhibited: "サーバー管理者はこのユーザーをブロックすることを禁止しています。" From 48719ebb088e2aef93a3294c4e21f5017653424c Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:54:00 +0900 Subject: [PATCH 05/13] typo --- locales/index.d.ts | 2 +- locales/ja-JP.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index caa9c0b5cf..8cd2a3a606 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5203,7 +5203,7 @@ export interface Locale extends ILocale { */ "forciblyFollowedUsers": string; /** - * 今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーのユーザーID改行区切りで指定します。 + * 今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーのユーザーIDを改行区切りで指定します。 */ "forciblyFollowedUsersDescription": string; /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ec96e38ea1..507cbbe107 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1296,7 +1296,7 @@ yourNameContainsProhibitedWordsDescription: "名前に禁止されている文 defaultFollowedUsers: "デフォルトでフォローするユーザー (ID)" defaultFollowedUsersDescription: "今後アカウントが作成された際に自動でフォローされるユーザー(解除可能)のユーザーIDを改行区切りで指定します。" forciblyFollowedUsers: "交流を断てないユーザー (ID)" -forciblyFollowedUsersDescription: "今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーのユーザーID改行区切りで指定します。" +forciblyFollowedUsersDescription: "今後アカウントが作成された際には自動でフォローされ、フォローの解除やミュート・ブロックができないユーザーのユーザーIDを改行区切りで指定します。" defaultFollowedUsersDuplicated: "「デフォルトでフォローするユーザー」と「交流を絶てないユーザー」が重複しています。" unfollowThisUserIsProhibited: "サーバー管理者はこのユーザーをフォロー解除することを禁止しています。" blockThisUserIsProhibited: "サーバー管理者はこのユーザーをブロックすることを禁止しています。" From d96b2b8e9d2e506129b20c705770b2631be3b715 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:13:39 +0900 Subject: [PATCH 06/13] =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=9D=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=A7=E9=98=B2=E3=81=92=E3=81=A6=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../processors/ImportBlockingProcessorService.ts | 15 +++++++++++++++ .../processors/ImportMutingProcessorService.ts | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index b78229c648..5441a3129b 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -6,12 +6,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; +import type { MiMeta } from '@/models/Meta.js'; import type { UsersRepository, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { RoleService } from '@/core/RoleService.js'; import { bindThis } from '@/decorators.js'; import { QueueService } from '@/core/QueueService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; @@ -23,12 +25,16 @@ export class ImportBlockingProcessorService { private logger: Logger; constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + private roleService: RoleService, private queueService: QueueService, private utilityService: UtilityService, private remoteUserResolveService: RemoteUserResolveService, @@ -93,6 +99,15 @@ export class ImportBlockingProcessorService { // skip myself if (target.id === job.data.user.id) return; + // skip if server prohibits blocking + if ( + this.serverSettings.forciblyFollowedUsers.includes(target.id) && + !await this.roleService.isModerator({ id: user.id, isRoot: false }) + ) { + this.logger.warn(`Cannot block due to server policy: ${target.id}`); + return; + } + this.logger.info(`Block ${target.id} ...`); this.queueService.createBlockJob([{ from: { id: user.id }, to: { id: target.id }, silent: true }]); diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index ec9d2b6c4c..8a89eac439 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -6,6 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; +import type { MiMeta } from '@/models/Meta.js'; import type { UsersRepository, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; @@ -13,6 +14,7 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { RoleService } from '@/core/RoleService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -23,12 +25,16 @@ export class ImportMutingProcessorService { private logger: Logger; constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + private roleService: RoleService, private utilityService: UtilityService, private userMutingService: UserMutingService, private remoteUserResolveService: RemoteUserResolveService, @@ -88,6 +94,15 @@ export class ImportMutingProcessorService { // skip myself if (target.id === job.data.user.id) continue; + // skip if server prohibits muting + if ( + this.serverSettings.forciblyFollowedUsers.includes(target.id) && + !await this.roleService.isModerator({ id: user.id, isRoot: false }) + ) { + this.logger.info(`Skip[${linenum}] ${target.id} because of server policy ...`); + continue; + } + this.logger.info(`Mute[${linenum}] ${target.id} ...`); await this.userMutingService.mute(user, target); From 0dc536a3fa39a9234203633890cb2f66246d6def Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:19:00 +0900 Subject: [PATCH 07/13] =?UTF-8?q?=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/api/endpoints/admin/update-meta.ts | 2 ++ 1 file changed, 2 insertions(+) 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 a8f24eb76b..8b1dd58349 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -39,11 +39,13 @@ export const paramDef = { defaultFollowedUsers: { type: 'array', nullable: true, items: { type: 'string', + format: 'misskey:id', }, }, forciblyFollowedUsers: { type: 'array', nullable: true, items: { type: 'string', + format: 'misskey:id', }, }, hiddenTags: { From 0978aa429ad080eadcf69455bce29cdff525ecad Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:20:16 +0900 Subject: [PATCH 08/13] =?UTF-8?q?Revert=20"=E3=82=A4=E3=83=B3=E3=83=9D?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=A7=E9=98=B2=E3=81=92=E3=81=A6=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d96b2b8e9d2e506129b20c705770b2631be3b715. --- .../processors/ImportBlockingProcessorService.ts | 15 --------------- .../processors/ImportMutingProcessorService.ts | 15 --------------- 2 files changed, 30 deletions(-) diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts index 5441a3129b..b78229c648 100644 --- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts @@ -6,14 +6,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { MiMeta } from '@/models/Meta.js'; import type { UsersRepository, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { RoleService } from '@/core/RoleService.js'; import { bindThis } from '@/decorators.js'; import { QueueService } from '@/core/QueueService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; @@ -25,16 +23,12 @@ export class ImportBlockingProcessorService { private logger: Logger; constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.usersRepository) private usersRepository: UsersRepository, @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - private roleService: RoleService, private queueService: QueueService, private utilityService: UtilityService, private remoteUserResolveService: RemoteUserResolveService, @@ -99,15 +93,6 @@ export class ImportBlockingProcessorService { // skip myself if (target.id === job.data.user.id) return; - // skip if server prohibits blocking - if ( - this.serverSettings.forciblyFollowedUsers.includes(target.id) && - !await this.roleService.isModerator({ id: user.id, isRoot: false }) - ) { - this.logger.warn(`Cannot block due to server policy: ${target.id}`); - return; - } - this.logger.info(`Block ${target.id} ...`); this.queueService.createBlockJob([{ from: { id: user.id }, to: { id: target.id }, silent: true }]); diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index 8a89eac439..ec9d2b6c4c 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -6,7 +6,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { MiMeta } from '@/models/Meta.js'; import type { UsersRepository, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; @@ -14,7 +13,6 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { RoleService } from '@/core/RoleService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -25,16 +23,12 @@ export class ImportMutingProcessorService { private logger: Logger; constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.usersRepository) private usersRepository: UsersRepository, @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - private roleService: RoleService, private utilityService: UtilityService, private userMutingService: UserMutingService, private remoteUserResolveService: RemoteUserResolveService, @@ -94,15 +88,6 @@ export class ImportMutingProcessorService { // skip myself if (target.id === job.data.user.id) continue; - // skip if server prohibits muting - if ( - this.serverSettings.forciblyFollowedUsers.includes(target.id) && - !await this.roleService.isModerator({ id: user.id, isRoot: false }) - ) { - this.logger.info(`Skip[${linenum}] ${target.id} because of server policy ...`); - continue; - } - this.logger.info(`Mute[${linenum}] ${target.id} ...`); await this.userMutingService.mute(user, target); From a3d77f318567427dce3f73b19d78ba1154c12d13 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:41:51 +0900 Subject: [PATCH 09/13] =?UTF-8?q?=E6=93=8D=E4=BD=9C=E3=83=96=E3=83=AD?= =?UTF-8?q?=E3=83=83=E3=82=AF=E3=81=AE=E6=8C=99=E5=8B=95=E3=82=92=E3=82=B5?= =?UTF-8?q?=E3=83=BC=E3=83=93=E3=82=B9=E5=81=B4=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/src/core/UserBlockingService.ts | 16 +++++++++++++ .../backend/src/core/UserFollowingService.ts | 13 +++++++++- .../backend/src/core/UserMutingService.ts | 16 +++++++++++++ .../src/core/UserRenoteMutingService.ts | 16 +++++++++++++ .../ImportMutingProcessorService.ts | 9 ++++++- .../RelationshipProcessorService.ts | 17 +++++++++++-- .../server/api/endpoints/blocking/create.ts | 24 +++++++------------ .../server/api/endpoints/following/delete.ts | 20 +++++----------- .../src/server/api/endpoints/mute/create.ts | 20 +++++----------- .../api/endpoints/renote-mute/create.ts | 20 +++++----------- 10 files changed, 110 insertions(+), 61 deletions(-) diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 2f1310b8ef..ca57857eb1 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -8,6 +8,8 @@ import { ModuleRef } from '@nestjs/core'; import { IdService } from '@/core/IdService.js'; import type { MiUser } from '@/models/User.js'; import type { MiBlocking } from '@/models/Blocking.js'; +import type { MiMeta } from '@/models/Meta.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -20,6 +22,7 @@ import { UserWebhookService } from '@/core/UserWebhookService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { RoleService } from '@/core/RoleService.js'; @Injectable() export class UserBlockingService implements OnModuleInit { @@ -29,6 +32,9 @@ export class UserBlockingService implements OnModuleInit { constructor( private moduleRef: ModuleRef, + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.followRequestsRepository) private followRequestsRepository: FollowRequestsRepository, @@ -41,6 +47,7 @@ export class UserBlockingService implements OnModuleInit { @Inject(DI.userListMembershipsRepository) private userListMembershipsRepository: UserListMembershipsRepository, + private roleService: RoleService, private cacheService: CacheService, private userEntityService: UserEntityService, private idService: IdService, @@ -59,6 +66,15 @@ export class UserBlockingService implements OnModuleInit { @bindThis public async block(blocker: MiUser, blockee: MiUser, silent = false) { + + // フォロー解除できない(=ブロックもできない)ユーザーの場合 + if ( + this.serverSettings.forciblyFollowedUsers.includes(blockee.id) && + !await this.roleService.isModerator(blocker) + ) { + throw new IdentifiableError('e2f04d25-0d94-4ac3-a4d8-ba401062741b', 'You cannot block that user due to server policy.'); + } + await Promise.all([ this.cancelRequest(blocker, blockee, silent), this.cancelRequest(blockee, blocker, silent), diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 8963003057..5e980f27af 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -27,6 +27,7 @@ import { CacheService } from '@/core/CacheService.js'; import type { Config } from '@/config.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { RoleService } from '@/core/RoleService.js'; import type { ThinUser } from '@/queue/types.js'; import Logger from '../logger.js'; @@ -73,6 +74,7 @@ export class UserFollowingService implements OnModuleInit { @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, + private roleService: RoleService, private cacheService: CacheService, private utilityService: UtilityService, private userEntityService: UserEntityService, @@ -365,13 +367,22 @@ export class UserFollowingService implements OnModuleInit { @bindThis public async unfollow( follower: { - id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; isRoot: MiUser['isRoot']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, followee: { id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, silent = false, ): Promise { + + // フォロー解除できないユーザーの場合 + if ( + this.meta.forciblyFollowedUsers.includes(followee.id) && + !await this.roleService.isModerator(follower) + ) { + throw new IdentifiableError('19f25f61-0141-4683-99dc-217a88d633cb', 'You cannot unfollow that user due to server policy.'); + } + const following = await this.followingsRepository.findOne({ relations: { follower: true, diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 06643be5fb..504b7e0e1b 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -5,19 +5,26 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MutingsRepository, MiMuting } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import type { MiUser } from '@/models/User.js'; +import type { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; import { CacheService } from '@/core/CacheService.js'; @Injectable() export class UserMutingService { constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, + private roleService: RoleService, private idService: IdService, private cacheService: CacheService, ) { @@ -25,6 +32,15 @@ export class UserMutingService { @bindThis public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { + + // フォロー解除できない(=ミュートもできない)ユーザーの場合 + if ( + this.serverSettings.forciblyFollowedUsers.includes(target.id) && + !await this.roleService.isModerator(user) + ) { + throw new IdentifiableError('15273a89-374d-49fa-8df6-8bb3feeea455', 'You cannot mute that user due to server policy.'); + } + await this.mutingsRepository.insert({ id: this.idService.gen(), expiresAt: expiresAt ?? null, diff --git a/packages/backend/src/core/UserRenoteMutingService.ts b/packages/backend/src/core/UserRenoteMutingService.ts index bdc5e23f4b..1f5c311e3b 100644 --- a/packages/backend/src/core/UserRenoteMutingService.ts +++ b/packages/backend/src/core/UserRenoteMutingService.ts @@ -10,16 +10,23 @@ import type { MiRenoteMuting } from '@/models/RenoteMuting.js'; import { IdService } from '@/core/IdService.js'; import type { MiUser } from '@/models/User.js'; +import type { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; import { CacheService } from '@/core/CacheService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; @Injectable() export class UserRenoteMutingService { constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, + private roleService: RoleService, private idService: IdService, private cacheService: CacheService, ) { @@ -27,6 +34,15 @@ export class UserRenoteMutingService { @bindThis public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { + + // フォロー解除できない(=リノートミュートもできない)ユーザーの場合 + if ( + this.serverSettings.forciblyFollowedUsers.includes(target.id) && + !await this.roleService.isModerator(user) + ) { + throw new IdentifiableError('15273a89-374d-49fa-8df6-8bb3feeea455', 'You cannot mute that user due to server policy.'); + } + await this.renoteMutingsRepository.insert({ id: this.idService.gen(), muterId: user.id, diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index ec9d2b6c4c..0ae1e6ae15 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -17,6 +17,7 @@ import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserImportJobData } from '../types.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; @Injectable() export class ImportMutingProcessorService { @@ -90,7 +91,13 @@ export class ImportMutingProcessorService { this.logger.info(`Mute[${linenum}] ${target.id} ...`); - await this.userMutingService.mute(user, target); + await this.userMutingService.mute(user, target).catch((err) => { + if (err instanceof IdentifiableError && err.id === '15273a89-374d-49fa-8df6-8bb3feeea455') { + // フォロー解除できない(=ミュートもできない)ユーザー。動作は正常のため、エラーを無視する + return; + } + throw err; + }); } catch (e) { this.logger.warn(`Error in line:${linenum} ${e}`); } diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts index 408b02fb38..16ee99a2e0 100644 --- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts +++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts @@ -16,6 +16,7 @@ import { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { RelationshipJobData } from '../types.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; @Injectable() export class RelationshipProcessorService { @@ -50,7 +51,13 @@ export class RelationshipProcessorService { this.usersRepository.findOneByOrFail({ id: job.data.from.id }), this.usersRepository.findOneByOrFail({ id: job.data.to.id }), ]) as [MiLocalUser | MiRemoteUser, MiLocalUser | MiRemoteUser]; - await this.userFollowingService.unfollow(follower, followee, job.data.silent); + await this.userFollowingService.unfollow(follower, followee, job.data.silent).catch((err) => { + if (err instanceof IdentifiableError && err.id === '19f25f61-0141-4683-99dc-217a88d633cb') { + // フォロー解除できないユーザー。動作は正常のため、エラーを無視する + return; + } + throw err; + }); return 'ok'; } @@ -61,7 +68,13 @@ export class RelationshipProcessorService { this.usersRepository.findOneByOrFail({ id: job.data.from.id }), this.usersRepository.findOneByOrFail({ id: job.data.to.id }), ]); - await this.userBlockingService.block(blockee, blocker, job.data.silent); + await this.userBlockingService.block(blockee, blocker, job.data.silent).catch((err) => { + if (err instanceof IdentifiableError && err.id === 'e2f04d25-0d94-4ac3-a4d8-ba401062741b') { + // フォロー解除できない(=ブロックもできない)ユーザー。動作は正常のため、エラーを無視する + return; + } + throw err; + }); return 'ok'; } diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 087140e4fa..30a5652cc3 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -7,13 +7,12 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, BlockingsRepository } from '@/models/_.js'; -import type { MiMeta } from '@/models/Meta.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; -import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; export const meta = { tags: ['account'], @@ -72,16 +71,12 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.usersRepository) private usersRepository: UsersRepository, @Inject(DI.blockingsRepository) private blockingsRepository: BlockingsRepository, - private roleService: RoleService, private userEntityService: UserEntityService, private getterService: GetterService, private userBlockingService: UserBlockingService, @@ -96,7 +91,9 @@ export default class extends Endpoint { // eslint- // Get blockee const blockee = await this.getterService.getUser(ps.userId).catch(err => { - if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + if (err instanceof IdentifiableError && err.id === '15348ddd-432d-49c2-8a5a-8069753becff') { + throw new ApiError(meta.errors.noSuchUser); + } throw err; }); @@ -112,14 +109,11 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyBlocking); } - if ( - this.serverSettings.forciblyFollowedUsers.includes(blockee.id) && - !await this.roleService.isModerator(blocker) - ) { - throw new ApiError(meta.errors.cannotBlockDueToServerPolicy); - } - - await this.userBlockingService.block(blocker, blockee); + await this.userBlockingService.block(blocker, blockee).catch((err) => { + if (err instanceof IdentifiableError && err.id === meta.errors.cannotBlockDueToServerPolicy.id) { + throw new ApiError(meta.errors.cannotBlockDueToServerPolicy); + } + }); return await this.userEntityService.pack(blockee.id, blocker, { schema: 'UserDetailedNotMe', diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 1709fb8e4c..ac1422c5ff 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -7,10 +7,9 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { FollowingsRepository } from '@/models/_.js'; -import type { MiMeta } from '@/models/Meta.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; -import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; @@ -72,13 +71,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, - private roleService: RoleService, private userEntityService: UserEntityService, private getterService: GetterService, private userFollowingService: UserFollowingService, @@ -109,14 +104,11 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.notFollowing); } - if ( - this.serverSettings.forciblyFollowedUsers.includes(followee.id) && - !await this.roleService.isModerator(follower) - ) { - throw new ApiError(meta.errors.cannotUnfollowDueToServerPolicy); - } - - await this.userFollowingService.unfollow(follower, followee); + await this.userFollowingService.unfollow(follower, followee).catch((err) => { + if (err instanceof IdentifiableError && err.id === meta.errors.cannotUnfollowDueToServerPolicy.id) { + throw new ApiError(meta.errors.cannotUnfollowDueToServerPolicy); + } + }); return await this.userEntityService.pack(followee.id, me); }); diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 69762610fa..c86d2b8de6 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -7,12 +7,11 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MutingsRepository } from '@/models/_.js'; -import type { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; -import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; export const meta = { tags: ['account'], @@ -71,13 +70,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, - private roleService: RoleService, private getterService: GetterService, private userMutingService: UserMutingService, ) { @@ -107,18 +102,15 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyMuting); } - if ( - this.serverSettings.forciblyFollowedUsers.includes(mutee.id) && - !await this.roleService.isModerator(muter) - ) { - throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); - } - if (ps.expiresAt && ps.expiresAt <= Date.now()) { return; } - await this.userMutingService.mute(muter, mutee, ps.expiresAt ? new Date(ps.expiresAt) : null); + await this.userMutingService.mute(muter, mutee, ps.expiresAt ? new Date(ps.expiresAt) : null).catch((err) => { + if (err instanceof IdentifiableError && err.id === meta.errors.cannotMuteDueToServerPolicy.id) { + throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); + } + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts index 709f1b170f..40eea71cad 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts @@ -9,10 +9,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; -import { RoleService } from '@/core/RoleService.js'; import { UserRenoteMutingService } from '@/core/UserRenoteMutingService.js'; import type { RenoteMutingsRepository } from '@/models/_.js'; -import type { MiMeta } from '@/models/Meta.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; export const meta = { tags: ['account'], @@ -66,13 +65,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, - private roleService: RoleService, private getterService: GetterService, private userRenoteMutingService: UserRenoteMutingService, ) { @@ -102,15 +97,12 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyMuting); } - if ( - this.serverSettings.forciblyFollowedUsers.includes(mutee.id) && - !await this.roleService.isModerator(muter) - ) { - throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); - } - // Create mute - await this.userRenoteMutingService.mute(muter, mutee); + await this.userRenoteMutingService.mute(muter, mutee).catch((err) => { + if (err instanceof IdentifiableError && err.id === meta.errors.cannotMuteDueToServerPolicy.id) { + throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); + } + }); }); } } From 3ba272fdb49b18b1e9a63da51e934e5b9e285384 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:50:01 +0900 Subject: [PATCH 10/13] lint --- packages/backend/src/core/UserBlockingService.ts | 1 - packages/backend/src/core/UserFollowingService.ts | 1 - packages/backend/src/core/UserMutingService.ts | 1 - packages/backend/src/core/UserRenoteMutingService.ts | 1 - 4 files changed, 4 deletions(-) diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index ca57857eb1..4ef1a60335 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -66,7 +66,6 @@ export class UserBlockingService implements OnModuleInit { @bindThis public async block(blocker: MiUser, blockee: MiUser, silent = false) { - // フォロー解除できない(=ブロックもできない)ユーザーの場合 if ( this.serverSettings.forciblyFollowedUsers.includes(blockee.id) && diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 5e980f27af..d2afade4dd 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -374,7 +374,6 @@ export class UserFollowingService implements OnModuleInit { }, silent = false, ): Promise { - // フォロー解除できないユーザーの場合 if ( this.meta.forciblyFollowedUsers.includes(followee.id) && diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 504b7e0e1b..f4ad9e02bb 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -32,7 +32,6 @@ export class UserMutingService { @bindThis public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { - // フォロー解除できない(=ミュートもできない)ユーザーの場合 if ( this.serverSettings.forciblyFollowedUsers.includes(target.id) && diff --git a/packages/backend/src/core/UserRenoteMutingService.ts b/packages/backend/src/core/UserRenoteMutingService.ts index 1f5c311e3b..ccba91879e 100644 --- a/packages/backend/src/core/UserRenoteMutingService.ts +++ b/packages/backend/src/core/UserRenoteMutingService.ts @@ -34,7 +34,6 @@ export class UserRenoteMutingService { @bindThis public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { - // フォロー解除できない(=リノートミュートもできない)ユーザーの場合 if ( this.serverSettings.forciblyFollowedUsers.includes(target.id) && From b3fc28824911201a4a6ba00f5aea7439d1addd66 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:57:23 +0900 Subject: [PATCH 11/13] =?UTF-8?q?=E3=83=AA=E3=83=A2=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=AB=E5=AF=BE=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=AF=E3=81=93=E3=81=AE=E5=88=B6=E9=99=90=E3=82=92?= =?UTF-8?q?=E3=81=8B=E3=81=91=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/UserBlockingService.ts | 1 + packages/backend/src/core/UserFollowingService.ts | 1 + packages/backend/src/core/UserMutingService.ts | 1 + packages/backend/src/core/UserRenoteMutingService.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 4ef1a60335..e18183f628 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -68,6 +68,7 @@ export class UserBlockingService implements OnModuleInit { public async block(blocker: MiUser, blockee: MiUser, silent = false) { // フォロー解除できない(=ブロックもできない)ユーザーの場合 if ( + blocker.host == null && this.serverSettings.forciblyFollowedUsers.includes(blockee.id) && !await this.roleService.isModerator(blocker) ) { diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index d2afade4dd..bdf215e8c5 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -376,6 +376,7 @@ export class UserFollowingService implements OnModuleInit { ): Promise { // フォロー解除できないユーザーの場合 if ( + follower.host == null && this.meta.forciblyFollowedUsers.includes(followee.id) && !await this.roleService.isModerator(follower) ) { diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index f4ad9e02bb..256d9b4025 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -34,6 +34,7 @@ export class UserMutingService { public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { // フォロー解除できない(=ミュートもできない)ユーザーの場合 if ( + user.host == null && this.serverSettings.forciblyFollowedUsers.includes(target.id) && !await this.roleService.isModerator(user) ) { diff --git a/packages/backend/src/core/UserRenoteMutingService.ts b/packages/backend/src/core/UserRenoteMutingService.ts index ccba91879e..155c844664 100644 --- a/packages/backend/src/core/UserRenoteMutingService.ts +++ b/packages/backend/src/core/UserRenoteMutingService.ts @@ -36,6 +36,7 @@ export class UserRenoteMutingService { public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { // フォロー解除できない(=リノートミュートもできない)ユーザーの場合 if ( + user.host == null && this.serverSettings.forciblyFollowedUsers.includes(target.id) && !await this.roleService.isModerator(user) ) { From 4a68df9c050b9d0f033cbb02f6010cf3fcfa6a55 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 18 Nov 2024 00:02:11 +0900 Subject: [PATCH 12/13] fix --- .../backend/migration/1728986848483-defaultFollowUsers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/migration/1728986848483-defaultFollowUsers.js b/packages/backend/migration/1728986848483-defaultFollowUsers.js index 53eed5d558..0a3efd050c 100644 --- a/packages/backend/migration/1728986848483-defaultFollowUsers.js +++ b/packages/backend/migration/1728986848483-defaultFollowUsers.js @@ -8,11 +8,11 @@ export class DefaultFollowUsers1728986848483 { async up(queryRunner) { await queryRunner.query(`ALTER TABLE "meta" ADD "defaultFollowedUsers" character varying(1024) array NOT NULL DEFAULT '{}'`); - await queryRunner.query(`ALTER TABLE "meta" ADD "permanentFollowedUsers" character varying(1024) array NOT NULL DEFAULT '{}'`); + await queryRunner.query(`ALTER TABLE "meta" ADD "forciblyFollowedUsers" character varying(1024) array NOT NULL DEFAULT '{}'`); } async down(queryRunner) { - await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "permanentFollowedUsers"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "forciblyFollowedUsers"`); await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "defaultFollowedUsers"`); } } From c85dd40871d484f2b5db438163ea20eec17e5b20 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 18 Nov 2024 00:03:35 +0900 Subject: [PATCH 13/13] fix: wrong i18n key --- packages/frontend/src/components/MkFollowButton.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 703805107b..bc4f4e6e96 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -103,7 +103,7 @@ async function onClick() { os.alert({ type: 'error', title: i18n.ts.permissionDeniedError, - text: i18n.ts.unfollowThisUserProhibited, + text: i18n.ts.unfollowThisUserIsProhibited, }); } });