Commands
Weeble has one command system for prefix commands, slash commands, and Discord context-menu commands. Create one command application, then register each command on it through the global discord.commands API.
Create the command application
Put the shared application in a small module so every command uses the same prefixes and error handling.
export const commands = discord.commands.create({
prefixes: ["!"],
mentionPrefix: true,
onInputError: async (error, context) => {
const usage = error.usage ? `\nUsage: ${error.usage}` : "";
await context.reply(`⚠️ ${error.message}${usage}`);
},
});
prefixes applies to every prefix command. mentionPrefix also accepts a mention of the bot as the prefix.
onInputError handles expected prefix parsing and argument failures. Unexpected handler, slash-payload, autocomplete, or platform failures use onError.
Prefix commands
A prefix command receives a message and typed arguments. The schema controls parsing, TypeScript inference, and generated usage text.
import { commands } from "./commands";
commands.prefix(
{
name: "say",
aliases: ["echo"],
description: "Repeat some text",
args: (argument) => ({
text: argument.rest("Text to repeat"),
}),
},
async (message, args) => {
await message.reply(args.text);
},
);
Prefix arguments are read from left to right. Use rest() only for the final argument. Available argument types include strings, numbers, durations, users, members, channels, roles, and custom parsers.
The declared schema consumes the complete input. Unexpected trailing words produce an input error unless the final argument uses rest().
Use .optional() when an argument may be omitted:
args: (argument) => ({
user: argument.user("User to inspect").optional(),
})
Optional resource arguments may be omitted, but a supplied malformed mention, unknown ID, or failed lookup still produces an input error.
Prefix commands require Discord’s Message Content intent. If slash commands work but prefix commands do not receive messages, check the deployment’s intents first.
Slash commands
Slash commands use Discord options and are synchronized when a revision is activated.
import { commands } from "./commands";
commands.slash(
{
name: "avatar",
description: "Show a user's avatar",
options: (option) => ({
user: option.user("User to inspect").optional(),
}),
},
async (interaction, args) => {
const user = args.user ?? interaction.user;
if (!user) {
await interaction.reply({
content: "Discord did not include a user for this interaction.",
ephemeral: true,
});
return;
}
await interaction.reply(user.displayAvatarURL({ size: 1024 }));
},
);
The returned object key becomes the option name and the handler receives the inferred value. Use .discordName() only when Discord should display a different name from the TypeScript property.
String and number options support choices, limits, localization, and autocomplete. Channel options can restrict the accepted channel types.
Permissions, filters, and cooldowns
These settings answer different questions:
Setting |
Purpose |
|---|---|
|
Discord permissions the person running the command must have |
|
Discord permissions the bot must have before the handler runs |
|
Application rules such as guild-only, allowed users, or required roles |
|
How often the command may run for a user, channel, or guild |
commands.prefix(
{
name: "staff-ping",
filters: [discord.filters.guildOnly()],
permissions: {
user: [discord.PermissionFlags.MANAGE_MESSAGES],
},
cooldown: {
durationMs: 5_000,
scope: "user",
},
},
async (message) => {
await message.reply("Staff command received.");
},
);
Filters and permissions on the application and a slash-command group accumulate with the command’s own requirements. Cooldowns belong to individual commands.
Custom prefix arguments
Use a custom argument when one token has a reusable format that the built-in parsers do not cover.
const ticket = (
input: string,
context: discord.CustomArgumentContext,
): number => {
const match = /^W-(\d+)$/.exec(input);
if (!match) context.fail("must look like W-42");
return Number(match[1]);
};
commands.prefix(
{
name: "ticket",
args: (argument) => ({
id: argument.custom("Ticket number", ticket),
}),
},
async (message, args) => {
await message.reply(`Ticket ${args.id}`);
},
);
Calling context.fail() produces a normal command input error, so the application’s onInputError handler formats it consistently.
Slash-command groups
Groups create real Discord subcommands and can share filters or permissions.
const settings = commands.group({
name: "settings",
description: "Manage server settings",
});
settings.slash(
{
name: "show",
description: "Show the current settings",
},
async (interaction) => {
await interaction.reply("No settings have been changed.");
},
);
Use commands.menu.user() and commands.menu.message() for Discord context-menu commands. Both accept a name directly or a configuration object when you need filters, permissions, cooldowns, or localizations. Use commands.list() when generating a help command from the commands registered by the current script.
See the SDK reference for every option, argument type, filter, and permission flag.