Skip to main content

@arcscord/middleware

@arcscord/middleware provides ready-to-use middleware classes for Arcscord commands and components.

This package does not define the middleware system itself. The core middleware API, execution flow, next, cancel, error, and custom middleware examples are documented in the middleware guide.

Links:

Install

pnpm add @arcscord/middleware

If your project does not already depend on Arcscord, install it too:

pnpm add arcscord @arcscord/middleware

Exports

import {
AuthorOnlyMiddleware,
CommandBotPermissionMiddleware,
ComponentBotPermissionMiddleware,
ComponentMemberPermissionMiddleware,
CommandUserAllowListMiddleware,
ComponentUserAllowListMiddleware,
CooldownMiddleware,
} from "@arcscord/middleware";

Localized Reusable Messages

Middleware messages can be static Discord.js message objects or callbacks. A callback receives the middleware-specific data plus ctx, locale, and t, which lets you centralize localized messages instead of redefining them 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 const createCooldownMiddleware = (duration: number) => {
return new CooldownMiddleware(duration, cooldownMessage);
};

Use the helper wherever you need the same behavior:

use: [
createCooldownMiddleware(10),
];

CooldownMiddleware

CooldownMiddleware prevents a user from running a command again before a configured duration expires.

import { CooldownMiddleware } from "@arcscord/middleware";
import { createCommand } from "arcscord";

export const pingCommand = createCommand({
build: {
slash: {
name: "ping",
description: "Ping with cooldown",
},
},
use: [
new CooldownMiddleware(10, ({ cooldownRemaining }) => ({
content: `Please wait ${Math.ceil(cooldownRemaining / 1000)} seconds.`,
})),
],
run: ctx => ctx.reply("Pong!"),
});

Constructor:

new CooldownMiddleware(duration, message, autoClear);
ParameterTypeDescription
durationnumberCooldown duration in seconds.
message(options) => BaseMessageOptionsMessage returned when the user is still on cooldown.
autoClearfalse | numberOptional cleanup interval in seconds. Defaults to 3600. Use false to disable automatic cleanup.

The message callback receives:

FieldDescription
userThe Discord user affected by the cooldown.
cooldownDurationThe configured duration in seconds.
cooldownRemainingRemaining time in milliseconds.
cooldownEndDate when the cooldown expires.
commandNameName of the command that triggered the cooldown.
ctxArcscord command context.
localeDetected interaction locale.
tFixed translation function for the detected locale.

Behavior

  • The cooldown is tracked per user.
  • When the user is still on cooldown, the middleware replies or edits the deferred reply and cancels the command.
  • When the cooldown has expired, the middleware stores the new cooldown end time and continues with next({}).
  • If autoClear is enabled, expired user entries are removed periodically.

CommandUserAllowListMiddleware

CommandUserAllowListMiddleware restricts a command to a fixed list of Discord user IDs.

import { CommandUserAllowListMiddleware } from "@arcscord/middleware";
import { createCommand } from "arcscord";

const developerIds = ["123456789"];

export const debugCommand = createCommand({
build: {
slash: {
name: "debug",
description: "Developer debug command",
},
},
use: [
new CommandUserAllowListMiddleware(developerIds, {
content: "This command is reserved for bot developers.",
}),
],
run: ctx => ctx.reply("Debug command accepted."),
});

Constructor:

new CommandUserAllowListMiddleware(userIds, message);
ParameterTypeDescription
userIdsIterable<string>Discord user IDs allowed to run the command. Empty strings are ignored and IDs are trimmed.
messageBaseMessageOptions | (options) => BaseMessageOptionsMessage returned when the current user is not allowed. Callback messages receive ctx, locale, and t.

Behavior

  • If ctx.user.id is in the allowlist, the middleware continues with next({ allowed: true }).
  • If the user is not allowed, the middleware replies or edits the deferred reply and cancels the command.
  • This middleware replaces the former core developerCommand option.

CommandBotPermissionMiddleware

CommandBotPermissionMiddleware restricts a command to interactions where the bot has every required Discord permission.

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."),
});

Constructor:

new CommandBotPermissionMiddleware(permissions, message);
ParameterTypeDescription
permissionsIterable<PermissionsString>Discord permissions required by the bot. Duplicate entries are ignored.
message(options) => BaseMessageOptionsMessage returned when the bot is missing permissions.

The message callback receives:

FieldDescription
missingPermissionsRequired permissions the bot does not have.
permissionsFull required permissions list.
userThe Discord user who triggered the command.
ctxArcscord command context.
localeDetected interaction locale.
tFixed translation function for the detected locale.

Behavior

  • If the bot has every required permission, the middleware continues with next({ allowed: true }).
  • If the bot is missing at least one permission, the middleware replies or edits the deferred reply and cancels the command handler.
  • Outside guild interactions, the middleware continues without checking permissions.

AuthorOnlyMiddleware

AuthorOnlyMiddleware restricts a message component to the user who created the original interaction.

import { AuthorOnlyMiddleware } from "@arcscord/middleware";
import { button, createButton } from "arcscord";

export const authorButton = createButton({
route: "author_button",
build: id => button({
customId: id(),
label: "Only me",
style: "primary",
}),
use: [
new AuthorOnlyMiddleware({
content: "Only the original author can use this component.",
}),
],
run: ctx => ctx.reply("Accepted."),
});

Constructor:

new AuthorOnlyMiddleware(message);
ParameterTypeDescription
messageBaseMessageOptions | (options) => BaseMessageOptionsMessage returned when another user tries to use the component. Callback messages receive ctx, locale, and t.

AuthorOnlyMiddleware returns one of these next values:

StatusMeaning
"author"The current user is the original interaction author.
"ignore"The middleware could not detect an original interaction author, so it did not block the component.

Behavior

  • If the context is not a message component context, the middleware continues with status: "ignore".
  • If the message does not include interaction metadata, the middleware continues with status: "ignore".
  • If the current user is not the original interaction author, the middleware replies or edits the deferred reply and cancels the component handler.
  • If the current user is the original author, the middleware continues with status: "author".

ComponentUserAllowListMiddleware

ComponentUserAllowListMiddleware restricts a component to a fixed list of Discord user IDs.

import { ComponentUserAllowListMiddleware } from "@arcscord/middleware";
import { button, createButton } from "arcscord";

const moderatorIds = ["123456789"];

export const moderationButton = createButton({
route: "moderation_button",
build: id => button({
customId: id(),
label: "Moderate",
style: "danger",
}),
use: [
new ComponentUserAllowListMiddleware(moderatorIds, {
content: "You cannot use this component.",
}),
],
run: ctx => ctx.reply("Moderation action accepted."),
});

Constructor:

new ComponentUserAllowListMiddleware(userIds, message);
ParameterTypeDescription
userIdsIterable<string>Discord user IDs allowed to run the component. Empty strings are ignored and IDs are trimmed.
messageBaseMessageOptions | (options) => BaseMessageOptionsMessage returned when the current user is not allowed. Callback messages receive ctx, locale, and t.

Behavior

  • If ctx.user.id is in the allowlist, the middleware continues with next({ allowed: true }).
  • If the user is not allowed, the middleware replies or edits the deferred reply and cancels the component handler.

ComponentMemberPermissionMiddleware

ComponentMemberPermissionMiddleware restricts a component to members with every required Discord permission.

import { ComponentMemberPermissionMiddleware } from "@arcscord/middleware";
import { button, createButton } from "arcscord";

export const deleteMessageButton = createButton({
route: "delete_message",
build: id => button({
customId: id(),
label: "Delete",
style: "danger",
}),
use: [
new ComponentMemberPermissionMiddleware(["ManageMessages"], ({ missingPermissions }) => ({
content: `Missing permissions: ${missingPermissions.join(", ")}`,
})),
],
run: ctx => ctx.reply("Message deleted."),
});

Constructor:

new ComponentMemberPermissionMiddleware(permissions, message);
ParameterTypeDescription
permissionsIterable<PermissionsString>Required Discord permissions. Duplicate entries are ignored.
message(options) => BaseMessageOptionsMessage returned when the current member is missing permissions.

The message callback receives:

FieldDescription
missingPermissionsRequired permissions the current member does not have.
permissionsFull required permissions list.
userThe Discord user who triggered the component.
ctxArcscord component context.
localeDetected interaction locale.
tFixed translation function for the detected locale.

Behavior

  • If the member has every required permission, the middleware continues with next({ allowed: true }).
  • If the member is missing at least one permission, the middleware replies or edits the deferred reply and cancels the component handler.
  • If the component is used without a guild member, every configured permission is treated as missing.

ComponentBotPermissionMiddleware

ComponentBotPermissionMiddleware restricts a component to interactions where the bot has every required Discord permission.

import { ComponentBotPermissionMiddleware } from "@arcscord/middleware";
import { button, createButton } from "arcscord";

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."),
});

Constructor:

new ComponentBotPermissionMiddleware(permissions, message);
ParameterTypeDescription
permissionsIterable<PermissionsString>Discord permissions required by the bot. Duplicate entries are ignored.
message(options) => BaseMessageOptionsMessage returned when the bot is missing permissions.

The message callback receives:

FieldDescription
missingPermissionsRequired permissions the bot does not have.
permissionsFull required permissions list.
userThe Discord user who triggered the component.
ctxArcscord component context.
localeDetected interaction locale.
tFixed translation function for the detected locale.

Behavior

  • If the bot has every required permission, the middleware continues with next({ allowed: true }).
  • If the bot is missing at least one permission, the middleware replies or edits the deferred reply and cancels the component handler.
  • Outside guild interactions, the middleware continues without checking permissions.

Combining With Custom Middleware

Package middlewares can be mixed with your own middleware in the same use array.

use: [
new CooldownMiddleware(10, () => ({
content: "Slow down.",
})),
new MyCustomMiddleware(),
];

Middlewares run in array order. See the middleware guide for the full execution model.