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] =?UTF-8?q?feat:=20=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB?= =?UTF-8?q?=E3=83=88=E3=81=A7=E3=83=95=E3=82=A9=E3=83=AD=E3=83=BC=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=82=92=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=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;