我该如何编写Discord.js机器人的事件/命令处理程序?

7

我已经开始使用Discord.js库在Node.js中创建一个Discord机器人,但是所有的代码都包含在一个单独的index文件中。

我该如何将每个命令和事件分别组织到不同的文件中,并在需要时运行它们?

1个回答

14

一种优秀而干净的组织机器人代码的方法是使用事件和命令处理程序。


简单来说

你可以从一个小的索引文件开始初始化客户端和其他代码。一个事件处理程序保持每个事件的文件,并在事件被触发时调用它们。然后,在你的客户端的 message 事件中,你可以通过运行预定命令文件中的代码来避免冗长的 if 链和 switch/case


什么是模块?

你需要理解的基本 Node.js 结构是一个 module

[模块是] 你想要包含在应用程序中的函数集合。

引自 w3schools.com

因此,把一个模块视为一个整洁的袋子,其中包含各种代码片段。你可以将其带到某个地方,打开它并拆开这些代码片段。在 JavaScript 中,你可以在程序的其他地方 require 模块,利用其中包含的代码。模块可以包含变量、类、函数等,在你的代码中不同位置上需要使用。


使用模块和导出

现在你知道了什么是模块,就要了解如何使用它们。

对于处理器,你只需要使用 module 对象的 exports 属性。通过使用 require() 导入一个模块,将返回 module.exports。请考虑以下设置。

单个导出

Question.js

class Question {
  constructor(author, details) {
    this.author = author;
    this.details = details;
    this.answers = [];
  }
}

module.exports = Question;

newQuestion.js

const Question = require('./Question.js');

const myQuestion = new Question('me', 'How to code event/command handlers?');

在`Question.js`中,创建了一个名为Question的新类,并将其分配给`module.exports`。然后,在`newQuestion.js`中需要`Question.js`时,将`Question`声明为导出的类。它可以像往常一样使用。
多个导出。
现在,例如,如果您需要导出多个类...
`Posts.js`
class Question {...}
class Answer {...}

module.exports = { Question, Answer };

// Alternatively...
// module.exports.Question = Question;
// module.exports.Answer = Answer;

newQuestion.js

const { Question } = require('./Posts.js');

const myQuestion = new Question(...);

以这种方式,module.exports被定义为一个对象,包含创建的类。这意味着require()将返回一个对象,因此您可以从对象中解构所需的类。

创建事件处理程序。

您应该首先创建一个用于存放事件的文件夹,并为每个事件创建一个文件。根据事件的名称命名文件。例如,对于您客户端的message事件,文件应命名为message.js

设置事件文件。

根据您现在了解的模块知识,您可以编写事件文件的代码。例如...

message.js

module.exports = (client, message) => {
  // This code will be executed when
  // the 'message' event is emitted.
};

设置处理程序。

为了创建实际的处理程序,您可以将以下代码放入一个函数中以加载事件...

const requireAll = require('require-all');   // Don't forget to install!

const files = requireAll({                   // Require all the files within your
  dirname: `${__dirname}/events`,            // event directory which have a name
  filter: /^(?!-)(.+)\.js$/                  // ending in '.js' NOT starting
});                                          // with '-' (a way to disable files).

client.removeAllListeners();                 // Prevent duplicate listeners on reload.
                                             // CAUTION: THIS REMOVES LISTENERS
                                             // ATTACHED BY DISCORD.JS!

for (const name in files) {                  // Iterate through the files object
  const event = files[name];                 // and attach listeners to each
                                             // event, passing 'client' as the
  client.on(name, event.bind(null, client)); // first parameter, and the rest
                                             // of the expected parameters
  console.log(`Event loaded: ${name}`);      // afterwards. Then, log the
}                                            // successful load to the console.

现在,当您的客户端发出您拥有文件的事件之一时,其中的代码将运行。

创建命令处理程序。

与事件处理程序一样,您应该首先创建一个单独的文件夹来存储您的命令,并为每个单独的命令创建文件。

设置命令文件。

您可以导出一个“run”函数和一个配置对象,而不是仅导出一个函数。

help.js

module.exports.run = async (client, message, args) => {
  // This code will be executed to
  // run the 'help' command.
};

module.exports.config = {
  name: 'help',
  aliases: ['h'] // Even if you don't want an alias, leave this as an array.
};

设置处理程序。

和事件处理程序一样,将此代码放入一个函数中以加载命令...

const requireAll = require('require-all');   // Using the same npm module...

const files = requireAll({                   // Require all the files within your
  dirname: `${__dirname}/commands`,          // command directory which have a name
  filter: /^(?!-)(.+)\.js$/                  // ending in '.js' NOT starting
});                                          // with '-' (a way to disable files).

client.commands = new Map();                 // Create new Maps for the corresponding
client.aliases = new Map();                  // command names/commands, and aliases.

for (const name in files) {                  // Iterate through the files object
  const cmd = files[name];                   // and set up the 'commands' and
                                             // 'aliases' Maps. Then, log the
  client.commands.set(cmd.config.name, cmd); // successful load to the console.
  for (const a of cmd.config.aliases) client.aliases.set(a, cmd.config.name);

  console.log(`Command loaded: ${cmd.config.name}`);
}

在你的客户端的message事件中,你可以使用以下代码来运行命令...
const prefix = '!'; // Example
const [cmd, ...args] = message.content.trim().slice(prefix.length).split(/\s+/g);

const command = client.commands.get(cmd) || client.commands.get(client.aliases.get(cmd));
if (command) {
  command.run(client, message, args);
  console.log(`Executing ${command.config.name} command for ${message.author.tag}.`);
}

常见问题解答.

如果我有一个与数据库相关或其他需要通过事件/命令传递的变量怎么办?

对于事件,您可以在event.on(...)中跟着client传递您的变量。然后在实际事件中,您的函数必须在client之后包含该参数。

对于命令,当在message事件中调用运行函数时,您可以将变量传递给它。同样,在您的函数中,您需要包含正确放置的参数。

如果我想在子文件夹中拥有命令/事件怎么办?

请查看此答案以进行递归搜索。

如何使用这些处理程序进行重新加载命令?

如果您将它们的代码放在函数内部,则可以设置一个“reload”命令来调用这些函数,再次加载事件和命令。

相关资源.

编辑内容...

  • client.removeAllListeners()将删除附加到客户端的所有侦听器,包括来自客户端实例的侦听器。这可能会导致与语音连接相关的错误,特别是抛出15秒内未建立语音连接。为防止此问题,请跟踪每个侦听器函数,并使用client.removeListener(listener)单独删除每个侦听器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接