fix: interaction callback

This commit is contained in:
CyberL1 2025-10-19 16:31:35 +02:00 committed by Rory&
parent d0ae4d06bc
commit bbd0617d53
8 changed files with 98 additions and 22 deletions

View File

@ -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",

View File

@ -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;

View File

@ -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<InteractionCreateSchema> = {
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,

View File

@ -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;
}

View File

@ -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<number, Snowflake>; // TODO: types?
context?: number;
attachment_size_limit: number;
}
interface InteractionGuild {
id: Snowflake;
features: string[];
locale: string;
}

View File

@ -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";

View File

@ -21,6 +21,7 @@ import { InteractionType, Snowflake } from "@spacebar/util";
interface PendingInteraction {
timeout: NodeJS.Timeout;
applicationId: string;
userId: string;
channelId?: string;
guildId?: string;

View File

@ -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 {