Merge remote-tracking branch 'misskey-mattyatea/emoji-request' into develop
# Conflicts: # CHANGELOG.md # locales/index.d.ts # locales/ja-JP.yml # packages/backend/src/core/CustomEmojiService.ts # packages/backend/src/models/RepositoryModule.ts # packages/backend/src/server/api/EndpointsModule.ts # packages/backend/src/server/api/endpoints.ts # packages/backend/src/server/api/endpoints/admin/emoji/update.ts # packages/frontend/src/components/MkCustomEmojiEditLocal.vue # packages/frontend/src/components/MkCustomEmojiEditRemote.vue # packages/frontend/src/components/MkEmojiEditDialog.vue # packages/frontend/src/pages/about.emojis.vue # packages/frontend/src/pages/admin/roles.editor.vue # packages/frontend/src/pages/custom-emojis-manager.vue # packages/frontend/src/pages/emojis.emoji.vue
This commit is contained in:
commit
68b48bc16f
35 changed files with 1286 additions and 476 deletions
13
packages/backend/migration/1698131657286-EmojiRequest.js
Normal file
13
packages/backend/migration/1698131657286-EmojiRequest.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export class EmojiRequest1698131657286 {
|
||||
name = 'EmojiRequest1698131657286'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "emoji_request" ("id" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "name" character varying(128) NOT NULL, "category" character varying(128), "originalUrl" character varying(512) NOT NULL, "publicUrl" character varying(512) NOT NULL DEFAULT '', "type" character varying(64), "aliases" character varying(128) array NOT NULL DEFAULT '{}', "license" character varying(1024), "fileId" character varying(1024) NOT NULL, "localOnly" boolean NOT NULL DEFAULT false, "isSensitive" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_3c74521e048dc744f0c7eb65f4a" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ea1d771e867e9843300f09d02c" ON "emoji_request" ("name") `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_ea1d771e867e9843300f09d02c"`);
|
||||
await queryRunner.query(`DROP TABLE "emoji_request"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -90,6 +90,7 @@ import { ClipEntityService } from './entities/ClipEntityService.js';
|
|||
import { DriveFileEntityService } from './entities/DriveFileEntityService.js';
|
||||
import { DriveFolderEntityService } from './entities/DriveFolderEntityService.js';
|
||||
import { EmojiEntityService } from './entities/EmojiEntityService.js';
|
||||
import { EmojiRequestsEntityService } from './entities/EmojiRequestsEntityService.js';
|
||||
import { FollowingEntityService } from './entities/FollowingEntityService.js';
|
||||
import { FollowRequestEntityService } from './entities/FollowRequestEntityService.js';
|
||||
import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js';
|
||||
|
|
@ -225,6 +226,7 @@ const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting
|
|||
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
|
||||
const $DriveFolderEntityService: Provider = { provide: 'DriveFolderEntityService', useExisting: DriveFolderEntityService };
|
||||
const $EmojiEntityService: Provider = { provide: 'EmojiEntityService', useExisting: EmojiEntityService };
|
||||
const $EmojiRequestsEntityService: Provider = { provide: 'EmojiRequestsEntityService', useExisting: EmojiRequestsEntityService };
|
||||
const $FollowingEntityService: Provider = { provide: 'FollowingEntityService', useExisting: FollowingEntityService };
|
||||
const $FollowRequestEntityService: Provider = { provide: 'FollowRequestEntityService', useExisting: FollowRequestEntityService };
|
||||
const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService', useExisting: GalleryLikeEntityService };
|
||||
|
|
@ -360,6 +362,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
DriveFileEntityService,
|
||||
DriveFolderEntityService,
|
||||
EmojiEntityService,
|
||||
EmojiRequestsEntityService,
|
||||
FollowingEntityService,
|
||||
FollowRequestEntityService,
|
||||
GalleryLikeEntityService,
|
||||
|
|
@ -490,6 +493,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$DriveFileEntityService,
|
||||
$DriveFolderEntityService,
|
||||
$EmojiEntityService,
|
||||
$EmojiRequestsEntityService,
|
||||
$FollowingEntityService,
|
||||
$FollowRequestEntityService,
|
||||
$GalleryLikeEntityService,
|
||||
|
|
@ -620,6 +624,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
DriveFileEntityService,
|
||||
DriveFolderEntityService,
|
||||
EmojiEntityService,
|
||||
EmojiRequestsEntityService,
|
||||
FollowingEntityService,
|
||||
FollowRequestEntityService,
|
||||
GalleryLikeEntityService,
|
||||
|
|
@ -749,6 +754,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$DriveFileEntityService,
|
||||
$DriveFolderEntityService,
|
||||
$EmojiEntityService,
|
||||
$EmojiRequestsEntityService,
|
||||
$FollowingEntityService,
|
||||
$FollowRequestEntityService,
|
||||
$GalleryLikeEntityService,
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
|||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiEmoji } from '@/models/Emoji.js';
|
||||
import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js';
|
||||
import type { EmojisRepository, EmojiRequestsRepository, MiRole, MiUser } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import type { Serialized } from '@/types.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
|
||||
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -32,6 +34,9 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
@Inject(DI.emojiRequestsRepository)
|
||||
private emojiRequestsRepository: EmojiRequestsRepository,
|
||||
|
||||
private utilityService: UtilityService,
|
||||
private idService: IdService,
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
|
|
@ -54,6 +59,41 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async request(data: {
|
||||
driveFile: MiDriveFile;
|
||||
name: string;
|
||||
category: string | null;
|
||||
aliases: string[];
|
||||
license: string | null;
|
||||
isSensitive: boolean;
|
||||
localOnly: boolean;
|
||||
}, me?: MiUser): Promise<MiEmojiRequest> {
|
||||
const emoji = await this.emojiRequestsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
updatedAt: new Date(),
|
||||
name: data.name,
|
||||
category: data.category,
|
||||
aliases: data.aliases,
|
||||
originalUrl: data.driveFile.url,
|
||||
publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
|
||||
type: data.driveFile.webpublicType ?? data.driveFile.type,
|
||||
license: data.license,
|
||||
isSensitive: data.isSensitive,
|
||||
localOnly: data.localOnly,
|
||||
fileId: data.driveFile.id,
|
||||
}).then(x => this.emojiRequestsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
if (me) {
|
||||
this.moderationLogService.log(me, 'addCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
emoji: emoji,
|
||||
});
|
||||
}
|
||||
|
||||
return emoji;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async add(data: {
|
||||
driveFile: MiDriveFile;
|
||||
|
|
@ -64,7 +104,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
license: string | null;
|
||||
isSensitive: boolean;
|
||||
localOnly: boolean;
|
||||
draft: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
||||
}, moderator?: MiUser): Promise<MiEmoji> {
|
||||
const emoji = await this.emojisRepository.insert({
|
||||
|
|
@ -81,7 +120,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
isSensitive: data.isSensitive,
|
||||
localOnly: data.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
draft: data.draft,
|
||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
if (data.host == null) {
|
||||
|
|
@ -111,7 +149,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
license?: string | null;
|
||||
isSensitive?: boolean;
|
||||
localOnly?: boolean;
|
||||
draft: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
|
||||
}, moderator?: MiUser): Promise<void> {
|
||||
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||
|
|
@ -126,7 +163,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
license: data.license,
|
||||
isSensitive: data.isSensitive,
|
||||
localOnly: data.localOnly,
|
||||
draft: data.draft,
|
||||
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,
|
||||
|
|
@ -161,6 +197,36 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async updateRequest(id: MiEmojiRequest['id'], data: {
|
||||
driveFile?: MiDriveFile;
|
||||
name?: string;
|
||||
category?: string | null;
|
||||
aliases?: string[];
|
||||
license?: string | null;
|
||||
isSensitive?: boolean;
|
||||
localOnly?: boolean;
|
||||
}, moderator?: MiUser): Promise<void> {
|
||||
const emoji = await this.emojiRequestsRepository.findOneByOrFail({ id: id });
|
||||
const sameNameEmoji = await this.emojiRequestsRepository.findOneBy({ name: data.name });
|
||||
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
|
||||
|
||||
await this.emojiRequestsRepository.update(emoji.id, {
|
||||
updatedAt: new Date(),
|
||||
name: data.name,
|
||||
category: data.category,
|
||||
aliases: data.aliases,
|
||||
license: data.license,
|
||||
isSensitive: data.isSensitive,
|
||||
localOnly: data.localOnly,
|
||||
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,
|
||||
});
|
||||
|
||||
this.localEmojisCache.refresh();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async addAliasesBulk(ids: MiEmoji['id'][], aliases: string[]) {
|
||||
const emojis = await this.emojisRepository.findBy({
|
||||
|
|
@ -232,36 +298,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
emojis: await this.emojiEntityService.packDetailedMany(ids),
|
||||
});
|
||||
}
|
||||
@bindThis
|
||||
public async setLocalOnlyBulk(ids: MiEmoji['id'][], localOnly: boolean | false) {
|
||||
await this.emojisRepository.update({
|
||||
id: In(ids),
|
||||
}, {
|
||||
updatedAt: new Date(),
|
||||
localOnly: localOnly,
|
||||
});
|
||||
|
||||
this.localEmojisCache.refresh();
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiUpdated', {
|
||||
emojis: await this.emojiEntityService.packDetailedMany(ids),
|
||||
});
|
||||
}
|
||||
@bindThis
|
||||
public async setisSensitiveBulk(ids: MiEmoji['id'][], isSensitive: boolean | false) {
|
||||
await this.emojisRepository.update({
|
||||
id: In(ids),
|
||||
}, {
|
||||
updatedAt: new Date(),
|
||||
isSensitive: isSensitive,
|
||||
});
|
||||
|
||||
this.localEmojisCache.refresh();
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiUpdated', {
|
||||
emojis: await this.emojiEntityService.packDetailedMany(ids),
|
||||
});
|
||||
}
|
||||
@bindThis
|
||||
public async setLicenseBulk(ids: MiEmoji['id'][], license: string | null) {
|
||||
await this.emojisRepository.update({
|
||||
|
|
@ -298,6 +335,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteRequest(id: MiEmojiRequest['id']) {
|
||||
const emoji = await this.emojiRequestsRepository.findOneByOrFail({ id: id });
|
||||
|
||||
await this.emojiRequestsRepository.delete(emoji.id);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) {
|
||||
const emojis = await this.emojisRepository.findBy({
|
||||
|
|
@ -419,11 +463,21 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
return this.emojisRepository.exist({ where: { name, host: IsNull() } });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public checkRequestDuplicate(name: string): Promise<boolean> {
|
||||
return this.emojiRequestsRepository.exist({ where: { name } });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getEmojiById(id: string): Promise<MiEmoji | null> {
|
||||
return this.emojisRepository.findOneBy({ id });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getEmojiRequestById(id: string): Promise<MiEmojiRequest | null> {
|
||||
return this.emojiRequestsRepository.findOneBy({ id });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.cache.dispose();
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||
inviteLimitCycle: 60 * 24 * 7,
|
||||
inviteExpirationTime: 0,
|
||||
canManageCustomEmojis: false,
|
||||
canRequestCustomEmojis: false,
|
||||
canManageAvatarDecorations: false,
|
||||
canRequestCustomEmojis: false,
|
||||
canSearchNotes: false,
|
||||
|
|
@ -336,6 +337,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
|
||||
inviteExpirationTime: calc('inviteExpirationTime', vs => Math.max(...vs)),
|
||||
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
|
||||
canRequestCustomEmojis: calc('canRequestCustomEmojis', vs => vs.some(v => v === true)),
|
||||
canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)),
|
||||
canRequestCustomEmojis: calc('canRequestCustomEmojis', vs => vs.some(v => v === true)),
|
||||
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
|
||||
|
|
|
|||
|
|
@ -133,7 +133,10 @@ export class DriveFileEntityService {
|
|||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getFromUrl(url: string): Promise<MiDriveFile | null> {
|
||||
return this.driveFilesRepository.findOneBy({ url: url });
|
||||
}
|
||||
@bindThis
|
||||
public async calcDriveUsageOf(user: MiUser['id'] | { id: MiUser['id'] }): Promise<number> {
|
||||
const id = typeof user === 'object' ? user.id : user;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { EmojiRequestsRepository } from '@/models/_.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
|
||||
@Injectable()
|
||||
export class EmojiRequestsEntityService {
|
||||
constructor(
|
||||
@Inject(DI.emojiRequestsRepository)
|
||||
private emojiRequestsRepository: EmojiRequestsRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packSimple(
|
||||
src: MiEmojiRequest['id'] | MiEmojiRequest,
|
||||
): Promise<Packed<'EmojiRequestSimple'>> {
|
||||
const emoji = typeof src === 'object' ? src : await this.emojiRequestsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
aliases: emoji.aliases,
|
||||
name: emoji.name,
|
||||
category: emoji.category,
|
||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||
url: emoji.publicUrl,
|
||||
isSensitive: emoji.isSensitive ? true : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public packSimpleMany(
|
||||
emojis: any[],
|
||||
) {
|
||||
return Promise.all(emojis.map(x => this.packSimple(x)));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packDetailed(
|
||||
src: MiEmojiRequest['id'] | MiEmojiRequest,
|
||||
): Promise<Packed<'EmojiRequestDetailed'>> {
|
||||
const emoji = typeof src === 'object' ? src : await this.emojiRequestsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: emoji.id,
|
||||
aliases: emoji.aliases,
|
||||
name: emoji.name,
|
||||
category: emoji.category,
|
||||
url: emoji.publicUrl,
|
||||
license: emoji.license,
|
||||
isSensitive: emoji.isSensitive,
|
||||
localOnly: emoji.localOnly,
|
||||
fileId: emoji.fileId,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public packDetailedMany(
|
||||
emojis: any[],
|
||||
) {
|
||||
return Promise.all(emojis.map(x => this.packDetailed(x)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -41,6 +41,7 @@ export const DI = {
|
|||
followRequestsRepository: Symbol('followRequestsRepository'),
|
||||
instancesRepository: Symbol('instancesRepository'),
|
||||
emojisRepository: Symbol('emojisRepository'),
|
||||
emojiRequestsRepository: Symbol('emojiRequestsRepository'),
|
||||
driveFilesRepository: Symbol('driveFilesRepository'),
|
||||
driveFoldersRepository: Symbol('driveFoldersRepository'),
|
||||
metasRepository: Symbol('metasRepository'),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { packedClipSchema } from '@/models/json-schema/clip.js';
|
|||
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
|
||||
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
|
||||
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
|
||||
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
|
||||
import { packedEmojiDetailedSchema, packedEmojiRequestSimpleSchema, packedEmojiSimpleSchema, packedEmojiRequestDetailedSchema } from '@/models/json-schema/emoji.js';
|
||||
import { packedFlashSchema } from '@/models/json-schema/flash.js';
|
||||
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
||||
import { packedSigninSchema } from '@/models/json-schema/signin.js';
|
||||
|
|
@ -73,7 +73,9 @@ export const refs = {
|
|||
FederationInstance: packedFederationInstanceSchema,
|
||||
GalleryPost: packedGalleryPostSchema,
|
||||
EmojiSimple: packedEmojiSimpleSchema,
|
||||
EmojiRequestSimple: packedEmojiRequestSimpleSchema,
|
||||
EmojiDetailed: packedEmojiDetailedSchema,
|
||||
EmojiRequestDetailed: packedEmojiRequestDetailedSchema,
|
||||
Flash: packedFlashSchema,
|
||||
Signin: packedSigninSchema,
|
||||
RoleLite: packedRoleLiteSchema,
|
||||
|
|
|
|||
71
packages/backend/src/models/EmojiRequest.ts
Normal file
71
packages/backend/src/models/EmojiRequest.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { PrimaryColumn, Entity, Index, Column } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
|
||||
@Entity('emoji_request')
|
||||
@Index(['name'], { unique: true })
|
||||
export class MiEmojiRequest {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
})
|
||||
public updatedAt: Date | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
})
|
||||
public category: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
})
|
||||
public originalUrl: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
default: '',
|
||||
})
|
||||
public publicUrl: string;
|
||||
|
||||
// publicUrlの方のtypeが入る
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true,
|
||||
})
|
||||
public type: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
array: true, length: 128, default: '{}',
|
||||
})
|
||||
public aliases: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: true,
|
||||
})
|
||||
public license: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: false,
|
||||
})
|
||||
public fileId: string;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public localOnly: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isSensitive: boolean;
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ import {
|
|||
MiDriveFile,
|
||||
MiDriveFolder,
|
||||
MiEmoji,
|
||||
MiFlash,
|
||||
MiEmojiRequest,MiFlash,
|
||||
MiFlashLike,
|
||||
MiFollowRequest,
|
||||
MiFollowing,
|
||||
|
|
@ -244,6 +244,12 @@ const $emojisRepository: Provider = {
|
|||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $emojiRequestsRepository: Provider = {
|
||||
provide: DI.emojiRequestsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiEmojiRequest),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $driveFilesRepository: Provider = {
|
||||
provide: DI.driveFilesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiDriveFile),
|
||||
|
|
@ -504,6 +510,7 @@ const $userMemosRepository: Provider = {
|
|||
$followRequestsRepository,
|
||||
$instancesRepository,
|
||||
$emojisRepository,
|
||||
$emojiRequestsRepository,
|
||||
$driveFilesRepository,
|
||||
$driveFoldersRepository,
|
||||
$metasRepository,
|
||||
|
|
@ -572,6 +579,7 @@ const $userMemosRepository: Provider = {
|
|||
$followRequestsRepository,
|
||||
$instancesRepository,
|
||||
$emojisRepository,
|
||||
$emojiRequestsRepository,
|
||||
$driveFilesRepository,
|
||||
$driveFoldersRepository,
|
||||
$metasRepository,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
|||
import { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import { MiDriveFolder } from '@/models/DriveFolder.js';
|
||||
import { MiEmoji } from '@/models/Emoji.js';
|
||||
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
import { MiFollowing } from '@/models/Following.js';
|
||||
import { MiFollowRequest } from '@/models/FollowRequest.js';
|
||||
import { MiGalleryLike } from '@/models/GalleryLike.js';
|
||||
|
|
@ -90,6 +91,7 @@ export {
|
|||
MiDriveFile,
|
||||
MiDriveFolder,
|
||||
MiEmoji,
|
||||
MiEmojiRequest,
|
||||
MiFollowing,
|
||||
MiFollowRequest,
|
||||
MiGalleryLike,
|
||||
|
|
@ -158,6 +160,7 @@ export type ClipFavoritesRepository = Repository<MiClipFavorite>;
|
|||
export type DriveFilesRepository = Repository<MiDriveFile>;
|
||||
export type DriveFoldersRepository = Repository<MiDriveFolder>;
|
||||
export type EmojisRepository = Repository<MiEmoji>;
|
||||
export type EmojiRequestsRepository = Repository<MiEmojiRequest>;
|
||||
export type FollowingsRepository = Repository<MiFollowing>;
|
||||
export type FollowRequestsRepository = Repository<MiFollowRequest>;
|
||||
export type GalleryLikesRepository = Repository<MiGalleryLike>;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,36 @@ export const packedEmojiSimpleSchema = {
|
|||
},
|
||||
},
|
||||
} as const;
|
||||
export const packedEmojiRequestSimpleSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedEmojiDetailedSchema = {
|
||||
type: 'object',
|
||||
|
|
@ -108,3 +138,51 @@ export const packedEmojiDetailedSchema = {
|
|||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedEmojiRequestDetailedSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
license: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
isSensitive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
localOnly: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
|||
import { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import { MiDriveFolder } from '@/models/DriveFolder.js';
|
||||
import { MiEmoji } from '@/models/Emoji.js';
|
||||
import { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
import { MiFollowing } from '@/models/Following.js';
|
||||
import { MiFollowRequest } from '@/models/FollowRequest.js';
|
||||
import { MiGalleryLike } from '@/models/GalleryLike.js';
|
||||
|
|
@ -164,6 +165,7 @@ export const entities = [
|
|||
MiPoll,
|
||||
MiPollVote,
|
||||
MiEmoji,
|
||||
MiEmojiRequest,
|
||||
MiHashtag,
|
||||
MiSwSubscription,
|
||||
MiAbuseUserReport,
|
||||
|
|
|
|||
|
|
@ -34,18 +34,20 @@ import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-al
|
|||
import * as ep___admin_emoji_setlocalOnlyBulk from './endpoints/admin/emoji/set-localonly-bulk.js';
|
||||
import * as ep___admin_emoji_setisSensitiveBulk from './endpoints/admin/emoji/set-issensitive-bulk.js';
|
||||
import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
|
||||
import * as ep___admin_emoji_addDraft from './endpoints/admin/emoji/add-draft.js';
|
||||
import * as ep___admin_emoji_addRequest from './endpoints/admin/emoji/add-request.js';
|
||||
import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
|
||||
import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
|
||||
import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
|
||||
import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js';
|
||||
import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js';
|
||||
import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
|
||||
import * as ep___admin_emoji_listRequest from './endpoints/admin/emoji/list-request.js';
|
||||
import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-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_update from './endpoints/admin/emoji/update.js';
|
||||
import * as ep___admin_emoji_updateRequest from './endpoints/admin/emoji/update-request.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_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
||||
|
|
@ -255,6 +257,7 @@ import * as ep___invite_list from './endpoints/invite/list.js';
|
|||
import * as ep___invite_limit from './endpoints/invite/limit.js';
|
||||
import * as ep___meta from './endpoints/meta.js';
|
||||
import * as ep___emojis from './endpoints/emojis.js';
|
||||
import * as ep___emojiRequests from './endpoints/emoji-requests.js';
|
||||
import * as ep___emoji from './endpoints/emoji.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||
|
|
@ -402,18 +405,20 @@ const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-ali
|
|||
const $admin_emoji_setlocalOnlyBulk: Provider = { provide: 'ep:admin/emoji/set-localonly-bulk', useClass: ep___admin_emoji_setlocalOnlyBulk.default };
|
||||
const $admin_emoji_setisSensitiveBulk: Provider = { provide: 'ep:admin/emoji/set-issensitive-bulk', useClass: ep___admin_emoji_setisSensitiveBulk.default };
|
||||
const $admin_emoji_add: Provider = { provide: 'ep:admin/emoji/add', useClass: ep___admin_emoji_add.default };
|
||||
const $admin_emoji_addDraft: Provider = { provide: 'ep:admin/emoji/add-draft', useClass: ep___admin_emoji_addDraft.default };
|
||||
const $admin_emoji_addRequest: Provider = { provide: 'ep:admin/emoji/add-request', useClass: ep___admin_emoji_addRequest.default };
|
||||
const $admin_emoji_copy: Provider = { provide: 'ep:admin/emoji/copy', useClass: ep___admin_emoji_copy.default };
|
||||
const $admin_emoji_deleteBulk: Provider = { provide: 'ep:admin/emoji/delete-bulk', useClass: ep___admin_emoji_deleteBulk.default };
|
||||
const $admin_emoji_delete: Provider = { provide: 'ep:admin/emoji/delete', useClass: ep___admin_emoji_delete.default };
|
||||
const $admin_emoji_importZip: Provider = { provide: 'ep:admin/emoji/import-zip', useClass: ep___admin_emoji_importZip.default };
|
||||
const $admin_emoji_listRemote: Provider = { provide: 'ep:admin/emoji/list-remote', useClass: ep___admin_emoji_listRemote.default };
|
||||
const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass: ep___admin_emoji_list.default };
|
||||
const $admin_emoji_listRequest: Provider = { provide: 'ep:admin/emoji/list-request', useClass: ep___admin_emoji_listRequest.default };
|
||||
const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default };
|
||||
const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.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_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
|
||||
const $admin_emoji_updateRequest: Provider = { provide: 'ep:admin/emoji/update-request', useClass: ep___admin_emoji_updateRequest.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_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
|
||||
|
|
@ -624,6 +629,7 @@ const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invit
|
|||
const $invite_limit: Provider = { provide: 'ep:invite/limit', useClass: ep___invite_limit.default };
|
||||
const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
|
||||
const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
|
||||
const $emoji_requests: Provider = { provide: 'ep:emoji-requests', useClass: ep___emojiRequests.default };
|
||||
const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
|
||||
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
|
||||
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
|
||||
|
|
@ -774,18 +780,20 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||
$admin_emoji_setlocalOnlyBulk,
|
||||
$admin_emoji_setisSensitiveBulk,
|
||||
$admin_emoji_add,
|
||||
$admin_emoji_addDraft,
|
||||
$admin_emoji_addRequest,
|
||||
$admin_emoji_copy,
|
||||
$admin_emoji_deleteBulk,
|
||||
$admin_emoji_delete,
|
||||
$admin_emoji_importZip,
|
||||
$admin_emoji_listRemote,
|
||||
$admin_emoji_list,
|
||||
$admin_emoji_listRequest,
|
||||
$admin_emoji_removeAliasesBulk,
|
||||
$admin_emoji_setAliasesBulk,
|
||||
$admin_emoji_setCategoryBulk,
|
||||
$admin_emoji_setLicenseBulk,
|
||||
$admin_emoji_update,
|
||||
$admin_emoji_updateRequest,
|
||||
$admin_federation_deleteAllFiles,
|
||||
$admin_federation_refreshRemoteInstanceMetadata,
|
||||
$admin_federation_removeAllFollowing,
|
||||
|
|
@ -996,6 +1004,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||
$invite_limit,
|
||||
$meta,
|
||||
$emojis,
|
||||
$emoji_requests,
|
||||
$emoji,
|
||||
$miauth_genToken,
|
||||
$mute_create,
|
||||
|
|
@ -1138,13 +1147,14 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||
$admin_drive_showFile,
|
||||
$admin_emoji_addAliasesBulk,
|
||||
$admin_emoji_add,
|
||||
$admin_emoji_addDraft,
|
||||
$admin_emoji_addRequest,
|
||||
$admin_emoji_copy,
|
||||
$admin_emoji_deleteBulk,
|
||||
$admin_emoji_delete,
|
||||
$admin_emoji_importZip,
|
||||
$admin_emoji_listRemote,
|
||||
$admin_emoji_list,
|
||||
$admin_emoji_listRequest,
|
||||
$admin_emoji_removeAliasesBulk,
|
||||
$admin_emoji_setAliasesBulk,
|
||||
$admin_emoji_setCategoryBulk,
|
||||
|
|
@ -1152,6 +1162,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||
$admin_emoji_setlocalOnlyBulk,
|
||||
$admin_emoji_setisSensitiveBulk,
|
||||
$admin_emoji_update,
|
||||
$admin_emoji_updateRequest,
|
||||
$admin_federation_deleteAllFiles,
|
||||
$i_userstats,
|
||||
$admin_federation_refreshRemoteInstanceMetadata,
|
||||
|
|
@ -1362,6 +1373,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||
$invite_limit,
|
||||
$meta,
|
||||
$emojis,
|
||||
$emoji_requests,
|
||||
$emoji,
|
||||
$miauth_genToken,
|
||||
$mute_create,
|
||||
|
|
|
|||
|
|
@ -33,18 +33,20 @@ import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
|||
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
||||
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
|
||||
import * as ep___admin_emoji_addDraft from './endpoints/admin/emoji/add-draft.js';
|
||||
import * as ep___admin_emoji_addRequest from './endpoints/admin/emoji/add-request.js';
|
||||
import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
|
||||
import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
|
||||
import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
|
||||
import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js';
|
||||
import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js';
|
||||
import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
|
||||
import * as ep___admin_emoji_listRequest from './endpoints/admin/emoji/list-request.js';
|
||||
import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
|
||||
import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-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_update from './endpoints/admin/emoji/update.js';
|
||||
import * as ep___admin_emoji_updateRequest from './endpoints/admin/emoji/update-request.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_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
|
||||
|
|
@ -255,6 +257,7 @@ import * as ep___invite_list from './endpoints/invite/list.js';
|
|||
import * as ep___invite_limit from './endpoints/invite/limit.js';
|
||||
import * as ep___meta from './endpoints/meta.js';
|
||||
import * as ep___emojis from './endpoints/emojis.js';
|
||||
import * as ep___emojiRequests from './endpoints/emoji-requests.js';
|
||||
import * as ep___emoji from './endpoints/emoji.js';
|
||||
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
|
||||
import * as ep___mute_create from './endpoints/mute/create.js';
|
||||
|
|
@ -397,13 +400,14 @@ const eps = [
|
|||
['admin/drive/show-file', ep___admin_drive_showFile],
|
||||
['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk],
|
||||
['admin/emoji/add', ep___admin_emoji_add],
|
||||
['admin/emoji/add-draft', ep___admin_emoji_addDraft],
|
||||
['admin/emoji/add-request', ep___admin_emoji_addRequest],
|
||||
['admin/emoji/copy', ep___admin_emoji_copy],
|
||||
['admin/emoji/delete-bulk', ep___admin_emoji_deleteBulk],
|
||||
['admin/emoji/delete', ep___admin_emoji_delete],
|
||||
['admin/emoji/import-zip', ep___admin_emoji_importZip],
|
||||
['admin/emoji/list-remote', ep___admin_emoji_listRemote],
|
||||
['admin/emoji/list', ep___admin_emoji_list],
|
||||
['admin/emoji/list-request', ep___admin_emoji_listRequest],
|
||||
['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk],
|
||||
['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk],
|
||||
['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
|
||||
|
|
@ -411,6 +415,7 @@ const eps = [
|
|||
['admin/emoji/set-issensitive-bulk', ep___admin_emoji_setisSensitiveBulk],
|
||||
['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
|
||||
['admin/emoji/update', ep___admin_emoji_update],
|
||||
['admin/emoji/update-request', ep___admin_emoji_updateRequest],
|
||||
['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
|
||||
['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
|
||||
['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
|
||||
|
|
@ -621,6 +626,7 @@ const eps = [
|
|||
['invite/limit', ep___invite_limit],
|
||||
['meta', ep___meta],
|
||||
['emojis', ep___emojis],
|
||||
['emoji-requests', ep___emojiRequests],
|
||||
['emoji', ep___emoji],
|
||||
['miauth/gen-token', ep___miauth_genToken],
|
||||
['mute/create', ep___mute_create],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canRequestCustomEmojis',
|
||||
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
|
||||
},
|
||||
duplicateName: {
|
||||
message: 'Duplicate name.',
|
||||
code: 'DUPLICATE_NAME',
|
||||
id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
||||
category: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
},
|
||||
aliases: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean', nullable: true },
|
||||
localOnly: { type: 'boolean', nullable: true },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['name', 'fileId'],
|
||||
} as const;
|
||||
|
||||
// TODO: ロジックをサービスに切り出す
|
||||
|
||||
@Injectable()
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private customEmojiService: CustomEmojiService,
|
||||
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
|
||||
const isRequestDuplicate = await this.customEmojiService.checkRequestDuplicate(ps.name);
|
||||
|
||||
if (isDuplicate || isRequestDuplicate) throw new ApiError(meta.errors.duplicateName);
|
||||
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
|
||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
|
||||
const emoji = await this.customEmojiService.request({
|
||||
driveFile,
|
||||
name: ps.name,
|
||||
category: ps.category ?? null,
|
||||
aliases: ps.aliases ?? [],
|
||||
license: ps.license ?? null,
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
});
|
||||
|
||||
await this.moderationLogService.log(me, 'addCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
emoji: emoji,
|
||||
});
|
||||
|
||||
return {
|
||||
id: emoji.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.customEmojiService.delete(ps.id, me);
|
||||
const emoji = await this.customEmojiService.getEmojiById(ps.id);
|
||||
const RequestEmoji = await this.customEmojiService.getEmojiRequestById(ps.id);
|
||||
if (emoji != null) {
|
||||
await this.customEmojiService.delete(ps.id, me);
|
||||
}
|
||||
if (RequestEmoji != null) {
|
||||
await this.customEmojiService.deleteRequest(ps.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { EmojiRequestsRepository } from '@/models/_.js';
|
||||
import type { MiEmojiRequest } from '@/models/EmojiRequest.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { EmojiRequestsEntityService } from '@/core/entities/EmojiRequestsEntityService.js';
|
||||
//import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', nullable: true, default: null },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.emojiRequestsRepository)
|
||||
private emojiRequestsRepository: EmojiRequestsRepository,
|
||||
|
||||
private emojiRequestsEntityService: EmojiRequestsEntityService,
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const q = this.queryService.makePaginationQuery(this.emojiRequestsRepository.createQueryBuilder('emoji'), ps.sinceId, ps.untilId);
|
||||
|
||||
let emojis: MiEmojiRequest[];
|
||||
|
||||
if (ps.query) {
|
||||
//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
|
||||
//const emojis = await q.limit(ps.limit).getMany();
|
||||
|
||||
emojis = await q.getMany();
|
||||
const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g);
|
||||
|
||||
if (queryarry) {
|
||||
emojis = emojis.filter(emoji =>
|
||||
queryarry.includes(`:${emoji.name}:`),
|
||||
);
|
||||
} else {
|
||||
emojis = emojis.filter(emoji =>
|
||||
emoji.name.includes(ps.query!) ||
|
||||
emoji.aliases.some(a => a.includes(ps.query!)) ||
|
||||
emoji.category?.includes(ps.query!));
|
||||
}
|
||||
emojis.splice(ps.limit + 1);
|
||||
} else {
|
||||
emojis = await q.limit(ps.limit).getMany();
|
||||
}
|
||||
|
||||
return this.emojiRequestsEntityService.packDetailedMany(emojis);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import type { DriveFilesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
|
||||
errors: {
|
||||
noSuchEmoji: {
|
||||
message: 'No such emoji.',
|
||||
code: 'NO_SUCH_EMOJI',
|
||||
id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8',
|
||||
},
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: '14fb9fd9-0731-4e2f-aeb9-f09e4740333d',
|
||||
},
|
||||
sameNameEmojiExists: {
|
||||
message: 'Emoji that have same name already exists.',
|
||||
code: 'SAME_NAME_EMOJI_EXISTS',
|
||||
id: '7180fe9d-1ee3-bff9-647d-fe9896d2ffb8',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
category: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
},
|
||||
aliases: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
license: { type: 'string', nullable: true },
|
||||
isSensitive: { type: 'boolean' },
|
||||
localOnly: { type: 'boolean' },
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
Request: { type: 'boolean' },
|
||||
},
|
||||
required: ['id', 'name', 'aliases'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
let driveFile;
|
||||
const isRequest = !!ps.Request;
|
||||
if (ps.fileId) {
|
||||
driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
|
||||
const emoji = await this.customEmojiService.getEmojiRequestById(ps.id);
|
||||
if (emoji != null) {
|
||||
if (ps.name !== emoji.name) {
|
||||
const isDuplicate = await this.customEmojiService.checkRequestDuplicate(ps.name);
|
||||
if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists);
|
||||
}
|
||||
} else {
|
||||
throw new ApiError(meta.errors.noSuchEmoji);
|
||||
}
|
||||
if (!isRequest) {
|
||||
const file = await this.driveFileEntityService.getFromUrl(emoji.originalUrl);
|
||||
if (file === null) throw new ApiError(meta.errors.noSuchFile);
|
||||
await this.customEmojiService.add({
|
||||
driveFile: file,
|
||||
name: ps.name,
|
||||
category: ps.category ?? null,
|
||||
aliases: ps.aliases ?? [],
|
||||
host: null,
|
||||
license: ps.license ?? null,
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||
}, me);
|
||||
await this.customEmojiService.deleteRequest(ps.id);
|
||||
} else {
|
||||
await this.customEmojiService.updateRequest(ps.id, {
|
||||
name: ps.name,
|
||||
category: ps.category ?? null,
|
||||
aliases: ps.aliases ?? [],
|
||||
license: ps.license ?? null,
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
}, me);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
import type { DriveFilesRepository , EmojisRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
|
@ -62,7 +63,7 @@ export const paramDef = {
|
|||
roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
draft: { type: 'boolean' },
|
||||
Request: { type: 'boolean' },
|
||||
},
|
||||
required: ['id', 'name', 'draft', 'aliases'],
|
||||
} as const;
|
||||
|
|
@ -73,10 +74,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
private customEmojiService: CustomEmojiService,
|
||||
private driveFileEntityService: DriveFileEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
let driveFile;
|
||||
|
||||
const isRequest = !!ps.Request;
|
||||
if (ps.fileId) {
|
||||
driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
|
|
@ -91,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.noSuchEmoji);
|
||||
}
|
||||
|
||||
await this.customEmojiService.update(ps.id, {
|
||||
if (!isRequest) {await this.customEmojiService.update(ps.id, {
|
||||
driveFile,
|
||||
name: ps.name,
|
||||
category: ps.category ?? null,
|
||||
|
|
@ -101,7 +103,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
localOnly: ps.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
draft: ps.draft,
|
||||
}, me);
|
||||
}, me);} else {
|
||||
const file = await this.driveFileEntityService.getFromUrl(emoji.originalUrl);
|
||||
if (file === null) throw new ApiError(meta.errors.noSuchFile);
|
||||
await this.customEmojiService.request({
|
||||
driveFile: file,
|
||||
name: ps.name,
|
||||
category: ps.category ?? null,
|
||||
aliases: ps.aliases ?? [],
|
||||
license: ps.license ?? null,
|
||||
isSensitive: ps.isSensitive ?? false,
|
||||
localOnly: ps.localOnly ?? false,
|
||||
}, me);
|
||||
await this.customEmojiService.delete(ps.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
64
packages/backend/src/server/api/endpoints/emoji-requests.ts
Normal file
64
packages/backend/src/server/api/endpoints/emoji-requests.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { EmojiRequestsRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { EmojiRequestsEntityService } from '@/core/entities/EmojiRequestsEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['meta'],
|
||||
|
||||
requireCredential: false,
|
||||
allowGet: true,
|
||||
cacheSec: 3600,
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
emojis: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'EmojiRequestSimple',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.emojiRequestsRepository)
|
||||
private emojiRequestsRepository: EmojiRequestsRepository,
|
||||
|
||||
private emojiRequestsEntityService: EmojiRequestsEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async () => {
|
||||
const emojis = await this.emojiRequestsRepository.find({
|
||||
order: {
|
||||
category: 'ASC',
|
||||
name: 'ASC',
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
emojis: await this.emojiRequestsEntityService.packSimpleMany(emojis),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,7 @@ export const moderationLogTypes = [
|
|||
'unsuspend',
|
||||
'updateUserNote',
|
||||
'addCustomEmoji',
|
||||
'requestCustomEmoji',
|
||||
'updateCustomEmoji',
|
||||
'deleteCustomEmoji',
|
||||
'assignRole',
|
||||
|
|
@ -117,6 +118,10 @@ export type ModerationLogPayloads = {
|
|||
emojiId: string;
|
||||
emoji: any;
|
||||
};
|
||||
requestCustomEmoji: {
|
||||
emojiId: string;
|
||||
emoji: any;
|
||||
};
|
||||
updateCustomEmoji: {
|
||||
emojiId: string;
|
||||
before: any;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue