Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I code event/command handlers for my Discord.js bot?

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?

like image 571
slothiful Avatar asked Jun 11 '19 17:06

slothiful


People also ask

What is event handler in Discord js?

The event handler will automatically retrieve and register it whenever you restart your bot. In most cases, you can access your client instance in other files by obtaining it from one of the other discord. js structures, e.g. interaction. client in the interactionCreate event.

What coding language does Discord js use?

In conclusion, Discord bots are coded in Python. However, if you don't know Python, you can consider a chatbot building platform such as Appy Pie Chatbot.


1 Answers

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


In simple terms.

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.


What's a module?

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

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

Quoted from w3schools.com.

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.


Working with modules and exports.

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

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.

A single export.

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?');

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.

Multiple exports.

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(...);

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.


Creating the event handler.

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.

Setting up the event files.

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.


Creating the command handler.

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

Setting up the command files.

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

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

FAQ.

What if I have a database related or other variable I need to pass through events/commands?

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.

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.

What if I want to have commands/events within subfolders?

Check out this answer to search recursively.

How do I use these handlers for a reload command?

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.


Related resources.

  • Node.js Documentation
  • MDN Documentation
  • W3Schools Tutorial
  • require-all Package
  • Discord.js Documentation

Edits...

  • 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).
like image 74
slothiful Avatar answered Oct 28 '22 22:10

slothiful