enhance(server): clean emoji response
This commit is contained in:
		
							parent
							
								
									fcfcb8da1d
								
							
						
					
					
						commit
						b2d6561bc6
					
				
					 11 changed files with 9 additions and 186 deletions
				
			
		|  | @ -21,6 +21,11 @@ You should also include the user name that made the change. | |||
| - 新たに動的なPagesを作ることはできなくなりました | ||||
| 	- 代わりに今後AiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能の実装を予定しています。 | ||||
| - iOS15以下のデバイスはサポートされなくなりました | ||||
| - API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました | ||||
| 	- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。 | ||||
| 	- e.g. `https://p1.a9z.dev/emoji/misskey.webp` | ||||
| 	- remote: `https://p1.a9z.dev/emoji/syuilo_birth_present@mk.f72u.net.webp` | ||||
| - API: `user`および`note`エンティティに`emojis`プロパティが含まれなくなりました | ||||
| 
 | ||||
| ### Improvements | ||||
| - Push notification of Antenna note @tamaina | ||||
|  |  | |||
|  | @ -1,29 +1,14 @@ | |||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import { DataSource, In, IsNull } from 'typeorm'; | ||||
| import { GlobalEventService } from '@/core/GlobalEventService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import type { DriveFile } from '@/models/entities/DriveFile.js'; | ||||
| import type { Emoji } from '@/models/entities/Emoji.js'; | ||||
| import { Cache } from '@/misc/cache.js'; | ||||
| import type { Note } from '@/models/entities/Note.js'; | ||||
| import type { EmojisRepository } from '@/models/index.js'; | ||||
| import { UtilityService } from '@/core/UtilityService.js'; | ||||
| import { ReactionService } from '@/core/ReactionService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| 
 | ||||
| /** | ||||
|  * 添付用絵文字情報 | ||||
|  */ | ||||
| type PopulatedEmoji = { | ||||
| 	name: string; | ||||
| 	url: string; | ||||
| }; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class CustomEmojiService { | ||||
| 	private cache: Cache<Emoji | null>; | ||||
| 
 | ||||
| 	constructor( | ||||
| 		@Inject(DI.db) | ||||
| 		private db: DataSource, | ||||
|  | @ -32,11 +17,7 @@ export class CustomEmojiService { | |||
| 		private emojisRepository: EmojisRepository, | ||||
| 
 | ||||
| 		private idService: IdService, | ||||
| 		private globalEventServie: GlobalEventService, | ||||
| 		private utilityService: UtilityService, | ||||
| 		private reactionService: ReactionService, | ||||
| 	) { | ||||
| 		this.cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12); | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
|  | @ -63,117 +44,4 @@ export class CustomEmojiService { | |||
| 
 | ||||
| 		return emoji; | ||||
| 	} | ||||
| 	 | ||||
| 	@bindThis | ||||
| 	private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { | ||||
| 	// クエリに使うホスト
 | ||||
| 		let host = src === '.' ? null	// .はローカルホスト (ここがマッチするのはリアクションのみ)
 | ||||
| 			: src === undefined ? noteUserHost	// ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない)
 | ||||
| 			: this.utilityService.isSelfHost(src) ? null	// 自ホスト指定
 | ||||
| 			: (src || noteUserHost);	// 指定されたホスト || ノートなどの所有者のホスト (こっちがリアクションにマッチすることはない)
 | ||||
| 
 | ||||
| 		host = this.utilityService.toPunyNullable(host); | ||||
| 
 | ||||
| 		return host; | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	private parseEmojiStr(emojiName: string, noteUserHost: string | null) { | ||||
| 		const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/); | ||||
| 		if (!match) return { name: null, host: null }; | ||||
| 
 | ||||
| 		const name = match[1]; | ||||
| 
 | ||||
| 		// ホスト正規化
 | ||||
| 		const host = this.utilityService.toPunyNullable(this.normalizeHost(match[2], noteUserHost)); | ||||
| 
 | ||||
| 		return { name, host }; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  * 添付用絵文字情報を解決する | ||||
|  * @param emojiName ノートやユーザープロフィールに添付された、またはリアクションのカスタム絵文字名 (:は含めない, リアクションでローカルホストの場合は@.を付ける (これはdecodeReactionで可能)) | ||||
|  * @param noteUserHost ノートやユーザープロフィールの所有者のホスト | ||||
|  * @returns 絵文字情報, nullは未マッチを意味する | ||||
|  */ | ||||
| 	@bindThis | ||||
| 	public async populateEmoji(emojiName: string, noteUserHost: string | null): Promise<PopulatedEmoji | null> { | ||||
| 		const { name, host } = this.parseEmojiStr(emojiName, noteUserHost); | ||||
| 		if (name == null) return null; | ||||
| 
 | ||||
| 		const queryOrNull = async () => (await this.emojisRepository.findOneBy({ | ||||
| 			name, | ||||
| 			host: host ?? IsNull(), | ||||
| 		})) ?? null; | ||||
| 
 | ||||
| 		const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull); | ||||
| 
 | ||||
| 		if (emoji == null) return null; | ||||
| 
 | ||||
| 		const isLocal = emoji.host == null; | ||||
| 		// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 | ||||
| 		const emojiUrl = emoji.publicUrl || emoji.originalUrl; | ||||
| 		const url = emojiUrl; | ||||
| 
 | ||||
| 		return { | ||||
| 			name: emojiName, | ||||
| 			url, | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  * 複数の添付用絵文字情報を解決する (キャシュ付き, 存在しないものは結果から除外される) | ||||
|  */ | ||||
| 	@bindThis | ||||
| 	public async populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<PopulatedEmoji[]> { | ||||
| 		const emojis = await Promise.all(emojiNames.map(x => this.populateEmoji(x, noteUserHost))); | ||||
| 		return emojis.filter((x): x is PopulatedEmoji => x != null); | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public aggregateNoteEmojis(notes: Note[]) { | ||||
| 		let emojis: { name: string | null; host: string | null; }[] = []; | ||||
| 		for (const note of notes) { | ||||
| 			emojis = emojis.concat(note.emojis | ||||
| 				.map(e => this.parseEmojiStr(e, note.userHost))); | ||||
| 			if (note.renote) { | ||||
| 				emojis = emojis.concat(note.renote.emojis | ||||
| 					.map(e => this.parseEmojiStr(e, note.renote!.userHost))); | ||||
| 				if (note.renote.user) { | ||||
| 					emojis = emojis.concat(note.renote.user.emojis | ||||
| 						.map(e => this.parseEmojiStr(e, note.renote!.userHost))); | ||||
| 				} | ||||
| 			} | ||||
| 			const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis; | ||||
| 			emojis = emojis.concat(customReactions); | ||||
| 			if (note.user) { | ||||
| 				emojis = emojis.concat(note.user.emojis | ||||
| 					.map(e => this.parseEmojiStr(e, note.userHost))); | ||||
| 			} | ||||
| 		} | ||||
| 		return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[]; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します | ||||
|  */ | ||||
| 	@bindThis | ||||
| 	public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> { | ||||
| 		const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null); | ||||
| 		const emojisQuery: any[] = []; | ||||
| 		const hosts = new Set(notCachedEmojis.map(e => e.host)); | ||||
| 		for (const host of hosts) { | ||||
| 			emojisQuery.push({ | ||||
| 				name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)), | ||||
| 				host: host ?? IsNull(), | ||||
| 			}); | ||||
| 		} | ||||
| 		const _emojis = emojisQuery.length > 0 ? await this.emojisRepository.find({ | ||||
| 			where: emojisQuery, | ||||
| 			select: ['name', 'host', 'originalUrl', 'publicUrl'], | ||||
| 		}) : []; | ||||
| 		for (const emoji of _emojis) { | ||||
| 			this.cache.set(`${emoji.name} ${emoji.host}`, emoji); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -22,7 +22,6 @@ export class EmojiEntityService { | |||
| 	@bindThis | ||||
| 	public async pack( | ||||
| 		src: Emoji['id'] | Emoji, | ||||
| 		opts: { omitUrl?: boolean } = {}, | ||||
| 	): Promise<Packed<'Emoji'>> { | ||||
| 		const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); | ||||
| 
 | ||||
|  | @ -32,17 +31,14 @@ export class EmojiEntityService { | |||
| 			name: emoji.name, | ||||
| 			category: emoji.category, | ||||
| 			host: emoji.host, | ||||
| 			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 | ||||
| 			url: opts.omitUrl ? undefined : (emoji.publicUrl || emoji.originalUrl), | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public packMany( | ||||
| 		emojis: any[], | ||||
| 		opts: { omitUrl?: boolean } = {}, | ||||
| 	) { | ||||
| 		return Promise.all(emojis.map(x => this.pack(x, opts))); | ||||
| 		return Promise.all(emojis.map(x => this.pack(x))); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,12 +11,12 @@ import type { User } from '@/models/entities/User.js'; | |||
| import type { Note } from '@/models/entities/Note.js'; | ||||
| import type { NoteReaction } from '@/models/entities/NoteReaction.js'; | ||||
| import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { OnModuleInit } from '@nestjs/common'; | ||||
| import type { CustomEmojiService } from '../CustomEmojiService.js'; | ||||
| import type { ReactionService } from '../ReactionService.js'; | ||||
| import type { UserEntityService } from './UserEntityService.js'; | ||||
| import type { DriveFileEntityService } from './DriveFileEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class NoteEntityService implements OnModuleInit { | ||||
|  | @ -300,7 +300,6 @@ export class NoteEntityService implements OnModuleInit { | |||
| 			repliesCount: note.repliesCount, | ||||
| 			reactions: this.reactionService.convertLegacyReactions(note.reactions), | ||||
| 			tags: note.tags.length > 0 ? note.tags : undefined, | ||||
| 			emojis: this.customEmojiService.populateEmojis(note.emojis.concat(reactionEmojiNames), host), | ||||
| 			fileIds: note.fileIds, | ||||
| 			files: this.driveFileEntityService.packMany(note.fileIds), | ||||
| 			replyId: note.replyId, | ||||
|  | @ -385,8 +384,6 @@ export class NoteEntityService implements OnModuleInit { | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		await this.customEmojiService.prefetchEmojis(this.customEmojiService.aggregateNoteEmojis(notes)); | ||||
| 
 | ||||
| 		return await Promise.all(notes.map(n => this.pack(n, me, { | ||||
| 			...options, | ||||
| 			_hint_: { | ||||
|  |  | |||
|  | @ -8,12 +8,12 @@ import type { Notification } from '@/models/entities/Notification.js'; | |||
| import type { NoteReaction } from '@/models/entities/NoteReaction.js'; | ||||
| import type { Note } from '@/models/entities/Note.js'; | ||||
| import type { Packed } from '@/misc/schema.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { OnModuleInit } from '@nestjs/common'; | ||||
| import type { CustomEmojiService } from '../CustomEmojiService.js'; | ||||
| import type { UserEntityService } from './UserEntityService.js'; | ||||
| import type { NoteEntityService } from './NoteEntityService.js'; | ||||
| import type { UserGroupInvitationEntityService } from './UserGroupInvitationEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class NotificationEntityService implements OnModuleInit { | ||||
|  | @ -143,8 +143,6 @@ export class NotificationEntityService implements OnModuleInit { | |||
| 			myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) ?? null); | ||||
| 		} | ||||
| 
 | ||||
| 		await this.customEmojiService.prefetchEmojis(this.customEmojiService.aggregateNoteEmojis(notes)); | ||||
| 
 | ||||
| 		return await Promise.all(notifications.map(x => this.pack(x, { | ||||
| 			_hintForEachNotes_: { | ||||
| 				myReactions: myReactionsMap, | ||||
|  |  | |||
|  | @ -408,7 +408,6 @@ export class UserEntityService implements OnModuleInit { | |||
| 				faviconUrl: instance.faviconUrl, | ||||
| 				themeColor: instance.themeColor, | ||||
| 			} : undefined) : undefined, | ||||
| 			emojis: this.customEmojiService.populateEmojis(user.emojis, user.host), | ||||
| 			onlineStatus: this.getOnlineStatus(user), | ||||
| 			driveCapacityOverrideMb: user.driveCapacityOverrideMb, | ||||
| 
 | ||||
|  |  | |||
|  | @ -141,24 +141,6 @@ export const packedNoteSchema = { | |||
| 			type: 'boolean', | ||||
| 			optional: true, nullable: false, | ||||
| 		}, | ||||
| 		emojis: { | ||||
| 			type: 'array', | ||||
| 			optional: false, nullable: false, | ||||
| 			items: { | ||||
| 				type: 'object', | ||||
| 				optional: false, nullable: false, | ||||
| 				properties: { | ||||
| 					name: { | ||||
| 						type: 'string', | ||||
| 						optional: false, nullable: false, | ||||
| 					}, | ||||
| 					url: { | ||||
| 						type: 'string', | ||||
| 						optional: false, nullable: true, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		reactions: { | ||||
| 			type: 'object', | ||||
| 			optional: false, nullable: false, | ||||
|  |  | |||
|  | @ -55,25 +55,6 @@ export const packedUserLiteSchema = { | |||
| 			type: 'boolean', | ||||
| 			nullable: false, optional: true, | ||||
| 		}, | ||||
| 		emojis: { | ||||
| 			type: 'array', | ||||
| 			nullable: false, optional: false, | ||||
| 			items: { | ||||
| 				type: 'object', | ||||
| 				nullable: false, optional: false, | ||||
| 				properties: { | ||||
| 					name: { | ||||
| 						type: 'string', | ||||
| 						nullable: false, optional: false, | ||||
| 					}, | ||||
| 					url: { | ||||
| 						type: 'string', | ||||
| 						nullable: false, optional: false, | ||||
| 						format: 'url', | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		onlineStatus: { | ||||
| 			type: 'string', | ||||
| 			format: 'url', | ||||
|  |  | |||
|  | @ -309,7 +309,6 @@ export const paramDef = { | |||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		detail: { type: 'boolean', default: true }, | ||||
| 		omitEmojiUrl: { type: 'boolean', default: false }, | ||||
| 	}, | ||||
| 	required: [], | ||||
| } as const; | ||||
|  | @ -391,7 +390,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				backgroundImageUrl: instance.backgroundImageUrl, | ||||
| 				logoImageUrl: instance.logoImageUrl, | ||||
| 				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
 | ||||
| 				emojis: await this.emojiEntityService.packMany(emojis, { omitUrl: ps.omitEmojiUrl }), | ||||
| 				emojis: await this.emojiEntityService.packMany(emojis), | ||||
| 				defaultLightTheme: instance.defaultLightTheme, | ||||
| 				defaultDarkTheme: instance.defaultDarkTheme, | ||||
| 				ads: ads.map(ad => ({ | ||||
|  |  | |||
|  | @ -81,7 +81,6 @@ useTooltip(buttonRef, async (showing) => { | |||
| 	os.popup(XDetails, { | ||||
| 		showing, | ||||
| 		reaction: props.reaction, | ||||
| 		emojis: props.note.emojis, | ||||
| 		users, | ||||
| 		count: props.count, | ||||
| 		targetElement: buttonRef.value, | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData | |||
| export async function fetchInstance() { | ||||
| 	const meta = await api('meta', { | ||||
| 		detail: false, | ||||
| 		omitEmojiUrl: true, | ||||
| 	}); | ||||
| 
 | ||||
| 	for (const [k, v] of Object.entries(meta)) { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue