diff --git a/fluxer_api/src/api/guild/services/channel/ChannelOperationsService.ts b/fluxer_api/src/api/guild/services/channel/ChannelOperationsService.ts index 94a3d298e..4f7eb3676 100644 --- a/fluxer_api/src/api/guild/services/channel/ChannelOperationsService.ts +++ b/fluxer_api/src/api/guild/services/channel/ChannelOperationsService.ts @@ -155,7 +155,7 @@ export class ChannelOperationsService { nsfw: requestedNsfwOverride, content_warning_level: requestedContentWarningLevel, content_warning_text: requestedContentWarningText, - rate_limit_per_user: 0, + rate_limit_per_user: params.data.rate_limit_per_user ?? 0, bitrate: params.data.type === ChannelTypes.GUILD_VOICE ? (params.data.bitrate ?? 64000) : null, user_limit: params.data.type === ChannelTypes.GUILD_VOICE ? (params.data.user_limit ?? 0) : null, voice_connection_limit: diff --git a/fluxer_api/src/api/guild/tests/GuildChannelManagement.test.ts b/fluxer_api/src/api/guild/tests/GuildChannelManagement.test.ts index 2c9286785..4794b9307 100644 --- a/fluxer_api/src/api/guild/tests/GuildChannelManagement.test.ts +++ b/fluxer_api/src/api/guild/tests/GuildChannelManagement.test.ts @@ -98,6 +98,24 @@ describe('Guild Channel Management', () => { }); }); describe('Channel Slowmode (rate_limit_per_user)', () => { + test('should set slowmode when creating a channel', async () => { + const account = await createTestAccount(harness); + const guild = await createGuild(harness, account.token, 'We go slow'); + const channel = await createBuilder(harness, account.token) + .post(`/guilds/${guild.id}/channels`) + .body({name: 'slooooow', type: ChannelTypes.GUILD_TEXT, rate_limit_per_user: 60}) + .execute(); + expect(channel.rate_limit_per_user).toBe(60); + }); + test('should reject invalid slowmode when creating a channel', async () => { + const account = await createTestAccount(harness); + const guild = await createGuild(harness, account.token, 'Test Guild'); + await createBuilder(harness, account.token) + .post(`/guilds/${guild.id}/channels`) + .body({name: 'too-fast-omg', type: ChannelTypes.GUILD_TEXT, rate_limit_per_user: Number.MAX_SAFE_INTEGER}) + .expect(HTTP_STATUS.BAD_REQUEST) + .execute(); + }); test('should set slowmode on text channel', async () => { const account = await createTestAccount(harness); const guild = await createGuild(harness, account.token, 'Test Guild'); diff --git a/fluxer_api/src/api/openapi/openapi.json b/fluxer_api/src/api/openapi/openapi.json index 9887d235c..febdd77e3 100644 --- a/fluxer_api/src/api/openapi/openapi.json +++ b/fluxer_api/src/api/openapi/openapi.json @@ -27597,6 +27597,10 @@ }, "description": "Permission overwrites for roles and members" }, + "rate_limit_per_user": { + "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], + "description": "Slowmode delay in seconds (0-21600)" + }, "nsfw": { "anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Legacy: setting true maps to nsfw_override=true; setting false maps to nsfw_override=null (inherit). Prefer nsfw_override." @@ -27610,10 +27614,6 @@ "anyOf": [{"type": "string", "maxLength": 200}, {"type": "null"}], "description": "Custom channel content warning text (max 200 characters); null inherits from parent or guild" }, - "rate_limit_per_user": { - "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], - "description": "Slowmode delay in seconds (0-21600)" - }, "icon": { "anyOf": [{"$ref": "#/components/schemas/Base64ImageType"}, {"type": "null"}], "description": "Base64-encoded icon image for group DM channels" @@ -27691,6 +27691,10 @@ }, "description": "Permission overwrites for roles and members" }, + "rate_limit_per_user": { + "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], + "description": "Slowmode delay in seconds (0-21600)" + }, "nsfw": { "anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Legacy: setting true maps to nsfw_override=true; setting false maps to nsfw_override=null (inherit). Prefer nsfw_override." @@ -27704,10 +27708,6 @@ "anyOf": [{"type": "string", "maxLength": 200}, {"type": "null"}], "description": "Custom channel content warning text (max 200 characters); null inherits from parent or guild" }, - "rate_limit_per_user": { - "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], - "description": "Slowmode delay in seconds (0-21600)" - }, "icon": { "anyOf": [{"$ref": "#/components/schemas/Base64ImageType"}, {"type": "null"}], "description": "Base64-encoded icon image for group DM channels" @@ -27775,6 +27775,10 @@ }, "description": "Permission overwrites for roles and members" }, + "rate_limit_per_user": { + "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], + "description": "Slowmode delay in seconds (0-21600)" + }, "nsfw": { "anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Legacy: setting true maps to nsfw_override=true; setting false maps to nsfw_override=null (inherit). Prefer nsfw_override." @@ -27788,10 +27792,6 @@ "anyOf": [{"type": "string", "maxLength": 200}, {"type": "null"}], "description": "Custom channel content warning text (max 200 characters); null inherits from parent or guild" }, - "rate_limit_per_user": { - "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], - "description": "Slowmode delay in seconds (0-21600)" - }, "icon": { "anyOf": [{"$ref": "#/components/schemas/Base64ImageType"}, {"type": "null"}], "description": "Base64-encoded icon image for group DM channels" @@ -27859,6 +27859,10 @@ }, "description": "Permission overwrites for roles and members" }, + "rate_limit_per_user": { + "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], + "description": "Slowmode delay in seconds (0-21600)" + }, "nsfw": { "anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Legacy: setting true maps to nsfw_override=true; setting false maps to nsfw_override=null (inherit). Prefer nsfw_override." @@ -27872,10 +27876,6 @@ "anyOf": [{"type": "string", "maxLength": 200}, {"type": "null"}], "description": "Custom channel content warning text (max 200 characters); null inherits from parent or guild" }, - "rate_limit_per_user": { - "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], - "description": "Slowmode delay in seconds (0-21600)" - }, "icon": { "anyOf": [{"$ref": "#/components/schemas/Base64ImageType"}, {"type": "null"}], "description": "Base64-encoded icon image for group DM channels" @@ -30814,6 +30814,10 @@ }, "description": "Permission overwrites for roles and members" }, + "rate_limit_per_user": { + "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], + "description": "Slowmode delay in seconds (0-21600)" + }, "nsfw": {"type": "boolean", "description": "Whether the channel is marked as NSFW"}, "nsfw_override": { "anyOf": [{"type": "boolean"}, {"type": "null"}], @@ -30878,6 +30882,10 @@ }, "description": "Permission overwrites for roles and members" }, + "rate_limit_per_user": { + "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], + "description": "Slowmode delay in seconds (0-21600)" + }, "nsfw": {"type": "boolean", "description": "Whether the channel is marked as NSFW"}, "nsfw_override": { "anyOf": [{"type": "boolean"}, {"type": "null"}], @@ -30942,6 +30950,10 @@ }, "description": "Permission overwrites for roles and members" }, + "rate_limit_per_user": { + "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], + "description": "Slowmode delay in seconds (0-21600)" + }, "nsfw": {"type": "boolean", "description": "Whether the channel is marked as NSFW"}, "nsfw_override": { "anyOf": [{"type": "boolean"}, {"type": "null"}], @@ -31006,6 +31018,10 @@ }, "description": "Permission overwrites for roles and members" }, + "rate_limit_per_user": { + "anyOf": [{"type": "integer", "minimum": 0, "maximum": 21600, "format": "int32"}, {"type": "null"}], + "description": "Slowmode delay in seconds (0-21600)" + }, "nsfw": {"type": "boolean", "description": "Whether the channel is marked as NSFW"}, "nsfw_override": { "anyOf": [{"type": "boolean"}, {"type": "null"}], diff --git a/packages/schema/src/domains/channel/ChannelRequestSchemas.ts b/packages/schema/src/domains/channel/ChannelRequestSchemas.ts index 99a08b381..fedffeb4d 100644 --- a/packages/schema/src/domains/channel/ChannelRequestSchemas.ts +++ b/packages/schema/src/domains/channel/ChannelRequestSchemas.ts @@ -83,6 +83,13 @@ const ChannelCommonBase = z.object({ .array(ChannelOverwriteRequest) .optional() .describe('Permission overwrites for roles and members'), + rate_limit_per_user: z + .number() + .int() + .min(CHANNEL_RATE_LIMIT_PER_USER_MIN) + .max(CHANNEL_RATE_LIMIT_PER_USER_MAX) + .nullish() + .describe(`Slowmode delay in seconds (${CHANNEL_RATE_LIMIT_PER_USER_MIN}-${CHANNEL_RATE_LIMIT_PER_USER_MAX})`), }); const ChannelContentWarningFields = { nsfw_override: z @@ -112,13 +119,6 @@ const ChannelUpdateCommon = ChannelCommonBase.extend({ 'Legacy: setting true maps to nsfw_override=true; setting false maps to nsfw_override=null (inherit). Prefer nsfw_override.', ), ...ChannelContentWarningFields, - rate_limit_per_user: z - .number() - .int() - .min(CHANNEL_RATE_LIMIT_PER_USER_MIN) - .max(CHANNEL_RATE_LIMIT_PER_USER_MAX) - .nullish() - .describe(`Slowmode delay in seconds (${CHANNEL_RATE_LIMIT_PER_USER_MIN}-${CHANNEL_RATE_LIMIT_PER_USER_MAX})`), icon: createBase64StringType(1, Math.ceil(AVATAR_MAX_SIZE * (4 / 3))) .nullish() .describe('Base64-encoded icon image for group DM channels'),