fix api call
This commit is contained in:
parent
041449e962
commit
c34d3234d5
|
@ -3,7 +3,6 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { setImmediate } from 'node:timers/promises';
|
|
||||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import { In, IsNull } from 'typeorm';
|
import { In, IsNull } from 'typeorm';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
|
@ -77,8 +76,10 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
@Inject(DI.emojisRepository)
|
@Inject(DI.emojisRepository)
|
||||||
private emojisRepository: EmojisRepository,
|
private emojisRepository: EmojisRepository,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private emojiEntityService: EmojiEntityService,
|
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
|
@bindThis
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.cache.dispose();
|
this.cache.dispose();
|
||||||
|
|
|
@ -109,12 +109,12 @@ async function onUpdateClicked() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function action() {
|
const action = () => {
|
||||||
const emptyStrToNull = (value: string) => value === '' ? null : value;
|
const emptyStrToNull = (value: string) => value === '' ? null : value;
|
||||||
const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim());
|
const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim());
|
||||||
|
|
||||||
for (const item of updatedItems) {
|
return updatedItems.map(item =>
|
||||||
await misskeyApi('admin/emoji/update', {
|
misskeyApi('admin/emoji/update', {
|
||||||
id: item.id!,
|
id: item.id!,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
category: emptyStrToNull(item.category),
|
category: emptyStrToNull(item.category),
|
||||||
|
@ -123,15 +123,11 @@ async function onUpdateClicked() {
|
||||||
isSensitive: item.isSensitive,
|
isSensitive: item.isSensitive,
|
||||||
localOnly: item.localOnly,
|
localOnly: item.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction),
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction),
|
||||||
});
|
}),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await os.promiseDialog(
|
|
||||||
action(),
|
|
||||||
() => {},
|
|
||||||
() => {},
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
await os.promiseDialog(Promise.all(action()));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDeleteClicked() {
|
async function onDeleteClicked() {
|
||||||
|
|
|
@ -153,14 +153,14 @@ async function onRegistryClicked() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = new Map<string, GridItem>(gridItems.value.map(it => [`${it.fileId}|${it.name}`, it]));
|
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 emptyStrToNull = (value: string) => value === '' ? null : value;
|
||||||
const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim());
|
const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim());
|
||||||
|
|
||||||
const result = Array.of<UploadResult>();
|
return [...items.entries()].slice(0, MAXIMUM_EMOJI_COUNT)
|
||||||
for (const [key, item] of [...items.entries()].slice(0, MAXIMUM_EMOJI_COUNT)) {
|
.map(([key, item]) =>
|
||||||
try {
|
misskeyApi(
|
||||||
await misskeyApi('admin/emoji/add', {
|
'admin/emoji/add', {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
category: emptyStrToNull(item.category),
|
category: emptyStrToNull(item.category),
|
||||||
aliases: emptyStrToEmptyArray(item.aliases),
|
aliases: emptyStrToEmptyArray(item.aliases),
|
||||||
|
@ -169,16 +169,13 @@ async function onRegistryClicked() {
|
||||||
localOnly: item.localOnly,
|
localOnly: item.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction),
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction),
|
||||||
fileId: item.fileId!,
|
fileId: item.fileId!,
|
||||||
});
|
})
|
||||||
result.push({ key, item, success: true, err: undefined });
|
.then((): UploadResult => ({ key, item, success: true, err: undefined }))
|
||||||
} catch (err: any) {
|
.catch((err: any): UploadResult => ({ key, item, success: false, err })),
|
||||||
result.push({ key, item, success: false, err });
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await os.promiseDialog(upload());
|
const result = await os.promiseDialog(Promise.all(upload()));
|
||||||
const failedItems = result.filter(it => !it.success);
|
const failedItems = result.filter(it => !it.success);
|
||||||
|
|
||||||
if (failedItems.length > 0) {
|
if (failedItems.length > 0) {
|
||||||
|
|
|
@ -61,7 +61,7 @@ const columnSettings: ColumnSetting[] = [
|
||||||
{ bindTo: 'host', title: 'host', type: 'text', editable: false, width: 'auto' },
|
{ 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 gridItems = ref<GridItem[]>([]);
|
||||||
const query = ref<string>('');
|
const query = ref<string>('');
|
||||||
const host = ref<string>('');
|
const host = ref<string>('');
|
||||||
|
@ -144,15 +144,13 @@ function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importEmojis(targets: GridItem[]) {
|
async function importEmojis(targets: GridItem[]) {
|
||||||
async function action() {
|
const action = () => {
|
||||||
for (const target of targets) {
|
return targets.map(target =>
|
||||||
await misskeyApi('admin/emoji/copy', {
|
misskeyApi('admin/emoji/copy', {
|
||||||
emojiId: target.id!,
|
emojiId: target.id!,
|
||||||
});
|
}),
|
||||||
}
|
);
|
||||||
|
};
|
||||||
await refreshCustomEmojis(query.value, host.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
|
@ -161,32 +159,24 @@ async function importEmojis(targets: GridItem[]) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!confirm.canceled) {
|
if (!confirm.canceled) {
|
||||||
await os.promiseDialog(
|
await os.promiseDialog(Promise.all(action()));
|
||||||
action(),
|
await refreshCustomEmojis();
|
||||||
() => {
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshCustomEmojis(query?: string, host?: string, sinceId?: string, untilId?: string) {
|
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,
|
limit: 100,
|
||||||
query: query?.length ? query : undefined,
|
query: {
|
||||||
host: host?.length ? host : undefined,
|
name: query,
|
||||||
sinceId,
|
host: host,
|
||||||
untilId,
|
sinceId: sinceId,
|
||||||
|
untilId: untilId,
|
||||||
|
hostType: 'remote',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sinceId) {
|
customEmojis.value = emojis.emojis;
|
||||||
// 通常はID降順だが、sinceIdを設定すると昇順での並び替えとなるので、逆順にする必要がある
|
|
||||||
emojis.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
customEmojis.value = emojis;
|
|
||||||
console.log(customEmojis.value);
|
|
||||||
gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it));
|
gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue