リモートのカスタム絵文字リアクションを表示できるように (#6239)
* リモートのカスタム絵文字リアクションを表示できるように * AP * DBマイグレーション * ローカルのリアクションの. * fix * fix * fix * space
This commit is contained in:
		
							parent
							
								
									cda1803e59
								
							
						
					
					
						commit
						9b07c5af05
					
				
					 12 changed files with 185 additions and 41 deletions
				
			
		
							
								
								
									
										12
									
								
								migration/1586641139527-remote-reaction.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								migration/1586641139527-remote-reaction.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  | 
 | ||||||
|  | export class remoteReaction1586641139527 implements MigrationInterface { | ||||||
|  |     name = 'remoteReaction1586641139527' | ||||||
|  |     public async up(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |       await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(260)`, undefined); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public async down(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |       await queryRunner.query(`ALTER TABLE "note_reaction" ALTER COLUMN "reaction" TYPE character varying(130)`, undefined); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -301,6 +301,14 @@ export default Vue.extend({ | ||||||
| 				case 'reacted': { | 				case 'reacted': { | ||||||
| 					const reaction = body.reaction; | 					const reaction = body.reaction; | ||||||
| 
 | 
 | ||||||
|  | 					if (body.emoji) { | ||||||
|  | 						const emojis = this.appearNote.emojis || []; | ||||||
|  | 						if (!emojis.includes(body.emoji)) { | ||||||
|  | 							emojis.push(body.emoji); | ||||||
|  | 							Vue.set(this.appearNote, 'emojis', emojis); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
| 					if (this.appearNote.reactions == null) { | 					if (this.appearNote.reactions == null) { | ||||||
| 						Vue.set(this.appearNote, 'reactions', {}); | 						Vue.set(this.appearNote, 'reactions', {}); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ | ||||||
| 			<fa :icon="faReply" v-else-if="notification.type === 'reply'"/> | 			<fa :icon="faReply" v-else-if="notification.type === 'reply'"/> | ||||||
| 			<fa :icon="faAt" v-else-if="notification.type === 'mention'"/> | 			<fa :icon="faAt" v-else-if="notification.type === 'mention'"/> | ||||||
| 			<fa :icon="faQuoteLeft" v-else-if="notification.type === 'quote'"/> | 			<fa :icon="faQuoteLeft" v-else-if="notification.type === 'quote'"/> | ||||||
| 			<x-reaction-icon v-else-if="notification.type === 'reaction'" :reaction="notification.reaction" :no-style="true"/> | 			<x-reaction-icon v-else-if="notification.type === 'reaction'" :reaction="notification.reaction" :customEmojis="notification.note.emojis" :no-style="true"/> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div class="tail"> | 	<div class="tail"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <template> | <template> | ||||||
| <mk-emoji :emoji="reaction.startsWith(':') ? null : reaction" :name="reaction.startsWith(':') ? reaction.substr(1, reaction.length - 2) : null" :is-reaction="true" :normal="true" :no-style="noStyle"/> | <mk-emoji :emoji="reaction.startsWith(':') ? null : reaction" :name="reaction.startsWith(':') ? reaction.substr(1, reaction.length - 2) : null" :customEmojis="customEmojis" :is-reaction="true" :normal="true" :no-style="noStyle"/> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|  | @ -12,6 +12,10 @@ export default Vue.extend({ | ||||||
| 			type: String, | 			type: String, | ||||||
| 			required: true | 			required: true | ||||||
| 		}, | 		}, | ||||||
|  | 		customEmojis: { | ||||||
|  | 			required: false, | ||||||
|  | 			default: () => [] | ||||||
|  | 		}, | ||||||
| 		noStyle: { | 		noStyle: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false, | 			required: false, | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
| 	ref="reaction" | 	ref="reaction" | ||||||
| 	v-particle | 	v-particle | ||||||
| > | > | ||||||
| 	<x-reaction-icon :reaction="reaction" ref="icon"/> | 	<x-reaction-icon :reaction="reaction" :customEmojis="note.emojis" ref="icon"/> | ||||||
| 	<span>{{ count }}</span> | 	<span>{{ count }}</span> | ||||||
| </button> | </button> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { emojiRegex } from './emoji-regex'; | import { emojiRegex } from './emoji-regex'; | ||||||
| import { fetchMeta } from './fetch-meta'; | import { fetchMeta } from './fetch-meta'; | ||||||
| import { Emojis } from '../models'; | import { Emojis } from '../models'; | ||||||
|  | import { toPunyNullable } from './convert-host'; | ||||||
| 
 | 
 | ||||||
| const legacies: Record<string, string> = { | const legacies: Record<string, string> = { | ||||||
| 	'like':     '👍', | 	'like':     '👍', | ||||||
|  | @ -40,12 +41,20 @@ export function convertLegacyReactions(reactions: Record<string, number>) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return _reactions; | 	const _reactions2 = {} as Record<string, number>; | ||||||
|  | 
 | ||||||
|  | 	for (const reaction of Object.keys(_reactions)) { | ||||||
|  | 		_reactions2[decodeReaction(reaction).reaction] = _reactions[reaction]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return _reactions2; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function toDbReaction(reaction?: string | null): Promise<string> { | export async function toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise<string> { | ||||||
| 	if (reaction == null) return await getFallbackReaction(); | 	if (reaction == null) return await getFallbackReaction(); | ||||||
| 
 | 
 | ||||||
|  | 	reacterHost = toPunyNullable(reacterHost); | ||||||
|  | 
 | ||||||
| 	// 文字列タイプのリアクションを絵文字に変換
 | 	// 文字列タイプのリアクションを絵文字に変換
 | ||||||
| 	if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; | 	if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; | ||||||
| 
 | 
 | ||||||
|  | @ -61,18 +70,58 @@ export async function toDbReaction(reaction?: string | null): Promise<string> { | ||||||
| 
 | 
 | ||||||
| 	const custom = reaction.match(/^:([\w+-]+):$/); | 	const custom = reaction.match(/^:([\w+-]+):$/); | ||||||
| 	if (custom) { | 	if (custom) { | ||||||
|  | 		const name = custom[1]; | ||||||
| 		const emoji = await Emojis.findOne({ | 		const emoji = await Emojis.findOne({ | ||||||
| 			host: null, | 			host: reacterHost || null, | ||||||
| 			name: custom[1], | 			name, | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		if (emoji) return reaction; | 		if (emoji) return reacterHost ? `:${name}@${reacterHost}:` : `:${name}:` | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return await getFallbackReaction(); | 	return await getFallbackReaction(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type DecodedReaction = { | ||||||
|  | 	/** | ||||||
|  | 	 * リアクション名 (Unicode Emoji or ':name@hostname' or ':name@.') | ||||||
|  | 	 */ | ||||||
|  | 	reaction: string; | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * name (カスタム絵文字の場合name, Emojiクエリに使う) | ||||||
|  | 	 */ | ||||||
|  | 	name?: string; | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * host (カスタム絵文字の場合host, Emojiクエリに使う) | ||||||
|  | 	 */ | ||||||
|  | 	host?: string | null; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function decodeReaction(str: string): DecodedReaction { | ||||||
|  | 	const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/); | ||||||
|  | 
 | ||||||
|  | 	if (custom) { | ||||||
|  | 		const name = custom[1]; | ||||||
|  | 		const host = custom[2] || null; | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			reaction: `:${name}@${host || '.'}:`,	// ローカル分は@以降を省略するのではなく.にする
 | ||||||
|  | 			name, | ||||||
|  | 			host | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return { | ||||||
|  | 		reaction: str, | ||||||
|  | 		name: undefined, | ||||||
|  | 		host: undefined | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function convertLegacyReaction(reaction: string): string { | export function convertLegacyReaction(reaction: string): string { | ||||||
|  | 	reaction = decodeReaction(reaction).reaction; | ||||||
| 	if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; | 	if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; | ||||||
| 	return reaction; | 	return reaction; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ export class NoteReaction { | ||||||
| 	public note: Note | null; | 	public note: Note | null; | ||||||
| 
 | 
 | ||||||
| 	@Column('varchar', { | 	@Column('varchar', { | ||||||
| 		length: 130 | 		length: 260 | ||||||
| 	}) | 	}) | ||||||
| 	public reaction: string; | 	public reaction: string; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,9 +5,11 @@ import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls | ||||||
| import { ensure } from '../../prelude/ensure'; | import { ensure } from '../../prelude/ensure'; | ||||||
| import { SchemaType } from '../../misc/schema'; | import { SchemaType } from '../../misc/schema'; | ||||||
| import { awaitAll } from '../../prelude/await-all'; | import { awaitAll } from '../../prelude/await-all'; | ||||||
| import { convertLegacyReaction, convertLegacyReactions } from '../../misc/reaction-lib'; | import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '../../misc/reaction-lib'; | ||||||
| import { toString } from '../../mfm/toString'; | import { toString } from '../../mfm/toString'; | ||||||
| import { parse } from '../../mfm/parse'; | import { parse } from '../../mfm/parse'; | ||||||
|  | import { Emoji } from '../entities/emoji'; | ||||||
|  | import { concat } from '../../prelude/array'; | ||||||
| 
 | 
 | ||||||
| export type PackedNote = SchemaType<typeof packedNoteSchema>; | export type PackedNote = SchemaType<typeof packedNoteSchema>; | ||||||
| 
 | 
 | ||||||
|  | @ -129,31 +131,61 @@ export class NoteRepository extends Repository<Note> { | ||||||
| 			}; | 			}; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		/** | ||||||
|  | 		 * 添付用emojisを解決する | ||||||
|  | 		 * @param emojiNames Note等に添付されたカスタム絵文字名 (:は含めない) | ||||||
|  | 		 * @param noteUserHost Noteのホスト | ||||||
|  | 		 * @param reactionNames Note等にリアクションされたカスタム絵文字名 (:は含めない) | ||||||
|  | 		 */ | ||||||
| 		async function populateEmojis(emojiNames: string[], noteUserHost: string | null, reactionNames: string[]) { | 		async function populateEmojis(emojiNames: string[], noteUserHost: string | null, reactionNames: string[]) { | ||||||
| 			const where = [] as {}[]; | 			let all = [] as { | ||||||
|  | 				name: string, | ||||||
|  | 				url: string | ||||||
|  | 			}[]; | ||||||
| 
 | 
 | ||||||
|  | 			// カスタム絵文字
 | ||||||
| 			if (emojiNames?.length > 0) { | 			if (emojiNames?.length > 0) { | ||||||
| 				where.push({ | 				const tmp = await Emojis.find({ | ||||||
| 					name: In(emojiNames), | 					where: { | ||||||
| 					host: noteUserHost | 						name: In(emojiNames), | ||||||
| 				}); | 						host: noteUserHost | ||||||
|  | 					}, | ||||||
|  | 					select: ['name', 'host', 'url'] | ||||||
|  | 				}).then(emojis => emojis.map((emoji: Emoji) => { | ||||||
|  | 					return { | ||||||
|  | 						name: emoji.name, | ||||||
|  | 						url: emoji.url, | ||||||
|  | 					}; | ||||||
|  | 				})); | ||||||
|  | 
 | ||||||
|  | 				all = concat([all, tmp]); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			reactionNames = reactionNames?.filter(x => x.match(/^:[^:]+:$/)).map(x => x.replace(/:/g, '')); | 			const customReactions = reactionNames?.map(x => decodeReaction(x)).filter(x => x.name); | ||||||
| 
 | 
 | ||||||
| 			if (reactionNames?.length > 0) { | 			if (customReactions?.length > 0) { | ||||||
| 				where.push({ | 				const where = [] as {}[]; | ||||||
| 					name: In(reactionNames), | 
 | ||||||
| 					host: null | 				for (const customReaction of customReactions) { | ||||||
| 				}); | 					where.push({ | ||||||
|  | 						name: customReaction.name, | ||||||
|  | 						host: customReaction.host | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				const tmp = await Emojis.find({ | ||||||
|  | 					where, | ||||||
|  | 					select: ['name', 'host', 'url'] | ||||||
|  | 				}).then(emojis => emojis.map((emoji: Emoji) => { | ||||||
|  | 					return { | ||||||
|  | 						name: `${emoji.name}@${emoji.host || '.'}`,	// @host付きでローカルは.
 | ||||||
|  | 						url: emoji.url, | ||||||
|  | 					}; | ||||||
|  | 				})); | ||||||
|  | 				all = concat([all, tmp]); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if (where.length === 0) return []; | 			return all; | ||||||
| 
 |  | ||||||
| 			return Emojis.find({ |  | ||||||
| 				where, |  | ||||||
| 				select: ['name', 'host', 'url', 'aliases'] |  | ||||||
| 			}); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		async function populateMyReaction() { | 		async function populateMyReaction() { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { IRemoteUser } from '../../../models/entities/user'; | import { IRemoteUser } from '../../../models/entities/user'; | ||||||
| import { ILike, getApId } from '../type'; | import { ILike, getApId } from '../type'; | ||||||
| import create from '../../../services/note/reaction/create'; | import create from '../../../services/note/reaction/create'; | ||||||
| import { fetchNote } from '../models/note'; | import { fetchNote, extractEmojis } from '../models/note'; | ||||||
| 
 | 
 | ||||||
| export default async (actor: IRemoteUser, activity: ILike) => { | export default async (actor: IRemoteUser, activity: ILike) => { | ||||||
| 	const targetUri = getApId(activity.object); | 	const targetUri = getApId(activity.object); | ||||||
|  | @ -11,6 +11,8 @@ export default async (actor: IRemoteUser, activity: ILike) => { | ||||||
| 
 | 
 | ||||||
| 	if (actor.id === note.userId) return `skip: cannot react to my note`; | 	if (actor.id === note.userId) return `skip: cannot react to my note`; | ||||||
| 
 | 
 | ||||||
|  | 	await extractEmojis(activity.tag || [], actor.host).catch(() => null); | ||||||
|  | 
 | ||||||
| 	await create(actor, note, activity._misskey_reaction || activity.content || activity.name); | 	await create(actor, note, activity._misskey_reaction || activity.content || activity.name); | ||||||
| 	return `ok`; | 	return `ok`; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,12 +1,30 @@ | ||||||
| import config from '../../../config'; | import config from '../../../config'; | ||||||
| import { NoteReaction } from '../../../models/entities/note-reaction'; | import { NoteReaction } from '../../../models/entities/note-reaction'; | ||||||
| import { Note } from '../../../models/entities/note'; | import { Note } from '../../../models/entities/note'; | ||||||
|  | import { Emojis } from '../../../models'; | ||||||
|  | import renderEmoji from './emoji'; | ||||||
| 
 | 
 | ||||||
| export const renderLike = (noteReaction: NoteReaction, note: Note) => ({ | export const renderLike = async (noteReaction: NoteReaction, note: Note) => { | ||||||
| 	type: 'Like', | 	const reaction = noteReaction.reaction; | ||||||
| 	id: `${config.url}/likes/${noteReaction.id}`, | 
 | ||||||
| 	actor: `${config.url}/users/${noteReaction.userId}`, | 	const object =  { | ||||||
| 	object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`, | 		type: 'Like', | ||||||
| 	content: noteReaction.reaction, | 		id: `${config.url}/likes/${noteReaction.id}`, | ||||||
| 	_misskey_reaction: noteReaction.reaction | 		actor: `${config.url}/users/${noteReaction.userId}`, | ||||||
| }); | 		object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`, | ||||||
|  | 		content: reaction, | ||||||
|  | 		_misskey_reaction: reaction | ||||||
|  | 	} as any; | ||||||
|  | 
 | ||||||
|  | 	if (reaction.startsWith(':')) { | ||||||
|  | 		const name = reaction.replace(/:/g, ''); | ||||||
|  | 		const emoji = await Emojis.findOne({ | ||||||
|  | 			name, | ||||||
|  | 			host: null | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		if (emoji) object.tag = [ renderEmoji(emoji) ]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return object; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ import { renderLike } from '../../../remote/activitypub/renderer/like'; | ||||||
| import DeliverManager from '../../../remote/activitypub/deliver-manager'; | import DeliverManager from '../../../remote/activitypub/deliver-manager'; | ||||||
| import { renderActivity } from '../../../remote/activitypub/renderer'; | import { renderActivity } from '../../../remote/activitypub/renderer'; | ||||||
| import { IdentifiableError } from '../../../misc/identifiable-error'; | import { IdentifiableError } from '../../../misc/identifiable-error'; | ||||||
| import { toDbReaction } from '../../../misc/reaction-lib'; | import { toDbReaction, decodeReaction } from '../../../misc/reaction-lib'; | ||||||
| import { User, IRemoteUser } from '../../../models/entities/user'; | import { User, IRemoteUser } from '../../../models/entities/user'; | ||||||
| import { Note } from '../../../models/entities/note'; | import { Note } from '../../../models/entities/note'; | ||||||
| import { NoteReactions, Users, NoteWatchings, Notes, UserProfiles } from '../../../models'; | import { NoteReactions, Users, NoteWatchings, Notes, UserProfiles, Emojis } from '../../../models'; | ||||||
| import { Not } from 'typeorm'; | import { Not } from 'typeorm'; | ||||||
| import { perUserReactionsChart } from '../../chart'; | import { perUserReactionsChart } from '../../chart'; | ||||||
| import { genId } from '../../../misc/gen-id'; | import { genId } from '../../../misc/gen-id'; | ||||||
|  | @ -20,7 +20,7 @@ export default async (user: User, note: Note, reaction?: string) => { | ||||||
| 		throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note'); | 		throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note'); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	reaction = await toDbReaction(reaction); | 	reaction = await toDbReaction(reaction, user.host); | ||||||
| 
 | 
 | ||||||
| 	const exist = await NoteReactions.findOne({ | 	const exist = await NoteReactions.findOne({ | ||||||
| 		noteId: note.id, | 		noteId: note.id, | ||||||
|  | @ -59,8 +59,27 @@ export default async (user: User, note: Note, reaction?: string) => { | ||||||
| 
 | 
 | ||||||
| 	perUserReactionsChart.update(user, note); | 	perUserReactionsChart.update(user, note); | ||||||
| 
 | 
 | ||||||
|  | 	// カスタム絵文字リアクションだったら絵文字情報も送る
 | ||||||
|  | 	const decodedReaction = decodeReaction(reaction); | ||||||
|  | 
 | ||||||
|  | 	let emoji = await Emojis.findOne({ | ||||||
|  | 		where: { | ||||||
|  | 			name: decodedReaction.name, | ||||||
|  | 			host: decodedReaction.host | ||||||
|  | 		}, | ||||||
|  | 		select: ['name', 'host', 'url'] | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	if (emoji) { | ||||||
|  | 		emoji = { | ||||||
|  | 			name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}`, | ||||||
|  | 			url: emoji.url | ||||||
|  | 		} as any; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	publishNoteStream(note.id, 'reacted', { | 	publishNoteStream(note.id, 'reacted', { | ||||||
| 		reaction: reaction, | 		reaction: reaction, | ||||||
|  | 		emoji: emoji, | ||||||
| 		userId: user.id | 		userId: user.id | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
|  | @ -96,7 +115,7 @@ export default async (user: User, note: Note, reaction?: string) => { | ||||||
| 
 | 
 | ||||||
| 	//#region 配信
 | 	//#region 配信
 | ||||||
| 	if (Users.isLocalUser(user) && !note.localOnly) { | 	if (Users.isLocalUser(user) && !note.localOnly) { | ||||||
| 		const content = renderActivity(renderLike(inserted, note)); | 		const content = renderActivity(await renderLike(inserted, note)); | ||||||
| 		const dm = new DeliverManager(user, content); | 		const dm = new DeliverManager(user, content); | ||||||
| 		if (note.userHost !== null) { | 		if (note.userHost !== null) { | ||||||
| 			const reactee = await Users.findOne(note.userId) | 			const reactee = await Users.findOne(note.userId) | ||||||
|  |  | ||||||
|  | @ -44,7 +44,7 @@ export default async (user: User, note: Note) => { | ||||||
| 
 | 
 | ||||||
| 	//#region 配信
 | 	//#region 配信
 | ||||||
| 	if (Users.isLocalUser(user) && !note.localOnly) { | 	if (Users.isLocalUser(user) && !note.localOnly) { | ||||||
| 		const content = renderActivity(renderUndo(renderLike(exist, note), user)); | 		const content = renderActivity(renderUndo(await renderLike(exist, note), user)); | ||||||
| 		const dm = new DeliverManager(user, content); | 		const dm = new DeliverManager(user, content); | ||||||
| 		if (note.userHost !== null) { | 		if (note.userHost !== null) { | ||||||
| 			const reactee = await Users.findOne(note.userId) | 			const reactee = await Users.findOne(note.userId) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue