diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 8f9ab77ac4..491e63d417 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -595,7 +595,7 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - public async ogogogo(noteIds: MiNote['id'][]) { + public async fetchDiffs(noteIds: MiNote['id'][]) { if (noteIds.length === 0) return []; const notes = await this.notesRepository.find({ @@ -603,25 +603,32 @@ export class NoteEntityService implements OnModuleInit { id: In(noteIds), }, select: { + id: true, + userHost: true, reactions: true, reactionAndUserPairCache: true, }, }); - console.log(notes); - const bufferedReactionsMap = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(noteIds) : null; - const results = []; - - for (const note of notes) { + const packings = notes.map(note => { const bufferedReactions = bufferedReactionsMap?.get(note.id); - const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/'))); + //const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/'))); - results.push({ + const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.deltas ?? {})); + + const reactionEmojiNames = Object.keys(reactions) + .filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ + .map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', '')); + + return this.customEmojiService.populateEmojis(reactionEmojiNames, note.userHost).then(reactionEmojis => ({ id: note.id, - reactions: this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.deltas ?? {})), - }); - } + reactions, + reactionEmojis, + })); + }); + + return await Promise.all(packings); } } diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index 34aaef3cc7..90b96f5722 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -317,6 +317,7 @@ export * as 'notes/replies' from './endpoints/notes/replies.js'; export * as 'notes/search' from './endpoints/notes/search.js'; export * as 'notes/search-by-tag' from './endpoints/notes/search-by-tag.js'; export * as 'notes/show' from './endpoints/notes/show.js'; +export * as 'notes/show-partial-bulk' from './endpoints/notes/show-partial-bulk.js'; export * as 'notes/state' from './endpoints/notes/state.js'; export * as 'notes/thread-muting/create' from './endpoints/notes/thread-muting/create.js'; export * as 'notes/thread-muting/delete' from './endpoints/notes/thread-muting/delete.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts b/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts index 619d59bb97..87b368e17e 100644 --- a/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts +++ b/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts @@ -38,9 +38,10 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + private noteEntityService: NoteEntityService, ) { super(meta, paramDef, async (ps, me) => { - + return await this.noteEntityService.fetchDiffs(ps.noteIds); }); } } diff --git a/packages/frontend/src/use/use-note-capture.ts b/packages/frontend/src/use/use-note-capture.ts index 9634121baf..69bde9cf84 100644 --- a/packages/frontend/src/use/use-note-capture.ts +++ b/packages/frontend/src/use/use-note-capture.ts @@ -10,6 +10,7 @@ import type { Ref, ShallowRef } from 'vue'; import { useStream } from '@/stream.js'; import { $i } from '@/i.js'; import { store } from '@/store.js'; +import { misskeyApi } from '@/utility/misskey-api.js'; const noteEvents = new EventEmitter<{ reacted: Misskey.entities.Note; @@ -18,16 +19,31 @@ const noteEvents = new EventEmitter<{ deleted: Misskey.entities.Note; }>(); +const fetchEvent = new EventEmitter<{ + [id: string]: Pick; +}>(); + const capturedNoteIdMapForPolling = new Map(); +const CAPTURE_MAX = 30; const POLLING_INTERVAL = 1000 * 10; window.setInterval(() => { - const ids = [...capturedNoteIdMapForPolling.keys()]; + const ids = [...capturedNoteIdMapForPolling.keys()].sort((a, b) => (a > b ? -1 : 1)).slice(0, CAPTURE_MAX); // 新しいものを優先するためにIDで降順ソート if (ids.length === 0) return; if (window.document.hidden) return; - console.log('Polling notes', ids); + // まとめてリクエストするのではなく、個別にHTTPリクエスト投げてCDNにキャッシュさせた方がサーバーの負荷低減には良いかもしれない + misskeyApi('notes/show-partial-bulk', { + noteIds: ids, + }).then((items) => { + for (const item of items) { + fetchEvent.emit(item.id, { + reactions: item.reactions, + reactionEmojis: item.reactionEmojis, + }); + } + }); }, POLLING_INTERVAL); function pseudoNoteCapture(props: { @@ -43,17 +59,27 @@ function pseudoNoteCapture(props: { } + function onFetched(data: Pick): void { + note.value.reactions = data.reactions; + note.value.reactionCount = Object.values(data.reactions).reduce((a, b) => a + b, 0); + note.value.reactionEmojis = data.reactionEmojis; + } + if (capturedNoteIdMapForPolling.has(note.value.id)) { capturedNoteIdMapForPolling.set(note.value.id, capturedNoteIdMapForPolling.get(note.value.id)! + 1); } else { capturedNoteIdMapForPolling.set(note.value.id, 1); } + fetchEvent.on(note.value.id, onFetched); + onUnmounted(() => { capturedNoteIdMapForPolling.set(note.value.id, capturedNoteIdMapForPolling.get(note.value.id)! - 1); if (capturedNoteIdMapForPolling.get(note.value.id) === 0) { capturedNoteIdMapForPolling.delete(note.value.id); } + + fetchEvent.off(note.value.id, onFetched); }); }