fix api call
This commit is contained in:
		
							parent
							
								
									041449e962
								
							
						
					
					
						commit
						c34d3234d5
					
				|  | @ -3,7 +3,6 @@ | |||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| import { setImmediate } from 'node:timers/promises'; | ||||
| import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; | ||||
| import { In, IsNull } from 'typeorm'; | ||||
| import * as Redis from 'ioredis'; | ||||
|  | @ -77,8 +76,10 @@ export class CustomEmojiService implements OnApplicationShutdown { | |||
| 	constructor( | ||||
| 		@Inject(DI.redis) | ||||
| 		private redisClient: Redis.Redis, | ||||
| 
 | ||||
| 		@Inject(DI.emojisRepository) | ||||
| 		private emojisRepository: EmojisRepository, | ||||
| 
 | ||||
| 		private utilityService: UtilityService, | ||||
| 		private idService: IdService, | ||||
| 		private emojiEntityService: EmojiEntityService, | ||||
|  | @ -530,173 +531,6 @@ export class CustomEmojiService implements OnApplicationShutdown { | |||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public async addBulk( | ||||
| 		params: { | ||||
| 			driveFile: MiDriveFile; | ||||
| 			name: string; | ||||
| 			category: string | null; | ||||
| 			aliases: string[]; | ||||
| 			host: string | null; | ||||
| 			license: string | null; | ||||
| 			isSensitive: boolean; | ||||
| 			localOnly: boolean; | ||||
| 			roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][]; | ||||
| 		}[], | ||||
| 		moderator?: MiUser, | ||||
| 	): Promise<MiEmoji[]> { | ||||
| 		const emojis = await this.emojisRepository | ||||
| 			.insert( | ||||
| 				params.map(it => ({ | ||||
| 					id: this.idService.gen(), | ||||
| 					updatedAt: new Date(), | ||||
| 					name: it.name, | ||||
| 					category: it.category, | ||||
| 					host: it.host, | ||||
| 					aliases: it.aliases, | ||||
| 					originalUrl: it.driveFile.url, | ||||
| 					publicUrl: it.driveFile.webpublicUrl ?? it.driveFile.url, | ||||
| 					type: it.driveFile.webpublicType ?? it.driveFile.type, | ||||
| 					license: it.license, | ||||
| 					isSensitive: it.isSensitive, | ||||
| 					localOnly: it.localOnly, | ||||
| 					roleIdsThatCanBeUsedThisEmojiAsReaction: it.roleIdsThatCanBeUsedThisEmojiAsReaction, | ||||
| 				})), | ||||
| 			) | ||||
| 			.then(x => this.emojisRepository.createQueryBuilder('emoji') | ||||
| 				.where({ id: In(x.identifiers) }) | ||||
| 				.getMany(), | ||||
| 			); | ||||
| 
 | ||||
| 		// 以降は絵文字登録による副作用なのでリクエストから切り離して実行
 | ||||
| 
 | ||||
| 		// noinspection ES6MissingAwait
 | ||||
| 		setImmediate(async () => { | ||||
| 			const localEmojis = emojis.filter(it => it.host == null); | ||||
| 			if (localEmojis.length > 0) { | ||||
| 				await this.localEmojisCache.refresh(); | ||||
| 
 | ||||
| 				const packedEmojis = await this.emojiEntityService.packDetailedMany(localEmojis); | ||||
| 				for (const emoji of packedEmojis) { | ||||
| 					this.globalEventService.publishBroadcastStream('emojiAdded', { emoji }); | ||||
| 				} | ||||
| 
 | ||||
| 				if (moderator) { | ||||
| 					for (const emoji of localEmojis) { | ||||
| 						await this.moderationLogService.log(moderator, 'addCustomEmoji', { | ||||
| 							emojiId: emoji.id, | ||||
| 							emoji: emoji, | ||||
| 						}); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		return emojis; | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public async updateBulk( | ||||
| 		params: { | ||||
| 			id: MiEmoji['id']; | ||||
| 			driveFile?: MiDriveFile; | ||||
| 			name?: string; | ||||
| 			category?: string | null; | ||||
| 			aliases?: string[]; | ||||
| 			license?: string | null; | ||||
| 			isSensitive?: boolean; | ||||
| 			localOnly?: boolean; | ||||
| 			roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][]; | ||||
| 		}[], | ||||
| 		moderator?: MiUser, | ||||
| 	): Promise<void> { | ||||
| 		const ids = params.map(it => it.id); | ||||
| 
 | ||||
| 		// IDに対応するものと、新しく設定しようとしている名前と同じ名前を持つレコードをそれぞれ取得する
 | ||||
| 		const [storedEmojis, sameNameEmojis] = await Promise.all([ | ||||
| 			this.emojisRepository.createQueryBuilder('emoji') | ||||
| 				.whereInIds(ids) | ||||
| 				.getMany() | ||||
| 				.then(emojis => new Map(emojis.map(it => [it.id, it]))), | ||||
| 			this.emojisRepository.createQueryBuilder('emoji') | ||||
| 				.where('emoji.name IN (:...names) AND emoji.host IS NULL', { names: params.map(it => it.name) }) | ||||
| 				.getMany(), | ||||
| 		]); | ||||
| 
 | ||||
| 		// 新しく設定しようとしている名前と同じ名前を持つ別レコードがある場合、重複とみなしてエラーとする
 | ||||
| 		const alreadyExists = Array.of<string>(); | ||||
| 		for (const sameNameEmoji of sameNameEmojis) { | ||||
| 			const emoji = storedEmojis.get(sameNameEmoji.id); | ||||
| 			if (emoji != null && emoji.id !== sameNameEmoji.id) { | ||||
| 				alreadyExists.push(sameNameEmoji.name); | ||||
| 			} | ||||
| 		} | ||||
| 		if (alreadyExists.length > 0) { | ||||
| 			throw new Error(`name already exists: ${alreadyExists.join(', ')}`); | ||||
| 		} | ||||
| 
 | ||||
| 		for (const emoji of params) { | ||||
| 			await this.emojisRepository.update(emoji.id, { | ||||
| 				updatedAt: new Date(), | ||||
| 				name: emoji.name, | ||||
| 				category: emoji.category, | ||||
| 				aliases: emoji.aliases, | ||||
| 				license: emoji.license, | ||||
| 				isSensitive: emoji.isSensitive, | ||||
| 				localOnly: emoji.localOnly, | ||||
| 				originalUrl: emoji.driveFile != null ? emoji.driveFile.url : undefined, | ||||
| 				publicUrl: emoji.driveFile != null ? (emoji.driveFile.webpublicUrl ?? emoji.driveFile.url) : undefined, | ||||
| 				type: emoji.driveFile != null ? (emoji.driveFile.webpublicType ?? emoji.driveFile.type) : undefined, | ||||
| 				roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined, | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		// 以降は絵文字更新による副作用なのでリクエストから切り離して実行
 | ||||
| 
 | ||||
| 		// noinspection ES6MissingAwait
 | ||||
| 		setImmediate(async () => { | ||||
| 			await this.localEmojisCache.refresh(); | ||||
| 
 | ||||
| 			// 名前が変わっていないものはそのまま更新としてイベント発信
 | ||||
| 			const updateEmojis = params.filter(it => storedEmojis.get(it.id)?.name === it.name); | ||||
| 			if (updateEmojis.length > 0) { | ||||
| 				const packedList = await this.emojiEntityService.packDetailedMany(updateEmojis); | ||||
| 				this.globalEventService.publishBroadcastStream('emojiUpdated', { | ||||
| 					emojis: packedList, | ||||
| 				}); | ||||
| 			} | ||||
| 
 | ||||
| 			// 名前が変わったものは削除・追加としてイベント発信
 | ||||
| 			const nameChangeEmojis = params.filter(it => storedEmojis.get(it.id)?.name !== it.name); | ||||
| 			if (nameChangeEmojis.length > 0) { | ||||
| 				const packedList = await this.emojiEntityService.packDetailedMany(nameChangeEmojis); | ||||
| 				this.globalEventService.publishBroadcastStream('emojiDeleted', { | ||||
| 					emojis: packedList, | ||||
| 				}); | ||||
| 
 | ||||
| 				for (const packed of packedList) { | ||||
| 					this.globalEventService.publishBroadcastStream('emojiAdded', { | ||||
| 						emoji: packed, | ||||
| 					}); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (moderator) { | ||||
| 				const updatedEmojis = await this.emojisRepository.createQueryBuilder('emoji') | ||||
| 					.whereInIds(storedEmojis.keys()) | ||||
| 					.getMany() | ||||
| 					.then(it => new Map(it.map(it => [it.id, it]))); | ||||
| 				for (const emoji of storedEmojis.values()) { | ||||
| 					await this.moderationLogService.log(moderator, 'updateCustomEmoji', { | ||||
| 						emojiId: emoji.id, | ||||
| 						before: emoji, | ||||
| 						after: updatedEmojis.get(emoji.id), | ||||
| 					}); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public dispose(): void { | ||||
| 		this.cache.dispose(); | ||||
|  |  | |||
|  | @ -109,12 +109,12 @@ async function onUpdateClicked() { | |||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	async function action() { | ||||
| 	const action = () => { | ||||
| 		const emptyStrToNull = (value: string) => value === '' ? null : value; | ||||
| 		const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim()); | ||||
| 
 | ||||
| 		for (const item of updatedItems) { | ||||
| 			await misskeyApi('admin/emoji/update', { | ||||
| 		return updatedItems.map(item => | ||||
| 			misskeyApi('admin/emoji/update', { | ||||
| 				id: item.id!, | ||||
| 				name: item.name, | ||||
| 				category: emptyStrToNull(item.category), | ||||
|  | @ -123,15 +123,11 @@ async function onUpdateClicked() { | |||
| 				isSensitive: item.isSensitive, | ||||
| 				localOnly: item.localOnly, | ||||
| 				roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction), | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| 			}), | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
| 	await os.promiseDialog( | ||||
| 		action(), | ||||
| 		() => {}, | ||||
| 		() => {}, | ||||
| 	); | ||||
| 	await os.promiseDialog(Promise.all(action())); | ||||
| } | ||||
| 
 | ||||
| async function onDeleteClicked() { | ||||
|  |  | |||
|  | @ -153,32 +153,29 @@ async function onRegistryClicked() { | |||
| 	} | ||||
| 
 | ||||
| 	const items = new Map<string, GridItem>(gridItems.value.map(it => [`${it.fileId}|${it.name}`, it])); | ||||
| 	const upload = async (): Promise<UploadResult[]> => { | ||||
| 	const upload = (): Promise<UploadResult>[] => { | ||||
| 		const emptyStrToNull = (value: string) => value === '' ? null : value; | ||||
| 		const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim()); | ||||
| 
 | ||||
| 		const result = Array.of<UploadResult>(); | ||||
| 		for (const [key, item] of [...items.entries()].slice(0, MAXIMUM_EMOJI_COUNT)) { | ||||
| 			try { | ||||
| 				await misskeyApi('admin/emoji/add', { | ||||
| 					name: item.name, | ||||
| 					category: emptyStrToNull(item.category), | ||||
| 					aliases: emptyStrToEmptyArray(item.aliases), | ||||
| 					license: emptyStrToNull(item.license), | ||||
| 					isSensitive: item.isSensitive, | ||||
| 					localOnly: item.localOnly, | ||||
| 					roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction), | ||||
| 					fileId: item.fileId!, | ||||
| 				}); | ||||
| 				result.push({ key, item, success: true, err: undefined }); | ||||
| 			} catch (err: any) { | ||||
| 				result.push({ key, item, success: false, err }); | ||||
| 			} | ||||
| 		} | ||||
| 		return result; | ||||
| 		return [...items.entries()].slice(0, MAXIMUM_EMOJI_COUNT) | ||||
| 			.map(([key, item]) => | ||||
| 				misskeyApi( | ||||
| 					'admin/emoji/add', { | ||||
| 						name: item.name, | ||||
| 						category: emptyStrToNull(item.category), | ||||
| 						aliases: emptyStrToEmptyArray(item.aliases), | ||||
| 						license: emptyStrToNull(item.license), | ||||
| 						isSensitive: item.isSensitive, | ||||
| 						localOnly: item.localOnly, | ||||
| 						roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction), | ||||
| 						fileId: item.fileId!, | ||||
| 					}) | ||||
| 					.then((): UploadResult => ({ key, item, success: true, err: undefined })) | ||||
| 					.catch((err: any): UploadResult => ({ key, item, success: false, err })), | ||||
| 			); | ||||
| 	}; | ||||
| 
 | ||||
| 	const result = await os.promiseDialog(upload()); | ||||
| 	const result = await os.promiseDialog(Promise.all(upload())); | ||||
| 	const failedItems = result.filter(it => !it.success); | ||||
| 
 | ||||
| 	if (failedItems.length > 0) { | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ const columnSettings: ColumnSetting[] = [ | |||
| 	{ bindTo: 'host', title: 'host', type: 'text', editable: false, width: 'auto' }, | ||||
| ]; | ||||
| 
 | ||||
| const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]); | ||||
| const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]); | ||||
| const gridItems = ref<GridItem[]>([]); | ||||
| const query = ref<string>(''); | ||||
| const host = ref<string>(''); | ||||
|  | @ -144,15 +144,13 @@ function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState) | |||
| } | ||||
| 
 | ||||
| async function importEmojis(targets: GridItem[]) { | ||||
| 	async function action() { | ||||
| 		for (const target of targets) { | ||||
| 			await misskeyApi('admin/emoji/copy', { | ||||
| 	const action = () => { | ||||
| 		return targets.map(target => | ||||
| 			misskeyApi('admin/emoji/copy', { | ||||
| 				emojiId: target.id!, | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		await refreshCustomEmojis(query.value, host.value); | ||||
| 	} | ||||
| 			}), | ||||
| 		); | ||||
| 	}; | ||||
| 
 | ||||
| 	const confirm = await os.confirm({ | ||||
| 		type: 'info', | ||||
|  | @ -161,32 +159,24 @@ async function importEmojis(targets: GridItem[]) { | |||
| 	}); | ||||
| 
 | ||||
| 	if (!confirm.canceled) { | ||||
| 		await os.promiseDialog( | ||||
| 			action(), | ||||
| 			() => { | ||||
| 			}, | ||||
| 			() => { | ||||
| 			}, | ||||
| 		); | ||||
| 		await os.promiseDialog(Promise.all(action())); | ||||
| 		await refreshCustomEmojis(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| async function refreshCustomEmojis(query?: string, host?: string, sinceId?: string, untilId?: string) { | ||||
| 	const emojis = await misskeyApi('admin/emoji/list-remote', { | ||||
| 	const emojis = await misskeyApi('admin/emoji/v2/list', { | ||||
| 		limit: 100, | ||||
| 		query: query?.length ? query : undefined, | ||||
| 		host: host?.length ? host : undefined, | ||||
| 		sinceId, | ||||
| 		untilId, | ||||
| 		query: { | ||||
| 			name: query, | ||||
| 			host: host, | ||||
| 			sinceId: sinceId, | ||||
| 			untilId: untilId, | ||||
| 			hostType: 'remote', | ||||
| 		}, | ||||
| 	}); | ||||
| 
 | ||||
| 	if (sinceId) { | ||||
| 		// 通常はID降順だが、sinceIdを設定すると昇順での並び替えとなるので、逆順にする必要がある | ||||
| 		emojis.reverse(); | ||||
| 	} | ||||
| 
 | ||||
| 	customEmojis.value = emojis; | ||||
| 	console.log(customEmojis.value); | ||||
| 	customEmojis.value = emojis.emojis; | ||||
| 	gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it)); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue