From 729abbef621aea5b8b697644181915935b74bbf8 Mon Sep 17 00:00:00 2001 From: おさむのひと <46447427+samunohito@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:39:21 +0900 Subject: feat: チャンネルミュートの実装 (#14105) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add channel_muting table and entities * add channel_muting services * タイムライン取得処理への組み込み * misskey-jsの型とインターフェース生成 * Channelスキーマにミュート情報を追加 * フロントエンドの実装 * 条件が逆だったのを修正 * 期限切れミュートを掃除する機能を実装 * TLの抽出条件調節 * 名前の変更と変更不要の差分をロールバック * 修正漏れ * isChannelRelatedの条件に誤りがあった * [wip] テスト追加 * テストの追加と検出した不備の修正 * fix test * fix CHANGELOG.md * 通常はFTTにしておく * 実装忘れ対応 * fix merge * fix merge * add channel tl test * fix CHANGELOG.md * remove unused import * fix lint * fix test * fix favorite -> favorited * exclude -> include * fix CHANGELOG.md * fix CHANGELOG.md * maintenance * fix CHANGELOG.md * fix * fix ci * regenerate * fix * Revert "fix" This reverts commit 699d50c6ec798777d8e9667cb5d45a26b06bfc93. * fixed --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- .../src/server/api/endpoints/users/notes.ts | 30 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/users') diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 5832790a61..b9710250cf 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -16,6 +16,7 @@ import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { FanoutTimelineName } from '@/core/FanoutTimelineService.js'; import { ApiError } from '@/server/api/error.js'; +import { ChannelMutingService } from '@/core/ChannelMutingService.js'; export const meta = { tags: ['users', 'notes'], @@ -77,12 +78,12 @@ export default class extends Endpoint { // eslint- @Inject(DI.notesRepository) private notesRepository: NotesRepository, - private noteEntityService: NoteEntityService, private queryService: QueryService, private cacheService: CacheService, private idService: IdService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, + private channelMutingService: ChannelMutingService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -165,6 +166,11 @@ export default class extends Endpoint { // eslint- withFiles: boolean, withRenotes: boolean, }, me: MiLocalUser | null) { + const mutingChannelIds = me + ? await this.channelMutingService + .list({ requestUserId: me.id }, { idOnly: true }) + .then(x => x.map(x => x.id)) + : []; const isSelf = me && (me.id === ps.userId); const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) @@ -177,14 +183,30 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('renote.user', 'renoteUser'); if (ps.withChannelNotes) { - if (!isSelf) query.andWhere(new Brackets(qb => { - qb.orWhere('note.channelId IS NULL'); - qb.orWhere('channel.isSensitive = false'); + query.andWhere(new Brackets(qb => { + if (mutingChannelIds.length > 0) { + qb.andWhere('note.channelId NOT IN (:...mutingChannelIds)', { mutingChannelIds: mutingChannelIds }); + } + + if (!isSelf) { + qb.andWhere(new Brackets(qb2 => { + qb2.orWhere('note.channelId IS NULL'); + qb2.orWhere('channel.isSensitive = false'); + })); + } })); } else { query.andWhere('note.channelId IS NULL'); } + // -- ミュートされたチャンネルのリノート対策 + if (mutingChannelIds.length > 0) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteChannelId IS NULL'); + qb.orWhere('note.renoteChannelId NOT IN (:...mutingChannelIds)', { mutingChannelIds }); + })); + } + this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBaseNoteFilteringQuery(query, me, { excludeAuthor: true, -- cgit v1.2.3-freya