feat(emoji): 管理用のカスタム絵文字フィールドの追加 (MisskeyIO#397)
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class EmojiMoreFields1706723072096 {
|
||||
name = 'EmojiMoreFields1706723072096'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" ADD "requestedBy" character varying(1024)`);
|
||||
await queryRunner.query(`ALTER TABLE "emoji" ADD "memo" character varying(8192) NOT NULL DEFAULT ''`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "memo"`);
|
||||
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "requestedBy"`);
|
||||
}
|
||||
}
|
@@ -66,6 +66,8 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
license: string | null;
|
||||
isSensitive: boolean;
|
||||
localOnly: boolean;
|
||||
requestedBy: string | null;
|
||||
memo: string | null;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: MiRole['id'][];
|
||||
}, moderator?: MiUser): Promise<MiEmoji> {
|
||||
@@ -82,6 +84,8 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
license: data.license,
|
||||
isSensitive: data.isSensitive,
|
||||
localOnly: data.localOnly,
|
||||
requestedBy: data.requestedBy,
|
||||
memo: data.memo,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: data.roleIdsThatCanNotBeUsedThisEmojiAsReaction,
|
||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||
@@ -113,6 +117,8 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
license?: string | null;
|
||||
isSensitive?: boolean;
|
||||
localOnly?: boolean;
|
||||
requestedBy?: string | null;
|
||||
memo?: string | null;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction?: MiRole['id'][];
|
||||
}, moderator?: MiUser): Promise<void> {
|
||||
@@ -131,6 +137,8 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
|
||||
publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
|
||||
type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
|
||||
requestedBy: data.requestedBy ?? undefined,
|
||||
memo: data.memo ?? undefined,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: data.roleIdsThatCanNotBeUsedThisEmojiAsReaction ?? undefined,
|
||||
});
|
||||
|
@@ -9,12 +9,15 @@ import type { EmojisRepository } from '@/models/_.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { MiEmoji } from '@/models/Emoji.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
@Injectable()
|
||||
export class EmojiEntityService {
|
||||
constructor(
|
||||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
private idService: IdService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -75,4 +78,39 @@ export class EmojiEntityService {
|
||||
.filter(result => result.status === 'fulfilled')
|
||||
.map(result => (result as PromiseFulfilledResult<Packed<'EmojiDetailed'>>).value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packInternal(
|
||||
src: MiEmoji['id'] | MiEmoji,
|
||||
): Promise<Packed<'EmojiDetailed'>> {
|
||||
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: emoji.id,
|
||||
createdAt: this.idService.parse(emoji.id).date.toISOString(),
|
||||
updatedAt: emoji.updatedAt?.toISOString() ?? null,
|
||||
aliases: emoji.aliases,
|
||||
name: emoji.name,
|
||||
category: emoji.category,
|
||||
host: emoji.host,
|
||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||
url: emoji.publicUrl || emoji.originalUrl,
|
||||
license: emoji.license,
|
||||
isSensitive: emoji.isSensitive,
|
||||
localOnly: emoji.localOnly,
|
||||
requestedBy: emoji.requestedBy,
|
||||
memo: emoji.memo,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packInternalMany(
|
||||
emojis: (MiEmoji['id'] | MiEmoji)[],
|
||||
) : Promise<Packed<'EmojiDetailed'>[]> {
|
||||
return (await Promise.allSettled(emojis.map(x => this.packInternal(x))))
|
||||
.filter(result => result.status === 'fulfilled')
|
||||
.map(result => (result as PromiseFulfilledResult<Packed<'EmojiDetailed'>>).value);
|
||||
}
|
||||
}
|
||||
|
@@ -76,6 +76,16 @@ export class MiEmoji {
|
||||
})
|
||||
public isSensitive: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: true,
|
||||
})
|
||||
public requestedBy: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 8192, default: '',
|
||||
})
|
||||
public memo: string | null;
|
||||
|
||||
// TODO: 定期ジョブで存在しなくなったロールIDを除去するようにする
|
||||
@Column('varchar', {
|
||||
array: true, length: 128, default: '{}',
|
||||
|
@@ -60,6 +60,16 @@ export const packedEmojiDetailedSchema = {
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
@@ -98,6 +108,14 @@ export const packedEmojiDetailedSchema = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
requestedBy: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
memo: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
|
@@ -102,6 +102,8 @@ export class ImportCustomEmojisProcessorService {
|
||||
license: emojiInfo.license,
|
||||
isSensitive: emojiInfo.isSensitive,
|
||||
localOnly: emojiInfo.localOnly,
|
||||
requestedBy: emojiInfo.requestedBy,
|
||||
memo: emojiInfo.memo,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: [],
|
||||
});
|
||||
|
@@ -50,6 +50,8 @@ export const paramDef = {
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean' },
|
||||
localOnly: { type: 'boolean' },
|
||||
requestedBy: { type: 'string', nullable: true },
|
||||
memo: { type: 'string', nullable: true },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
format: 'misskey:id',
|
||||
@@ -89,6 +91,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
license: ps.license ?? null,
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
requestedBy: ps.requestedBy ?? null,
|
||||
memo: ps.memo ?? null,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: ps.roleIdsThatCanNotBeUsedThisEmojiAsReaction ?? [],
|
||||
}, me);
|
||||
|
@@ -106,6 +106,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
license: emoji.license,
|
||||
isSensitive: emoji.isSensitive,
|
||||
localOnly: emoji.localOnly,
|
||||
requestedBy: emoji.requestedBy,
|
||||
memo: emoji.memo,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanNotBeUsedThisEmojiAsReaction,
|
||||
}, me);
|
||||
|
@@ -106,7 +106,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
.limit(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return this.emojiEntityService.packDetailedMany(emojis);
|
||||
return this.emojiEntityService.packInternalMany(emojis);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,6 @@ import type { MiEmoji } from '@/models/Emoji.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
//import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -88,28 +87,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
let emojis: MiEmoji[];
|
||||
|
||||
if (ps.query) {
|
||||
//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
|
||||
//const emojis = await q.limit(ps.limit).getMany();
|
||||
if (ps.query.startsWith(':') && ps.query.endsWith(':') && ps.query.length > 2) {
|
||||
// 登録名と完全一致の検索
|
||||
q.andWhere('emoji.name = :q', { q: ps.query.slice(1, -1) });
|
||||
|
||||
emojis = await q.getMany();
|
||||
const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g);
|
||||
|
||||
if (queryarry) {
|
||||
emojis = emojis.filter(emoji =>
|
||||
queryarry.includes(`:${emoji.name}:`),
|
||||
);
|
||||
emojis = await q.limit(ps.limit).getMany();
|
||||
} else {
|
||||
emojis = emojis.filter(emoji =>
|
||||
emoji.name.includes(ps.query!) ||
|
||||
emoji.aliases.some(a => a.includes(ps.query!)) ||
|
||||
emoji.category?.includes(ps.query!));
|
||||
// 登録名、エイリアス、カテゴリーの部分一致の検索
|
||||
// TODO: クエリーで処理したいが、aliasesがarrayなので複雑になりすぎるためいったん放置
|
||||
emojis = (await q.getMany())
|
||||
.filter(emoji =>
|
||||
emoji.name.includes(ps.query!) ||
|
||||
emoji.aliases.some(a => a.includes(ps.query!)) ||
|
||||
emoji.category?.includes(ps.query!))
|
||||
.splice(ps.limit + 1);
|
||||
}
|
||||
emojis.splice(ps.limit + 1);
|
||||
} else {
|
||||
emojis = await q.limit(ps.limit).getMany();
|
||||
}
|
||||
|
||||
return this.emojiEntityService.packDetailedMany(emojis);
|
||||
return this.emojiEntityService.packInternalMany(emojis);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -53,6 +53,8 @@ export const paramDef = {
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean' },
|
||||
localOnly: { type: 'boolean' },
|
||||
requestedBy: { type: 'string', nullable: true },
|
||||
memo: { type: 'string', nullable: true },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
format: 'misskey:id',
|
||||
@@ -98,6 +100,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
license: ps.license ?? null,
|
||||
isSensitive: ps.isSensitive,
|
||||
localOnly: ps.localOnly,
|
||||
requestedBy: ps.requestedBy ?? null,
|
||||
memo: ps.memo ?? null,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
roleIdsThatCanNotBeUsedThisEmojiAsReaction: ps.roleIdsThatCanNotBeUsedThisEmojiAsReaction,
|
||||
}, me);
|
||||
|
Reference in New Issue
Block a user