add list v2 endpoint
This commit is contained in:
parent
d5db737469
commit
f8529a01b9
|
@ -3,6 +3,7 @@
|
||||||
* 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';
|
||||||
|
@ -21,6 +22,53 @@ import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
|
||||||
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
||||||
|
|
||||||
|
export const fetchEmojisHostTypes = [
|
||||||
|
'local',
|
||||||
|
'remote',
|
||||||
|
'all',
|
||||||
|
] as const;
|
||||||
|
export type FetchEmojisHostTypes = typeof fetchEmojisHostTypes[number];
|
||||||
|
export const fetchEmojisSortKeys = [
|
||||||
|
'id',
|
||||||
|
'updatedAt',
|
||||||
|
'name',
|
||||||
|
'host',
|
||||||
|
'uri',
|
||||||
|
'publicUrl',
|
||||||
|
'type',
|
||||||
|
'aliases',
|
||||||
|
'category',
|
||||||
|
'license',
|
||||||
|
'isSensitive',
|
||||||
|
'localOnly',
|
||||||
|
] as const;
|
||||||
|
export type FetchEmojisSortKeys = typeof fetchEmojisSortKeys[number];
|
||||||
|
export type FetchEmojisParams = {
|
||||||
|
query?: {
|
||||||
|
updatedAtFrom?: string;
|
||||||
|
updatedAtTo?: string;
|
||||||
|
name?: string;
|
||||||
|
host?: string;
|
||||||
|
uri?: string;
|
||||||
|
publicUrl?: string;
|
||||||
|
type?: string;
|
||||||
|
aliases?: string;
|
||||||
|
category?: string;
|
||||||
|
license?: string;
|
||||||
|
isSensitive?: boolean;
|
||||||
|
localOnly?: boolean;
|
||||||
|
hostType?: FetchEmojisHostTypes;
|
||||||
|
},
|
||||||
|
sinceId?: string;
|
||||||
|
untilId?: string;
|
||||||
|
limit?: number;
|
||||||
|
page?: number;
|
||||||
|
sort?: {
|
||||||
|
key : FetchEmojisSortKeys;
|
||||||
|
order : 'ASC' | 'DESC';
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CustomEmojiService implements OnApplicationShutdown {
|
export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
private cache: MemoryKVCache<MiEmoji | null>;
|
private cache: MemoryKVCache<MiEmoji | null>;
|
||||||
|
@ -99,64 +147,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
return emoji;
|
return emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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').whereInIds(x.identifiers).getMany());
|
|
||||||
|
|
||||||
const localEmojis = emojis.filter(it => it.host == null);
|
|
||||||
if (localEmojis.length > 0) {
|
|
||||||
this.localEmojisCache.refresh();
|
|
||||||
|
|
||||||
this.emojiEntityService.packDetailedMany(localEmojis).then(it => {
|
|
||||||
for (const emoji of it) {
|
|
||||||
this.globalEventService.publishBroadcastStream('emojiAdded', { emoji });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (moderator) {
|
|
||||||
for (const emoji of localEmojis) {
|
|
||||||
this.moderationLogService.log(moderator, 'addCustomEmoji', {
|
|
||||||
emojiId: emoji.id,
|
|
||||||
emoji: emoji,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return emojis;
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async update(id: MiEmoji['id'], data: {
|
public async update(id: MiEmoji['id'], data: {
|
||||||
driveFile?: MiDriveFile;
|
driveFile?: MiDriveFile;
|
||||||
|
@ -214,103 +204,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
this.moderationLogService.log(moderator, 'updateCustomEmoji', {
|
|
||||||
emojiId: emoji.id,
|
|
||||||
before: emoji,
|
|
||||||
after: updatedEmojis.get(emoji.id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async addAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) {
|
public async addAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) {
|
||||||
const emojis = await this.emojisRepository.findBy({
|
const emojis = await this.emojisRepository.findBy({
|
||||||
|
@ -545,6 +438,265 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
return this.emojisRepository.findOneBy({ id });
|
return this.emojisRepository.findOneBy({ id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async fetchEmojis(params?: FetchEmojisParams) {
|
||||||
|
const builder = this.emojisRepository.createQueryBuilder('emoji');
|
||||||
|
if (params?.query) {
|
||||||
|
const q = params.query;
|
||||||
|
if (q.updatedAtFrom) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.updatedAt >= :updateAtFrom', { updateAtFrom: q.updatedAtFrom });
|
||||||
|
}
|
||||||
|
if (q.updatedAtTo) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.updatedAt <= :updateAtTo', { updateAtTo: q.updatedAtTo });
|
||||||
|
}
|
||||||
|
if (q.name) {
|
||||||
|
builder.andWhere('emoji.name LIKE :name', { name: `%${q.name}%` });
|
||||||
|
}
|
||||||
|
if (q.hostType === 'local') {
|
||||||
|
builder.andWhere('emoji.host LIKE :host', { host: `%${q.host}%` });
|
||||||
|
} else {
|
||||||
|
if (q.host) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.host LIKE :host', { host: `%${q.host}%` });
|
||||||
|
} else {
|
||||||
|
builder.andWhere('emoji.host IS NOT NULL');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (q.uri) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.uri LIKE :uri', { url: `%${q.uri}%` });
|
||||||
|
}
|
||||||
|
if (q.publicUrl) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.publicUrl LIKE :publicUrl', { publicUrl: `%${q.publicUrl}%` });
|
||||||
|
}
|
||||||
|
if (q.type) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.type LIKE :type', { type: `%${q.type}%` });
|
||||||
|
}
|
||||||
|
if (q.aliases) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.aliases ANY(:aliases)', { aliases: q.aliases });
|
||||||
|
}
|
||||||
|
if (q.category) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.category LIKE :category', { category: `%${q.category}%` });
|
||||||
|
}
|
||||||
|
if (q.license) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.license LIKE :license', { license: `%${q.license}%` });
|
||||||
|
}
|
||||||
|
if (q.isSensitive != null) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.isSensitive = :isSensitive', { isSensitive: q.isSensitive });
|
||||||
|
}
|
||||||
|
if (q.localOnly != null) {
|
||||||
|
// noIndexScan
|
||||||
|
builder.andWhere('emoji.localOnly = :localOnly', { localOnly: q.localOnly });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params?.sinceId) {
|
||||||
|
builder.andWhere('emoji.id > :sinceId', { sinceId: params.sinceId });
|
||||||
|
}
|
||||||
|
if (params?.untilId) {
|
||||||
|
builder.andWhere('emoji.id < :untilId', { untilId: params.untilId });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params?.sort) {
|
||||||
|
for (const sort of params.sort) {
|
||||||
|
builder.addOrderBy(`emoji.${sort.key}`, sort.order);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.addOrderBy('emoji.id', 'DESC');
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = params?.limit ?? 10;
|
||||||
|
if (params?.page) {
|
||||||
|
builder.skip((params.page - 1) * limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.take(limit);
|
||||||
|
|
||||||
|
const [emojis, count] = await builder.getManyAndCount();
|
||||||
|
|
||||||
|
return {
|
||||||
|
emojis,
|
||||||
|
count: (count > limit ? emojis.length : count),
|
||||||
|
allCount: count,
|
||||||
|
allPages: Math.ceil(count / limit),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
|
|
@ -70,5 +70,35 @@ export class EmojiEntityService {
|
||||||
): Promise<Packed<'EmojiDetailed'>[]> {
|
): Promise<Packed<'EmojiDetailed'>[]> {
|
||||||
return Promise.all(emojis.map(x => this.packDetailed(x)));
|
return Promise.all(emojis.map(x => this.packDetailed(x)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packDetailedAdmin(
|
||||||
|
src: MiEmoji['id'] | MiEmoji,
|
||||||
|
): Promise<Packed<'EmojiDetailedAdmin'>> {
|
||||||
|
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: emoji.id,
|
||||||
|
updatedAt: emoji.updatedAt?.toISOString() ?? null,
|
||||||
|
name: emoji.name,
|
||||||
|
host: emoji.host,
|
||||||
|
uri: emoji.uri,
|
||||||
|
type: emoji.type,
|
||||||
|
aliases: emoji.aliases,
|
||||||
|
category: emoji.category,
|
||||||
|
publicUrl: emoji.publicUrl,
|
||||||
|
license: emoji.license,
|
||||||
|
localOnly: emoji.localOnly,
|
||||||
|
isSensitive: emoji.isSensitive,
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public packDetailedAdminMany(
|
||||||
|
emojis: any[],
|
||||||
|
): Promise<Packed<'EmojiDetailedAdmin'>[]> {
|
||||||
|
return Promise.all(emojis.map(x => this.packDetailedAdmin(x)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,11 @@ import { packedClipSchema } from '@/models/json-schema/clip.js';
|
||||||
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
|
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
|
||||||
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
|
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
|
||||||
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
|
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
|
||||||
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
|
import {
|
||||||
|
packedEmojiDetailedAdminSchema,
|
||||||
|
packedEmojiDetailedSchema,
|
||||||
|
packedEmojiSimpleSchema,
|
||||||
|
} from '@/models/json-schema/emoji.js';
|
||||||
import { packedFlashSchema } from '@/models/json-schema/flash.js';
|
import { packedFlashSchema } from '@/models/json-schema/flash.js';
|
||||||
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
||||||
import { packedSigninSchema } from '@/models/json-schema/signin.js';
|
import { packedSigninSchema } from '@/models/json-schema/signin.js';
|
||||||
|
@ -75,6 +79,7 @@ export const refs = {
|
||||||
GalleryPost: packedGalleryPostSchema,
|
GalleryPost: packedGalleryPostSchema,
|
||||||
EmojiSimple: packedEmojiSimpleSchema,
|
EmojiSimple: packedEmojiSimpleSchema,
|
||||||
EmojiDetailed: packedEmojiDetailedSchema,
|
EmojiDetailed: packedEmojiDetailedSchema,
|
||||||
|
EmojiDetailedAdmin: packedEmojiDetailedAdminSchema,
|
||||||
Flash: packedFlashSchema,
|
Flash: packedFlashSchema,
|
||||||
Signin: packedSigninSchema,
|
Signin: packedSigninSchema,
|
||||||
RoleLite: packedRoleLiteSchema,
|
RoleLite: packedRoleLiteSchema,
|
||||||
|
|
|
@ -100,3 +100,74 @@ export const packedEmojiDetailedSchema = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const packedEmojiDetailedAdminSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'id',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date-time',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
description: 'The local host is represented with `null`.',
|
||||||
|
},
|
||||||
|
publicUrl: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
uri: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
aliases: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'id',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
license: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
localOnly: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
isSensitive: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
|
@ -43,6 +43,7 @@ import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-al
|
||||||
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
||||||
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
||||||
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
||||||
|
import * as ep___admin_emoji_v2_list from './endpoints/admin/emoji/v2/list.js';
|
||||||
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
||||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||||
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
||||||
|
@ -414,6 +415,7 @@ const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-ali
|
||||||
const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
|
const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
|
||||||
const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
|
const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
|
||||||
const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
|
const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
|
||||||
|
const $admin_emoji_v2_list: Provider = { provide: 'ep:admin/emoji/v2/list', useClass: ep___admin_emoji_v2_list.default };
|
||||||
const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
|
const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
|
||||||
const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
|
const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
|
||||||
const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
|
const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
|
||||||
|
@ -789,6 +791,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_emoji_setCategoryBulk,
|
$admin_emoji_setCategoryBulk,
|
||||||
$admin_emoji_setLicenseBulk,
|
$admin_emoji_setLicenseBulk,
|
||||||
$admin_emoji_update,
|
$admin_emoji_update,
|
||||||
|
$admin_emoji_v2_list,
|
||||||
$admin_federation_deleteAllFiles,
|
$admin_federation_deleteAllFiles,
|
||||||
$admin_federation_refreshRemoteInstanceMetadata,
|
$admin_federation_refreshRemoteInstanceMetadata,
|
||||||
$admin_federation_removeAllFollowing,
|
$admin_federation_removeAllFollowing,
|
||||||
|
@ -1158,6 +1161,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_emoji_setCategoryBulk,
|
$admin_emoji_setCategoryBulk,
|
||||||
$admin_emoji_setLicenseBulk,
|
$admin_emoji_setLicenseBulk,
|
||||||
$admin_emoji_update,
|
$admin_emoji_update,
|
||||||
|
$admin_emoji_v2_list,
|
||||||
$admin_federation_deleteAllFiles,
|
$admin_federation_deleteAllFiles,
|
||||||
$admin_federation_refreshRemoteInstanceMetadata,
|
$admin_federation_refreshRemoteInstanceMetadata,
|
||||||
$admin_federation_removeAllFollowing,
|
$admin_federation_removeAllFollowing,
|
||||||
|
|
|
@ -44,6 +44,7 @@ import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-al
|
||||||
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
|
||||||
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
|
||||||
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
||||||
|
import * as ep___admin_emoji_v2_list from './endpoints/admin/emoji/v2/list.js';
|
||||||
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
|
||||||
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
|
||||||
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
||||||
|
@ -413,6 +414,7 @@ const eps = [
|
||||||
['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
|
['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
|
||||||
['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
|
['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
|
||||||
['admin/emoji/update', ep___admin_emoji_update],
|
['admin/emoji/update', ep___admin_emoji_update],
|
||||||
|
['admin/emoji/v2/list', ep___admin_emoji_v2_list],
|
||||||
['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
|
['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
|
||||||
['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
|
['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
|
||||||
['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
|
['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||||
|
import { CustomEmojiService, FetchEmojisParams } from '@/core/CustomEmojiService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canManageCustomEmojis',
|
||||||
|
kind: 'read:admin:emoji',
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
emojis: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
ref: 'EmojiDetailedAdmin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
count: { type: 'integer' },
|
||||||
|
allCount: { type: 'integer' },
|
||||||
|
allPages: { type: 'integer' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
query: {
|
||||||
|
type: 'object',
|
||||||
|
nullable: true,
|
||||||
|
properties: {
|
||||||
|
updatedAtFrom: { type: 'string' },
|
||||||
|
updatedAtTo: { type: 'string' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
host: { type: 'string' },
|
||||||
|
uri: { type: 'string' },
|
||||||
|
publicUrl: { type: 'string' },
|
||||||
|
type: { type: 'string' },
|
||||||
|
aliases: { type: 'string' },
|
||||||
|
category: { type: 'string' },
|
||||||
|
license: { type: 'string' },
|
||||||
|
isSensitive: { type: 'boolean' },
|
||||||
|
localOnly: { type: 'boolean' },
|
||||||
|
hostType: { type: 'string', enum: ['local', 'remote', 'all'], default: 'all' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sinceId: { type: 'string', format: 'misskey:id' },
|
||||||
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||||
|
page: { type: 'integer' },
|
||||||
|
sort: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
key: {
|
||||||
|
type: 'string',
|
||||||
|
enum: [
|
||||||
|
'id',
|
||||||
|
'updatedAt',
|
||||||
|
'name',
|
||||||
|
'host',
|
||||||
|
'uri',
|
||||||
|
'publicUrl',
|
||||||
|
'type',
|
||||||
|
'aliases',
|
||||||
|
'category',
|
||||||
|
'license',
|
||||||
|
'isSensitive',
|
||||||
|
'localOnly',
|
||||||
|
],
|
||||||
|
default: 'id',
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['ASC', 'DESC'],
|
||||||
|
default: 'DESC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['key', 'order'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private customEmojiService: CustomEmojiService,
|
||||||
|
private emojiEntityService: EmojiEntityService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const params: FetchEmojisParams = {};
|
||||||
|
|
||||||
|
if (ps.query) {
|
||||||
|
params.query = {
|
||||||
|
updatedAtFrom: ps.query.updatedAtFrom,
|
||||||
|
updatedAtTo: ps.query.updatedAtTo,
|
||||||
|
name: ps.query.name,
|
||||||
|
host: ps.query.host,
|
||||||
|
uri: ps.query.uri,
|
||||||
|
publicUrl: ps.query.publicUrl,
|
||||||
|
type: ps.query.type,
|
||||||
|
aliases: ps.query.aliases,
|
||||||
|
category: ps.query.category,
|
||||||
|
license: ps.query.license,
|
||||||
|
isSensitive: ps.query.isSensitive,
|
||||||
|
localOnly: ps.query.localOnly,
|
||||||
|
hostType: ps.query.hostType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
params.sinceId = ps.sinceId;
|
||||||
|
params.untilId = ps.untilId;
|
||||||
|
params.limit = ps.limit;
|
||||||
|
params.page = ps.page;
|
||||||
|
params.sort = ps.sort?.map(it => ({
|
||||||
|
key: it.key,
|
||||||
|
order: it.order,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const result = await this.customEmojiService.fetchEmojis(params);
|
||||||
|
|
||||||
|
return {
|
||||||
|
emojis: await this.emojiEntityService.packDetailedAdminMany(result.emojis),
|
||||||
|
count: result.count,
|
||||||
|
allCount: result.allCount,
|
||||||
|
allPages: result.allPages,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,12 +22,12 @@ export type GridItem = {
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: string;
|
roleIdsThatCanBeUsedThisEmojiAsReaction: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fromEmojiDetailed(it: Misskey.entities.EmojiDetailed): GridItem {
|
export function fromEmojiDetailedAdmin(it: Misskey.entities.EmojiDetailedAdmin): GridItem {
|
||||||
return {
|
return {
|
||||||
checked: false,
|
checked: false,
|
||||||
id: it.id,
|
id: it.id,
|
||||||
fileId: undefined,
|
fileId: undefined,
|
||||||
url: it.url,
|
url: it.publicUrl,
|
||||||
name: it.name,
|
name: it.name,
|
||||||
host: it.host ?? '',
|
host: it.host ?? '',
|
||||||
category: it.category ?? '',
|
category: it.category ?? '',
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
import { computed, ref, toRefs, watch } from 'vue';
|
import { computed, ref, toRefs, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { fromEmojiDetailed, GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
|
import { fromEmojiDetailedAdmin, GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
|
||||||
import MkGrid from '@/components/grid/MkGrid.vue';
|
import MkGrid from '@/components/grid/MkGrid.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -81,7 +81,7 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
customEmojis: Misskey.entities.EmojiDetailed[];
|
customEmojis: Misskey.entities.EmojiDetailedAdmin[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { customEmojis } = toRefs(props);
|
const { customEmojis } = toRefs(props);
|
||||||
|
@ -287,7 +287,7 @@ function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState)
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshGridItems() {
|
function refreshGridItems() {
|
||||||
gridItems.value = customEmojis.value.map(it => fromEmojiDetailed(it));
|
gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it));
|
||||||
originGridItems.value = JSON.parse(JSON.stringify(gridItems.value));
|
originGridItems.value = JSON.parse(JSON.stringify(gridItems.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,22 +29,14 @@ import XRegisterComponent from '@/pages/admin/custom-emojis-grid.local.register.
|
||||||
|
|
||||||
type PageMode = 'list' | 'register';
|
type PageMode = 'list' | 'register';
|
||||||
|
|
||||||
const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]);
|
const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
|
||||||
const modeTab = ref<PageMode>('list');
|
const modeTab = ref<PageMode>('list');
|
||||||
const query = ref<string>();
|
const query = ref<string>();
|
||||||
|
|
||||||
async function refreshCustomEmojis(query?: string, sinceId?: string, untilId?: string) {
|
async function refreshCustomEmojis(query?: string, sinceId?: string, untilId?: string) {
|
||||||
const emojis = await misskeyApi('admin/emoji/list', {
|
const emojis = await misskeyApi('admin/emoji/v2/list', {
|
||||||
limit: 100,
|
limit: 100,
|
||||||
query: query?.length ? query : undefined,
|
}).then(it => it.emojis);
|
||||||
sinceId,
|
|
||||||
untilId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sinceId) {
|
|
||||||
// 通常はID降順だが、sinceIdを設定すると昇順での並び替えとなるので、逆順にする必要がある
|
|
||||||
emojis.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
customEmojis.value = emojis;
|
customEmojis.value = emojis;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkGrid from '@/components/grid/MkGrid.vue';
|
import MkGrid from '@/components/grid/MkGrid.vue';
|
||||||
import { ColumnSetting } from '@/components/grid/column.js';
|
import { ColumnSetting } from '@/components/grid/column.js';
|
||||||
import { fromEmojiDetailed, GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
|
import { fromEmojiDetailedAdmin, GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
|
||||||
import {
|
import {
|
||||||
GridCellContextMenuEvent,
|
GridCellContextMenuEvent,
|
||||||
GridCellValueChangeEvent,
|
GridCellValueChangeEvent,
|
||||||
|
@ -187,7 +187,7 @@ async function refreshCustomEmojis(query?: string, host?: string, sinceId?: stri
|
||||||
|
|
||||||
customEmojis.value = emojis;
|
customEmojis.value = emojis;
|
||||||
console.log(customEmojis.value);
|
console.log(customEmojis.value);
|
||||||
gridItems.value = customEmojis.value.map(it => fromEmojiDetailed(it));
|
gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it));
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
|
@ -169,6 +169,12 @@ type AdminEmojiSetLicenseBulkRequest = operations['admin/emoji/set-license-bulk'
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminEmojiUpdateRequest = operations['admin/emoji/update']['requestBody']['content']['application/json'];
|
type AdminEmojiUpdateRequest = operations['admin/emoji/update']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminEmojiV2ListRequest = operations['admin/emoji/v2/list']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminEmojiV2ListResponse = operations['admin/emoji/v2/list']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminFederationDeleteAllFilesRequest = operations['admin/federation/delete-all-files']['requestBody']['content']['application/json'];
|
type AdminFederationDeleteAllFilesRequest = operations['admin/federation/delete-all-files']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
@ -996,6 +1002,9 @@ type EmojiDeleted = {
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type EmojiDetailed = components['schemas']['EmojiDetailed'];
|
type EmojiDetailed = components['schemas']['EmojiDetailed'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type EmojiDetailedAdmin = components['schemas']['EmojiDetailedAdmin'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type EmojiRequest = operations['emoji']['requestBody']['content']['application/json'];
|
type EmojiRequest = operations['emoji']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
@ -1133,6 +1142,8 @@ declare namespace entities {
|
||||||
AdminEmojiSetCategoryBulkRequest,
|
AdminEmojiSetCategoryBulkRequest,
|
||||||
AdminEmojiSetLicenseBulkRequest,
|
AdminEmojiSetLicenseBulkRequest,
|
||||||
AdminEmojiUpdateRequest,
|
AdminEmojiUpdateRequest,
|
||||||
|
AdminEmojiV2ListRequest,
|
||||||
|
AdminEmojiV2ListResponse,
|
||||||
AdminFederationDeleteAllFilesRequest,
|
AdminFederationDeleteAllFilesRequest,
|
||||||
AdminFederationRefreshRemoteInstanceMetadataRequest,
|
AdminFederationRefreshRemoteInstanceMetadataRequest,
|
||||||
AdminFederationRemoveAllFollowingRequest,
|
AdminFederationRemoveAllFollowingRequest,
|
||||||
|
@ -1668,6 +1679,7 @@ declare namespace entities {
|
||||||
GalleryPost,
|
GalleryPost,
|
||||||
EmojiSimple,
|
EmojiSimple,
|
||||||
EmojiDetailed,
|
EmojiDetailed,
|
||||||
|
EmojiDetailedAdmin,
|
||||||
Flash,
|
Flash,
|
||||||
Signin,
|
Signin,
|
||||||
RoleLite,
|
RoleLite,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.7
|
* version: 2024.2.0-beta.7
|
||||||
* generatedAt: 2024-02-04T07:16:03.625Z
|
* generatedAt: 2024-02-05T06:03:40.656Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { SwitchCaseResponseType } from '../api.js';
|
import type { SwitchCaseResponseType } from '../api.js';
|
||||||
|
@ -416,6 +416,17 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:admin:emoji*
|
||||||
|
*/
|
||||||
|
request<E extends 'admin/emoji/v2/list', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.7
|
* version: 2024.2.0-beta.7
|
||||||
* generatedAt: 2024-02-04T07:16:03.623Z
|
* generatedAt: 2024-02-05T06:03:40.654Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
@ -54,6 +54,8 @@ import type {
|
||||||
AdminEmojiSetCategoryBulkRequest,
|
AdminEmojiSetCategoryBulkRequest,
|
||||||
AdminEmojiSetLicenseBulkRequest,
|
AdminEmojiSetLicenseBulkRequest,
|
||||||
AdminEmojiUpdateRequest,
|
AdminEmojiUpdateRequest,
|
||||||
|
AdminEmojiV2ListRequest,
|
||||||
|
AdminEmojiV2ListResponse,
|
||||||
AdminFederationDeleteAllFilesRequest,
|
AdminFederationDeleteAllFilesRequest,
|
||||||
AdminFederationRefreshRemoteInstanceMetadataRequest,
|
AdminFederationRefreshRemoteInstanceMetadataRequest,
|
||||||
AdminFederationRemoveAllFollowingRequest,
|
AdminFederationRemoveAllFollowingRequest,
|
||||||
|
@ -596,6 +598,7 @@ export type Endpoints = {
|
||||||
'admin/emoji/set-category-bulk': { req: AdminEmojiSetCategoryBulkRequest; res: EmptyResponse };
|
'admin/emoji/set-category-bulk': { req: AdminEmojiSetCategoryBulkRequest; res: EmptyResponse };
|
||||||
'admin/emoji/set-license-bulk': { req: AdminEmojiSetLicenseBulkRequest; res: EmptyResponse };
|
'admin/emoji/set-license-bulk': { req: AdminEmojiSetLicenseBulkRequest; res: EmptyResponse };
|
||||||
'admin/emoji/update': { req: AdminEmojiUpdateRequest; res: EmptyResponse };
|
'admin/emoji/update': { req: AdminEmojiUpdateRequest; res: EmptyResponse };
|
||||||
|
'admin/emoji/v2/list': { req: AdminEmojiV2ListRequest; res: AdminEmojiV2ListResponse };
|
||||||
'admin/federation/delete-all-files': { req: AdminFederationDeleteAllFilesRequest; res: EmptyResponse };
|
'admin/federation/delete-all-files': { req: AdminFederationDeleteAllFilesRequest; res: EmptyResponse };
|
||||||
'admin/federation/refresh-remote-instance-metadata': { req: AdminFederationRefreshRemoteInstanceMetadataRequest; res: EmptyResponse };
|
'admin/federation/refresh-remote-instance-metadata': { req: AdminFederationRefreshRemoteInstanceMetadataRequest; res: EmptyResponse };
|
||||||
'admin/federation/remove-all-following': { req: AdminFederationRemoveAllFollowingRequest; res: EmptyResponse };
|
'admin/federation/remove-all-following': { req: AdminFederationRemoveAllFollowingRequest; res: EmptyResponse };
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.7
|
* version: 2024.2.0-beta.7
|
||||||
* generatedAt: 2024-02-04T07:16:03.621Z
|
* generatedAt: 2024-02-05T06:03:40.652Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { operations } from './types.js';
|
import { operations } from './types.js';
|
||||||
|
@ -56,6 +56,8 @@ export type AdminEmojiSetAliasesBulkRequest = operations['admin/emoji/set-aliase
|
||||||
export type AdminEmojiSetCategoryBulkRequest = operations['admin/emoji/set-category-bulk']['requestBody']['content']['application/json'];
|
export type AdminEmojiSetCategoryBulkRequest = operations['admin/emoji/set-category-bulk']['requestBody']['content']['application/json'];
|
||||||
export type AdminEmojiSetLicenseBulkRequest = operations['admin/emoji/set-license-bulk']['requestBody']['content']['application/json'];
|
export type AdminEmojiSetLicenseBulkRequest = operations['admin/emoji/set-license-bulk']['requestBody']['content']['application/json'];
|
||||||
export type AdminEmojiUpdateRequest = operations['admin/emoji/update']['requestBody']['content']['application/json'];
|
export type AdminEmojiUpdateRequest = operations['admin/emoji/update']['requestBody']['content']['application/json'];
|
||||||
|
export type AdminEmojiV2ListRequest = operations['admin/emoji/v2/list']['requestBody']['content']['application/json'];
|
||||||
|
export type AdminEmojiV2ListResponse = operations['admin/emoji/v2/list']['responses']['200']['content']['application/json'];
|
||||||
export type AdminFederationDeleteAllFilesRequest = operations['admin/federation/delete-all-files']['requestBody']['content']['application/json'];
|
export type AdminFederationDeleteAllFilesRequest = operations['admin/federation/delete-all-files']['requestBody']['content']['application/json'];
|
||||||
export type AdminFederationRefreshRemoteInstanceMetadataRequest = operations['admin/federation/refresh-remote-instance-metadata']['requestBody']['content']['application/json'];
|
export type AdminFederationRefreshRemoteInstanceMetadataRequest = operations['admin/federation/refresh-remote-instance-metadata']['requestBody']['content']['application/json'];
|
||||||
export type AdminFederationRemoveAllFollowingRequest = operations['admin/federation/remove-all-following']['requestBody']['content']['application/json'];
|
export type AdminFederationRemoveAllFollowingRequest = operations['admin/federation/remove-all-following']['requestBody']['content']['application/json'];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.7
|
* version: 2024.2.0-beta.7
|
||||||
* generatedAt: 2024-02-04T07:16:03.620Z
|
* generatedAt: 2024-02-05T06:03:40.651Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { components } from './types.js';
|
import { components } from './types.js';
|
||||||
|
@ -37,6 +37,7 @@ export type FederationInstance = components['schemas']['FederationInstance'];
|
||||||
export type GalleryPost = components['schemas']['GalleryPost'];
|
export type GalleryPost = components['schemas']['GalleryPost'];
|
||||||
export type EmojiSimple = components['schemas']['EmojiSimple'];
|
export type EmojiSimple = components['schemas']['EmojiSimple'];
|
||||||
export type EmojiDetailed = components['schemas']['EmojiDetailed'];
|
export type EmojiDetailed = components['schemas']['EmojiDetailed'];
|
||||||
|
export type EmojiDetailedAdmin = components['schemas']['EmojiDetailedAdmin'];
|
||||||
export type Flash = components['schemas']['Flash'];
|
export type Flash = components['schemas']['Flash'];
|
||||||
export type Signin = components['schemas']['Signin'];
|
export type Signin = components['schemas']['Signin'];
|
||||||
export type RoleLite = components['schemas']['RoleLite'];
|
export type RoleLite = components['schemas']['RoleLite'];
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.7
|
* version: 2024.2.0-beta.7
|
||||||
* generatedAt: 2024-02-04T07:16:03.539Z
|
* generatedAt: 2024-02-05T06:03:40.574Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -351,6 +351,15 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['admin/emoji/update'];
|
post: operations['admin/emoji/update'];
|
||||||
};
|
};
|
||||||
|
'/admin/emoji/v2/list': {
|
||||||
|
/**
|
||||||
|
* admin/emoji/v2/list
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:admin:emoji*
|
||||||
|
*/
|
||||||
|
post: operations['admin/emoji/v2/list'];
|
||||||
|
};
|
||||||
'/admin/federation/delete-all-files': {
|
'/admin/federation/delete-all-files': {
|
||||||
/**
|
/**
|
||||||
* admin/federation/delete-all-files
|
* admin/federation/delete-all-files
|
||||||
|
@ -4281,6 +4290,24 @@ export type components = {
|
||||||
localOnly: boolean;
|
localOnly: boolean;
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
||||||
};
|
};
|
||||||
|
EmojiDetailedAdmin: {
|
||||||
|
/** Format: id */
|
||||||
|
id: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
updatedAt: string | null;
|
||||||
|
name: string;
|
||||||
|
/** @description The local host is represented with `null`. */
|
||||||
|
host: string | null;
|
||||||
|
publicUrl: string;
|
||||||
|
uri: string | null;
|
||||||
|
type: string | null;
|
||||||
|
aliases: string[];
|
||||||
|
category: string | null;
|
||||||
|
license: string | null;
|
||||||
|
localOnly: boolean;
|
||||||
|
isSensitive: boolean;
|
||||||
|
roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
|
||||||
|
};
|
||||||
Flash: {
|
Flash: {
|
||||||
/**
|
/**
|
||||||
* Format: id
|
* Format: id
|
||||||
|
@ -6839,6 +6866,103 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* admin/emoji/v2/list
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:admin:emoji*
|
||||||
|
*/
|
||||||
|
'admin/emoji/v2/list': {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
query?: ({
|
||||||
|
/** Format: date-time */
|
||||||
|
updatedAtFrom?: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
updatedAtTo?: string;
|
||||||
|
name?: string;
|
||||||
|
host?: string;
|
||||||
|
uri?: string;
|
||||||
|
publicUrl?: string;
|
||||||
|
type?: string;
|
||||||
|
aliases?: string;
|
||||||
|
category?: string;
|
||||||
|
license?: string;
|
||||||
|
isSensitive?: boolean;
|
||||||
|
localOnly?: boolean;
|
||||||
|
/**
|
||||||
|
* @default all
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
hostType?: 'local' | 'remote' | 'all';
|
||||||
|
}) | null;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
sinceId?: string;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
untilId?: string;
|
||||||
|
/** @default 10 */
|
||||||
|
limit?: number;
|
||||||
|
page?: number;
|
||||||
|
sort?: ({
|
||||||
|
/**
|
||||||
|
* @default id
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
key: 'id' | 'updatedAt' | 'name' | 'host' | 'uri' | 'publicUrl' | 'type' | 'aliases' | 'category' | 'license' | 'isSensitive' | 'localOnly';
|
||||||
|
/**
|
||||||
|
* @default DESC
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
order: 'ASC' | 'DESC';
|
||||||
|
})[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (with results) */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
emojis: components['schemas']['EmojiDetailedAdmin'][];
|
||||||
|
count: number;
|
||||||
|
allCount: number;
|
||||||
|
allPages: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* admin/federation/delete-all-files
|
* admin/federation/delete-all-files
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
|
Loading…
Reference in New Issue