Merge pull request #1393 from MathMan05/webhookFix

This commit is contained in:
Cyber 2025-11-19 16:09:33 +01:00 committed by GitHub
commit 652657d78f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,58 +1,15 @@
import { handleMessage, postHandleMessage } from "@spacebar/api"; import { handleMessage, postHandleMessage } from "@spacebar/api";
import { import { Attachment, Config, DiscordApiErrors, emitEvent, FieldErrors, Message, MessageCreateEvent, uploadFile, ValidateName, Webhook } from "@spacebar/util";
Attachment,
Config,
DiscordApiErrors,
emitEvent,
FieldErrors,
Message,
MessageCreateEvent,
uploadFile,
ValidateName,
Webhook,
} from "@spacebar/util";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { MoreThan } from "typeorm"; import { MoreThan } from "typeorm";
import { WebhookExecuteSchema } from "@spacebar/schemas" import { WebhookExecuteSchema } from "@spacebar/schemas";
export const executeWebhook = async (req: Request, res: Response) => { export const executeWebhook = async (req: Request, res: Response) => {
const { wait } = req.query; const body = req.body as WebhookExecuteSchema;
if (!wait) return res.status(204).send();
const { webhook_id, token } = req.params; const { webhook_id, token } = req.params;
const body = req.body as WebhookExecuteSchema;
const attachments: Attachment[] = [];
// ensure one of content, embeds, components, or file is present
if (
!body.content &&
!body.embeds &&
!body.components &&
!body.file &&
!body.attachments
) {
throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE;
}
// block username from containing certain words
// TODO: configurable additions
if (body.username) {
ValidateName(body.username);
}
// block username from being certain words
// TODO: configurable additions
const blockedEquals = ["everyone", "here"];
for (const word of blockedEquals) {
if (body.username?.toLowerCase() === word) {
return res.status(400).json({
username: [`Username cannot be "${word}"`],
});
}
}
const webhook = await Webhook.findOne({ const webhook = await Webhook.findOne({
where: { where: {
id: webhook_id, id: webhook_id,
@ -64,52 +21,66 @@ export const executeWebhook = async (req: Request, res: Response) => {
throw DiscordApiErrors.UNKNOWN_WEBHOOK; throw DiscordApiErrors.UNKNOWN_WEBHOOK;
} }
if (!webhook.channel.isWritable()) {
throw new HTTPError(
`Cannot send messages to channel of type ${webhook.channel.type}`,
400,
);
}
if (webhook.token !== token) { if (webhook.token !== token) {
throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
} }
if (body.username) {
ValidateName(body.username);
}
// ensure one of content, embeds, components, or file is present
if (!body.content && !body.embeds && !body.components && !body.file && !body.attachments) {
throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE;
}
const wait = req.query.wait === "true";
if (!wait) {
res.status(204).send();
}
const attachments: Attachment[] = [];
if (!webhook.channel.isWritable()) {
if (wait) {
throw new HTTPError(`Cannot send messages to channel of type ${webhook.channel.type}`, 400);
} else {
return;
}
}
// TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one? // TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one?
const limits = Config.get().limits; const limits = Config.get().limits;
if (limits.absoluteRate.register.enabled) { if (limits.absoluteRate.register.enabled) {
const count = await Message.count({ const count = await Message.count({
where: { where: {
channel_id: webhook.channel_id, channel_id: webhook.channel_id,
timestamp: MoreThan( timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
new Date(
Date.now() - limits.absoluteRate.sendMessage.window,
),
),
}, },
}); });
if (count >= limits.absoluteRate.sendMessage.limit) if (count >= limits.absoluteRate.sendMessage.limit)
if (wait) {
throw FieldErrors({ throw FieldErrors({
channel_id: { channel_id: {
code: "TOO_MANY_MESSAGES", code: "TOO_MANY_MESSAGES",
message: req.t("common:toomany.MESSAGE"), message: req.t("common:toomany.MESSAGE"),
}, },
}); });
} else {
return;
}
} }
const files = (req.files as Express.Multer.File[]) ?? []; const files = (req.files as Express.Multer.File[]) ?? [];
for (const currFile of files) { for (const currFile of files) {
try { try {
const file = await uploadFile( const file = await uploadFile(`/attachments/${webhook.channel.id}`, currFile);
`/attachments/${webhook.channel.id}`, attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
currFile,
);
attachments.push(
Attachment.create({ ...file, proxy_url: file.url }),
);
} catch (error) { } catch (error) {
return res.status(400).json({ message: error?.toString() }); if (wait) res.status(400).json({ message: error?.toString() });
return;
} }
} }
@ -146,9 +117,7 @@ export const executeWebhook = async (req: Request, res: Response) => {
]); ]);
// no await as it shouldnt block the message send function and silently catch error // no await as it shouldnt block the message send function and silently catch error
postHandleMessage(message).catch((e) => postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
console.error("[Message] post-message handler failed", e), if (wait) res.json(message);
); return;
return res.json(message);
}; };