From cfe4e298ca52402e558fa244e3d9296590e7e9a7 Mon Sep 17 00:00:00 2001 From: CyberL1 Date: Tue, 28 Oct 2025 16:43:45 +0100 Subject: [PATCH 1/3] fix: check if components are beetwen 1 to 5 in each row --- .../#interaction_token/callback.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts index 8554c41c..a91326db 100644 --- a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts +++ b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts @@ -19,13 +19,33 @@ import { InteractionCallbackSchema, InteractionCallbackType, MessageCreateSchema, MessageType } from "@spacebar/schemas"; import { route } from "@spacebar/api"; import { Request, Response, Router } from "express"; -import { emitEvent, InteractionSuccessEvent, Message, MessageCreateEvent, pendingInteractions, User } from "@spacebar/util"; +import { emitEvent, FieldErrors, InteractionSuccessEvent, Message, MessageCreateEvent, pendingInteractions, User } from "@spacebar/util"; const router = Router({ mergeParams: true }); router.post("/", route({}), async (req: Request, res: Response) => { const body = req.body as InteractionCallbackSchema; + // TODO: Only one error is ever returned. That's not the case on discord's side + const errors: Record = {}; + + for (const row of body.data.components || []) { + if (!row.components) { + continue; + } + + if (row.components.length < 1 || row.components.length > 5) { + errors[`data.components[${body.data.components!.indexOf(row)}].components`] = { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 5 in length.`, + }; + } + } + + if (Object.keys(errors).length > 0) { + throw FieldErrors(errors); + } + const interactionId = req.params.interaction_id; const interaction = pendingInteractions.get(req.params.interaction_id); From 180200dc9cd6034a7b32531d8e430c2690a1cfe0 Mon Sep 17 00:00:00 2001 From: CyberL1 Date: Tue, 28 Oct 2025 16:53:06 +0100 Subject: [PATCH 2/3] chore: remove solved TODO comment --- .../interactions/#interaction_id/#interaction_token/callback.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts index a91326db..5f88f786 100644 --- a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts +++ b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts @@ -26,7 +26,6 @@ const router = Router({ mergeParams: true }); router.post("/", route({}), async (req: Request, res: Response) => { const body = req.body as InteractionCallbackSchema; - // TODO: Only one error is ever returned. That's not the case on discord's side const errors: Record = {}; for (const row of body.data.components || []) { From 56dd6c87ee4ea9b9ae4ccbf16892ecacb52d6fba Mon Sep 17 00:00:00 2001 From: CyberL1 Date: Tue, 28 Oct 2025 17:36:41 +0100 Subject: [PATCH 3/3] fix: check for duplicated component IDs --- .../#interaction_token/callback.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts index 5f88f786..8a785ac7 100644 --- a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts +++ b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { InteractionCallbackSchema, InteractionCallbackType, MessageCreateSchema, MessageType } from "@spacebar/schemas"; +import { ButtonStyle, InteractionCallbackSchema, InteractionCallbackType, MessageComponentType, MessageCreateSchema, MessageType } from "@spacebar/schemas"; import { route } from "@spacebar/api"; import { Request, Response, Router } from "express"; import { emitEvent, FieldErrors, InteractionSuccessEvent, Message, MessageCreateEvent, pendingInteractions, User } from "@spacebar/util"; @@ -27,6 +27,7 @@ router.post("/", route({}), async (req: Request, res: Response) => { const body = req.body as InteractionCallbackSchema; const errors: Record = {}; + const knownComponentIds: string[] = []; for (const row of body.data.components || []) { if (!row.components) { @@ -39,6 +40,26 @@ router.post("/", route({}), async (req: Request, res: Response) => { message: `Must be between 1 and 5 in length.`, }; } + + for (const component of row.components) { + if (component.type == MessageComponentType.Button && component.style != ButtonStyle.Link) { + if (component.custom_id?.trim() === "") { + errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { + code: "BUTTON_COMPONENT_CUSTOM_ID_REQUIRED", + message: "A custom id required", + }; + } + + if (knownComponentIds.includes(component.custom_id!)) { + errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { + code: "COMPONENT_CUSTOM_ID_DUPLICATED", + message: "Component custom id cannot be duplicated", + }; + } else { + knownComponentIds.push(component.custom_id!); + } + } + } } if (Object.keys(errors).length > 0) {