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 ed56fe0d40..c78cdec2a9 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -19,6 +19,7 @@ class GlobalTimelineChannel extends Channel { public static requireCredential = false as const; private withRenotes: boolean; private withFiles: boolean; + private withCats: boolean; constructor( private metaService: MetaService, @@ -39,6 +40,7 @@ class GlobalTimelineChannel extends Channel { this.withRenotes = !!(params.withRenotes ?? true); this.withFiles = !!(params.withFiles ?? false); + this.withCats = !!(params.withCats ?? false); // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -47,6 +49,7 @@ class GlobalTimelineChannel extends Channel { @bindThis private async onNote(note: Packed<'Note'>) { if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return; + if (this.withCats && (note.user.isCat == null || note.user.isCat === false)) return; if (note.visibility !== 'public') return; if (note.channelId != null) return; 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 66644ed58c..0c6f4f3a1b 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -18,6 +18,7 @@ class HomeTimelineChannel extends Channel { public static kind = 'read:account'; private withRenotes: boolean; private withFiles: boolean; + private withCats: boolean; constructor( private noteEntityService: NoteEntityService, @@ -33,6 +34,7 @@ class HomeTimelineChannel extends Channel { public async init(params: JsonObject) { this.withRenotes = !!(params.withRenotes ?? true); this.withFiles = !!(params.withFiles ?? false); + this.withCats = !!(params.withCats ?? false); this.subscriber.on('notesStream', this.onNote); } @@ -42,6 +44,7 @@ class HomeTimelineChannel extends Channel { const isMe = this.user!.id === note.userId; if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return; + if (this.withCats && (note.user.isCat == null || note.user.isCat === false)) return; if (note.channelId) { if (!this.followingChannels.has(note.channelId)) return; 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 5681311493..9d01efcb7f 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -21,6 +21,7 @@ class HybridTimelineChannel extends Channel { private withRenotes: boolean; private withReplies: boolean; private withFiles: boolean; + private withCats: boolean; constructor( private metaService: MetaService, @@ -42,6 +43,7 @@ class HybridTimelineChannel extends Channel { this.withRenotes = !!(params.withRenotes ?? true); this.withReplies = !!(params.withReplies ?? false); this.withFiles = !!(params.withFiles ?? false); + this.withCats = !!(params.withCats ?? false); // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -52,6 +54,7 @@ class HybridTimelineChannel extends Channel { const isMe = this.user!.id === note.userId; if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return; + if (this.withCats && (note.user.isCat == null || note.user.isCat === false)) return; // チャンネルの投稿ではなく、自分自身の投稿 または // チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または 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 491029f5de..0e79ddd88c 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -20,6 +20,7 @@ class LocalTimelineChannel extends Channel { private withRenotes: boolean; private withReplies: boolean; private withFiles: boolean; + private withCats: boolean; constructor( private metaService: MetaService, @@ -41,6 +42,7 @@ class LocalTimelineChannel extends Channel { this.withRenotes = !!(params.withRenotes ?? true); this.withReplies = !!(params.withReplies ?? false); this.withFiles = !!(params.withFiles ?? false); + this.withCats = !!(params.withCats ?? false); // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -49,6 +51,7 @@ class LocalTimelineChannel extends Channel { @bindThis private async onNote(note: Packed<'Note'>) { if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return; + if (this.withCats && (note.user.isCat == null || note.user.isCat === false)) return; if (note.user.host !== null) return; if (note.visibility !== 'public') return; diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts index 76bf10cf66..4316756f1c 100644 --- a/packages/backend/test/e2e/streaming.ts +++ b/packages/backend/test/e2e/streaming.ts @@ -7,9 +7,9 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { WebSocket } from 'ws'; -import { MiFollowing } from '@/models/Following.js'; import { api, createAppToken, initTestDb, port, post, signup, waitFire } from '../utils.js'; import type * as misskey from 'cherrypick-js'; +import { MiFollowing } from '@/models/Following.js'; describe('Streaming', () => { let Followings: any; @@ -306,6 +306,33 @@ describe('Streaming', () => { assert.strictEqual(fired, true); }); + test('withCats: true のときノートが流れる', async () => { + await api('i/update', { + isCat: true, + }, kyoko); + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { text: 'meow' }, kyoko), // cat kyoko note + msg => msg.type === 'note' && msg.body.userId === kyoko.id, // wait kyoko + { withCats: true }, + ); + + assert.strictEqual(fired, true); + await api('i/update', { + isCat: false, + }, kyoko); + }); + + test('withCats: true のときノートが流れない', async () => { + const fired = await waitFire( + ayano, 'homeTimeline', // ayano:home + () => api('notes/create', { text: 'test' }, kyoko), // kyoko note + msg => msg.type === 'note' && msg.body.userId === kyoko.id, // wait kyoko + { withCats: true }, + ); + + assert.strictEqual(fired, false); + }); test('withReplies: true のとき自分のfollowers投稿に対するリプライが流れる', async () => { const erinNote = await post(erin, { text: 'hi', visibility: 'followers' }); const fired = await waitFire(