summaryrefslogtreecommitdiff
path: root/packages/backend/src/core/FanoutTimelineEndpointService.ts
diff options
context:
space:
mode:
authoranatawa12 <anatawa12@icloud.com>2023-12-04 14:38:21 +0900
committerGitHub <noreply@github.com>2023-12-04 14:38:21 +0900
commit18109fcef760dee8171364fd0382375c4047b8e7 (patch)
tree20bc2c25c8e90070ea8aa77c06cb3b7fa3a28810 /packages/backend/src/core/FanoutTimelineEndpointService.ts
parentfix dev build (#12566) (diff)
downloadmisskey-18109fcef760dee8171364fd0382375c4047b8e7.tar.gz
misskey-18109fcef760dee8171364fd0382375c4047b8e7.tar.bz2
misskey-18109fcef760dee8171364fd0382375c4047b8e7.zip
Filter User / Instance Mutes in FanoutTimelineEndpointService (#12565)
* fix: unnecessary logging in FanoutTimelineEndpointService * chore: TimelineOptions * chore: add FanoutTimelineName type * chore: forbid specifying both withReplies and withFiles since it's not implemented correctly * chore: filter mutes, replies, renotes, files in FanoutTimelineEndpointService * revert unintended changes * use isReply in NoteCreateService * fix: excludePureRenotes is not implemented * fix: replies to me is excluded from local timeline * chore(frontend): forbid enabling both withReplies and withFiles * docs(changelog): インスタンスミュートが効かない問題の修正について言及
Diffstat (limited to 'packages/backend/src/core/FanoutTimelineEndpointService.ts')
-rw-r--r--packages/backend/src/core/FanoutTimelineEndpointService.ts103
1 files changed, 75 insertions, 28 deletions
diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts
index 157fcbe877..6775f0051a 100644
--- a/packages/backend/src/core/FanoutTimelineEndpointService.ts
+++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts
@@ -11,7 +11,29 @@ import type { MiNote } from '@/models/Note.js';
import { Packed } from '@/misc/json-schema.js';
import type { NotesRepository } from '@/models/_.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
-import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
+import { FanoutTimelineName, FanoutTimelineService } from '@/core/FanoutTimelineService.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import { isPureRenote } from '@/misc/is-pure-renote.js';
+import { CacheService } from '@/core/CacheService.js';
+import { isReply } from '@/misc/is-reply.js';
+import { isInstanceMuted } from '@/misc/is-instance-muted.js';
+
+type TimelineOptions = {
+ untilId: string | null,
+ sinceId: string | null,
+ limit: number,
+ allowPartial: boolean,
+ me?: { id: MiUser['id'] } | undefined | null,
+ useDbFallback: boolean,
+ redisTimelines: FanoutTimelineName[],
+ noteFilter?: (note: MiNote) => boolean,
+ alwaysIncludeMyNotes?: boolean;
+ ignoreAuthorFromMute?: boolean;
+ excludeNoFiles?: boolean;
+ excludeReplies?: boolean;
+ excludePureRenotes: boolean;
+ dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise<MiNote[]>,
+};
@Injectable()
export class FanoutTimelineEndpointService {
@@ -20,37 +42,18 @@ export class FanoutTimelineEndpointService {
private notesRepository: NotesRepository,
private noteEntityService: NoteEntityService,
+ private cacheService: CacheService,
private fanoutTimelineService: FanoutTimelineService,
) {
}
@bindThis
- async timeline(ps: {
- untilId: string | null,
- sinceId: string | null,
- limit: number,
- allowPartial: boolean,
- me?: { id: MiUser['id'] } | undefined | null,
- useDbFallback: boolean,
- redisTimelines: string[],
- noteFilter: (note: MiNote) => boolean,
- dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise<MiNote[]>,
- }): Promise<Packed<'Note'>[]> {
+ async timeline(ps: TimelineOptions): Promise<Packed<'Note'>[]> {
return await this.noteEntityService.packMany(await this.getMiNotes(ps), ps.me);
}
@bindThis
- private async getMiNotes(ps: {
- untilId: string | null,
- sinceId: string | null,
- limit: number,
- allowPartial: boolean,
- me?: { id: MiUser['id'] } | undefined | null,
- useDbFallback: boolean,
- redisTimelines: string[],
- noteFilter: (note: MiNote) => boolean,
- dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise<MiNote[]>,
- }): Promise<MiNote[]> {
+ private async getMiNotes(ps: TimelineOptions): Promise<MiNote[]> {
let noteIds: string[];
let shouldFallbackToDb = false;
@@ -67,10 +70,57 @@ export class FanoutTimelineEndpointService {
shouldFallbackToDb = shouldFallbackToDb || (noteIds.length === 0);
if (!shouldFallbackToDb) {
+ let filter = ps.noteFilter ?? (_note => true);
+
+ if (ps.alwaysIncludeMyNotes && ps.me) {
+ const me = ps.me;
+ const parentFilter = filter;
+ filter = (note) => note.userId === me.id || parentFilter(note);
+ }
+
+ if (ps.excludeNoFiles) {
+ const parentFilter = filter;
+ filter = (note) => note.fileIds.length !== 0 && parentFilter(note);
+ }
+
+ if (ps.excludeReplies) {
+ const parentFilter = filter;
+ filter = (note) => !isReply(note, ps.me?.id) && parentFilter(note);
+ }
+
+ if (ps.excludePureRenotes) {
+ const parentFilter = filter;
+ filter = (note) => !isPureRenote(note) && parentFilter(note);
+ }
+
+ if (ps.me) {
+ const me = ps.me;
+ const [
+ userIdsWhoMeMuting,
+ userIdsWhoMeMutingRenotes,
+ userIdsWhoBlockingMe,
+ userMutedInstances,
+ ] = await Promise.all([
+ this.cacheService.userMutingsCache.fetch(ps.me.id),
+ this.cacheService.renoteMutingsCache.fetch(ps.me.id),
+ this.cacheService.userBlockedCache.fetch(ps.me.id),
+ this.cacheService.userProfileCache.fetch(me.id).then(p => new Set(p.mutedInstances)),
+ ]);
+
+ const parentFilter = filter;
+ filter = (note) => {
+ if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromMute)) return false;
+ if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false;
+ if (isPureRenote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false;
+ if (isInstanceMuted(note, userMutedInstances)) return false;
+
+ return parentFilter(note);
+ };
+ }
+
const redisTimeline: MiNote[] = [];
let readFromRedis = 0;
let lastSuccessfulRate = 1; // rateをキャッシュする?
- let trialCount = 1;
while ((redisResultIds.length - readFromRedis) !== 0) {
const remainingToRead = ps.limit - redisTimeline.length;
@@ -81,12 +131,10 @@ export class FanoutTimelineEndpointService {
readFromRedis += noteIds.length;
- const gotFromDb = await this.getAndFilterFromDb(noteIds, ps.noteFilter);
+ const gotFromDb = await this.getAndFilterFromDb(noteIds, filter);
redisTimeline.push(...gotFromDb);
lastSuccessfulRate = gotFromDb.length / noteIds.length;
- console.log(`fanoutTimelineTrial#${trialCount++}: req: ${ps.limit}, tried: ${noteIds.length}, got: ${gotFromDb.length}, rate: ${lastSuccessfulRate}, total: ${redisTimeline.length}, fromRedis: ${redisResultIds.length}`);
-
if (ps.allowPartial ? redisTimeline.length !== 0 : redisTimeline.length >= ps.limit) {
// 十分Redisからとれた
return redisTimeline.slice(0, ps.limit);
@@ -97,7 +145,6 @@ export class FanoutTimelineEndpointService {
const remainingToRead = ps.limit - redisTimeline.length;
const gotFromDb = await ps.dbFallback(noteIds[noteIds.length - 1], ps.sinceId, remainingToRead);
redisTimeline.push(...gotFromDb);
- console.log(`fanoutTimelineTrial#db: req: ${ps.limit}, tried: ${remainingToRead}, got: ${gotFromDb.length}, since: ${noteIds[noteIds.length - 1]}, until: ${ps.untilId}, total: ${redisTimeline.length}`);
return redisTimeline;
}