discord.js v13 中如何使用文件对命令进行分类

How to categorize commands with files in discord.js v13

问题:
大家好,我想知道如何将我的命令分类到文件中,而不是全部放在 commands 文件中。例如,文件布局如下所示:

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

我的index.js代码:

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 });
        }
    });
});

我想知道的:

我知道这个显示有点乱,但请注意 ├── ── commands/,请注意我是如何添加另一个文件夹并将命令放入其中的,而不是将命令放入 ├── ── commands/ 本身。如何自定义 index.js 文件以查看 commands 文件夹中的每个文件夹,然后获取 category 文件夹中的每个文件?我试图添加一个 * 以查看它是否会抓取每个文件夹,但它只是抛出了一个错误,说 commands/* 不是一个目录。那我该怎么做呢?我以前也看到过这样做,所以我知道这是可能的。

对于这种类型的文件匹配,您需要使用glob。它 returns 一组东西,你可以使用模式 commands/**/* 让它工作。

我想通了,并使用以下代码解决了这个问题:

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 });
        }
    });
});

如果我没理解错的话,你想做的事情可以使用 dynamic imports 来完成。我猜您没有使用工具链来构建它,而是直接从 CLI 执行脚本,所以我不打算进行转译。

尝试按照这些步骤操作,这些只是建议,因此请随意实施它,但最适合您的项目:

  • 找一个可以帮助你检索文件列表的库,glob是一个很好的开始选择。

  • 在您的脚本中,首先创建一个函数来注册命令,我们假设该函数的签名是 registerFileCommand(filename: string, handler: () => void)

  • 检索并循环遍历您的模块,每次迭代导入并注册它们:

    glob("./commands/*.js", {}, (err, files) => {
        files.forEach(async file => {
            const cmd = generateCommandName(file);
            registerFileCommand(file, (await import(file)).default);
        });
    });
    
    
  1. 使用这种方法时需要注意以下几点:
  • registerFileCommand 应该处理命令注册逻辑,这意味着命令模块的默认导出应该是调用命令时由 DiscordJS 执行的函数。

  • 立即导入所有模块可能会出现问题,因此您可能希望导入命令 on-demand 而不是在启动时。那看起来像这样:

    registerFileCommand(file, params => {
        import(file).then(({ default }) => default(...params));
    });
    
  • 您将需要实施额外的逻辑来将模块名称解析为命令名称。这将完全取决于您的命名方案。例如,我们选择以 kebab 大小写 (my-command).

    命名我们的命令模块
  • 如果您想提供提示或帮助对话框来记录命令的可能用法,您可能还需要在导出中实现额外的逻辑。