This commit is contained in:
syuilo 2025-04-16 13:53:20 +09:00
parent 631715c86e
commit 417969604e
4 changed files with 49 additions and 14 deletions

View File

@ -595,7 +595,7 @@ export class NoteEntityService implements OnModuleInit {
} }
@bindThis @bindThis
public async ogogogo(noteIds: MiNote['id'][]) { public async fetchDiffs(noteIds: MiNote['id'][]) {
if (noteIds.length === 0) return []; if (noteIds.length === 0) return [];
const notes = await this.notesRepository.find({ const notes = await this.notesRepository.find({
@ -603,25 +603,32 @@ export class NoteEntityService implements OnModuleInit {
id: In(noteIds), id: In(noteIds),
}, },
select: { select: {
id: true,
userHost: true,
reactions: true, reactions: true,
reactionAndUserPairCache: true, reactionAndUserPairCache: true,
}, },
}); });
console.log(notes);
const bufferedReactionsMap = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(noteIds) : null; const bufferedReactionsMap = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(noteIds) : null;
const results = []; const packings = notes.map(note => {
for (const note of notes) {
const bufferedReactions = bufferedReactionsMap?.get(note.id); 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, id: note.id,
reactions: this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.deltas ?? {})), reactions,
reactionEmojis,
}));
}); });
}
return await Promise.all(packings);
} }
} }

View File

@ -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' from './endpoints/notes/search.js';
export * as 'notes/search-by-tag' from './endpoints/notes/search-by-tag.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' 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/state' from './endpoints/notes/state.js';
export * as 'notes/thread-muting/create' from './endpoints/notes/thread-muting/create.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'; export * as 'notes/thread-muting/delete' from './endpoints/notes/thread-muting/delete.js';

View File

@ -38,9 +38,10 @@ export const paramDef = {
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor( constructor(
private noteEntityService: NoteEntityService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
return await this.noteEntityService.fetchDiffs(ps.noteIds);
}); });
} }
} }

View File

@ -10,6 +10,7 @@ import type { Ref, ShallowRef } from 'vue';
import { useStream } from '@/stream.js'; import { useStream } from '@/stream.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
import { store } from '@/store.js'; import { store } from '@/store.js';
import { misskeyApi } from '@/utility/misskey-api.js';
const noteEvents = new EventEmitter<{ const noteEvents = new EventEmitter<{
reacted: Misskey.entities.Note; reacted: Misskey.entities.Note;
@ -18,16 +19,31 @@ const noteEvents = new EventEmitter<{
deleted: Misskey.entities.Note; deleted: Misskey.entities.Note;
}>(); }>();
const fetchEvent = new EventEmitter<{
[id: string]: Pick<Misskey.entities.Note, 'reactions' | 'reactionEmojis'>;
}>();
const capturedNoteIdMapForPolling = new Map<string, number>(); const capturedNoteIdMapForPolling = new Map<string, number>();
const CAPTURE_MAX = 30;
const POLLING_INTERVAL = 1000 * 10; const POLLING_INTERVAL = 1000 * 10;
window.setInterval(() => { 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 (ids.length === 0) return;
if (window.document.hidden) 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); }, POLLING_INTERVAL);
function pseudoNoteCapture(props: { function pseudoNoteCapture(props: {
@ -43,17 +59,27 @@ function pseudoNoteCapture(props: {
} }
function onFetched(data: Pick<Misskey.entities.Note, 'reactions' | 'reactionEmojis'>): 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)) { if (capturedNoteIdMapForPolling.has(note.value.id)) {
capturedNoteIdMapForPolling.set(note.value.id, capturedNoteIdMapForPolling.get(note.value.id)! + 1); capturedNoteIdMapForPolling.set(note.value.id, capturedNoteIdMapForPolling.get(note.value.id)! + 1);
} else { } else {
capturedNoteIdMapForPolling.set(note.value.id, 1); capturedNoteIdMapForPolling.set(note.value.id, 1);
} }
fetchEvent.on(note.value.id, onFetched);
onUnmounted(() => { onUnmounted(() => {
capturedNoteIdMapForPolling.set(note.value.id, capturedNoteIdMapForPolling.get(note.value.id)! - 1); capturedNoteIdMapForPolling.set(note.value.id, capturedNoteIdMapForPolling.get(note.value.id)! - 1);
if (capturedNoteIdMapForPolling.get(note.value.id) === 0) { if (capturedNoteIdMapForPolling.get(note.value.id) === 0) {
capturedNoteIdMapForPolling.delete(note.value.id); capturedNoteIdMapForPolling.delete(note.value.id);
} }
fetchEvent.off(note.value.id, onFetched);
}); });
} }