discord-command-registry

A structure for Discord.js slash commands that allow you to define, register, and execute slash commands all in one place.

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
discord-command-registry
301.2.2a year agoa year agoMinified + gzip package size for discord-command-registry in KB

Readme

Discord.js Command Registry
LGPL-3.0 Logo
This is a data structure that lets you define Discord.js slash commands, register them with Discord's API, and route Discord.js Interaction events to your handler for that command.
Currently Discord.js separates slash command creation into three different, weirdly disjoined processes. They want you to:
  1. Define your commands with a builder,
which is only used to construct the data of an HTTP call.
  1. Deploy them with a separate HTTP PUT call,
which uses an entirely separate library that directly relies on the Discord API
  1. Set up a fairly complicated file structure for each command,
which still requires you to write your own router and juggle your own handlers
This library simplifies this process by letting you do this all in one place.
It also provides some bridging functionality to support additional option types:
  • Application
  • Emoji

Usage

Defining commands with the SlashCommandRegistry

This library adds a new builder SlashCommandRegistry that serves as the entry point for defining all of your commands. Existing builders from @discordjs/builders still work as you expect, but there's a new function added to all of them: .setHandler(handler). The handler is a callback function that expects a Discord.js Interaction instance. The SlashCommandRegistry will figure out which handler to call based on the received Interaction.
const {
    ApplicationCommandType,
    SlashCommandRegistry,
    // Can also import these directly, but you don't need them
    // ContextMenuCommandBuilder,
    // SlashCommandBuilder,
    // SlashCommandSubcommandBuilder,
    // SlashCommandSubcommandGroupBuilder,
} = require('discord-command-registry');

const commands = new SlashCommandRegistry()
    .addDefaultHandler(interaction => interaction.reply("I can't do this yet"))
    .addCommand(command => command
        .setName('ping')
        .setDescription('Ping pong command')
        .setHandler(interaction => interaction.reply('pong'))
    )
    .addCommand(command => command
        .setName('info')
        .setDescription('Gets some info on something')
        .addSubCommand(sub => sub
            .setName('all')
            .setDescription('Gets all the info ever created')
            .setHandler(interaction => interaction.reply('All things'))
        )
        .addSubcommand(sub => sub
            .setname('user')
            .setDescription('Gets info for a user')
            .addUserOption(opt => opt
                .setName('user')
                .setDescription('The user whose info to list')
                .setHandler(interaction => interaction.reply('User info'))
            )
        )
    )
    .addContextMenuCommand(command => command
        .setName('select')
        .setType(ApplicationCommandType.Message)
        .setHandler(interaction => interaction.reply('selected a message'))
    );

Registering commands with Discord

The SlashCommandRegistry can register commands with Discord's API with a single function call.
commands.registerCommands({
    application_id: 'your bot client ID',
    token: 'your bot token here',
    guild: 'a guild ID', // If provided, commands are registered for this guild.
                         // If omitted, commands are registered globally.
})
.then(res => console.log('Successfully registered', res))
.catch(err => console.error('Something went wrong', err));

Ok cool, but what if you need more control? You also can restrict this to register only a subset of commands.
await commands.registerCommands({
    application_id: 'your bot client ID',
    token: 'your bot token here',
    commands: ['ping'],
});
await commands.registerCommands({
    application_id: 'your bot client ID',
    token: 'your bot token here',
    guild: 'some guild ID',
    commands: ['info']
});

You can also store the application_id and token in the registry to avoid repeating it:
commands
    .setApplicationID('your bot client ID')
    .setToken('your bot token here');

commands.registerCommands({ commands: ['ping'] });

Executing commands

You can pipe Discord.js interaction events directly into a SlashCommandRegistry's execute() method.
const Discord = require('discord.js');
const { SlashCommandRegistry } = require('discord-command-registry');

const client = new Discord.Client({...});
const commands = new SlashCommandRegistry();
// Additional setup omitted for brevity...

client.on(Discord.Constants.Events.INTERACTION_CREATE, (interaction) => {
    commands.execute(interaction)
        .then(result => console.log('Command returned this', result))
        .catch(err => console.error('Command failed', err));
});

This library does not do anything with the Interaction object other than route it to the appropriate handler function. It's up to you to extract relevant data (such as options) from the Interaction.

Which handler gets called?

I added a handler to a subcommand, the group that subcommand belongs to, the command that group belongs to, and to the registry itself. Which one actually gets used when I execute an interaction?
The SlashCommandRegistry picks the most specific handler it can find, according to this priority list:
  1. Subcommand
  2. Subcommand Group
  3. Top-level Command
  4. Registry's default

In other words, if your command and subcommand both have a handler, only the subcommand's handler will be called. Using a lower-priority handler can give you some flexibility if you have many commands that all use similar code.

Additional option types

Discord (and Discord.js) does not currently support command options for things like Applications. This library provides functions to approximate these additional option types:
  • getApplication(interaction, option_name, required=false)
  • getEmoji(interaction, option_name, required=false)

To use these, register the option as a string option, then use the Options.getX(...) helpers to retrieve the value.
For example, this is a functional example of an Application option:
const {
    Options,
    SlashCommandRegistry,
} = require('discord-command-registry');

const commands = new SlashCommandRegistry()
    .addCommand(command => command
        .addName('mycmd')
        .addDescription('Example command that has an application option')
        // Add your application option as a string option
        .addStringOption(option => option
            .setName('app')
            .setDescription('An application ID')
        )
        .setHandler(async (interaction) => {
            // Use this function to resolve that string option into an application.
            // NOTE this makes an HTTP call and so returns a promise.
            const app = await Options.getApplication(interaction, 'app');
            return interaction.reply(`Application name: ${app.name}`);
        });
    );

Other stuff from @discordjs/builders

The Discord.js builders package has a lot of neat helper functions. The command registry passes all of these functions through, so they can be included directly (preventing the need to add / import @discordjs/builders).
const { bold, hyperlink, time } = require('discord-command-registry');

Dependencies

This library is built using the following libraries. You will, of course, need Node and Discord.js, but you don't need any of the others. This library downloads these dependencies for you, and you interact with them through this library.

License

Copyright 2021 Mimickal
This code is licensed under the LGPL-3.0 license.
Basically, you are free to use this library in your closed source projects, but any modifications to this library must be made open source.