fix(threads): announce threads above the latest five messages (#3307)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-08 20:04:23 +02:00
1 parent c486c2d3d0
commit aa42bf13ce
2 files changed
+22 -11

No files matched your search

@@ -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<boolean> {
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<AuthenticatedChannel> {
return this.ctx.channelAuth.getChannelAuthenticated({userId, channelId, viewer});
}
@@ -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');
for (const message of [nearBottom, pushedUp]) {
await threadsRequest(harness, s.owner.token)
.post(`/channels/${s.channelId}/messages/${message.id}/threads`)
.body({name: 'Later'})
.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 () => {