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

2022-01-10 00:00:00 node.js javascript discord.js

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

I've started creating a Discord bot in Node.js using the Discord.js library. However, all the code is contained within a single index file.

如何将命令和事件分别组织到单独的文件中,并在需要时运行它们?

How do I organize the commands and events each into separate files, and run them when needed?

推荐答案

为您的机器人组织代码的一种出色、简洁的方法是使用事件和命令处理程序.

An excellent, clean way to organize the code for your bot is to employ event and command handlers.

您从一个小的索引文件开始初始化客户端和其余代码.事件处理程序保存每个事件的文件,并在事件发出时调用它们.然后,在客户端的 message 事件中,您可以通过运行来自预期命令文件的代码.

You start out with a small index file to initialize the client and the rest of the code. An event handler keeps the files for each event, and calls on them when the event is emitted. Then, in your client's message event, you can avoid long if chains and switch/case altogether by running the code from the intended command's file.

您需要了解的基本 Node.js 结构是 module.

The basic Node.js structure you'll need to understand is a module.

[A module is a] 您希望在应用程序中包含的一组函数.

[A module is a] set of functions you want to include in your application.

引自 w3schools.com.

因此,可以将模块视为一个包含代码片段的整齐粘贴的盒子.你可以把包裹带到某个地方,打开它,然后拆开包装.在 JavaScript 术语中,您可以在程序中的其他地方要求该模块,并利用其中包含的代码.模块可以包含您需要在代码的不同位置使用的变量、类、函数等.

So, think of a module as a neatly taped up box containing pieces of code. You can take the package somewhere, open it up, and unpack the pieces. In JavaScript terms, you can require the module somewhere else in your program, and utilize the code contained within it. Modules can contain variables, classes, functions, etc. that you need to use throughout different locations across your code.

既然您知道什么是模块,那么您必须了解如何使用它们.

Now that you know what a module is, you have to understand how to work with them.

出于处理程序的目的,您将只使用 module 对象的 exports 属性.通过对模块使用 require()module.exports 返回.请考虑以下设置.

For the purpose of the handlers, you're only going to be using the exports property of the module object. By using require() for a module, module.exports is returned. Consider the following setups.

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 被声明为导出的类.它可以像往常一样使用.

In Question.js, a new class, Question, is created and assigned to module.exports. Then, when Question.js is required in newQuestion.js, Question is declared as the exported class. It can be used just as usual.

现在,例如,如果您需要导出多个类...

Now, for example, if you needed to export multiple classes...

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() 将返回一个对象,因此您可以 从对象中解构所需的类.

In this way, module.exports is defined as an object, containing the created classes. This means that require() will return an object instead, so you can destructure the needed class from the object.

您应该首先为您的活动创建一个文件夹,然后为每个活动创建一个文件.根据事件的名称命名文件.例如,对于您客户的 message event,该文件应命名为 message.js.

You should start by creating a folder for your events, and create a file for each one. Name the files according to the name of the event. For example, for your client's message event, the file should be named message.js.

实现您现在对模块的了解,您可以编写事件文件.比如……

Implementing what you now know about modules, you can code the event files. For example...

message.js

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

设置处理程序.

要制作实际的处理程序,您可以将以下代码放在函数中以加载事件...

Setting up the handler.

To make the actual handler, you can place the following code in a function to load events...

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.

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

Now, when your client emits one of the events you have a file for, the code inside of it is run.

就像事件处理程序一样,您应该首先为您的命令创建一个单独的文件夹,然后为每个单独的命令创建文件.

Just like for the event handler, you should start by creating a separate folder for your commands, and create files for each individual command.

您可以导出一个运行"函数和一个配置对象,而不是只导出一个函数.

Instead of exporting just one function, you can export a "run" function and a configuration object.

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

设置处理程序.

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

Setting up the handler.

Just like the event handler, place this code in a function to load commands...

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 事件中,您可以使用以下代码运行命令...

In your client's message event, you can use the following code to run the commands...

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 之后包含该参数.

For events, you can pass your variable in event.on(...), following client. Then in your actual events, your function must include that parameter after client.

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

For commands, you can pass your variable into the run function when calling it in the message event. Again, in your function, you need to include the properly placed parameter.

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

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

Check out this answer to search recursively.

如何将这些处理程序用于重新加载命令?

如果您将它们的代码放在函数中,您可以设置一个重新加载"命令来调用这些函数,再次加载事件和命令.

If you placed the code for them inside of functions, you can set up a "reload" command that calls those functions, loading the events and commands again.

  • Node.js 文档
  • MDN 文档
  • W3Schools 教程
  • require-all
  • Discord.js 文档
  • client.removeAllListeners() 将删除附加到客户端的所有 监听器,包括那些源自客户端实例化的监听器.这可能会导致与语音连接相关的错误,特别是 语音连接未在 15 秒内建立 被抛出.为防止出现此问题,请跟踪每个侦听器函数并使用 client.removeListener(listener) 单独删除每个函数.
  • client.removeAllListeners() will remove all listeners attached to the client, including those originating from client instantiation. This can cause voice connection related errors, specifically Voice connection not established within 15 seconds being thrown. To prevent this issue, keep track of every listener function and remove each individually using client.removeListener(listener).

相关文章