npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@brylan/djs-commands

v3.0.9

Published

A basic slash commands system for Discord.js

Downloads

25

Readme

Brylân DJS Commands

A simple slash commands module for Discord.js

Made for Discord.js 14. Use with other versions is not guaranteed to work.

Upgrading from version 1 or 2? Check out the (short) upgrade guide.

Table of contents

How to use

Installation: npm i @brylan/djs-commands

If you are using CommonJS modules instead of ES6, use version 2 instead: npm i @brylan/djs-commands@commonJS

Put all your commands in a folder (see Command structure) and write the following code in your main file to load and register all your commands:

import loadCommands from "@brylan/djs-commands";
client.once("ready", () => {
    loadCommands(client, options);
});

Where client is your Discord.js Client, and options an object with the following properties:

  • folder: defaults to "commands". The folder where your commands are.
  • ownerCommand: defaults to "owner". The name of the subfolder under folder where your owner commands are. Ignored if you do not also set an ownerServer.
  • ownerServer: the id of your server, where the owner commands will be set (if you defined any).
  • singleServer: defaults to false. If a guild id, all commands will be set as guild commands of that guild instead of global ones. If true, the first guild in the cache will be used. This overrides ownerServer.
  • autoSubcommands: defaults to true. If true, files in subfolders will be treated as subcommands, and further subfolders as subcommand groups. See Subfolders and subcommands.
  • debug: defaults to false. If true, debug commands will not be ignored. Also, if singleServer was not set, it is set to the owner server if you defined one, or client.guilds.cache.first() otherwise.
  • makeEnumsGlobal: defaults to false. If true, the SNAKE_CASE enumerations will be global and you won't have to import them.
  • defaultDmPermission: defaults to false. The default value for dmPermission. Incompatible with singleServer.
  • middleware: a function to apply to each command when it is loaded (excluding owner commands), before it is sent to Discord. Takes the command name and command object as its sole arguments.

All options are... well, optional. You can skip this argument entirely.

Example:

import loadCommands from "@brylan/djs-commands";
client.once("ready", () => {
    loadCommands(client, {
        ownerServer: "1234567890123456789",
        defaultDmPermission: true,
        middleware: (name, command) => console.log(`Command ${name} loaded:`, command.description),
    });
});

loadCommands returns a Promise that resolves to a Collection of commands. It registers all commands, updating the existing ones, and deleting the ones you removed from your code.

Commands are run in an async context, even if their run function itself is not async.

Command structure

If you need an example you can check out the source code for Steam News.

Each file is one command. The file has to be .js. Each file must export a description and a run function. The run function takes a CommandInteraction as its sole argument.

This version of djs-commands expects ES6 modules. If you want to use CommonJS modules (with exports and require), install version 2.x.

It may also export an autocomplete function that will handle its autocomplete options. It should take a AutocompleteInteraction as its sole argument.

You can also export any other ApplicationCommandData.

Important note: if you export the name, it will be ignored. The file name (without .js) is always used as the command name. You can however export nameLocalizations.

Example of a simple command with options:

import { enums } from "@brylan/djs-commands";
const { STRING, CHANNEL, ALL_TEXT_CHANNEL_TYPES } = enums;

export const description = "Send a message to a channel";
export const options = [{
    type: STRING, name: "message", required: true,
    description: "The message to send",
}, {
    type: CHANNEL, name: "channel",
    channelTypes: ALL_TEXT_CHANNEL_TYPES,
    description: "The channel where to send the message (defaults to current channel if not provided)",
}];
export function run(interaction) {
    const message = interaction.options.getString("message");
    const channel = interaction.options.getChannel("channel") || interaction.channel;
    channel.send(message);
    interaction.reply({ ephemeral: true, content: "Message sent!" });
}

Note: you do not need to import those enums if you set makeEnumsGlobal to true;

If you want to add JS files in your command folder that aren't commands, just prefix their name with # and they will be ignored. Folders starting will # will also be ignored, as will files not ending in .js.

If you prefer, you can also export the properties as default in an object.

export default {
    description: "Send a message to a channel",
    options: [...options],
    run: interaction => {
        // do something
    },
};

Subfolders and subcommands

Unless autoSubcommands was set to false, any subfolders of the commands folder will be treated as a command, and the files within it its subcommands. A second-level subfolder would be a subcommand group. Subcommand groups cannot contain other groups, so any further subfolder will cause an error.

Subfolders can have a #info.js file that export command properties like the description, defaultMemberPermissions or localizations. It should not however export any options, as these will be defined by the files of the folder. If it does not exist, the command's description is set to its name.

Consider the following file tree:

commands
 └ shop
   └ offer
     ├ create.js
     ├ delete.js
   ├ ~info.js
   ├ buy.js
 ├ inventory.js

This would create the following commands:

  • /shop offer create
  • /shop offer delete
  • /shop buy
  • /inventory

Where ~info.js can be something like this:

export const description = "Shop commands";

Special folders

  • ~debug: commands only created if the debug option is set. Meant for commands that help you debug your code but should not exist in production.
  • owner: (or whatever you set as ownerCommand.name) Owner commands are only available in the provided ownerServer and can only be used by its admins. This is intended for commands only the bot owner should have access to, for instance a /shutdown command.
  • ~guild: folder for guild commands (or if singleServer was set, optional commands).

Owner commands

The ownerCommand parameter is "owner" by default. It should be either a string, which will be its name, or an object with its name, description and default member permissions. For example:

import { PermissionFlagsBits } from "discord.js";
const { ManageChannels, ManageMessages, BanMembers } = PermissionFlagsBits;

loadCommands(client, {
    ownerCommand: {
        name: "admin",
        description: "Admin commands",
        defaultMemberPermissions: ManageChannels | ManageMessages | BanMembers,
    }
});

Note: the defaultMemberPermissions is "0" by default, which only lets administrators use it.

The command name must be the name of the subfolder in your commands folder where all the owner commands will be.

Owner commands are grouped as subcommands of a single command named /owner (or whatever you set as the name). If you have another command with that name, it will be overriden. However, the names of each subcommand can be used.

Note that the owner folder is not read recursively. Any of its subfolders will be ignored.

Since your owner commands will get the SUBCOMMAND type by default, if you need to have subcommands for an owner command, you will need to set its type to SUBCOMMAND_GROUP.

import { enums } from "@brylan/djs-commands";
const { SUBCOMMAND, SUBCOMMAND_GROUP } = enums;
export const type = SUBCOMMAND_GROUP;
export const options = [{
    type: SUBCOMMAND, // etc
}];

Guild commands

Guild commands require another export: shouldCreateFor, a function that takes a guild id as its sole argument, and should return a boolean value: true if the command should be present in that guild, false otherwise.
If shouldCreateFor is not defined, the command will be created for all guilds.

It may also have a getOptions function that takes the same argument. It should return an array of options, that can be different for every server. If getOptions does not exist or returns a falsy value, options will be used instead; or an empty array if it is not defined.

You can import the following functions from guildCommands to control guild commands:

createCmd(command, guild, skipCheck = false)
Creates a command if its shouldCreateFor function returns true for the provided Guild.
This is automatically called when the bot joins a server.

  • command: the command name
  • guild: the Guild object
  • [skipCheck]: If true, shouldCreateFor will not be called and the command will be added to the guild regardless of what its return value would have been. Returns false if shouldCreateFor returned false, or a Promise that resolves when the command has been created.

updateCmd(command, guild, createIfNotExists = true)
Updates a command if its shouldCreateFor function returns true for the provided Guild, deletes it otherwise.

  • command: the command name
  • guild: the Guild object
  • [createIfNotExists]: If true, the command will be created if it does not exist. Returns a Promise that resolves when the command has been updated. Throws an Error if the command did not exist and createIfNotExists was set to false.

deleteCmd(command, guild)
Deletes the command.

  • command: the command name
  • guild: the Guild object Returns false if the command was not in that guild, or false if it was not in that guild.

isIn(command, guild) Checkes if the given command is in a guild.

  • command: the command name
  • guild: the Guild object Returns true or false.

Example: an optional /hello command. It is managed by a /set-hello command that only admins can use, which lets them choose the style of greetings to enable.

commands/set-hello.js

import { guildCommands, enums } from "@brylan/djs-commands";
const { isIn } = guildCommands;
const { STRING } = enums;
import { setStyle, removeStyle } from "./~guild/hello.js";
export const defaultMemberPermissions = "0";
export const description = "Sets the type of greeting for /hello";
export const options = [{
    type: STRING, name: "style", required: true,
    description: "The greeting style. 'none' deleted the command.",
    choices: [
        { name: "Formal", value: "formal" },
        { name: "Normal", value: "normal" },
        { name: "Informal", value: "informal" },
        { name: "None", value: "none" },
    ],
}];
export function run(interaction) {
    const style = interaction.options.getString("style");
    const { guild } = interaction;
    if(style === "none") {
        if(isIn("hello", guild)) {
            removeStyle(guild).then(() => interaction.reply("Command `/hello` removed."));
        }
        else {
            interaction.reply("The `/hello` command was not in this server.");
        }
    }
    else {
        setStyle(guild, style).then(() => interaction.reply("`/hello` command updated."));
    }
}

commands/guild/hello.js

const styles = new Map();
const thisCmd = import.meta.url.slice(1 + import.meta.url.lastIndexOf("/"), -3);

import { guildCommands } from "@brylan/djs-commands";
const { updateCmd, deleteCmd } = guildCommands;

export function setStyle(guild, style) {
    styles.set(guild.id, style);
    return updateCmd(thisCmd, guild);
}
export function removeStyle(guild) {
    styles.delete(guild.id);
    return deleteCmd(thisCmd, guild);
}
export const shouldCreateFor = styles.has.bind(styles);

const greetings = {
    formal: ["Greetings", "Salutations", "Good day"],
    normal: ["Hello", "Hi"],
    informal: ["Wassup", "'sup", "Yo"],
};

function toChoices(choice) {
    return { name: choice, value: choice };
}
export const getOptions = (guildId) => [{
    type: STRING, name: "greeting", required: true,
    description: "The greeting to use",
    choices: greetings[styles.get(guildId)].map(toChoices),
}];
export const description = "Say hello";
export function run(interaction) {
    const { options, user } = interaction;
    interaction.reply(`${options.getString("greeting")} ${user}!`);
}

Managing commands

You can import the commands object that contains all the command files. For instance if your commands folder has hello.js, roles/get-role.js and roles/remove-role.js, its keys will be 'hello', 'get-role' and 'remove-role', and the values are these files' exports. The list includes all guild commands and the owner command.

Hot-reloading commands is not supported, as the ES6 module cache is immutable. The only way to "reload" a command is to reboot the bot.

Enums

The enums that are available from import { enums } from "@brylan/djs-commands" (and globally unless you set makeEnumsGlobal to true) are SNAKE_CASE versions of Discord.js' ApplicationCommandType, ApplicationCommandOptionType ComponentType, ButtonStyle and TextInputStyle.

import { enums } from "@brylan/djs-commands";
import { ApplicationCommandOptionType } from "discord.js";
const { SubcommandGroup } = ApplicationCommandOptionType;
console.log(enums.SUBCOMMAND_GROUP === SubcommandGroup); // true

It also exports ALL_TEXT_CHANNEL_TYPES, which as the name implies, includes all types of text channel/thread.

  • But why?
    A matter of naming convention. As far as I am concerned, PascalCase is for class names, not enum values. (C# devs will tell you it's also for function and variable names; but we are doing JavaScript here).
    Enum values and constants/globals should be in SNAKE_CASE. Call me old fashioned, but this is how it has been done at least since C. I do not know why the people of Discord.js went on a crusade against so called "SCREAMING_CASE" with version 14 but I disagree with it, hence why this package exports those SNAKE_CASE enum values.

If you prefer the PascalCase, you can use Discord.js' enums. You do not have to use my SNAKE_CASE enum if you do not want to.

For reference: Code Conventions for JavaScript by Douglas Crockford. Not gospel of course, but if you are new to JS and do not have your own code conventionss set, this is a good guide.