diff --git a/scripts/schemaExclusions.json b/scripts/schemaExclusions.json
index f25e8f0..82e545a 100644
--- a/scripts/schemaExclusions.json
+++ b/scripts/schemaExclusions.json
@@ -3,7 +3,8 @@
"MessageInteractionSchema"
],
"includeRe": [
- "^MessageComponentType\\..*"
+ "^MessageComponentType\\..*",
+ "^InteractionCallbackType\\..*"
],
"manual": [
"DefaultSchema",
diff --git "a/src/api/routes/interactions/\043interaction_id/\043interaction_token/callback.ts" "b/src/api/routes/interactions/\043interaction_id/\043interaction_token/callback.ts"
index d9af736..6c7fc12 100644
--- "a/src/api/routes/interactions/\043interaction_id/\043interaction_token/callback.ts"
+++ "b/src/api/routes/interactions/\043interaction_id/\043interaction_token/callback.ts"
@@ -16,7 +16,7 @@
along with this program. If not, see .
*/
-import { BaseMessageComponents, InteractionCallbackSchema, InteractionCallbackType, MessageType } from "@spacebar/schemas";
+import { BaseMessageComponents, InteractionCallbackSchema, InteractionCallbacksSchema, InteractionCallbackType, InteractionFailureReason, MessageType } from "@spacebar/schemas";
import { handleComps, route, sendMessage } from "@spacebar/api";
import { Request, Response, Router } from "express";
import { Config, emitEvent, InteractionSuccessEvent, Message, MessageUpdateEvent, pendingInteractions, User, InteractionFailureEvent } from "@spacebar/util";
@@ -24,40 +24,46 @@
const router = Router({ mergeParams: true });
-router.post("/", route({}), async (req: Request, res: Response) => {
- const body = req.body as InteractionCallbackSchema;
+router.post(
+ "/",
+ route({
+ stripNulls: true,
+ requestBody: "InteractionCallbacksSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as InteractionCallbacksSchema;
- const interactionId = req.params.interaction_id as string;
- const interaction = pendingInteractions.get(req.params.interaction_id);
+ const interactionId = req.params.interaction_id as string;
+ const interaction = pendingInteractions.get(req.params.interaction_id);
- if (!interaction) {
- return;
- }
+ if (!interaction) {
+ return;
+ }
- clearTimeout(interaction.timeout);
+ clearTimeout(interaction.timeout);
await emitEvent({
- event: "INTERACTION_SUCCESS",
- user_id: interaction?.userId,
- data: {
- id: interactionId,
- nonce: interaction?.nonce,
- },
- } as InteractionSuccessEvent);
+ event: "INTERACTION_SUCCESS",
+ user_id: interaction?.userId,
+ data: {
+ id: interactionId,
+ nonce: interaction?.nonce,
+ },
+ } as InteractionSuccessEvent);
- switch (body.type) {
- case InteractionCallbackType.PONG:
- // TODO
- break;
- case InteractionCallbackType.ACKNOWLEDGE:
- // Deprected
- break;
- case InteractionCallbackType.CHANNEL_MESSAGE:
- // TODO
- break;
- case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: {
- const user = await User.findOneOrFail({ where: { id: interaction.userId } });
- /*
+ switch (body.type) {
+ case InteractionCallbackType.PONG:
+ // TODO
+ break;
+ case InteractionCallbackType.ACKNOWLEDGE:
+ // Deprected
+ break;
+ case InteractionCallbackType.CHANNEL_MESSAGE:
+ // TODO
+ break;
+ case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: {
+ const user = await User.findOneOrFail({ where: { id: interaction.userId } });
+ /*
const files = (req.files as Express.Multer.File[]) ?? [];
//I don't think traditional attachments are allowed anyways
const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = [];
@@ -70,123 +76,126 @@
}
}
*/
- await sendMessage({
- type: MessageType.APPLICATION_COMMAND,
- timestamp: new Date(),
- application_id: interaction.applicationId,
- channel_id: interaction.channelId,
- author_id: interaction.applicationId,
- nonce: interaction.nonce,
- content: body.data.content,
- components: body.data.components || [],
- tts: body.data.tts,
- embeds: body.data.embeds || [],
- attachments: body.data.attachments,
- poll: body.data.poll,
- flags: body.data.flags,
- reactions: [],
- // webhook_id: interaction.applicationId, // This one requires a webhook to be created first
- interaction: {
- id: interactionId,
- name: interaction.commandName,
- type: 2,
- user,
- },
- interaction_metadata: {
- id: interactionId,
- type: 2,
- user_id: interaction.userId,
- user,
- authorizing_integration_owners: {
- "1": interaction.userId,
- },
- name: interaction.commandName,
- command_type: interaction.commandType,
- },
- });
-
- break;
- }
- case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE:
- // TODO
- break;
- case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE:
- //TODO keep track of state of this
- interaction.timeout = setTimeout(() => {
- emitEvent({
- event: "INTERACTION_FAILURE",
- user_id: req.user_id,
- data: {
+ await sendMessage({
+ type: MessageType.APPLICATION_COMMAND,
+ timestamp: new Date(),
+ application_id: interaction.applicationId,
+ channel_id: interaction.channelId,
+ author_id: interaction.applicationId,
+ nonce: interaction.nonce,
+ content: body.data.content,
+ components: body.data.components || [],
+ tts: body.data.tts,
+ embeds: body.data.embeds || [],
+ attachments: body.data.attachments,
+ poll: body.data.poll,
+ flags: body.data.flags,
+ reactions: [],
+ // webhook_id: interaction.applicationId, // This one requires a webhook to be created first
+ interaction: {
id: interactionId,
- nonce: interaction.nonce,
- reason_code: InteractionFailureReason.TIMEOUT,
+ name: interaction.commandName,
+ type: 2,
+ user,
},
- } as InteractionFailureEvent);
- }, 30000);
- pendingInteractions.delete(interactionId);
- res.sendStatus(204);
- return;
- case InteractionCallbackType.UPDATE_MESSAGE:
- {
- if (!interaction.messageId) throw new HTTPError("no. That was not a message");
- const message = await Message.findOneOrFail({
- relations: {
- author: true,
- webhook: true,
- application: true,
- mentions: true,
- mention_roles: true,
- mention_channels: true,
- sticker_items: true,
- attachments: true,
- thread: {
- recipients: {
- user: true,
- },
+ interaction_metadata: {
+ id: interactionId,
+ type: 2,
+ user_id: interaction.userId,
+ user,
+ authorizing_integration_owners: {
+ "1": interaction.userId,
},
- channel: true,
- },
- where: {
- id: interaction.messageId,
+ name: interaction.commandName,
+ command_type: interaction.commandType,
},
});
- if (body.data.content && body.data.content.length > Config.get().limits.message.maxCharacters) {
- throw new HTTPError("Content length over max character limit");
- }
- if (body.data.components) stripNull(body.data.components);
- message.embeds = body.data.embeds || [];
- const handle = body.data.components ? handleComps(body.data.components, message.flags) : undefined;
- await handle?.(message.id, message.author as User, message.channel);
- message.components = body.data.components;
- await message.save();
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id: message.channel_id,
- data: message.toJSON(),
- } satisfies MessageUpdateEvent);
- }
- // TODO
- break;
- case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT:
- // TODO
- break;
- case InteractionCallbackType.MODAL:
- // TODO
- break;
- case InteractionCallbackType.PREMIUM_REQUIRED:
- // Deprecated
- break;
- case InteractionCallbackType.IFRAME_MODAL:
- // TODO
- break;
- case InteractionCallbackType.LAUNCH_ACTIVITY:
- // TODO
- break;
- }
- pendingInteractions.delete(interactionId);
- res.sendStatus(204);
-});
+ break;
+ }
+ case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE:
+ // TODO
+ break;
+ case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE:
+ //TODO keep track of state of this
+ interaction.timeout = setTimeout(() => {
+ emitEvent({
+ event: "INTERACTION_FAILURE",
+ user_id: req.user_id,
+ data: {
+ id: interactionId,
+ nonce: interaction.nonce,
+ reason_code: InteractionFailureReason.TIMEOUT,
+ },
+ } as InteractionFailureEvent);
+ }, 30000);
+ pendingInteractions.delete(interactionId);
+ res.sendStatus(204);
+ return;
+ case InteractionCallbackType.UPDATE_MESSAGE:
+ {
+ if (!interaction.messageId) throw new HTTPError("no. That was not a message");
+ const message = await Message.findOneOrFail({
+ relations: {
+ author: true,
+ webhook: true,
+ application: true,
+ mentions: true,
+ mention_roles: true,
+ mention_channels: true,
+ sticker_items: true,
+ attachments: true,
+ thread: {
+ recipients: {
+ user: true,
+ },
+ },
+ channel: true,
+ },
+ where: {
+ id: interaction.messageId,
+ },
+ });
+ if (body.data.content && body.data.content.length > Config.get().limits.message.maxCharacters) {
+ throw new HTTPError("Content length over max character limit");
+ }
+ if (body.data.components) stripNull(body.data.components);
+ message.embeds = body.data.embeds || [];
+ const handle = body.data.components ? handleComps(body.data.components, message.flags) : undefined;
+ await handle?.(message.id, message.author as User, message.channel);
+ message.components = body.data.components;
+ await message.save();
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id: message.channel_id,
+ data: message.toJSON(),
+ } satisfies MessageUpdateEvent);
+ }
+ // TODO
+ break;
+ /*
+ case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT:
+ // TODO
+ break;
+ case InteractionCallbackType.MODAL:
+ // TODO
+ break;
+ case InteractionCallbackType.PREMIUM_REQUIRED:
+ // Deprecated
+ break;
+ case InteractionCallbackType.IFRAME_MODAL:
+ // TODO
+ break;
+ case InteractionCallbackType.LAUNCH_ACTIVITY:
+ // TODO
+ break;
+ */
+ }
+
+ pendingInteractions.delete(interactionId);
+ res.sendStatus(204);
+ },
+);
export default router;
function stripNull(components: BaseMessageComponents[]) {
diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts
index 0dd4a4b..c00a959 100644
--- a/src/api/util/handlers/route.ts
+++ b/src/api/util/handlers/route.ts
@@ -20,6 +20,7 @@
import { AnyValidateFunction } from "ajv/dist/core";
import { NextFunction, Request, Response } from "express";
import { ajv } from "@spacebar/schemas";
+import { BigNumber } from "bignumber.js";
const ignoredRequestSchemas = [
// skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix?
@@ -98,6 +99,20 @@
}
}
}
+//It's pretty safe to assume numbers over the number limit aren't really meant to be numbers, so we turn them to strings.
+export function bigNumberToString(obj1: unknown) {
+ if (obj1 && typeof obj1 === "object") {
+ for (const [key, value] of Object.entries(obj1)) {
+ if (typeof value === "object") {
+ if (value instanceof BigNumber) {
+ //@ts-expect-error this is fine lol
+ obj1[key] = value.toString();
+ }
+ bigNumberToString(value);
+ }
+ }
+ }
+}
export function route(opts: RouteOptions) {
let validate: AnyValidateFunction | undefined;
if (opts.requestBody) {
@@ -133,15 +148,16 @@
throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string);
}
}
+ bigNumberToString(req.body);
if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) {
if (opts.stripNulls) {
if (opts.stripNulls === true) stripNull(req.body);
else followNullPath(req.body, opts.stripNulls);
}
+
const valid = validate(req.body);
if (!valid) {
- //console.log(JSON.stringify(req.body));
const fields: Record = {};
validate.errors?.forEach(
(x) =>
diff --git a/src/schemas/api/bots/InteractionCallbackSchema.ts b/src/schemas/api/bots/InteractionCallbackSchema.ts
index 66a4e66..dd09fb9 100644
--- a/src/schemas/api/bots/InteractionCallbackSchema.ts
+++ b/src/schemas/api/bots/InteractionCallbackSchema.ts
@@ -18,8 +18,54 @@
import { Message } from "@spacebar/util";
import { InteractionCallbackType } from "./InteractionCallbackType";
+import { AllowedMentions, BaseMessageComponents, Embed, MessageComponentType } from "../messages";
+import { MessageCreateAttachment, MessageCreateCloudAttachment, PollCreationSchema } from "#schemas/uncategorised";
export interface InteractionCallbackSchema {
type: InteractionCallbackType;
- data: Message;
+ data: unknown;
+}
+export interface PongCallback extends InteractionCallbackSchema {
+ type: InteractionCallbackType.PONG;
+}
+export interface AckCallback extends InteractionCallbackSchema {
+ type: InteractionCallbackType.ACKNOWLEDGE;
+}
+export interface MessageCallback extends InteractionCallbackSchema {
+ type: InteractionCallbackType.CHANNEL_MESSAGE;
+}
+export interface MessageWSourceCallback extends InteractionCallbackSchema {
+ type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE;
+ data: InteractionMessage;
+}
+export interface MessageDWSourceCallback extends InteractionCallbackSchema {
+ type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE;
+ data: InteractionMessage;
+}
+export interface MessageUpdateCallback extends InteractionCallbackSchema {
+ type: InteractionCallbackType.UPDATE_MESSAGE;
+ data: InteractionMessage;
+}
+export interface MessageDUpdateCallback extends InteractionCallbackSchema {
+ type: InteractionCallbackType.DEFERRED_UPDATE_MESSAGE;
+ data: InteractionMessage;
+}
+export type InteractionCallbacksSchema =
+ | PongCallback
+ | AckCallback
+ | MessageCallback
+ | MessageWSourceCallback
+ | MessageDWSourceCallback
+ | MessageUpdateCallback
+ | MessageDUpdateCallback;
+
+export interface InteractionMessage {
+ content?: string;
+ tts?: boolean;
+ embeds?: Embed[];
+ allowed_mentions?: AllowedMentions;
+ components?: BaseMessageComponents[];
+ flags?: number;
+ attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment)[];
+ poll?: PollCreationSchema;
}