diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts index 83b98925..0821f811 100644 --- a/src/api/middlewares/Authentication.ts +++ b/src/api/middlewares/Authentication.ts @@ -32,7 +32,7 @@ export const NO_AUTHORIZATION_ROUTES = [ "GET /invites/", // Routes with a seperate auth system /^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth - // /^POST \/interactions\/\d+\/\w+\/callback/, // This is marked as Unauthoricated route on https://discord.food. But when it is then not all requests have req.user_id? + /^POST \/interactions\/\d+\/[A-Za-z0-9_-]+\/callback/, // Public information endpoints "GET /ping", "GET /gateway", 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 1860e0b3..77f0112f 100644 --- a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts +++ b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts @@ -55,13 +55,13 @@ router.post("/", route({}), async (req: Request, res: Response) => { // TODO break; case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: { - const message = Message.create({ + const message = await Message.createWithDefaults({ type: MessageType.APPLICATION_COMMAND, timestamp: new Date(), - application_id: req.user_id, + application_id: interaction.applicationId, guild_id: interaction.guildId, channel_id: interaction.channelId, - author_id: req.user_id, + author_id: interaction.applicationId, content: body.data.content, tts: body.data.tts, embeds: body.data.embeds || [], @@ -69,7 +69,7 @@ router.post("/", route({}), async (req: Request, res: Response) => { poll: body.data.poll, flags: body.data.flags, reactions: [], - // webhook_id: req.user_id, // This one requires a webhook to be created first + // webhook_id: interaction.applicationId, // This one requires a webhook to be created first interaction: { id: interactionId, name: interaction.commandName, @@ -98,9 +98,9 @@ router.post("/", route({}), async (req: Request, res: Response) => { event: "MESSAGE_CREATE", channel_id: interaction.channelId, data: { - application_id: message.application_id, + application_id: interaction.applicationId, attachments: message.attachments, - author: (await User.findOneOrFail({ where: { id: message.author_id } })).toPublicUser(), + author: message.author?.toPublicUser(), channel_id: message.channel_id, channel_type: 0, components: message.components, @@ -131,7 +131,7 @@ router.post("/", route({}), async (req: Request, res: Response) => { timestamp: message.timestamp, tss: message.tts, type: message.type, - webhook_id: req.user_id, + webhook_id: interaction.applicationId, } as MessageCreateSchema, } as MessageCreateEvent); break; diff --git a/src/api/routes/interactions/index.ts b/src/api/routes/interactions/index.ts index 8789b291..05571ace 100644 --- a/src/api/routes/interactions/index.ts +++ b/src/api/routes/interactions/index.ts @@ -20,8 +20,9 @@ import { randomBytes } from "crypto"; import { InteractionSchema } from "@spacebar/schemas"; import { route } from "@spacebar/api"; import { Request, Response, Router } from "express"; -import { emitEvent, InteractionCreateEvent, InteractionFailureEvent, Snowflake } from "@spacebar/util"; -import { pendingInteractions } from "../../../util/imports/Interactions"; +import { emitEvent, Guild, InteractionCreateEvent, InteractionFailureEvent, InteractionType, Member, Message, Snowflake, User } from "@spacebar/util"; +import { pendingInteractions } from "@spacebar/util/imports/Interactions"; +import { InteractionCreateSchema } from "@spacebar/schemas/api/bots/InteractionCreateSchema"; const router = Router({ mergeParams: true }); @@ -40,18 +41,56 @@ router.post("/", route({}), async (req: Request, res: Response) => { }, } as InteractionCreateEvent); + const user = await User.findOneOrFail({ where: { id: req.user_id } }); + + const interactionData: Partial = { + id: interactionId, + application_id: body.application_id, + channel_id: body.channel_id, + type: body.type, + token: interactionToken, + version: 1, + app_permissions: "0", // TODO: add this later + entitlements: [], + authorizing_integration_owners: { "0": req.user_id }, + context: 0, // TODO: add this later + attachment_size_limit: 0, // TODO: add this later + }; + + if (body.type === InteractionType.ApplicationCommand || body.data.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) { + interactionData.data = body.data; + } + + if (body.type != InteractionType.Ping) { + interactionData.locale = user?.settings?.locale; + } + + if (body.guild_id) { + interactionData.guild_id = body.guild_id; + + const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } }); + const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: ["user"] }); + + interactionData.guild = { + id: guild.id, + features: guild.features, + locale: guild.preferred_locale!, + }; + + interactionData.guild_locale = guild.preferred_locale; + interactionData.member = member.toPublicMember(); + } else { + interactionData.user = user.toPublicUser(); + } + + if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) { + interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id } }); + } + emitEvent({ event: "INTERACTION_CREATE", user_id: body.application_id, - data: { - channel_id: body.channel_id, - guild_id: body.guild_id, - id: interactionId, - member_id: req.user_id, - token: interactionToken, - type: body.type, - nonce: body.nonce, - }, + data: interactionData, } as InteractionCreateEvent); const interactionTimeout = setTimeout(() => { @@ -69,6 +108,7 @@ router.post("/", route({}), async (req: Request, res: Response) => { pendingInteractions.set(interactionId, { timeout: interactionTimeout, nonce: body.nonce, + applicationId: body.application_id, userId: req.user_id, guildId: body.guild_id, channelId: body.channel_id, diff --git a/src/schemas/api/bots/InteractionCallbackSchema.ts b/src/schemas/api/bots/InteractionCallbackSchema.ts index bbf68e80..9bc27217 100644 --- a/src/schemas/api/bots/InteractionCallbackSchema.ts +++ b/src/schemas/api/bots/InteractionCallbackSchema.ts @@ -1,7 +1,7 @@ -import { MessageCreateSchema } from "@spacebar/schemas"; +import { Message } from "@spacebar/util"; import { InteractionCallbackType } from "./InteractionCallbackType"; export interface InteractionCallbackSchema { type: InteractionCallbackType; - data: MessageCreateSchema; + data: Message; } diff --git a/src/schemas/api/bots/InteractionCreateSchema.ts b/src/schemas/api/bots/InteractionCreateSchema.ts new file mode 100644 index 00000000..4bcf09ab --- /dev/null +++ b/src/schemas/api/bots/InteractionCreateSchema.ts @@ -0,0 +1,32 @@ +import { PublicMember, PublicUser, Snowflake } from "@spacebar/schemas"; +import { Channel, InteractionType, Message } from "@spacebar/util"; + +export interface InteractionCreateSchema { + version: number; // TODO: types? + id: Snowflake; + application_id: Snowflake; + type: InteractionType; + token: string; + data?: object; // TODO: types? + guild?: InteractionGuild; + guild_id?: Snowflake; + guild_locale?: string; + channel?: Channel; // TODO: is this right? + channel_id?: Snowflake; + member?: PublicMember; // TODO: is this right? + user?: PublicUser; // TODO: is this right? + locale?: string; + message?: Message; // TODO: is this right? + app_permissions: string; + entitlements?: object[]; // TODO: types? + entitlement_sku_ids?: Snowflake[]; // DEPRECATED + authorizing_integration_owners?: Record; // TODO: types? + context?: number; + attachment_size_limit: number; +} + +interface InteractionGuild { + id: Snowflake; + features: string[]; + locale: string; +} diff --git a/src/schemas/api/bots/index.ts b/src/schemas/api/bots/index.ts index b9d0b396..a08972f1 100644 --- a/src/schemas/api/bots/index.ts +++ b/src/schemas/api/bots/index.ts @@ -20,6 +20,7 @@ export * from "./ApplicationCommandSchema"; export * from "./InteractionSchema"; export * from "./InteractionCallbackSchema"; export * from "./InteractionCallbackType"; +export * from "./InteractionCreateSchema"; export * from "./SendableApplicationCommandDataSchema"; export * from "./SendableMessageComponentDataSchema"; export * from "./SendableModalSubmitDataSchema"; diff --git a/src/util/imports/Interactions.ts b/src/util/imports/Interactions.ts index dd3ca515..49ec4933 100644 --- a/src/util/imports/Interactions.ts +++ b/src/util/imports/Interactions.ts @@ -21,6 +21,7 @@ import { InteractionType, Snowflake } from "@spacebar/util"; interface PendingInteraction { timeout: NodeJS.Timeout; + applicationId: string; userId: string; channelId?: string; guildId?: string; diff --git a/src/util/interfaces/Interaction.ts b/src/util/interfaces/Interaction.ts index 29fc931d..09068df8 100644 --- a/src/util/interfaces/Interaction.ts +++ b/src/util/interfaces/Interaction.ts @@ -30,9 +30,11 @@ export interface Interaction { } export enum InteractionType { - SelfCommand = 0, Ping = 1, ApplicationCommand = 2, + MessageComponent = 3, + ApplicationCommandAutocomplete = 4, + ModalSubmit = 5, } export enum InteractionResponseType {