diff --git a/CHANGELOG.md b/CHANGELOG.md index 6258805f48..80a847adc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - JSONによるClient Information Discoveryを行うには、レスポンスの`Content-Type`ヘッダーが`application/json`である必要があります - 従来の実装(12 February 2022版・HTML Microformat形式)も引き続きサポートされます - Enhance: メモリ使用量を削減 +- Fix: リアルタイム更新時にロックダウン設定が考慮されていない問題を修正 ## 2025.12.2 diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index e7847ba74e..6f562ed8ae 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -17,6 +17,7 @@ import { DebounceLoader } from '@/misc/loader.js'; import { IdService } from '@/core/IdService.js'; import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js'; import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; +import { CacheService } from '@/core/CacheService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; @@ -101,6 +102,7 @@ export class NoteEntityService implements OnModuleInit { //private reactionService: ReactionService, //private reactionsBufferingService: ReactionsBufferingService, //private idService: IdService, + private cacheService: CacheService, ) { } @@ -125,75 +127,65 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise { - if (meId === packedNote.userId) return; - + public async shouldHideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise { + if (meId === packedNote.userId) return false; // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) - let hide = false; if (packedNote.user.requireSigninToViewContents && meId == null) { - hide = true; + return true; } - if (!hide) { - const hiddenBefore = packedNote.user.makeNotesHiddenBefore; - if (shouldHideNoteByTime(hiddenBefore, packedNote.createdAt)) { - hide = true; - } + const hiddenBefore = packedNote.user.makeNotesHiddenBefore; + if (shouldHideNoteByTime(hiddenBefore, packedNote.createdAt)) { + return true; } // visibility が specified かつ自分が指定されていなかったら非表示 - if (!hide) { - if (packedNote.visibility === 'specified') { - if (meId == null) { - hide = true; - } else { - // 指定されているかどうか - const specified = packedNote.visibleUserIds!.some(id => meId === id); + if (packedNote.visibility === 'specified') { + if (meId == null) { + return true; + } else { + // 指定されているかどうか + const specified = packedNote.visibleUserIds!.some(id => meId === id); - if (!specified) { - hide = true; - } + if (!specified) { + return true; } } } // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 - if (!hide) { - if (packedNote.visibility === 'followers') { - if (meId == null) { - hide = true; - } else if (packedNote.reply && (meId === packedNote.reply.userId)) { - // 自分の投稿に対するリプライ - hide = false; - } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { - // 自分へのメンション - hide = false; - } else { - // フォロワーかどうか - // TODO: 当関数呼び出しごとにクエリが走るのは重そうだからなんとかする - const isFollowing = await this.followingsRepository.exists({ - where: { - followeeId: packedNote.userId, - followerId: meId, - }, - }); - - hide = !isFollowing; + if (packedNote.visibility === 'followers') { + if (meId == null) { + return true; + } else if (packedNote.reply && (meId === packedNote.reply.userId)) { + // 自分の投稿に対するリプライ + return false; + } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { + // 自分へのメンション + return false; + } else { + // フォロワーかどうか + const followings = await this.cacheService.userFollowingsCache.fetch(meId); + if (!Object.hasOwn(followings, packedNote.userId)) { + return true; } } } - if (hide) { - packedNote.visibleUserIds = undefined; - packedNote.fileIds = []; - packedNote.files = []; - packedNote.text = null; - packedNote.poll = undefined; - packedNote.cw = null; - packedNote.isHidden = true; - // TODO: hiddenReason みたいなのを提供しても良さそう - } + return false; + } + + @bindThis + public hideNote(packedNote: Packed<'Note'>): void { + packedNote.visibleUserIds = undefined; + packedNote.fileIds = []; + packedNote.files = []; + packedNote.text = null; + packedNote.poll = undefined; + packedNote.cw = null; + packedNote.isHidden = true; + // TODO: hiddenReason みたいなのを提供しても良さそう } @bindThis @@ -468,8 +460,8 @@ export class NoteEntityService implements OnModuleInit { this.treatVisibility(packed); - if (!opts.skipHide) { - await this.hideNote(packed, meId); + if (!opts.skipHide && await this.shouldHideNote(packed, meId)) { + this.hideNote(packed); } return packed; diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index 8259a2a9e4..cfdf9fb5cb 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -22,6 +22,7 @@ import { SigninApiService } from './api/SigninApiService.js'; import { SigninService } from './api/SigninService.js'; import { SignupApiService } from './api/SignupApiService.js'; import { StreamingApiServerService } from './api/StreamingApiServerService.js'; +import { NoteStreamingLockdownService } from './api/stream/NoteStreamingLockdownService.js'; import { OpenApiServerService } from './api/openapi/OpenApiServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; import { HtmlTemplateService } from './web/HtmlTemplateService.js'; @@ -80,6 +81,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j SigninService, SignupApiService, StreamingApiServerService, + NoteStreamingLockdownService, MainChannel, AdminChannel, AntennaChannel, diff --git a/packages/backend/src/server/api/stream/NoteStreamingLockdownService.ts b/packages/backend/src/server/api/stream/NoteStreamingLockdownService.ts new file mode 100644 index 0000000000..e29fe3b85c --- /dev/null +++ b/packages/backend/src/server/api/stream/NoteStreamingLockdownService.ts @@ -0,0 +1,129 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; +import type { Packed } from '@/misc/json-schema.js'; +import type { MiUser } from '@/models/User.js'; + +type HiddenLayer = 'note' | 'renote' | 'renoteRenote'; + +type LockdownCheckResult = + | { shouldSkip: true } + | { shouldSkip: false; hiddenLayers: Set }; + +@Injectable() +export class NoteStreamingLockdownService { + constructor( + private noteEntityService: NoteEntityService, + ) {} + + /** + * ロックダウン設定に基づいてノートの可視性を判定する(純粋関数) + * 副作用なしで判定のみを行う + * + * @param note - 判定対象のノート + * @param meId - 閲覧者のユーザーID(未ログインの場合はnull) + * @returns shouldSkip: true の場合はノートを流さない、false の場合は hiddenLayers に基づいて隠す + */ + @bindThis + public async checkLockdown( + note: Packed<'Note'>, + meId: MiUser['id'] | null, + ): Promise { + const hiddenLayers = new Set(); + + // 1階層目: note自体 + const shouldHideThisNote = await this.noteEntityService.shouldHideNote(note, meId); + if (shouldHideThisNote) { + if (isRenotePacked(note) && isQuotePacked(note)) { + // 引用リノートの場合、内容を隠して流す + hiddenLayers.add('note'); + } else if (isRenotePacked(note)) { + // 純粋リノートの場合、流さない + return { shouldSkip: true }; + } else { + // 通常ノートの場合、内容を隠して流す + hiddenLayers.add('note'); + } + } + + // 2階層目: note.renote + if (isRenotePacked(note) && note.renote) { + const shouldHideRenote = await this.noteEntityService.shouldHideNote(note.renote, meId); + if (shouldHideRenote) { + if (isQuotePacked(note)) { + // noteが引用リノートの場合、renote部分だけ隠す + hiddenLayers.add('renote'); + } else { + // noteが純粋リノートの場合、流さない + return { shouldSkip: true }; + } + } + } + + // 3階層目: note.renote.renote + if (isRenotePacked(note) && note.renote && + isRenotePacked(note.renote) && note.renote.renote) { + const shouldHideRenoteRenote = await this.noteEntityService.shouldHideNote(note.renote.renote, meId); + if (shouldHideRenoteRenote) { + if (isQuotePacked(note.renote)) { + // note.renoteが引用リノートの場合、renote.renote部分だけ隠す + hiddenLayers.add('renoteRenote'); + } else { + // note.renoteが純粋リノートの場合、note.renoteの意味がなくなるので流さない + return { shouldSkip: true }; + } + } + } + + return { shouldSkip: false, hiddenLayers }; + } + + /** + * hiddenLayersに基づいてノートの内容を隠す(副作用あり) + * + * @param note - 処理対象のノート + * @param hiddenLayers - 隠す階層のセット + */ + @bindThis + public applyHiding( + note: Packed<'Note'>, + hiddenLayers: Set, + ): void { + if (hiddenLayers.has('note')) { + this.noteEntityService.hideNote(note); + } + if (hiddenLayers.has('renote') && note.renote) { + this.noteEntityService.hideNote(note.renote); + } + if (hiddenLayers.has('renoteRenote') && note.renote && note.renote.renote) { + this.noteEntityService.hideNote(note.renote.renote); + } + } + + /** + * ストリーミング配信用にノートのロックダウン処理を適用する(便利メソッド) + * checkLockdown + applyHiding を一括で行う + * + * @param note - 処理対象のノート(必要に応じて内容が隠される) + * @param meId - 閲覧者のユーザーID(未ログインの場合はnull) + * @returns shouldSkip: true の場合はノートを流さない + */ + @bindThis + public async processLockdown( + note: Packed<'Note'>, + meId: MiUser['id'] | null, + ): Promise<{ shouldSkip: boolean }> { + const result = await this.checkLockdown(note, meId); + if (result.shouldSkip) { + return { shouldSkip: true }; + } + this.applyHiding(note, result.hiddenLayers); + return { shouldSkip: false }; + } +} diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index ece9d2c8b1..1cf8595110 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -6,9 +6,11 @@ import { Inject, Injectable, Scope } from '@nestjs/common'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type ChannelRequest } from '../channel.js'; +import { NoteStreamingLockdownService } from '../NoteStreamingLockdownService.js'; import { REQUEST } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) @@ -24,6 +26,7 @@ export class AntennaChannel extends Channel { request: ChannelRequest, private noteEntityService: NoteEntityService, + private noteStreamingFilterService: NoteStreamingLockdownService, ) { super(request); //this.onEvent = this.onEvent.bind(this); @@ -45,6 +48,18 @@ export class AntennaChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; + const { shouldSkip: shouldSkipByLockdown } = await this.noteStreamingFilterService.processLockdown(note, this.user?.id ?? null); + if (shouldSkipByLockdown) return; + + if (this.user) { + if (isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } + } + } + this.send('note', note); } else { this.send(data.type, data.body); diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 1706b17526..f66c1880f2 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -12,6 +12,7 @@ import { isInstanceMuted } from '@/misc/is-instance-muted.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type ChannelRequest } from '../channel.js'; +import { NoteStreamingLockdownService } from '../NoteStreamingLockdownService.js'; import { REQUEST } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) @@ -26,6 +27,7 @@ export class ChannelChannel extends Channel { request: ChannelRequest, private noteEntityService: NoteEntityService, + private noteStreamingFilterService: NoteStreamingLockdownService, ) { super(request); //this.onNote = this.onNote.bind(this); @@ -50,10 +52,15 @@ export class ChannelChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; + const { shouldSkip: shouldSkipByLockdown } = await this.noteStreamingFilterService.processLockdown(note, this.user?.id ?? null); + if (shouldSkipByLockdown) return; + + if (this.user) { + if (isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } } } diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index be6be1b1e7..f1b507e3a5 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -12,6 +12,7 @@ import { RoleService } from '@/core/RoleService.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type ChannelRequest } from '../channel.js'; +import { NoteStreamingLockdownService } from '../NoteStreamingLockdownService.js'; import { REQUEST } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) @@ -29,6 +30,7 @@ export class GlobalTimelineChannel extends Channel { private metaService: MetaService, private roleService: RoleService, private noteEntityService: NoteEntityService, + private noteStreamingFilterService: NoteStreamingLockdownService, ) { super(request); //this.onNote = this.onNote.bind(this); @@ -60,10 +62,15 @@ export class GlobalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; + const { shouldSkip: shouldSkipByLockdown } = await this.noteStreamingFilterService.processLockdown(note, this.user?.id ?? null); + if (shouldSkipByLockdown) return; + + if (this.user) { + if (isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } } } diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 1456b4f262..2cb6e5c3d3 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -11,6 +11,7 @@ import { bindThis } from '@/decorators.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type ChannelRequest } from '../channel.js'; +import { NoteStreamingLockdownService } from '../NoteStreamingLockdownService.js'; import { REQUEST } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) @@ -25,6 +26,7 @@ export class HashtagChannel extends Channel { request: ChannelRequest, private noteEntityService: NoteEntityService, + private noteStreamingFilterService: NoteStreamingLockdownService, ) { super(request); //this.onNote = this.onNote.bind(this); @@ -48,10 +50,15 @@ export class HashtagChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; + const { shouldSkip: shouldSkipByLockdown } = await this.noteStreamingFilterService.processLockdown(note, this.user?.id ?? null); + if (shouldSkipByLockdown) return; + + if (this.user) { + if (isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } } } 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 665c11b692..26d8255a82 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -10,6 +10,7 @@ import { bindThis } from '@/decorators.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type ChannelRequest } from '../channel.js'; +import { NoteStreamingLockdownService } from '../NoteStreamingLockdownService.js'; import { REQUEST } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) @@ -26,6 +27,7 @@ export class HomeTimelineChannel extends Channel { request: ChannelRequest, private noteEntityService: NoteEntityService, + private noteStreamingFilterService: NoteStreamingLockdownService, ) { super(request); //this.onNote = this.onNote.bind(this); @@ -84,10 +86,15 @@ export class HomeTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; + const { shouldSkip: shouldSkipByLockdown } = await this.noteStreamingFilterService.processLockdown(note, this.user?.id ?? null); + if (shouldSkipByLockdown) return; + + if (this.user) { + if (isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } } } 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 54250d2a90..841f1170d6 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -12,6 +12,7 @@ import { RoleService } from '@/core/RoleService.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type ChannelRequest } from '../channel.js'; +import { NoteStreamingLockdownService } from '../NoteStreamingLockdownService.js'; import { REQUEST } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) @@ -31,6 +32,7 @@ export class HybridTimelineChannel extends Channel { private metaService: MetaService, private roleService: RoleService, private noteEntityService: NoteEntityService, + private noteStreamingFilterService: NoteStreamingLockdownService, ) { super(request); //this.onNote = this.onNote.bind(this); @@ -104,10 +106,15 @@ export class HybridTimelineChannel extends Channel { } } - if (this.user && note.renoteId && !note.text) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; + const { shouldSkip: shouldSkipByLockdown } = await this.noteStreamingFilterService.processLockdown(note, this.user?.id ?? null); + if (shouldSkipByLockdown) return; + + if (this.user) { + if (isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } } } diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index b394e9663f..61a37ed768 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -12,6 +12,7 @@ import { RoleService } from '@/core/RoleService.js'; import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type ChannelRequest } from '../channel.js'; +import { NoteStreamingLockdownService } from '../NoteStreamingLockdownService.js'; import { REQUEST } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) @@ -30,6 +31,7 @@ export class LocalTimelineChannel extends Channel { private metaService: MetaService, private roleService: RoleService, private noteEntityService: NoteEntityService, + private noteStreamingFilterService: NoteStreamingLockdownService, ) { super(request); //this.onNote = this.onNote.bind(this); @@ -70,10 +72,15 @@ export class LocalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; + const { shouldSkip: shouldSkipByLockdown } = await this.noteStreamingFilterService.processLockdown(note, this.user?.id ?? null); + if (shouldSkipByLockdown) return; + + if (this.user) { + if (isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } } } diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts index 99e0b69023..279d85f420 100644 --- a/packages/backend/src/server/api/stream/channels/role-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts @@ -7,9 +7,11 @@ import { Inject, Injectable, Scope } from '@nestjs/common'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type ChannelRequest } from '../channel.js'; +import { NoteStreamingLockdownService } from '../NoteStreamingLockdownService.js'; import { REQUEST } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) @@ -25,6 +27,7 @@ export class RoleTimelineChannel extends Channel { private noteEntityService: NoteEntityService, private roleservice: RoleService, + private noteStreamingFilterService: NoteStreamingLockdownService, ) { super(request); //this.onNote = this.onNote.bind(this); @@ -50,6 +53,18 @@ export class RoleTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; + const { shouldSkip: shouldSkipByLockdown } = await this.noteStreamingFilterService.processLockdown(note, this.user?.id ?? null); + if (shouldSkipByLockdown) return; + + if (this.user) { + if (isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } + } + } + this.send('note', note); } else { this.send(data.type, data.body); diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 2f7345e150..fec74271b2 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -12,6 +12,7 @@ import { bindThis } from '@/decorators.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { JsonObject } from '@/misc/json-value.js'; import Channel, { type ChannelRequest } from '../channel.js'; +import { NoteStreamingLockdownService } from '../NoteStreamingLockdownService.js'; import { REQUEST } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) @@ -36,6 +37,7 @@ export class UserListChannel extends Channel { request: ChannelRequest, private noteEntityService: NoteEntityService, + private noteStreamingFilterService: NoteStreamingLockdownService, ) { super(request); //this.updateListUsers = this.updateListUsers.bind(this); @@ -117,10 +119,15 @@ export class UserListChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; + const { shouldSkip: shouldSkipByLockdown } = await this.noteStreamingFilterService.processLockdown(note, this.user?.id ?? null); + if (shouldSkipByLockdown) return; + + if (this.user) { + if (isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); + note.renote.myReaction = myRenoteReaction; + } } }