diff --git a/fluxer_api/src/api/channel/services/thread/ThreadCreationService.ts b/fluxer_api/src/api/channel/services/thread/ThreadCreationService.ts index 3b8a25ef2..fac1e2ada 100644 --- a/fluxer_api/src/api/channel/services/thread/ThreadCreationService.ts +++ b/fluxer_api/src/api/channel/services/thread/ThreadCreationService.ts @@ -76,10 +76,9 @@ import type { } from '@fluxer/schema/src/domains/channel/ForumRequestSchemas'; import type {ThreadChannelResponse} from '@fluxer/schema/src/domains/channel/ThreadRequestSchemas'; import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas'; -import {snowflakeToDate} from '@fluxer/snowflake/src/Snowflake'; import * as BucketUtils from '@fluxer/snowflake/src/SnowflakeBuckets'; -const THREAD_CREATED_MESSAGE_MIN_SOURCE_AGE_MS = 5 * 60 * 1000; +const THREAD_CREATED_MESSAGE_RECENT_WINDOW = 5; function slowmodeThreadKey(parentId: ChannelID, userId: UserID): string { return `slowmode-thread:${parentId}:${userId}`; @@ -159,13 +158,22 @@ export class ThreadCreationService { } const starter = await this.buildStarterEvent(params.viewer, params.user.id, view, params.requestCache); if (starter) events.push(starter); - if (Date.now() - snowflakeToDate(source.id).getTime() >= THREAD_CREATED_MESSAGE_MIN_SOURCE_AGE_MS) { + if (!(await this.isAmongRecentMessages(parentAuth.channel.id, source.id))) { events.push(await this.createThreadCreatedMessage(parentAuth.channel, view, params.user.id)); } await dispatchThreadEvents(this.ctx.gatewayService, view.state.guildId, events); return mapThreadToResponse(view); } + private async isAmongRecentMessages(channelId: ChannelID, messageId: MessageID): Promise { + const recent = await this.ctx.channelRepository.messages.listMessages( + channelId, + undefined, + THREAD_CREATED_MESSAGE_RECENT_WINDOW, + ); + return recent.some((message) => message.id === messageId); + } + async authenticateParent(viewer: ThreadViewer, userId: UserID, channelId: ChannelID): Promise { return this.ctx.channelAuth.getChannelAuthenticated({userId, channelId, viewer}); } diff --git a/fluxer_api/src/api/channel/tests/ThreadRoutes.test.ts b/fluxer_api/src/api/channel/tests/ThreadRoutes.test.ts index 8b762bfca..a2802c8f0 100644 --- a/fluxer_api/src/api/channel/tests/ThreadRoutes.test.ts +++ b/fluxer_api/src/api/channel/tests/ThreadRoutes.test.ts @@ -268,25 +268,28 @@ describe('thread routes', () => { expect(member?.flags).toBe(ThreadMemberFlags.HAS_INTERACTED); }); - it('announces threads started from older messages in the parent without moving its last message', async () => { + it('announces threads started from messages above the latest five without moving its last message', async () => { const s = await setup(); - const message = await sendMessage(harness, s.owner.token, s.channelId, 'old news'); - const now = Date.now(); - vi.spyOn(Date, 'now').mockReturnValue(now + 6 * 60 * 1000); + const pushedUp = await sendMessage(harness, s.owner.token, s.channelId, 'old news'); + const nearBottom = await sendMessage(harness, s.owner.token, s.channelId, 'recent news'); + let latest = nearBottom; + for (let i = 0; i < 4; i++) latest = await sendMessage(harness, s.owner.token, s.channelId, `filler ${i}`); const dispatch = vi.spyOn(NoopGatewayService.prototype, 'dispatchGuild'); - await threadsRequest(harness, s.owner.token) - .post(`/channels/${s.channelId}/messages/${message.id}/threads`) - .body({name: 'Later'}) - .expect(201) - .execute(); + for (const message of [nearBottom, pushedUp]) { + await threadsRequest(harness, s.owner.token) + .post(`/channels/${s.channelId}/messages/${message.id}/threads`) + .body({name: message.content}) + .expect(201) + .execute(); + } const announced = dispatch.mock.calls .map(([params]) => params.data as MessageResponse) .filter((data) => data.type === MessageTypes.THREAD_CREATED); expect(announced).toHaveLength(1); - expect(announced[0]).toMatchObject({channel_id: s.channelId, content: 'Later'}); - expect(announced[0]!.message_reference?.channel_id).toBe(message.id); + expect(announced[0]).toMatchObject({channel_id: s.channelId, content: 'old news'}); + expect(announced[0]!.message_reference?.channel_id).toBe(pushedUp.id); const parent = await repository.findUnique(createChannelID(BigInt(s.channelId))); - expect(parent?.lastMessageId?.toString()).toBe(message.id); + expect(parent?.lastMessageId?.toString()).toBe(latest.id); }); it('refuses a second thread on the same message', async () => {