final pk fixes

This commit is contained in:
MathMan05
2026-03-18 05:19:53 +01:00
committed by Rory&
parent a8296bf52f
commit 1932e9ff06
4 changed files with 215 additions and 143 deletions
+2 -1
View File
@@ -3,7 +3,8 @@
"MessageInteractionSchema"
],
"includeRe": [
"^MessageComponentType\\..*"
"^MessageComponentType\\..*",
"^InteractionCallbackType\\..*"
],
"manual": [
"DefaultSchema",
@@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 @@ import { HTTPError } from "#util/util/lambert-server";
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 @@ router.post("/", route({}), async (req: Request, res: Response) => {
}
}
*/
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[]) {
+17 -1
View File
@@ -20,6 +20,7 @@ import { DiscordApiErrors, EVENT, FieldErrors, PermissionResolvable, Permissions
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 @@ export function followNullPath(obj1: any, nullObj: stripNulls) {
}
}
}
//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 @@ export function route(opts: RouteOptions) {
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<string, { code?: string; message: string }> = {};
validate.errors?.forEach(
(x) =>
@@ -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;
}