Making Discord Bots for the Professional World
David Crawford / October 11, 2021
Haven’t heard of Discord? It’s the de facto communication tool of the next generation, especially for video games, a myriad of growing communities. For those reasons, when I used to work for Bravo LT, we decided to use it for the business as well.
We had a desire to have a place for our employees to continue internal team conversations, but also be able to have open-mic sessions or easy video calls. Additionally, we wanted a way to retain non-employee contacts and grow a developer community. When we facilitated a code camp, what better way to keep the energy afterwards than by having a place to keep the conversation going? If there are developers that have had a positive impact on the community, and we want to keep them in the loop, Discord is the way. So now that you know what Discord is doing, what’s a Discord Bot?
A Discord Bot is a custom application that interacts with Discord users through chat commands. They’re typically made to automate tasks, like server moderation, or easily and quickly provide information specific to an organization’s needs. The uses are virtually unlimited because they’re typically custom built from scratch.
In this simpler case, I wanted to help make a few common tasks fully automated:
- The ability to quickly see what job postings were available at Bravo LT (and link to the job description pdf)
- Cosmetic role moderation needed to be user manageable
- We wanted the bot to produce a random dog gif on command
In order to accomplish this, I had to choose a tech stack, and a server for where the bot could live. Discord doesn’t provide a one-stop-shop for your code. However, they do provide a great API, and easy GUI for bot creation and management.
Tech Stack
Discord’s API works with most major languages, as long as there’s a community library for it! I like to rely on more official things, so I chose Discord.js, their Javascript-based API. But I also wanted to use Typescript, for various reasons, though mostly because I wanted static typing and interfaces. Also, Typescript is cooler.
I chose Heroku at the time as the test server to run this off, because it was free and simple, and you could use the worker command so that it runs 24/7. But anything that can host a NodeJs application will do.
The Setup
I’m not going to go super in-depth about this project in this post, as there are a lot of examples out there for Discord Bot creation, and there’s my project as well which is in a public repository that you can play around with. So for this post, I just wanted to go over some major elements of how this works.
- My bot’s private API key (provided by Discord whenever you create a bot) is in an .env file that’s naturally not checked into source control. I have an .env.example file for you to look at to see the pattern. When published to Heroku, you can add your token on your app’s settings page:
- The tsconfig.json file is set up to convert the Typescript source code into Javascript. Notice the exclusion list. I excluded tests, test coverage outputs, and my prettier config. You may find that some other files you work with are problematic, and you likely don’t want/need them converted.
- The main index.ts file is the starting point of everything, and logs the app into Discord’s bot service, making it “online” for whatever server it’s on. This file is simple, and after getting the API setup, has listeners for some basic Discord server events.
- The message event is the most important part of the bot interaction we’re wanting. This looks for any messages sent in a given server, like below:
- This message object contains a lot of information, not just the raw text. So we have everything we need to do a lot of crazy stuff with it. After the commandHandler is invoked, it does a few quick checks, like if the message starts with a bot command prefix. If we don't check for the prefix, the Bot will be trying to decipher way too many messages. In Tori's message above, the Bot would ignore the message, because it doesn't start with an exclamation point.
// Determines whether or not a message is a user command
private isCommand(message: Message): boolean {
return message.content.startsWith(this.prefix);
}
- We wanted to be able to add more commands easily, so whenever I made a new command class, I could just run my index generator (the commented code, more about that pattern here) in VS Code and it would automatically add the imports to a command index.ts file:
//@index(['./*.ts(x)?','!**/*.*.*'], f => `export * from '${f.path}';`)
export * from './commandInterface';
export * from './greetCommand';
export * from './helpCommand';
export * from './jobsCommand';
export * from './puppyCommand';
export * from './rolesCommand';
export * from './windowCommand';
- The commandHandler looks through all of our command classes, and instantiates whichever command matches the commandNames array. So in the case of our jobs command, this logic is run:
- The getJobs function essentially scrapes Bravo LT’s job openings site, and grabs all the titles and their pdf descriptions into a nice, formatted string. This message is then sent by the bot itself:
Takeaway
We can do a lot with the setup I’ve briefly gone through. Bots can react to messages (see the role command I made in the repo), send audio/video in voice channels, or whatever you want!
You can check out my repository for this bot here. Take note of the unit tests, and how easy it is to mock various Discord services! Unit testing has been a breeze for this project.
Subscribe to the newsletter
Get emails from me about web development, tech, and early access to new articles.