diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 8c55673590..195a4410ca 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -15,6 +15,8 @@ import { CacheService } from '@/core/CacheService.js'; import { MetaService } from '@/core/MetaService.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { MiLocalUser } from '@/models/User.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; +import { isChannelRelated } from '@/misc/is-channel-related.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -71,6 +73,7 @@ export default class extends Endpoint { // eslint- private cacheService: CacheService, private activeUsersChart: ActiveUsersChart, private metaService: MetaService, + private channelMutingService: ChannelMutingService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -92,6 +95,9 @@ export default class extends Endpoint { // eslint- return await this.noteEntityService.packMany(await this.getFromDb({ untilId, sinceId, limit: ps.limit, channelId: channel.id }, me), me); } + const mutingChannelIds = me + ? await this.channelMutingService.mutingChannelsCache.get(me.id) ?? new Set() + : new Set(); return await this.fanoutTimelineEndpointService.timeline({ untilId, sinceId, @@ -101,6 +107,11 @@ export default class extends Endpoint { // eslint- useDbFallback: true, redisTimelines: [`channelTimeline:${channel.id}`], excludePureRenotes: false, + noteFilter: note => { + // 共通機能を使うと見ているチャンネルそのものもミュートしてしまうので閲覧中のチャンネル以外を除く形にする + if (note.channelId === channel.id) return true; + return !isChannelRelated(note, mutingChannelIds); + }, dbFallback: async (untilId, sinceId, limit) => { return await this.getFromDb({ untilId, sinceId, limit, channelId: channel.id }, me); }, @@ -125,6 +136,14 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('note.channel', 'channel'); if (me) { + const mutingChannelIds = await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id).filter(x => x !== ps.channelId)); + if (mutingChannelIds.length > 0) { + query.andWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + query.andWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + } + this.queryService.generateMutedUserQuery(query, me); this.queryService.generateBlockedUserQuery(query, me); } diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index da6cf39889..7a83cf63f9 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -8,6 +8,7 @@ import { isInstanceMuted } from '@/misc/is-instance-muted.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js'; import type { Packed } from '@/misc/json-schema.js'; +import { isChannelRelated } from '@/misc/is-channel-related.js'; import type Connection from './Connection.js'; /** @@ -77,6 +78,9 @@ export default abstract class Channel { // 流れてきたNoteがリノートをミュートしてるユーザが行ったもの if (isRenotePacked(note) && !isQuotePacked(note) && this.userIdsWhoMeMutingRenotes.has(note.user.id)) return true; + // 流れてきたNoteがミュートしているチャンネルと関わる + if (isChannelRelated(note, this.mutingChannels)) return true; + return false; } diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 140dd3dd9b..7f24c347c3 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -7,7 +7,9 @@ import { Injectable } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; -import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; +import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js'; +import { isInstanceMuted } from '@/misc/is-instance-muted.js'; +import { isUserRelated } from '@/misc/is-user-related.js'; import Channel, { type MiChannelService } from '../channel.js'; class ChannelChannel extends Channel { @@ -18,7 +20,6 @@ class ChannelChannel extends Channel { constructor( private noteEntityService: NoteEntityService, - id: string, connection: Channel['connection'], ) { @@ -52,6 +53,35 @@ class ChannelChannel extends Channel { this.send('note', note); } + /* + * ミュートとブロックされてるを処理する + */ + protected override isNoteMutedOrBlocked(note: Packed<'Note'>): boolean { + // 流れてきたNoteがインスタンスミュートしたインスタンスが関わる + if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return true; + + // 流れてきたNoteがミュートしているユーザーが関わる + if (isUserRelated(note, this.userIdsWhoMeMuting)) return true; + // 流れてきたNoteがブロックされているユーザーが関わる + if (isUserRelated(note, this.userIdsWhoBlockingMe)) return true; + + // 流れてきたNoteがリノートをミュートしてるユーザが行ったもの + if (isRenotePacked(note) && !isQuotePacked(note) && this.userIdsWhoMeMutingRenotes.has(note.user.id)) return true; + + // このソケットで見ているチャンネルがミュートされていたとしても、チャンネルを直接見ている以上は流すようにしたい + // ただし、他のミュートしているチャンネルは流さないようにもしたい + // ノート自体のチャンネルIDはonNoteでチェックしているので、ここではリノートのチャンネルIDをチェックする + if ( + (note.renote) && + (note.renote.channelId !== this.channelId) && + (note.renote.channelId && this.mutingChannels.has(note.renote.channelId)) + ) { + return true; + } + + return false; + } + @bindThis public dispose() { // Unsubscribe events diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index f450336914..5fe1ce6ee0 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -44,8 +44,8 @@ class HomeTimelineChannel extends Channel { if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return; if (note.channelId) { - // そのチャンネルをフォローしていない or そのチャンネル(リノート・引用リノート含む)はミュートしている - if (!this.followingChannels.has(note.channelId) || isChannelRelated(note, this.mutingChannels)) { + // そのチャンネルをフォローしていない + if (!this.followingChannels.has(note.channelId)) { return; } } else { diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 6bd9f2a68b..c706f87158 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -67,11 +67,7 @@ class HybridTimelineChannel extends Channel { } } else { // 以下の条件に該当するノートのみ後続処理に通す(ので、以下のif文は該当しないノートをすべて弾くようにする) - // - ミュートしていないチャンネルの投稿(リノート・引用リノートもチェック対象) // - フォローしているチャンネルの投稿 - if (isChannelRelated(note, this.mutingChannels)) { - return; - } if (!this.followingChannels.has(note.channelId)) { return; }