follow-up fixes to NoteVisibilityService changes
This commit is contained in:
parent
4b57d7d6dd
commit
4bab56609f
3 changed files with 107 additions and 163 deletions
|
|
@ -19,7 +19,7 @@ import { isQuote, isRenote } from '@/misc/is-renote.js';
|
|||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { isReply } from '@/misc/is-reply.js';
|
||||
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||
import { NoteVisibilityService, PopulatedNote } from '@/core/NoteVisibilityService.js';
|
||||
import { NotePopulationData, NoteVisibilityService, PopulatedNote } from '@/core/NoteVisibilityService.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
|
||||
type TimelineOptions = {
|
||||
|
|
@ -85,29 +85,29 @@ export class FanoutTimelineEndpointService {
|
|||
const shouldFallbackToDb = noteIds.length === 0 || ps.sinceId != null && ps.sinceId < oldestNoteId;
|
||||
|
||||
if (!shouldFallbackToDb) {
|
||||
let filter = ps.noteFilter ?? (_note => true);
|
||||
let filter: (note: MiNote, populated: PopulatedNote) => boolean = ps.noteFilter ?? (() => true);
|
||||
|
||||
if (ps.excludeNoFiles) {
|
||||
const parentFilter = filter;
|
||||
filter = (note) => note.fileIds.length !== 0 && parentFilter(note);
|
||||
filter = (note, populated) => note.fileIds.length !== 0 && parentFilter(note, populated);
|
||||
}
|
||||
|
||||
if (ps.excludeReplies) {
|
||||
const parentFilter = filter;
|
||||
filter = (note) => {
|
||||
filter = (note, populated) => {
|
||||
if (note.userId !== ps.me?.id && isReply(note, ps.me?.id)) return false;
|
||||
return parentFilter(note);
|
||||
return parentFilter(note, populated);
|
||||
};
|
||||
}
|
||||
|
||||
if (ps.excludeBots) {
|
||||
const parentFilter = filter;
|
||||
filter = (note) => !note.user?.isBot && parentFilter(note);
|
||||
filter = (note, populated) => !note.user?.isBot && parentFilter(note, populated);
|
||||
}
|
||||
|
||||
if (ps.excludePureRenotes) {
|
||||
const parentFilter = filter;
|
||||
filter = (note) => (!isRenote(note) || isQuote(note)) && parentFilter(note);
|
||||
filter = (note, populated) => (!isRenote(note) || isQuote(note)) && parentFilter(note, populated);
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -115,37 +115,37 @@ export class FanoutTimelineEndpointService {
|
|||
const data = await this.noteVisibilityService.populateData(me);
|
||||
|
||||
const parentFilter = filter;
|
||||
filter = (note) => {
|
||||
const { accessible, silence } = this.noteVisibilityService.checkNoteVisibility(note as PopulatedNote, me, { data, filters: { includeSilencedAuthor: ps.ignoreAuthorFromUserSilence } });
|
||||
filter = (note, populated) => {
|
||||
const { accessible, silence } = this.noteVisibilityService.checkNoteVisibility(populated, me, { data, filters: { includeSilencedAuthor: ps.ignoreAuthorFromUserSilence } });
|
||||
if (!accessible || silence) return false;
|
||||
|
||||
return parentFilter(note);
|
||||
return parentFilter(note, populated);
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
const parentFilter = filter;
|
||||
filter = (note) => {
|
||||
filter = (note, populated) => {
|
||||
if (!ps.ignoreAuthorFromInstanceBlock) {
|
||||
if (note.user?.instance?.isBlocked) return false;
|
||||
}
|
||||
if (note.userId !== note.renoteUserId && note.renote?.user?.instance?.isBlocked) return false;
|
||||
if (note.userId !== note.replyUserId && note.reply?.user?.instance?.isBlocked) return false;
|
||||
|
||||
return parentFilter(note);
|
||||
return parentFilter(note, populated);
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
const parentFilter = filter;
|
||||
filter = (note) => {
|
||||
filter = (note, populated) => {
|
||||
if (!ps.ignoreAuthorFromUserSuspension) {
|
||||
if (note.user?.isSuspended) return false;
|
||||
}
|
||||
if (note.userId !== note.renoteUserId && note.renote?.user?.isSuspended) return false;
|
||||
if (note.userId !== note.replyUserId && note.reply?.user?.isSuspended) return false;
|
||||
|
||||
return parentFilter(note);
|
||||
return parentFilter(note, populated);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ export class FanoutTimelineEndpointService {
|
|||
return await ps.dbFallback(ps.untilId, ps.sinceId, ps.limit);
|
||||
}
|
||||
|
||||
private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
|
||||
private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote, populated: PopulatedNote) => boolean, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||
.leftJoinAndSelect('note.reply', 'reply')
|
||||
|
|
@ -198,52 +198,78 @@ export class FanoutTimelineEndpointService {
|
|||
.leftJoinAndSelect('note.channel', 'channel')
|
||||
|
||||
// Needed for populated note
|
||||
.leftJoinAndSelect('renote.renote', 'renoteRenote')
|
||||
.leftJoinAndSelect('renote.reply', 'renoteReply')
|
||||
;
|
||||
|
||||
const notes = await query.getMany();
|
||||
|
||||
// Manually populate user/instance since it's cacheable and avoids many joins.
|
||||
// These fields *must* be populated or NoteVisibilityService won't work right!
|
||||
await this.populateUsers(notes);
|
||||
|
||||
return notes
|
||||
.filter(noteFilter)
|
||||
.sort((a, b) => idCompare(a.id, b.id));
|
||||
const populatedNotes = await this.populateNotes(notes);
|
||||
return populatedNotes
|
||||
.filter(({ note, populated }) => noteFilter(note, populated))
|
||||
.sort((a, b) => idCompare(a.id, b.id))
|
||||
.map(({ note }) => note);
|
||||
}
|
||||
|
||||
private async populateUsers(notes: MiNote[]): Promise<void> {
|
||||
// Enumerate users and instances
|
||||
/**
|
||||
* Given a sample of notes to return, populates the relations from cache and generates a NotePopulationData hint object.
|
||||
* This is messy and kinda gross, but it allows us to use the synchronous checkNoteVisibility from within the filter callbacks.
|
||||
*/
|
||||
private async populateNotes(notes: MiNote[]): Promise<{ id: string, note: MiNote, populated: PopulatedNote }[]> {
|
||||
// Manually populate user/instance since it's cacheable and avoids many joins.
|
||||
// These fields *must* be populated or NoteVisibilityService won't work right!
|
||||
const populationData = await this.populateUsers(notes);
|
||||
|
||||
// This is async, but it should never await because we populate above.
|
||||
return await Promise.all(notes.map(async note => ({
|
||||
id: note.id,
|
||||
note: note,
|
||||
populated: await this.noteVisibilityService.populateNote(note, populationData),
|
||||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* This does two things:
|
||||
* 1. Populates the user/instance relations of every note in the object graph.
|
||||
* 2. Returns fetched note/user/instance maps for use as hint data for NoteVisibilityService.
|
||||
*/
|
||||
private async populateUsers(notes: MiNote[]): Promise<NotePopulationData> {
|
||||
// Enumerate all related data
|
||||
const allNotes = new Map<string, MiNote>();
|
||||
const usersToFetch = new Set<string>();
|
||||
const instancesToFetch = new Set<string>();
|
||||
|
||||
for (const note of notes) {
|
||||
// note
|
||||
allNotes.set(note.id, note);
|
||||
usersToFetch.add(note.userId);
|
||||
if (note.userHost) {
|
||||
instancesToFetch.add(note.userHost);
|
||||
}
|
||||
|
||||
// note.reply
|
||||
if (note.reply) {
|
||||
allNotes.set(note.reply.id, note.reply);
|
||||
usersToFetch.add(note.reply.userId);
|
||||
if (note.reply.userHost) {
|
||||
instancesToFetch.add(note.reply.userHost);
|
||||
}
|
||||
}
|
||||
|
||||
// note.renote
|
||||
if (note.renote) {
|
||||
allNotes.set(note.renote.id, note.renote);
|
||||
usersToFetch.add(note.renote.userId);
|
||||
if (note.renote.userHost) {
|
||||
instancesToFetch.add(note.renote.userHost);
|
||||
}
|
||||
if (note.renote.reply) {
|
||||
usersToFetch.add(note.renote.reply.userId);
|
||||
if (note.renote.reply.userHost) {
|
||||
instancesToFetch.add(note.renote.reply.userHost);
|
||||
}
|
||||
}
|
||||
if (note.renote.renote) {
|
||||
usersToFetch.add(note.renote.renote.userId);
|
||||
if (note.renote.renote.userHost) {
|
||||
instancesToFetch.add(note.renote.renote.userHost);
|
||||
}
|
||||
}
|
||||
|
||||
// note.renote.reply
|
||||
if (note.renote?.reply) {
|
||||
allNotes.set(note.renote.reply.id, note.renote.reply);
|
||||
usersToFetch.add(note.renote.reply.userId);
|
||||
if (note.renote.reply.userHost) {
|
||||
instancesToFetch.add(note.renote.reply.userHost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -271,10 +297,10 @@ export class FanoutTimelineEndpointService {
|
|||
if (note.renote.reply) {
|
||||
note.renote.reply.user = users.get(note.renote.reply.userId) ?? null;
|
||||
}
|
||||
if (note.renote.renote) {
|
||||
note.renote.renote.user = users.get(note.renote.renote.userId) ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization: return our accumulated data to avoid duplicate lookups later
|
||||
return { users, instances, notes: allNotes };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue