@arcscord/middleware
About
Reusable middleware for Arcscord command and component handlers.
This package provides common guards for cooldowns, user allowlists, component authors, and Discord permissions. Middleware can cancel the handler with a Discord response or continue and expose typed data through ctx.additional.
Documentation: https://arcscord.github.io/arcscord/
Install
pnpm add @arcscord/middlewareLocalized Reusable Messages
Every middleware message can be either a static BaseMessageOptions object or a callback. Message callbacks receive the middleware data plus ctx, locale, and t, so you can create one localized factory and reuse it instead of redefining the same message on every command.
import type { CooldownMessageOptions, MessageOptions } from "@arcscord/middleware";
import type { CommandContext } from "arcscord";
import { CooldownMiddleware } from "@arcscord/middleware";
const cooldownMessage: MessageOptions<CooldownMessageOptions, CommandContext> = ({ cooldownRemaining, t }) => ({
content: t($ => $.middleware.cooldown, {
seconds: Math.ceil(cooldownRemaining / 1000),
}),
});
export function createCooldownMiddleware(duration: number) {
return new CooldownMiddleware(duration, cooldownMessage);
}Then reuse the factory wherever needed:
use: [
createCooldownMiddleware(10),
];Command Middleware
CooldownMiddleware
Prevents a user from running a command again before the configured duration expires.
import { CooldownMiddleware } from "@arcscord/middleware";
import { createCommand } from "arcscord";
export const pingCommand = createCommand({
build: {
slash: {
name: "ping",
description: "Reply with pong.",
},
},
use: [
new CooldownMiddleware(10, ({ cooldownRemaining }) => ({
content: `Wait ${Math.ceil(cooldownRemaining / 1000)}s before using this command again.`,
})),
],
run: ctx => ctx.reply("Pong!"),
});The first argument is the cooldown duration in seconds. The optional third argument controls automatic cleanup of expired entries: pass a number of seconds, or false to disable it.
CommandUserAllowListMiddleware
Restricts a command to a fixed list of Discord user IDs.
import { CommandUserAllowListMiddleware } from "@arcscord/middleware";
import { createCommand } from "arcscord";
export const adminCommand = createCommand({
build: {
slash: {
name: "admin",
description: "Run an admin-only action.",
},
},
use: [
new CommandUserAllowListMiddleware(["123456789012345678"], {
content: "You are not allowed to use this command.",
}),
],
run: (ctx) => {
return ctx.reply(`Allowed: ${ctx.additional.userAllowList.allowed}`);
},
});CommandBotPermissionMiddleware
Restricts a command to interactions where the bot has every required Discord permission. Outside guild interactions, the middleware continues without checking permissions.
import { CommandBotPermissionMiddleware } from "@arcscord/middleware";
import { createCommand } from "arcscord";
export const pruneCommand = createCommand({
build: {
slash: {
name: "prune",
description: "Delete recent messages.",
contexts: ["guild"],
},
},
use: [
new CommandBotPermissionMiddleware(["ManageMessages"], ({ missingPermissions }) => ({
content: `I am missing: ${missingPermissions.join(", ")}`,
})),
],
run: ctx => ctx.reply("Messages pruned."),
});Component Middleware
AuthorOnlyMiddleware
Restricts a message component to the user who created the original interaction.
import { AuthorOnlyMiddleware } from "@arcscord/middleware";
import { button, createButton } from "arcscord";
import { MessageFlags } from "discord.js";
export const confirmButton = createButton({
route: "confirm",
build: id => button({
customId: id(),
label: "Confirm",
style: "green",
}),
use: [
new AuthorOnlyMiddleware({
content: "Only the original user can use this component.",
}),
],
run: ctx => ctx.reply("Confirmed.", { flags: MessageFlags.Ephemeral }),
});When the original interaction author cannot be detected, this middleware continues with ctx.additional.authorOnly.status set to "ignore".
ComponentMemberPermissionMiddleware
Restricts a component handler to members that have every required Discord permission.
import { ComponentMemberPermissionMiddleware } from "@arcscord/middleware";
import { button, createButton } from "arcscord";
import { MessageFlags } from "discord.js";
export const moderateButton = createButton({
route: "moderate",
build: id => button({
customId: id(),
label: "Moderate",
style: "red",
}),
use: [
new ComponentMemberPermissionMiddleware(["ManageMessages"], ({ missingPermissions }) => ({
content: `Missing permissions: ${missingPermissions.join(", ")}`,
})),
],
run: ctx => ctx.reply(`Allowed: ${ctx.additional.componentMemberPermission.allowed}`, {
flags: MessageFlags.Ephemeral,
}),
});ComponentMemberPermissionMiddleware checks permissions held by the member who triggered the component.
ComponentBotPermissionMiddleware
Restricts a component to interactions where the bot has every required Discord permission. Outside guild interactions, the middleware continues without checking permissions.
import { ComponentBotPermissionMiddleware } from "@arcscord/middleware";
import { button, createButton } from "arcscord";
import { MessageFlags } from "discord.js";
export const publishButton = createButton({
route: "publish",
build: id => button({
customId: id(),
label: "Publish",
style: "primary",
}),
use: [
new ComponentBotPermissionMiddleware(["SendMessages"], ({ missingPermissions }) => ({
content: `I am missing: ${missingPermissions.join(", ")}`,
})),
],
run: ctx => ctx.reply("Published.", {
flags: MessageFlags.Ephemeral,
}),
});ComponentUserAllowListMiddleware
Restricts a component handler to a fixed list of Discord user IDs.
import { ComponentUserAllowListMiddleware } from "@arcscord/middleware";
import { button, createButton } from "arcscord";
import { MessageFlags } from "discord.js";
export const privateButton = createButton({
route: "private",
build: id => button({
customId: id(),
label: "Private",
style: "secondary",
}),
use: [
new ComponentUserAllowListMiddleware(["123456789012345678"], {
content: "You are not allowed to use this component.",
}),
],
run: ctx => ctx.reply(`Allowed: ${ctx.additional.userAllowList.allowed}`, {
flags: MessageFlags.Ephemeral,
}),
});Exports
import type { MessageOptions } from "@arcscord/middleware";
import {
AuthorOnlyMiddleware,
CommandBotPermissionMiddleware,
CommandUserAllowListMiddleware,
ComponentBotPermissionMiddleware,
ComponentMemberPermissionMiddleware,
ComponentUserAllowListMiddleware,
CooldownMiddleware
} from "@arcscord/middleware";CommandBotPermissionMiddleware and ComponentBotPermissionMiddleware check permissions held by the bot for the current interaction. ComponentMemberPermissionMiddleware checks permissions held by the member who triggered the component.
Notes
- Middleware names must be unique in a single
usearray. - Static messages use Discord.js
BaseMessageOptions. - Callback messages receive middleware-specific data,
ctx,locale, andt, then returnBaseMessageOptions. - User IDs passed to allowlist middleware are trimmed, and empty values are ignored.
License
MIT
