From c0bd1b82abbb937f35ba0fa9e75149efb56def5a Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Thu, 25 May 2023 16:53:29 +0000
Subject: [PATCH] wip

---
 .../backend/src/server/api/endpoint-base.ts   |   4 +-
 .../endpoints/admin/emoji/add-aliases-bulk.ts |  25 +-
 .../server/api/endpoints/admin/emoji/add.ts   |  45 +---
 .../server/api/endpoints/admin/emoji/copy.ts  |  42 +--
 .../api/endpoints/admin/emoji/delete-bulk.ts  |  16 +-
 .../api/endpoints/admin/emoji/delete.ts       |  28 +-
 .../api/endpoints/admin/emoji/import-zip.ts   |   5 +-
 .../api/endpoints/admin/emoji/list-remote.ts  |  70 +----
 .../server/api/endpoints/admin/emoji/list.ts  |  64 +----
 packages/misskey-js/package.json              |   1 +
 packages/misskey-js/src/endpoints.ts          | 245 ++++++++++++++++--
 packages/misskey-js/src/endpoints.types.ts    |   6 +-
 packages/misskey-js/src/schemas.ts            |   2 +-
 packages/misskey-js/src/schemas/emoji.ts      |  22 +-
 pnpm-lock.yaml                                |  18 +-
 15 files changed, 292 insertions(+), 301 deletions(-)

diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts
index 9365051647..df064e8f86 100644
--- a/packages/backend/src/server/api/endpoint-base.ts
+++ b/packages/backend/src/server/api/endpoint-base.ts
@@ -21,7 +21,7 @@ type File = {
 	path: string;
 };
 
-export type Executor<T extends IEndpointMeta, P = SchemaOrUndefined<T['defines'][number]['req']>> =
+export type Executor<T extends IEndpointMeta, P = SchemaOrUndefined<T['defines'][number]['req'], true>> =
 	(
 		params: P,
 		user: LocalUser | (T['requireCredential'] extends true ? never : null),
@@ -30,7 +30,7 @@ export type Executor<T extends IEndpointMeta, P = SchemaOrUndefined<T['defines']
 		cleanup?: () => any,
 		ip?: string | null,
 		headers?: Record<string, string> | null
-	) => Promise<WeakSerialized<ResponseOf<T, P>>>;
+	) => Promise<WeakSerialized<ResponseOf<T, P, true>>>;
 
 // ExecutorWrapperの型はあえて緩くしておく
 export type ExecutorWrapper =
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
index 6e604ed885..5425f92537 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
@@ -2,33 +2,14 @@ import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
 
-export const meta = {
-	tags: ['admin'],
-
-	requireCredential: true,
-	requireRolePolicy: 'canManageCustomEmojis',
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		ids: { type: 'array', items: {
-			type: 'string', format: 'misskey:id',
-		} },
-		aliases: { type: 'array', items: {
-			type: 'string',
-		} },
-	},
-	required: ['ids', 'aliases'],
-} as const;
-
 // eslint-disable-next-line import/no-default-export
 @Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
+export default class extends Endpoint<'admin/emoji/add-aliases-bulk'> {
+	name = 'admin/emoji/add-aliases-bulk' as const;
 	constructor(
 		private customEmojiService: CustomEmojiService,
 	) {
-		super(meta, paramDef, async (ps, me) => {
+		super(async (ps, me) => {
 			await this.customEmojiService.addAliasesBulk(ps.ids, ps.aliases);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
index 509224e9c3..01afd54b57 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -7,49 +7,12 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
 import { ApiError } from '../../../error.js';
 
-export const meta = {
-	tags: ['admin'],
-
-	requireCredential: true,
-	requireRolePolicy: 'canManageCustomEmojis',
-
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
-		fileId: { type: 'string', format: 'misskey:id' },
-		category: {
-			type: 'string',
-			nullable: true,
-			description: 'Use `null` to reset the category.',
-		},
-		aliases: { type: 'array', items: {
-			type: 'string',
-		} },
-		license: { type: 'string', nullable: true },
-		isSensitive: { type: 'boolean' },
-		localOnly: { type: 'boolean' },
-		roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
-			type: 'string',
-		} },
-	},
-	required: ['name', 'fileId'],
-} as const;
-
 // TODO: ロジックをサービスに切り出す
 
 // eslint-disable-next-line import/no-default-export
 @Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
+export default class extends Endpoint<'admin/emoji/add'> {
+	name = 'admin/emoji/add' as const;
 	constructor(
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
@@ -58,9 +21,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 		private moderationLogService: ModerationLogService,
 	) {
-		super(meta, paramDef, async (ps, me) => {
+		super(async (ps, me) => {
 			const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
-			if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
+			if (driveFile == null) throw new ApiError(this.meta.errors.noSuchFile);
 
 			const emoji = await this.customEmojiService.add({
 				driveFile,
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
index 82dca9cc70..f8d4fba6db 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -10,46 +10,12 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 import { ApiError } from '../../../error.js';
 
-export const meta = {
-	tags: ['admin'],
-
-	requireCredential: true,
-	requireRolePolicy: 'canManageCustomEmojis',
-
-	errors: {
-		noSuchEmoji: {
-			message: 'No such emoji.',
-			code: 'NO_SUCH_EMOJI',
-			id: 'e2785b66-dca3-4087-9cac-b93c541cc425',
-		},
-	},
-
-	res: {
-		type: 'object',
-		optional: false, nullable: false,
-		properties: {
-			id: {
-				type: 'string',
-				optional: false, nullable: false,
-				format: 'id',
-			},
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		emojiId: { type: 'string', format: 'misskey:id' },
-	},
-	required: ['emojiId'],
-} as const;
-
 // TODO: ロジックをサービスに切り出す
 
 // eslint-disable-next-line import/no-default-export
 @Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
+export default class extends Endpoint<'admin/emoji/copy'> {
+	name = 'admin/emoji/copy' as const;
 	constructor(
 		@Inject(DI.db)
 		private db: DataSource,
@@ -62,11 +28,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private globalEventService: GlobalEventService,
 		private driveService: DriveService,
 	) {
-		super(meta, paramDef, async (ps, me) => {
+		super(async (ps, me) => {
 			const emoji = await this.emojisRepository.findOneBy({ id: ps.emojiId });
 
 			if (emoji == null) {
-				throw new ApiError(meta.errors.noSuchEmoji);
+				throw new ApiError(this.meta.errors.noSuchEmoji);
 			}
 
 			let driveFile: DriveFile;
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
index 9f8263629b..f2c65d8013 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
@@ -3,29 +3,19 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
 
 export const meta = {
-	tags: ['admin'],
-
-	requireCredential: true,
-	requireRolePolicy: 'canManageCustomEmojis',
 } as const;
 
 export const paramDef = {
-	type: 'object',
-	properties: {
-		ids: { type: 'array', items: {
-			type: 'string', format: 'misskey:id',
-		} },
-	},
-	required: ['ids'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
 @Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
+export default class extends Endpoint<'admin/emoji/delete-bulk'> {
+	name = 'admin/emoji/delete-bulk' as const;
 	constructor(
 		private customEmojiService: CustomEmojiService,
 	) {
-		super(meta, paramDef, async (ps, me) => {
+		super(async (ps, me) => {
 			await this.customEmojiService.deleteBulk(ps.ids);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
index 429c819fe0..8f117fbe20 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
@@ -2,36 +2,14 @@ import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
 
-export const meta = {
-	tags: ['admin'],
-
-	requireCredential: true,
-	requireRolePolicy: 'canManageCustomEmojis',
-
-	errors: {
-		noSuchEmoji: {
-			message: 'No such emoji.',
-			code: 'NO_SUCH_EMOJI',
-			id: 'be83669b-773a-44b7-b1f8-e5e5170ac3c2',
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		id: { type: 'string', format: 'misskey:id' },
-	},
-	required: ['id'],
-} as const;
-
 // eslint-disable-next-line import/no-default-export
 @Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
+export default class extends Endpoint<'admin/emoji/delete'> {
+	name = 'admin/emoji/delete' as const;
 	constructor(
 		private customEmojiService: CustomEmojiService,
 	) {
-		super(meta, paramDef, async (ps, me) => {
+		super(async (ps, me) => {
 			await this.customEmojiService.delete(ps.id);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
index e26f0506ce..21c6832a6c 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
@@ -18,11 +18,12 @@ export const paramDef = {
 
 // eslint-disable-next-line import/no-default-export
 @Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
+export default class extends Endpoint<'admin/emoji/import-zip'> {
+	name = 'admin/emoji/import-zip' as const;
 	constructor(
 		private queueService: QueueService,
 	) {
-		super(meta, paramDef, async (ps, me) => {
+		super(async (ps, me) => {
 			this.queueService.createImportCustomEmojisJob(me, ps.fileId);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
index df3c28deff..04852d556b 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
@@ -7,74 +7,10 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 import { DI } from '@/di-symbols.js';
 import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
-export const meta = {
-	tags: ['admin'],
-
-	requireCredential: true,
-	requireRolePolicy: 'canManageCustomEmojis',
-
-	res: {
-		type: 'array',
-		optional: false, nullable: false,
-		items: {
-			type: 'object',
-			optional: false, nullable: false,
-			properties: {
-				id: {
-					type: 'string',
-					optional: false, nullable: false,
-					format: 'id',
-				},
-				aliases: {
-					type: 'array',
-					optional: false, nullable: false,
-					items: {
-						type: 'string',
-						optional: false, nullable: false,
-					},
-				},
-				name: {
-					type: 'string',
-					optional: false, nullable: false,
-				},
-				category: {
-					type: 'string',
-					optional: false, nullable: true,
-				},
-				host: {
-					type: 'string',
-					optional: false, nullable: true,
-					description: 'The local host is represented with `null`.',
-				},
-				url: {
-					type: 'string',
-					optional: false, nullable: false,
-				},
-			},
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		query: { type: 'string', nullable: true, default: null },
-		host: {
-			type: 'string',
-			nullable: true,
-			default: null,
-			description: 'Use `null` to represent the local host.',
-		},
-		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
-		sinceId: { type: 'string', format: 'misskey:id' },
-		untilId: { type: 'string', format: 'misskey:id' },
-	},
-	required: [],
-} as const;
-
 // eslint-disable-next-line import/no-default-export
 @Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
+export default class extends Endpoint<'admin/emoji/list-remote'> {
+	name = 'admin/emoji/list-remote' as const;
 	constructor(
 		@Inject(DI.emojisRepository)
 		private emojisRepository: EmojisRepository,
@@ -83,7 +19,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private queryService: QueryService,
 		private emojiEntityService: EmojiEntityService,
 	) {
-		super(meta, paramDef, async (ps, me) => {
+		super(async (ps, me) => {
 			const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId);
 
 			if (ps.host == null) {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
index 4aa4ad82b4..ba5a533430 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
@@ -7,68 +7,10 @@ import { DI } from '@/di-symbols.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 //import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
-export const meta = {
-	tags: ['admin'],
-
-	requireCredential: true,
-	requireRolePolicy: 'canManageCustomEmojis',
-
-	res: {
-		type: 'array',
-		optional: false, nullable: false,
-		items: {
-			type: 'object',
-			optional: false, nullable: false,
-			properties: {
-				id: {
-					type: 'string',
-					optional: false, nullable: false,
-					format: 'id',
-				},
-				aliases: {
-					type: 'array',
-					optional: false, nullable: false,
-					items: {
-						type: 'string',
-						optional: false, nullable: false,
-					},
-				},
-				name: {
-					type: 'string',
-					optional: false, nullable: false,
-				},
-				category: {
-					type: 'string',
-					optional: false, nullable: true,
-				},
-				host: {
-					type: 'string',
-					optional: false, nullable: true,
-					description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.',
-				},
-				url: {
-					type: 'string',
-					optional: false, nullable: false,
-				},
-			},
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		query: { type: 'string', nullable: true, default: null },
-		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
-		sinceId: { type: 'string', format: 'misskey:id' },
-		untilId: { type: 'string', format: 'misskey:id' },
-	},
-	required: [],
-} as const;
-
 // eslint-disable-next-line import/no-default-export
 @Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
+export default class extends Endpoint<'admin/emoji/list'> {
+	name = 'admin/emoji/list' as const;
 	constructor(
 		@Inject(DI.emojisRepository)
 		private emojisRepository: EmojisRepository,
@@ -76,7 +18,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private emojiEntityService: EmojiEntityService,
 		private queryService: QueryService,
 	) {
-		super(meta, paramDef, async (ps, me) => {
+		super(async (ps, me) => {
 			const q = this.queryService.makePaginationQuery(this.emojisRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId)
 				.andWhere('emoji.host IS NULL');
 
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 871875b3a6..dee1e41aa9 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -6,6 +6,7 @@
 	"types": "./built/index.d.ts",
 	"scripts": {
 		"build": "tsc",
+		"dev": "tsc -w",
 		"tsd": "tsd",
 		"api": "pnpm api-extractor run --local --verbose",
 		"api-prod": "pnpm api-extractor run --verbose",
diff --git a/packages/misskey-js/src/endpoints.ts b/packages/misskey-js/src/endpoints.ts
index 355a2f2b70..1ffafa6aea 100644
--- a/packages/misskey-js/src/endpoints.ts
+++ b/packages/misskey-js/src/endpoints.ts
@@ -1,8 +1,8 @@
-import { IEndpointMeta } from "./endpoints.types";
-import { localUsernameSchema, passwordSchema } from "./schemas/user";
+import { IEndpointMeta } from './endpoints.types';
+import { localUsernameSchema, passwordSchema } from './schemas/user';
 
 export const endpoints = {
-    "admin/accounts/create": {
+    'admin/accounts/create': {
         tags: ['admin'],
         defines: [{
             req: {
@@ -28,7 +28,7 @@ export const endpoints = {
             },
         }],
     },
-    "admin/accounts/delete": {
+    'admin/accounts/delete': {
         tags: ['admin'],
     
         requireCredential: true,
@@ -44,7 +44,7 @@ export const endpoints = {
             res: undefined,
         }],
     },
-	"admin/ad/create": {
+	'admin/ad/create': {
 		tags: ['admin'],
 
 		requireCredential: true,
@@ -68,7 +68,7 @@ export const endpoints = {
 			res: undefined,
 		}]
 	},
-	"admin/ad/delete": {
+	'admin/ad/delete': {
 		tags: ['admin'],
 
 		requireCredential: true,
@@ -93,7 +93,7 @@ export const endpoints = {
 			res: undefined,
 		}],
 	},
-	"admin/ad/list": {
+	'admin/ad/list': {
 		tags: ['admin'],
 	
 		requireCredential: true,
@@ -117,7 +117,7 @@ export const endpoints = {
 			},
 		}],
 	},
-	"admin/ad/update": {
+	'admin/ad/update': {
 		tags: ['admin'],
 
 		requireCredential: true,
@@ -150,7 +150,7 @@ export const endpoints = {
 			res: undefined,
 		}],
 	},
-	"admin/announcements/create": {
+	'admin/announcements/create': {
 		tags: ['admin'],
 
 		requireCredential: true,
@@ -176,7 +176,7 @@ export const endpoints = {
 			},
 		}],
 	},
-	"admin/announcements/delete": {
+	'admin/announcements/delete': {
 		tags: ['admin'],
 
 		requireCredential: true,
@@ -201,7 +201,7 @@ export const endpoints = {
 			res: undefined,
 		}],
 	},
-	"admin/announcements/list": {
+	'admin/announcements/list': {
 		tags: ['admin'],
 
 		requireCredential: true,
@@ -225,7 +225,7 @@ export const endpoints = {
 			},
 		}],
 	},
-	"admin/announcements/update": {
+	'admin/announcements/update': {
 		tags: ['admin'],
 	
 		requireCredential: true,
@@ -258,7 +258,7 @@ export const endpoints = {
 			res: undefined,
 		}],
 	},
-	"admin/drive/clean-remote-files": {
+	'admin/drive/clean-remote-files': {
 		tags: ['admin'],
 	
 		requireCredential: true,
@@ -269,7 +269,7 @@ export const endpoints = {
 			res: undefined,
 		}],
 	},
-	"admin/drive/cleanup": {
+	'admin/drive/cleanup': {
 		tags: ['admin'],
 
 		requireCredential: true,
@@ -280,7 +280,7 @@ export const endpoints = {
 			res: undefined,
 		}],
 	},
-	"admin/drive/files": {
+	'admin/drive/files': {
 		tags: ['admin'],
 	
 		requireCredential: true,
@@ -327,7 +327,7 @@ export const endpoints = {
 			},
 		}],
 	},
-	"admin/drive/show-file": {
+	'admin/drive/show-file': {
 		tags: ['admin'],
 	
 		requireCredential: true,
@@ -445,6 +445,219 @@ export const endpoints = {
 			},
 		}],
 	},
+	'admin/emoji/add-aliases-bulk': {
+		tags: ['admin'],
+
+		requireCredential: true,
+		requireRolePolicy: 'canManageCustomEmojis',
+
+		defines: [{
+			req: {
+				type: 'object',
+				properties: {
+					ids: { type: 'array', items: {
+						type: 'string', format: 'misskey:id',
+					} },
+					aliases: { type: 'array', items: {
+						type: 'string',
+					} },
+				},
+				required: ['ids', 'aliases'],
+			},
+			res: undefined,
+		}],
+	},
+	'admin/emoji/add': {
+		tags: ['admin'],
+
+		requireCredential: true,
+		requireRolePolicy: 'canManageCustomEmojis',
+
+		errors: {
+			noSuchFile: {
+				message: 'No such file.',
+				code: 'NO_SUCH_FILE',
+				id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
+			},
+		},
+
+		defines: [{
+			req: {
+				type: 'object',
+				properties: {
+					name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
+					fileId: { type: 'string', format: 'misskey:id' },
+					category: {
+						type: ['string', 'null'],
+						description: 'Use `null` to reset the category.',
+					},
+					aliases: { type: 'array', items: {
+						type: 'string',
+					} },
+					license: { type: ['string', 'null'] },
+					isSensitive: { type: 'boolean' },
+					localOnly: { type: 'boolean' },
+					roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
+						type: 'string',
+					} },
+				},
+				required: ['name', 'fileId'],
+			},
+			res: {
+				type: 'object',
+				properties: {
+					id: { $ref: 'https://misskey-hub.net/api/schemas/Id' },
+				},
+				required: ['id'],
+			},
+		}],
+	},
+	'admin/emoji/copy': {
+		tags: ['admin'],
+
+		requireCredential: true,
+		requireRolePolicy: 'canManageCustomEmojis',
+
+		errors: {
+			noSuchEmoji: {
+				message: 'No such emoji.',
+				code: 'NO_SUCH_EMOJI',
+				id: 'e2785b66-dca3-4087-9cac-b93c541cc425',
+			},
+		},
+
+		defines: [{
+			req: {
+				type: 'object',
+				properties: {
+					emojiId: { type: 'string', format: 'misskey:id' },
+				},
+				required: ['emojiId'],
+			},
+			res: {
+				type: 'object',
+				properties: {
+					id: { $ref: 'https://misskey-hub.net/api/schemas/Id' },
+				},
+				required: ['id'],
+			}
+		}],
+	},
+	'admin/emoji/delete-bulk': {
+		tags: ['admin'],
+
+		requireCredential: true,
+		requireRolePolicy: 'canManageCustomEmojis',
+
+		defines: [{
+			req: {
+				type: 'object',
+				properties: {
+					ids: { type: 'array', items: {
+						type: 'string', format: 'misskey:id',
+					} },
+				},
+				required: ['ids'],
+			},
+			res: undefined,
+		}],
+	},
+	'admin/emoji/delete': {
+		tags: ['admin'],
+	
+		requireCredential: true,
+		requireRolePolicy: 'canManageCustomEmojis',
+	
+		errors: {
+			noSuchEmoji: {
+				message: 'No such emoji.',
+				code: 'NO_SUCH_EMOJI',
+				id: 'be83669b-773a-44b7-b1f8-e5e5170ac3c2',
+			},
+		},
+
+		defines: [{
+			req: {
+				type: 'object',
+				properties: {
+					id: { type: 'string', format: 'misskey:id' },
+				},
+				required: ['id'],
+			},
+			res: undefined,
+		}],
+	},
+	'admin/emoji/import-zip': {
+		secure: true,
+		requireCredential: true,
+		requireRolePolicy: 'canManageCustomEmojis',
+
+		defines: [{
+			req: {
+				type: 'object',
+				properties: {
+					fileId: { type: 'string', format: 'misskey:id' },
+				},
+				required: ['fileId'],
+			},
+			res: undefined,
+		}],
+	},
+	'admin/emoji/list-remote': {
+		tags: ['admin'],
+
+		requireCredential: true,
+		requireRolePolicy: 'canManageCustomEmojis',
+
+		defines: [{
+			req: {
+				type: 'object',
+				properties: {
+					query: { type: ['string', 'null'], default: null },
+					host: {
+						type: ['string', 'null'],
+						default: null,
+						description: 'Use `null` to represent the local host.',
+					},
+					limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+					sinceId: { type: 'string', format: 'misskey:id' },
+					untilId: { type: 'string', format: 'misskey:id' },
+				},
+				required: [],
+			},
+			res: {
+				type: 'array',
+				items: {
+					$ref: 'https://misskey-hub.net/api/schemas/EmojiDetailed',
+				},
+			},
+		}],
+	},
+	'admin/emoji/list': {
+		tags: ['admin'],
+
+		requireCredential: true,
+		requireRolePolicy: 'canManageCustomEmojis',
+
+		defines: [{
+			req: {
+				type: 'object',
+				properties: {
+					query: { type: ['string', 'null'], default: null },
+					limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+					sinceId: { type: 'string', format: 'misskey:id' },
+					untilId: { type: 'string', format: 'misskey:id' },
+				},
+				required: [],
+			},
+			res: {
+				type: 'array',
+				items: {
+					$ref: 'https://misskey-hub.net/api/schemas/EmojiDetailed',
+				},
+			}
+		}],
+	},
 } as const satisfies { [x: string]: IEndpointMeta; };
 
 export function getEndpointSchema(reqres: 'req' | 'res', key: keyof typeof endpoints) {
diff --git a/packages/misskey-js/src/endpoints.types.ts b/packages/misskey-js/src/endpoints.types.ts
index 8696e44361..4120e68408 100644
--- a/packages/misskey-js/src/endpoints.types.ts
+++ b/packages/misskey-js/src/endpoints.types.ts
@@ -127,9 +127,9 @@ export interface IEndpointMeta {
 	readonly cacheSec?: number;
 }
 
-export type SchemaOrUndefined<T extends JSONSchema7 | undefined> = T extends JSONSchema7 ? SchemaType<T, References> : (void | Record<string, never>);
+export type SchemaOrUndefined<T extends JSONSchema7 | undefined, IsResponse extends boolean = false> = T extends JSONSchema7 ? SchemaType<T, References, IsResponse> : (void | Record<string, never>);
 
-export type ResponseOf<D extends IEndpointMeta, P, DD extends D['defines'][number] = D['defines'][number]> =
-	P extends SchemaOrUndefined<DD['req']> ? SchemaOrUndefined<DD['res']> : never;
+export type ResponseOf<D extends IEndpointMeta, P, IsResponse extends boolean = false, DD extends D['defines'][number] = D['defines'][number]> =
+	P extends SchemaOrUndefined<DD['req'], IsResponse> ? SchemaOrUndefined<DD['res']> : never;
 
 export type Endpoints = typeof endpoints;
diff --git a/packages/misskey-js/src/schemas.ts b/packages/misskey-js/src/schemas.ts
index a66ccfb089..147cc6b7a5 100644
--- a/packages/misskey-js/src/schemas.ts
+++ b/packages/misskey-js/src/schemas.ts
@@ -82,5 +82,5 @@ export const refs = {
 
 export type References = GetRefs<typeof refs>;
 
-export type Packed<x extends GetKeys<References, 'https://misskey-hub.net/api/schemas/'>> = GetDef<References, x, 'https://misskey-hub.net/api/schemas/'>;
+export type Packed<x extends GetKeys<References, 'https://misskey-hub.net/api/schemas/'>> = GetDef<References, x, false, 'https://misskey-hub.net/api/schemas/'>;
 export type Def<x extends GetKeys<References>> = GetDef<References, x>;
diff --git a/packages/misskey-js/src/schemas/emoji.ts b/packages/misskey-js/src/schemas/emoji.ts
index 1834abc775..0107f7101d 100644
--- a/packages/misskey-js/src/schemas/emoji.ts
+++ b/packages/misskey-js/src/schemas/emoji.ts
@@ -18,6 +18,13 @@ export const packedEmojiSimpleSchema = {
 		url: {
 			type: 'string',
 		},
+		isSensitive: {
+			type: 'boolean',
+		},
+		roleIdsThatCanBeUsedThisEmojiAsReaction: {
+			type: 'array',
+			items: { $ref: 'https://misskey-hub.net/api/schemas/Id' },
+		}
 	},
 	required: [
 		'aliases',
@@ -36,6 +43,13 @@ export const packedEmojiDetailedSchema = {
 	}, {
 		type: 'object',
 		properties: {
+			isSensitive: {
+				type: 'boolean',
+			},
+			roleIdsThatCanBeUsedThisEmojiAsReaction: {
+				type: 'array',
+				items: { $ref: 'https://misskey-hub.net/api/schemas/Id' },
+			},
 			id: { $ref: 'https://misskey-hub.net/api/schemas/Id' },
 			host: {
 				type: ['string', 'null'],
@@ -43,12 +57,18 @@ export const packedEmojiDetailedSchema = {
 			},
 			license: {
 				type: ['string', 'null'],
-			}
+			},
+			localOnly: {
+				type: 'boolean',
+			},
 		},
 		required: [
+			'isSensitive',
+			'roleIdsThatCanBeUsedThisEmojiAsReaction',
 			'id',
 			'host',
 			'license',
+			'localOnly',
 		],
 	}],
 } as const satisfies JSONSchema7Definition;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 774d139141..ea6a2474ee 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -230,8 +230,8 @@ importers:
         specifier: 10.8.6
         version: 10.8.6
       meilisearch:
-        specifier: 0.32.4
-        version: 0.32.4
+        specifier: 0.32.3
+        version: 0.32.3
       mfm-js:
         specifier: 0.23.3
         version: 0.23.3
@@ -626,7 +626,7 @@ importers:
         version: 29.5.0
       schema-type:
         specifier: github:misskey-dev/schema-type
-        version: github.com/misskey-dev/schema-type/3e1f60a3486ad51a01912bd7bb092d5bcf47473e(typescript@5.0.4)
+        version: github.com/misskey-dev/schema-type/a06bd29b7082bf3344cb5639064065f38c3792c2(typescript@5.0.4)
 
   packages/frontend:
     dependencies:
@@ -1026,7 +1026,7 @@ importers:
         version: 4.4.0
       schema-type:
         specifier: github:misskey-dev/schema-type
-        version: github.com/misskey-dev/schema-type/3e1f60a3486ad51a01912bd7bb092d5bcf47473e(typescript@5.0.4)
+        version: github.com/misskey-dev/schema-type/a06bd29b7082bf3344cb5639064065f38c3792c2(typescript@5.0.4)
       ts-essentials:
         specifier: ^9.3.2
         version: 9.3.2(typescript@5.0.4)
@@ -14616,8 +14616,8 @@ packages:
     engines: {node: '>= 0.6'}
     dev: true
 
-  /meilisearch@0.32.4:
-    resolution: {integrity: sha512-QvPtQ6F2TaqAT9fw072/MDjSCMpQifdtUBFeIk3M5jSnFpeSiv1iwfJWNfP6ByaCgR/s++K1Cqtf9vjcZe7prg==}
+  /meilisearch@0.32.3:
+    resolution: {integrity: sha512-EOgfBuRE5SiIPIpEDYe2HO0D7a4z5bexIgaAdJFma/dH5hx1kwO+u/qb2g3qKyjG+iA3l8MlmTj/Xd72uahaAw==}
     dependencies:
       cross-fetch: 3.1.6
     transitivePeerDependencies:
@@ -20518,9 +20518,9 @@ packages:
     version: 0.0.0
     dev: false
 
-  github.com/misskey-dev/schema-type/3e1f60a3486ad51a01912bd7bb092d5bcf47473e(typescript@5.0.4):
-    resolution: {tarball: https://codeload.github.com/misskey-dev/schema-type/tar.gz/3e1f60a3486ad51a01912bd7bb092d5bcf47473e}
-    id: github.com/misskey-dev/schema-type/3e1f60a3486ad51a01912bd7bb092d5bcf47473e
+  github.com/misskey-dev/schema-type/a06bd29b7082bf3344cb5639064065f38c3792c2(typescript@5.0.4):
+    resolution: {tarball: https://codeload.github.com/misskey-dev/schema-type/tar.gz/a06bd29b7082bf3344cb5639064065f38c3792c2}
+    id: github.com/misskey-dev/schema-type/a06bd29b7082bf3344cb5639064065f38c3792c2
     name: schema-type
     version: 1.0.0
     dependencies: