dressed
Version:
A sleek, serverless-ready Discord bot framework.
132 lines • 5.8 kB
JavaScript
import { createServer as createHttpServer } from "node:http";
import { ApplicationWebhookType, InteractionType, } from "discord-api-types/v10";
import { override } from "../utils/build.js";
import { serverConfig } from "../utils/env.js";
import { logError, logSuccess } from "../utils/log.js";
import { createInteraction } from "./extenders/interaction.js";
import { setupCommands } from "./handlers/commands.js";
import { setupComponents } from "./handlers/components.js";
import { setupEvents } from "./handlers/events.js";
import { verifySignature } from "./signature.js";
/**
* Starts a server to handle interactions.
* @returns The server instance
*/
export function createServer(commands, components, events, config = {}) {
var _a, _b, _c;
config = override(serverConfig, config);
const endpoint = new URL((_a = config.endpoint) !== null && _a !== void 0 ? _a : "/", `http://localhost:${(_b = config.port) !== null && _b !== void 0 ? _b : 8000}`);
const server = createHttpServer(async (req, res) => {
if (req.url !== endpoint.pathname) {
return res.writeHead(404).end();
}
else if (req.method !== "POST") {
return res.writeHead(405).end();
}
const handlerRes = await handleRequest(new Request(endpoint, {
method: "POST",
// @ts-expect-error Headers will init from an object, the type is just weird
headers: req.headers,
// @ts-expect-error The node:http req reads to a body
body: req,
duplex: "half", // Undici throws if this isn't present when body is a stream -inb
}), commands, components, events, config);
res.writeHead(handlerRes.status, { "Content-Type": "application/json" }).end(await handlerRes.text());
});
const port = (_c = config.port) !== null && _c !== void 0 ? _c : 8000;
const shutdown = () => server.close(() => process.exit());
server.listen(port, "0.0.0.0", () => logSuccess("Bot is now listening on", endpoint.href));
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
return server;
}
/**
* Handles a request from Discord.
* @param req The request from Discord
* @param commands A list of commands or the function to run a command
* @param components A list of components or the function to run a component
* @param events A list of events or the function to run an event
* @param config Configuration for your server
* @returns The response to send back to Discord
*/
export async function handleRequest(req, commands, components, events, config = serverConfig) {
const body = await req.text();
const verified = await verifySignature(body, req.headers.get("x-signature-ed25519"), req.headers.get("x-signature-timestamp"));
if (!verified) {
logError("Invalid signature");
return new Response(null, { status: 401 });
}
try {
const json = JSON.parse(body);
let status = 500;
// The interaction response token
if ("token" in json) {
status = await handleInteraction(commands, components, json, config.middleware);
}
else {
status = await handleEvent(events, json, config.middleware);
}
return new Response(status === 200 ? '{"type":1}' : null, {
status,
});
}
catch (error) {
logError("Failed to process request:", error);
return new Response(null, { status: 500 });
}
}
/**
* Runs an interaction, takes functions to run commands/components/middleware and the request body
*/
export async function handleInteraction(commands, components, json, middleware) {
switch (json.type) {
case InteractionType.Ping: {
logSuccess("Received ping test");
return 200;
}
case InteractionType.ApplicationCommand: {
const interaction = createInteraction(json);
const runCommand = typeof commands === "function" ? commands : setupCommands(commands);
await runCommand(interaction, middleware === null || middleware === void 0 ? void 0 : middleware.commands);
return 202;
}
case InteractionType.ApplicationCommandAutocomplete: {
const interaction = createInteraction(json);
const runCommand = typeof commands === "function" ? commands : setupCommands(commands);
await runCommand(interaction, undefined, "autocomplete");
return 202;
}
case InteractionType.MessageComponent:
case InteractionType.ModalSubmit: {
const interaction = createInteraction(json);
const runComponent = typeof components === "function" ? components : setupComponents(components);
await runComponent(interaction, middleware === null || middleware === void 0 ? void 0 : middleware.components);
return 202;
}
default: {
logError("Received unknown interaction type:", json.type);
return 404;
}
}
}
/**
* Runs an event, takes a function to run events/middleware and the request body
*/
export async function handleEvent(events, json, middleware) {
switch (json.type) {
case ApplicationWebhookType.Ping: {
logSuccess("Received ping test");
return 200;
}
case ApplicationWebhookType.Event: {
const runEvent = typeof events === "function" ? events : setupEvents(events);
await runEvent(json.event, middleware === null || middleware === void 0 ? void 0 : middleware.events);
return 202;
}
default: {
logError("Received unknown event type:", json.type);
return 404;
}
}
}
//# sourceMappingURL=server.js.map