Building norabot: A Discord bot built in TypeScript

My experience building my own personal discord bot

Aneesh Edara

15 minute read

Discord Bots…

Nowadays, there are thousands of bots roaming the millions of guilds founds on Discord, most of whom, pretty much do the same thing. There are bots for literally everything: music, minigames, anime, etc. So I’d understand if most people would be confused as to why I decided to make another Discord bot to join the hoard. Well, the reason I made the bot is the exact reason why people thought making the bot was pretty useless: there are so many bots. Like, seriously, why do I need to invite 4 different bots onto my server to get all the functionality that I want. Why do I need to invite a bot for playing music and another one for server administration? I really wanted a bot that could do everything I needed in one cohesive interface, so I set out to make it, and since I recently watched Noragami, I decided to call it NoraBot.

Ultimately it was a very challenging project, but I am very glad that I did it. I’ve learned so much from building this bot, and it’s a project that I am personally very proud of because of the number of original designs I put into this bot due to lack of documentation (I’ll get into this later). Below I’m going to go through the journey of creating this bot, all the extra engineering I put into the bot, and why I think it’s a great project for anyone looking for a challenge.

The Beginning

So starting off, if I’m going to be completely honest…I followed the official discord.js guide verbatim. While I’m a little bit ashamed, I think it’s perfectly fine when you’re just starting with a new language, API, or framework. I learn by doing, so following this guide helped me gain a basic mastery of the library, enough that after going through it, I was able to stay afloat on my own.

The anatomy of a Discord bot is actually really simple, though it can be confusing to some people just because Javascript doesn’t have type definitions (I wonder why I wrote my bot in Typescript). First (and note that this code can be found in the repository history over here) all this bot does parse incoming messages and check to see if they are a valid command:

// Parse the message
const args = message.content.slice(prefix.length).split(/ +/);
const commandName = args.shift().toLowerCase();
 
// Check if the command exists
const command = client.commands.get(commandName)
   || client.commands.find(cmd => cmd.aliases && cmd.aliases.includes(commandName));
 
if (!command) return;

The existing commands are just found by searching through the /commands directory:

for (const file of commandFiles) {
   const command = require(`./commands/${file}`);
   client.commands.set(command.name, command);
}

Then it just checked for correct arguments and cooldown timers. If those were met, then it tried to execute the command:

command.execute(message, args);

The anatomy of a command is also incredibly simple. Here’s the ping command from that original bot:

module.exports = {
   name: 'ping',
   description: 'A basic ping command to test bot availability',
   execute(message, args) {
       message.channel.send('Pong!')
   },
};

So all-in-all, making a discord bot was not that hard. But that’s just because I didn’t challenge myself.

My first original feature

When I say “original”, I don’t mean I was the first to make it. I just mean that I did in my way without copying anybody else. If somebody else’s code looks like mine, it’s probably just a coincidence.

I found making a discord bot really easy. There wasn’t much there, and I didn’t learn that much either. Most of the code I wrote was just stuff taken directly from the guide. Because of that, I decided to put myself up to my first challenge. If I wanted a Discord bot that can do everything then it would probably be ideal to have multiple customizable prefixes depending on the function. I haven’t seen many bots do this, but I was pretty sure it existed. So to make it more fun, I decided to have a module system based on folders, and a configuration file that allowed a self-hosting user to assign a custom prefix to a specific module. The repository for this version can be found here.

To start I just re-organized my commands, which at the time were just dumped all in one folder. I started off making the admin and basic modules, moving my commands around so it ended up like this:

> commands
   > admin
       > prune.js
   > basic
       > help.js
       > icon.js
       > ping.js
       > server-info.js

Pretty simple. I followed that up by making a simple configuration file in JSON (since parsing JSON is ludicrously easy in Javascript–Typescript maybe not as much). It just looked like this:

{
 "admin": {
   "prefix": "a",
   "group": "Admin"
 },
 "basic": {
   "prefix": "b",
   "group": "Basic"
 }
}

To this day, this configuration file is not something I’m particularly proud of (I still use a similar configuration file). It mostly arises from the fact that I have to manually update the file every time I created a new module. The idea of it being static bothers me still, and lately, I had some plans of possibly implementing an autogenerated configuration file that is created every time you start-up the bot. I’ll get around to that sometime.

Updating my command handler was probably the hardest part (though still, it wasn’t as bad as I hoped it would be; I was looking for a challenge). I started by realizing that I need to keep of 3 things now: the group, the associated prefix, and the associated commands.

I started by making a Discord Collection of prefixes that was tied to my Discord Client:

client.prefixes = new Discord.Collection();

As a side note, something that bothers me. Discord Collection indexes start at 1 not 0! I haven’t read into why this is the case, but it made me lose a few hours in development trying to figure out why my prefixes weren’t matching up.

Anyhow, I needed to modify the code that ran through the folders to make an index of commands so that it would now support the new system.

const commandFolders = fs.readdirSync('./commands');
 
// Loop through folders
for (const folder of commandFolders) {
   const commands = new Discord.Collection();
  
   // Find all JS files
   const commandFiles = fs.readdirSync(`./commands/${folder}`).filter(file => file.endsWith('.js'));
 
   // Loop through all the files in each folder
   for (const file of commandFiles) {
       const command = require(`./commands/${folder}/${file}`);
 
       // Asign the name of the command to the object
       commands.set(command.name, command);
   }
 
   // For each folder (module) assign the prefix to the collection of commands
   client.prefixes.set(commandConfig[folder].prefix, commands);
 
   // To be honest, I couldn't really figure out why I wrote this line, but it works, so hey...
   commands.set('name', commandConfig[folder].prefix);
}

This part confused me because I was dumb and forgot that whiteboards exist for a reason. Nowadays, I realized how damn valuable a whiteboard or pen & paper is for planning out code. I always thought it felt like cheating because you had to visualize it elsewhere. But stuff like this reminds why I probably shouldn’t think of it that way.

Next, I needed to update my command handler to process the new format correctly.

let commandType;
 
   // For every message, look through the prefixes collections
   for (const prefixCollection of client.prefixes) {
       // Check if the message starts with the module-specific prefix + the global prefix
       if (message.content.startsWith(prefixCollection[1].get('name') + prefix)) {
           // Then set the command type (which is the module it belongs to)
           commandType = prefixCollection[1].get('name');
       }
   }
   if (!commandType) return;

That code I wrote just to find the group it belonged to. I wrote some more code to find the specific command (though I now realize I probably could have compressed most of this).

const command = client.prefixes.get(commandType).get(commandName)
       || client.prefixes.get(commandType).find(cmd => cmd.aliases && cmd.aliases.includes(commandName));

This just checks through the list of commands based on the module we found earlier (and also checks through aliases which are stored on the command itself). So there it was. My command handler, which at the time, I thought very highly off. I thought it was super cool and unique, plus, I did it without reference to a guide.

After this, I just added some more menial commands such as adding and deleting roles on a server. I also made some simple modifications to my command handler that handled server only and admin only commands, of which (stupid me), I was checking manually in every single command. I learned that if you’re repeating code more than once, you’re probably doing something wrong (which is probably something I should have learned earlier, but at least I know it now!).

So let’s add some types!

Around this time, we started to use TypeScript in Delphus, a project I was (and still am) working on at Scintillating. I didn’t understand it much at the time, and I thought it wasn’t worth it because I found Javascript waaaay easier at first. I didn’t think types were that necessary. Oh boy, was I wrong. I eventually decided to just do it because it would be a great learning opportunity for me; I could then be more efficient when working on Delphus.

After some research, I set up my tsconfig.json, ts-node, and other stuff like that. Then I made the first move, changing index.js to index.ts. The file was subsequently riddled with errors. I decided to take it linearly, taking one error at a time. I don’t have many of the specific issues I faced since I’m writing this a good deal into the future, but I’ll talk about some of the key features I implemented below.

What the hell is a command?

What confused me for a ** long time was why TypeScript wasn’t able to read the properties of my commands. That’s when I realized that TypeScript wanted a strong declaration of what a command was. In Javascript, all I had to do was add a property to my module.exports object and Javascript would just know that it’s there. I learned that TypeScript didn’t work that way. That’s when I made my first type definition, for my command. I thought, other than extending my file size by almost 50%, I thought it was long and ugly.

export class Command {
 name: string;
 description: string;
 aliases: string[] | undefined;
 usage: string;
 guildOnly: boolean;
 adminRequired: boolean;
 argsRequired: boolean;
 cooldown: number;
 execute: (message: Discord.Message, args?: string[]) => void;
 constructor(props: {
   name: string;
   description: string;
   aliases: string[] | undefined;
   usage: string;
   guildOnly: boolean;
   adminReq: boolean;
   argsRequired: boolean;
   cooldown: number;
   execute: (message: Discord.Message, args?: string[]) => void;
 }) {
   this.name = props.name;
   this.description = props.description;
   this.aliases = props.aliases;
   this.usage = props.usage;
   this.guildOnly = props.guildOnly;
   this.adminRequired = props.adminReq;
   this.execute = props.execute;
   this.argsRequired = props.argsRequired;
   this.cooldown = props.cooldown;
 }
}

This was my first type in TypeScript! Before making this, my module.exports were really sloppy. In some places, my adminRequired property was called adminReq (those were places when adminReq was set to false, so it didn’t impact the Javascript). After making this, I decided to re-format all my commands so that they displayed all of these properties, regardless if they were relevant (I changed this very recently). After this initial change, I realized how much easier it was to write code using defined types because, for one, I always knew what everything was at all times, two, I could find properties without guessing what its name was, and three, it made debugging a lot easier since I could see type discrepancies and such. Overall, adding this opened my eyes a lot to the usefulness of hard typed languages.

I added one more property to the type definition to define my client. This one I found more challenging because I had to incorporate my prefix system into the type definition.

export class NoraClient extends Discord.Client {
 handler: any;
 prefixes: Discord.Collection<
   string,
   Discord.Collection<string, string | Command>
 >;
 constructor(props: Discord.ClientOptions) {
   super(props);
   this.prefixes = new Discord.Collection();
   const commandFolders: string[] = fs.readdirSync("./commands");
 
   console.log("Cooking up commands...");
   for (const folder of commandFolders) {
     const commands: Discord.Collection<
       string,
       any
     > = new Discord.Collection();
     const commandFiles: string[] = fs
       .readdirSync(`./commands/${folder}`)
       .filter(file => file.endsWith(".ts"));
     for (const file of commandFiles) {
       const command: any = require(`./commands/${folder}/${file}`);
       commands.set(command.name, command);
     }
     this.prefixes.set(commandCfg[folder].prefix, commands);
     commands.set("name", commandCfg[folder].prefix);
   }
   console.log("Sending client off to college...");
 }
}

As per usual, after coding it in, I found easy and straightforward.

Undefined…what?

Another thing that I had to adapt to in TypeScript was the fact that I had to strictly deal with the possibility of an undefined type. In Typescript, I had to say beforehand whether or not a variable could be undefined, and I had to subsequently deal with it. This wasn’t too hard, but it took me a while adjusting to the extra code I had to write for things that were pretty simple in Javascript. Some really simple examples are as such:

for (const prefixes of client.prefixes) {
if (message.content.startsWith(prefixes[1].get("name") + prefix)) {
   commandType = prefixes[1].get("name") as string;
}
}
 
if (isUndefined(commandType)) return;
const _commandName: string | undefined = args.shift();
if (!_commandName) return;
const commandName: string = _commandName.toLowerCase();
 
const possibleCommands:
| Discord.Collection<string, string | Command>
| undefined = client.prefixes.get(commandType);
if (isUndefined(possibleCommands)) return;

Not that impressive now that I look at it, though I was pretty impressed. Keep in mind I was coming from Python and Javascript.

Configurations too!?

Seems like TypeScript followed me everywhere, including in my JSON configuration files. When I imported my JSON files into TypeScript, it wanted to know exactly how it would come in, meaning that I had to refactor my configuration files so that they were uniform. I used to have the delete-timer for messages stored in the commands.json file since it was more related to commands than it was to config. But I had to change that since I needed to make an accurate declaration in TypeScript, which became much easier after that.

 
export const commandCfg: {
 
 
 [index: string]: { prefix: string; group: string };
 
} = commandConfig;

What am I doing now?

I didn’t stop there, I continue to learn more about programming, and my abilities as I continue to work on this bot for fun. As of late, I have made some fundamental changes and discoveries. I’ll go over them briefly below:

Streamlining my command handler

As I was adding voice support, I found that even after providing type definitions, my command code was still very sloppy, and was also repetitive.

For voice, I was checking in every command whether the server had been added to my voice registry (it stores audio queues and dispatchers), and whether or not the user was in a voice channel.

This was pretty easy to centralize: I simply added a new property to my Command class for voiceRequired and went through all my commands in the voice module updating them. That’s when I started to notice that the properties I was supplying were inconsistent between commands. Some commands I supplied adminReq or cooldown, and others I didn’t. I thought that it was probably really confusing for anyone new looking at my code. They wouldn’t know where these properties were coming from, and where they were going.

I decided to first, add all properties of commands to each command. So all my commands, now regardless of relevancy, has the properties ‘name’, ‘description’, ‘aliases’, ‘usage’, ‘guildOnly’, ‘adminRequired’, ‘argsRequired’, ‘voiceRequired’, and ‘cooldown’. Now it was probably easier to figure out the commonality between all the files, and the fact that they are all commands.

But then I started to feel like it was redundant. Why would I list off so many useless properties if they’re completely irrelevant to the command? I wanted to make commands as streamlined and simple as possible, with as many scenarios as possible handled by my command handler. At the same time, I wanted it to be easily recognizable that these were commands.

What I first realized was that my module.exports had no class type on it. Its type was just the object with the command properties and execute function inside of it. So I refactored my code so that commands were now defined like this:

module.exports = new Command({...});

The ... replaced with the attributes. That solved the issue of identification, but in doing so, it now forced me to supply all the attributes when making a command. After some tinkering with my types, I realized that an optimal way of cutting down on redundant fields was to make the default to false or 0. I updated my Command class so that certain fields would default to a value.

name,
description,
aliases,
usage,
guildOnly = false,
adminRequired = false,
argsRequired = false,
voiceRequired = false,
cooldown = 0,
execute = (message: Discord.Message, args: string[] = []) => void {}

Now, unless the command needed to be guildOnly or needed arguments, you didn’t have to specify the property. Also, note that arguments are not parsed if argsRequired is not true, so that’s why I set args to default at [] instead of not existing (which meant you’d have to deal with undefined…ew).

So all-in-all, I was able to accomplish both my goals: Create a simple and streamlined interface for creating commands with as much being handled by the command handler as possible, while remaining identifiable to the reader.

Misc. Stuff

In terms of other changes, I haven’t done much else. The most notable change I made was moving my type definitions from my index.ts file to a separate folder called /types. This made my index.ts a lot easier to read and interpret, along with the classes.

Conclusion

So here I’m just going to list off some of the stuff I’ve learned from this project. - Type definitions can never hurt - Never repeat code. Ever. - Comment your code! Or else it’s going to be hard to write a blog post about it months later. - Always think about the developer experience along with the user experience. It’s possible to make both great. - Always try to streamline interfaces; in the end, development will be much faster and it can be easier to track down errors. - Some other small stuff that I now use on a day to day basis.