简体   繁体   中英

How to categorize commands with files in discord.js v13

Question:
Hey guys, I was wondering how I could categorize my commands into files instead of them all being in the commands file. For example the file layout would look something like the following:

discord-bot/
├── node_modules
├── src/
    ├── commands/
        └── Moderation/
            └── command.js
    ├── Data/
        └── config.json
    ├── .env
    └── index.js
├── package-lock.json
└── package.json

My index.js code:

client.once('ready', () => {
    incrementVersionNumber(config.version, ".");

    console.log(`Successfully logged in as ${client.user.tag}`);
    
    // Where the main part of adding the command files begins

    const commands = [];
    const commands_information = new Collection();
    const commandFiles = fs.readdirSync("./src/commands").filter(file => file.endsWith(".js"));

    const clientId = process.env.CLIENTID;

    for(const file of commandFiles){
        const command = require(`./commands/${file}`);
        console.log(`Command loaded: ${command.data.name}`);
        commands.push(command.data.toJSON());
        commands_information.set(command.data.name, command);
    }

    // Where getting the command files ends

    const rest = new REST({ version: '9' }).setToken(token);

    (async () => {
        try {
            console.log('Started refreshing application (/) commands.');
    
            await rest.put(
                Routes.applicationGuildCommands(clientId, 'guildId'),
                { body: commands },
            );
    
            console.log('Successfully reloaded application (/) commands.');
            console.log('-----------------------------------------------');
        } catch (error) {
            console.error(error);
        }
    })();

    client.on('interactionCreate', async interaction => {
        if (!interaction.isCommand()) return;
    
        const { commandName } = interaction;
    
        if (!commands_information.has(commandName)) return;
    
        try {
            await commands_information.get(commandName).execute(client, interaction, config);
        } catch (error) {
            console.error(error);
            await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
        }
    });
});

What I'd like to know:

I know this display is a bit messy but focus on ├── ── commands/ , notice how I added another folder and put commands inside of it instead of putting commands inside ├── ── commands/ its self. How can I customize my index.js file to look through every folder in the commands folder, then get every file inside of the category folder? I've attempted to add a * to see if it would grab every folder but it just threw an error saying commands/* was not a directory. So how can I do this? I have also seen this done before so I know it is possible.

If I understand properly, what you're looking to do can be accomplished using dynamic imports . I'm guessing you are not using a toolchain to build this but rather just executing the script directly from the CLI so I'm not going to go into transpiling.

Try following these steps, these are just recommendations so feel free to implement it however best fits your project:

  • Find a library that can help you retrieve the file list, glob is a good starting option.

  • In your script, begin by creating a function to register the command, let's assume the signature of that function is registerFileCommand(filename: string, handler: () => void)

  • Retrieve and loop through your modules, importing and registering them each iteration:

     glob("./commands/*.js", {}, (err, files) => { files.forEach(async file => { const cmd = generateCommandName(file); registerFileCommand(file, (await import(file)).default); }); });
  1. There are a few things to keep in mind when using this approach:
  • registerFileCommand should handle the command registration logic, meaning that the default export of your command module should be a function that is executed by DiscordJS when the command is called.

  • Importing all modules off the bat could become problematic, so you might want to import the commands on-demand rather than on boot. That would look something like this:

     registerFileCommand(file, params => { import(file).then(({ default }) => default(...params)); });
  • You will need to implement additional logic to resolve a module name to a command name. That would completely depend on your naming scheme. We for example choose to name our command modules in kebab case (my-command).

  • You might also need to implement additional logic in your export if you want to provide hints or assistance dialogs that document your command's possible usages.

For this type of file matching, you need to use glob . It returns an array of stuff, and you can use the pattern commands/**/* for it to work.

I figured it out and was able to solve this by using the following code:

client.once('ready', () => {
    incrementVersionNumber(config.version, ".");

    console.log(`Successfully logged in as ${client.user.tag}`);

    client.user.setActivity(`${client.guilds.fetch.length} Servers`, {type: 'WATCHING'});

    categories = [
        "Config",
        "Entertainment",
        "Games",
        "Information",
        "Miscellaneous",
        "Moderation",
        "Music",
    ];

    const commands = [];

    for (var i = 0; i < fs.readdirSync('./src/commands').length - 1; i++) {
        const commands_information = new Collection();
        const commandFiles = fs.readdirSync(`./src/commands/${categories[i]}`).filter(file => file.endsWith(".js"));

        for(const file of commandFiles){
            const command = require(`./commands/${categories[i]}/${file}`);
            console.log(`Command loaded: ${command.data.name}`);
            commands.push(command.data.toJSON());
            commands_information.set(command.data.name, command);
    }
    }

    const rest = new REST({ version: '9' }).setToken(token);

    (async () => {
        try {
            console.log('Started refreshing application (/) commands.');
    
            await rest.put(
                Routes.applicationGuildCommands(clientId, '910339489770111036'),
                { body: commands },
            );
    
            console.log('Successfully reloaded application (/) commands.');
            console.log('-----------------------------------------------');
        } catch (error) {
            console.error(error);
        }
    })();

    client.on('interactionCreate', async interaction => {
        if (!interaction.isCommand()) return;
    
        const { commandName } = interaction;
    
        if (!commands_information.has(commandName)) return;
    
        try {
            await commands_information.get(commandName).execute(client, interaction, config);
        } catch (error) {
            console.error(error);
            await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
        }
    });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM