fix api call

This commit is contained in:
samunohito 2024-02-06 17:21:47 +09:00
parent 041449e962
commit c34d3234d5
4 changed files with 45 additions and 228 deletions

View File

@ -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();

View File

@ -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() {

View File

@ -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) {

View File

@ -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) {
// IDsinceId
emojis.reverse();
}
customEmojis.value = emojis;
console.log(customEmojis.value);
customEmojis.value = emojis.emojis;
gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it));
}