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 7a7812678..93a146f9f 100644 --- a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts +++ b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts @@ -19,7 +19,18 @@ 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"; +import { + Config, + emitEvent, + InteractionSuccessEvent, + Message, + MessageUpdateEvent, + pendingInteractions, + User, + InteractionFailureEvent, + InteractionModalCreateEvent, + Application, +} from "@spacebar/util"; import { HTTPError } from "#util/util/lambert-server"; const router = Router({ mergeParams: true }); @@ -47,9 +58,9 @@ router.post( user_id: interaction?.userId, data: { id: interactionId, - nonce: interaction.nonce ?? "", // TODO: did i do this right? + nonce: interaction.nonce ?? "", // TODO: did i do this right? }, - } satisfies InteractionSuccessEvent); + } satisfies InteractionSuccessEvent); switch (body.type) { case InteractionCallbackType.PONG: @@ -172,13 +183,25 @@ router.post( } // TODO break; + case InteractionCallbackType.MODAL: + emitEvent({ + event: "INTERACTION_MODAL_CREATE", + user_id: interaction.userId, + data: { + ...body.data, + id: interaction.interactionId, + application: await Application.findOneOrFail({ where: { id: interaction.applicationId } }), + channel_id: interaction.channelId as string, + nonce: interaction.nonce, + }, + } satisfies InteractionModalCreateEvent); + console.log(body); + break; /* case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT: // TODO break; - case InteractionCallbackType.MODAL: - // TODO - break; + case InteractionCallbackType.PREMIUM_REQUIRED: // Deprecated break; diff --git a/src/api/routes/interactions/index.ts b/src/api/routes/interactions/index.ts index 33375b145..bb7d0c927 100644 --- a/src/api/routes/interactions/index.ts +++ b/src/api/routes/interactions/index.ts @@ -91,24 +91,26 @@ router.post("/", route({}), async (req: Request, res: Response) => { } if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) { - interactionData.message = await Message.findOneOrFail({ - where: { id: body.message_id, flags: undefined }, - relations: { - author: true, - webhook: true, - application: true, - mentions: true, - mention_roles: true, - mention_channels: true, - sticker_items: true, - attachments: true, - thread: { - recipients: { - user: true, + interactionData.message = ( + await Message.findOneOrFail({ + where: { id: body.message_id, flags: undefined }, + relations: { + author: true, + webhook: true, + application: true, + mentions: true, + mention_roles: true, + mention_channels: true, + sticker_items: true, + attachments: true, + thread: { + recipients: { + user: true, + }, }, }, - }, - }); + }) + ).toJSON(); } await emitEvent({ @@ -144,6 +146,7 @@ router.post("/", route({}), async (req: Request, res: Response) => { commandType: body.data.type, commandName: body.data.name, messageId: body.message_id, + interactionId, }); res.sendStatus(204); diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts index c00a959d3..f6a951713 100644 --- a/src/api/util/handlers/route.ts +++ b/src/api/util/handlers/route.ts @@ -158,6 +158,7 @@ export function route(opts: RouteOptions) { 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 dd09fb90a..53ca39310 100644 --- a/src/schemas/api/bots/InteractionCallbackSchema.ts +++ b/src/schemas/api/bots/InteractionCallbackSchema.ts @@ -16,9 +16,8 @@ along with this program. If not, see . */ -import { Message } from "@spacebar/util"; import { InteractionCallbackType } from "./InteractionCallbackType"; -import { AllowedMentions, BaseMessageComponents, Embed, MessageComponentType } from "../messages"; +import { AllowedMentions, BaseMessageComponents, Embed, ModalCallback } from "../messages"; import { MessageCreateAttachment, MessageCreateCloudAttachment, PollCreationSchema } from "#schemas/uncategorised"; export interface InteractionCallbackSchema { @@ -50,6 +49,10 @@ export interface MessageDUpdateCallback extends InteractionCallbackSchema { type: InteractionCallbackType.DEFERRED_UPDATE_MESSAGE; data: InteractionMessage; } +export interface ModalCallbackSchema extends InteractionCallbackSchema { + type: InteractionCallbackType.MODAL; + data: ModalCallback; +} export type InteractionCallbacksSchema = | PongCallback | AckCallback @@ -57,7 +60,8 @@ export type InteractionCallbacksSchema = | MessageWSourceCallback | MessageDWSourceCallback | MessageUpdateCallback - | MessageDUpdateCallback; + | MessageDUpdateCallback + | ModalCallbackSchema; export interface InteractionMessage { content?: string; @@ -68,4 +72,5 @@ export interface InteractionMessage { flags?: number; attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment)[]; poll?: PollCreationSchema; + enforce_nonce?: boolean; } diff --git a/src/schemas/api/bots/InteractionCreateSchema.ts b/src/schemas/api/bots/InteractionCreateSchema.ts index 44e7e651d..96a359d3f 100644 --- a/src/schemas/api/bots/InteractionCreateSchema.ts +++ b/src/schemas/api/bots/InteractionCreateSchema.ts @@ -16,8 +16,8 @@ along with this program. If not, see . */ -import { PublicMember, PublicUser, Snowflake } from "@spacebar/schemas"; -import { Channel, InteractionType, Message } from "@spacebar/util"; +import { PublicMember, PublicMessage, PublicUser, Snowflake } from "@spacebar/schemas"; +import { Channel, InteractionType } from "@spacebar/util"; export interface InteractionCreateSchema { version: number; // TODO: types? @@ -34,7 +34,7 @@ export interface InteractionCreateSchema { member?: PublicMember; user?: PublicUser; locale?: string; - message?: Message; + message?: PublicMessage; app_permissions: string; entitlements?: object[]; // TODO: types? entitlement_sku_ids?: Snowflake[]; // DEPRECATED diff --git a/src/schemas/api/messages/Components.ts b/src/schemas/api/messages/Components.ts index efa33e064..2286c936f 100644 --- a/src/schemas/api/messages/Components.ts +++ b/src/schemas/api/messages/Components.ts @@ -84,6 +84,54 @@ export interface ActionRowComponent extends MessageComponent { components: (ButtonComponent | StringSelectMenuComponent | SelectMenuComponent | TextInputComponent)[]; } +export interface FileUploadComponent extends MessageComponent { + type: MessageComponentType.FileUpload; + custom_id: string; + min_values?: number; + max_values?: number; + required?: boolean; +} + +export interface CheckboxGroupComponent extends MessageComponent { + type: MessageComponentType.CheckboxGroup; + custom_id: string; + options: { + value: string; + label: string; + description?: string; + default?: boolean; + }[]; + required: boolean; +} +export interface CheckboxComponent extends MessageComponent { + type: MessageComponentType.Checkbox; + custom_id: string; + required: boolean; +} +export interface LabelComponent extends MessageComponent { + type: MessageComponentType.Label; + label: string; + description?: string; + component: StringSelectMenuComponent | TextInputComponent | SelectMenuComponent | FileUploadComponent | RadioGroupComponent | CheckboxGroupComponent | CheckboxComponent; +} +export interface ModalCallback { + custom_id: string; + title: string; + components: (LabelComponent | ActionRowComponent | TextDisplayComponent)[]; +} + +export interface RadioGroupComponent extends MessageComponent { + type: MessageComponentType.RadioGroup; + custom_id: string; + options: { + value: string; + label: string; + description?: string; + default?: boolean; + }[]; + required: boolean; +} + export interface ContainerComponent extends MessageComponent { type: MessageComponentType.Container; components: (ActionRowComponent | TextDisplayComponent | SectionComponent | MediaGalleryComponent | SeperatorComponent | FileComponent)[]; diff --git a/src/util/entities/Application.ts b/src/util/entities/Application.ts index e00b4a988..97c5614c0 100644 --- a/src/util/entities/Application.ts +++ b/src/util/entities/Application.ts @@ -21,6 +21,7 @@ import { BaseClass } from "./BaseClass"; import { Team } from "./Team"; import { User } from "./User"; import { Guild } from "./Guild"; +import { JsonRemoveEmpty } from "../util/Decorators"; @Entity({ name: "applications", @@ -99,6 +100,7 @@ export class Application extends BaseClass { cover_image?: string; // the application's default rich presence invite cover image hash @Column({ type: "simple-json", nullable: true }) + @JsonRemoveEmpty install_params?: { scopes: string[]; permissions: string }; @Column({ nullable: true }) diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts index b343a0cce..b692f757f 100644 --- a/src/util/entities/Message.ts +++ b/src/util/entities/Message.ts @@ -33,7 +33,13 @@ import { ApplicationCommandType, BaseMessageComponents, Embed, - MessageComponentType, MessageSnapshot, MessageType, PartialMessage, Poll, PublicMessage, Reaction, + MessageComponentType, + MessageSnapshot, + MessageType, + PartialMessage, + Poll, + PublicMessage, + Reaction, UnfurledMediaItem, } from "@spacebar/schemas"; import { PartialUser } from "@spacebar/schemas"; @@ -291,8 +297,9 @@ export class Message extends BaseClass { poll: this.poll ?? undefined, content: this.content ?? "", pinned: this.pinned, - thread: this.thread ? this.thread.toJSON() : this.thread, + thread: this.thread ? this.thread.toJSON() : undefined, referenced_message: this.referenced_message && !shallow ? this.referenced_message.toJSON(true) : undefined, + message_snapshots: (typeof this.message_snapshots === "string" ? JSON.parse(this.message_snapshots) : this.message_snapshots) ?? undefined, }; } diff --git a/src/util/imports/Interactions.ts b/src/util/imports/Interactions.ts index 304241cdd..4ab975136 100644 --- a/src/util/imports/Interactions.ts +++ b/src/util/imports/Interactions.ts @@ -30,6 +30,7 @@ interface PendingInteraction { type: InteractionType; commandType: ApplicationCommandType; commandName: string; + interactionId: string; } export const pendingInteractions = new Map(); diff --git a/src/util/interfaces/Event.ts b/src/util/interfaces/Event.ts index 7f7b017fc..04873844d 100644 --- a/src/util/interfaces/Event.ts +++ b/src/util/interfaces/Event.ts @@ -37,6 +37,7 @@ import { GuildOrUnavailable, Snowflake, ThreadMember, + Application, } from "@spacebar/util"; import { JsonValue } from "@protobuf-ts/runtime"; import { @@ -337,6 +338,7 @@ export interface GuildRoleDeleteEvent extends Event { role_id: string; }; } +import { ModalCallback } from "@spacebar/schemas"; export interface InviteCreateEvent extends Event { event: "INVITE_CREATE"; @@ -543,6 +545,19 @@ export interface InteractionCreateEvent extends Event { nonce?: string; }; } +export interface InteractionModalCreateEvent extends Event { + event: "INTERACTION_MODAL_CREATE"; + data: { + id: Snowflake; + channel_id: Snowflake; + custom_id: string; //TODO this needs to be corrected + application: Application; + title: string; + components: ModalCallback["components"]; + nonce?: string; + //TODO resolved + }; +} export interface InteractionSuccessEvent extends Event { event: "INTERACTION_SUCCESS"; @@ -719,6 +734,7 @@ export type EventData = | InteractionCreateEvent | InteractionSuccessEvent | InteractionFailureEvent + | InteractionModalCreateEvent | MessageAckEvent | RelationshipAddEvent | RelationshipRemoveEvent @@ -856,6 +872,7 @@ export type EVENT = | "THREAD_LIST_SYNC" | "THREAD_MEMBER_UPDATE" | "THREAD_MEMBERS_UPDATE" + | "INTERACTION_MODAL_CREATE" | CUSTOMEVENTS; export type CUSTOMEVENTS = "INVALIDATED" | "RATELIMIT" | "SB_SESSION_REMOVE" | "SB_SESSION_CLOSE";