From 613e05dcc98117b10cfbacbc7974cfda6dadb260 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 11 Aug 2025 16:25:08 -0400 Subject: [PATCH] fix muted note/thread checks in QueryService --- packages/backend/src/core/QueryService.ts | 52 ++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index d0de136938..bd9248ecf4 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -106,11 +106,24 @@ export class QueryService { @bindThis public generateMutedNoteThreadQuery(q: SelectQueryBuilder, me: { id: MiUser['id'] }): SelectQueryBuilder { + // Muted thread + this.andNotMutingThread(q, ':meId', 'coalesce(note.threadId, note.id)'); + + // Muted note + this.andNotMutingNote(q, ':meId', 'note.id'); + + q.andWhere(new Brackets(qb => qb + .orWhere('note.renoteId IS NULL') + .orWhere(new Brackets(qbb => { + // Renote muted thread + this.andNotMutingThread(qbb, ':meId', 'coalesce(renote.threadId, renote.id)'); + + // Renote muted note + this.andNotMutingNote(qbb, ':meId', 'renote.id'); + })))); + return this - .andNotMutingThread(q, ':meId', 'note.id') - .andWhere(new Brackets(qb => this - .orNotMutingThread(qb, ':meId', 'note.threadId') - .orWhere('note.threadId IS NULL'))) + .leftJoin(q, 'note.renote', 'renote') .setParameters({ meId: me.id }); } @@ -581,7 +594,36 @@ export class QueryService { const threadMutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted') .select('1') .andWhere(`threadMuted.userId = ${muterProp}`) - .andWhere(`threadMuted.threadId = ${muteeProp}`); + .andWhere(`threadMuted.threadId = ${muteeProp}`) + .andWhere('threadMuted.isPostMute = false'); + + return q[join](`NOT EXISTS (${threadMutedQuery.getQuery()})`, threadMutedQuery.getParameters()); + } + + /** + * Adds OR condition that muterProp (user ID) is not muting muteeProp (note ID). + * Both props should be expressions, not raw values. + */ + @bindThis + public orNotMutingNote(q: Q, muterProp: string, muteeProp: string): Q { + return this.excludeMutingNote(q, muterProp, muteeProp, 'orWhere'); + } + + /** + * Adds AND condition that muterProp (user ID) is not muting muteeProp (note ID). + * Both props should be expressions, not raw values. + */ + @bindThis + public andNotMutingNote(q: Q, muterProp: string, muteeProp: string): Q { + return this.excludeMutingNote(q, muterProp, muteeProp, 'andWhere'); + } + + private excludeMutingNote(q: Q, muterProp: string, muteeProp: string, join: 'andWhere' | 'orWhere'): Q { + const threadMutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted') + .select('1') + .andWhere(`threadMuted.userId = ${muterProp}`) + .andWhere(`threadMuted.threadId = ${muteeProp}`) + .andWhere('threadMuted.isPostMute = true'); return q[join](`NOT EXISTS (${threadMutedQuery.getQuery()})`, threadMutedQuery.getParameters()); }