Sep 18, 2022Discord bot tutorialLearn to build a reactions bot for your discord server.
Winnie Zhang βΈ± 7 min read

You may see in some Discord servers that there are bots that do some funky tasks! These tasks range from:

  • Playing music (e.g. Rythm, Groovy, Chip),
  • Assigning server roles (e.g. Carl-bot, YAGPDB),
  • Study help (e.g. Pomomo the Pomodoro Timer Bot), or
  • Being a minigame (e.g. Tatsu where you build your own house and have pets).

There is a wide variety of bots out there and there is a lot more that can be done!

But have you ever wondered how to make a Discord bot? Well, you have stumbled on the right place! Here, we are going to step through a simple starter project that will familiarise you with the Discord coding library and making a bot for your own server.

New tools to add to your Coding Toolkit:

  • Typescript,
  • Node,
  • Discord.js library, and
  • Understanding Discord more and its server permissions.

Project

Goal: To make a bot that accepts slash commands and returns an emoji.

We will be creating a Discord bot for your server that handles a slash command /react. This command replies with an emoji according to a requested name.

Emojis and their respective names are as following:

{
"happy": πŸ˜„,
"sad": πŸ˜”,
"angry":😠,
"wink": πŸ˜‰,
"thinking": πŸ€”
}

Constraints

Only one name is requested each time (don’t need to handle multiple options). When the requested name is not one of the five names above, reply with an appropriate error messages.

Example interaction:

User: /react wink

Bot: πŸ˜‰

User: /react random

Bot: Sorry, I don't understand.

Setting Up Your Coding Space

A step 0 that I use to organise the file structure of a discord bot project is linked below: https://sabe.io/tutorials/how-to-build-discord-bot-typescript

I recommend going through this first before embarking onwards, as I will be assuming it was followed for the rest of this article.

As part of the set up too, it would be good add a bit of security to the bot, especially if would like to put your code on GitHub publicly. Every Discord Bot is given a unique token (a security authentication code). If you share your token (such as having it appear publicly on GitHub), Discord will reset the token, and you will need to change your code again.

To avoid this, you can create a .env file that can store your token. This will be a local file that does not get pushed to the repository!

This is done by running

$ yarn add dotenv

Create a file called .env and put: TOKEN=....

Where your bot's token replaces ….

Then put

Bot.ts
import "dotenv/config";

at the top of the Bot.ts file (if you followed the above sabe.io tutorial). This will add in the correct .env file to use, where you would use the line in:

Bot.ts
const token = process.env.TOKEN; // access value in .env like this

Jumping into the Project

Now for the fun! I recommend trying to give the project task a go on your own, then looking back here for the step through if needed.

So firstly, which files might we want to change? We would want to add a new file in the commands folder, because that holds all the available slash commands.

Let us create one called Emoji.ts.

Let us copy over the Hello.ts contents and rename the sections accordingly:

Emoji.ts
  name: "react",
  description: "Returns an emoji",
  options: [
    {
      name: "emotion",
      description: "The emotion of the emoji",
      required: true,
      type: Constants.ApplicationCommandOptionTypes.STRING,
    }
  ],

But this time, our slash command takes in input from the user, and this input will need to be observed to know what the bot should respond.

So, underneath that, we will add a function with the type async, which means that it will create and wait for a request to fetch what the user has inputted in their slash command. Let us start off like this:

Emoji.ts
run: async (client: Client, interaction: BaseCommandInteraction) => {
    const inputEmotion = interaction.options.get("emotion")!.value;
    // …
}

Here, we have put this function as part of the whole slash command object. We want to put the user/client who sent that message as a parameter, as well as the interaction type we saw in interactionCreate.ts. This type handles slash commands.

Then we create the variable inputEmotion and set it to the user’s input. The interaction.options.get("emotion")!.value; is built into the Discord library (the Discord library has so many helpful functions which is very powerful). This takes the interaction and gets the user’s input. Since we wrote above in the options part of the file that the name was β€œemotion”, we use the name again here.

Now that we have the input, we can do some classic coding logic to tell our bot how to reply to our user.

Let us set the default message to be the error message, then run through if statements to decide the right response. Here is an example below:

Emoji.ts
run: async (client: Client, interaction: BaseCommandInteraction) => {
    const inputEmotion = interaction.options.get("emotion")!.value;
    let content = "Sorry, I don't understand.";

    if (interaction.channelId !== "964428440675684395") {
      content = "Can only use in `react` channel of `not engsoc` server.";
    } else if (inputEmotion === "happy") {
      content = "πŸ˜„";
    } else if (inputEmotion === "sad") {
      content = "πŸ˜”";
    } else if (inputEmotion === "angry") {
      content = "😠";
    } else if (inputEmotion === "wink") {
      content = "πŸ˜‰";
    } else if (inputEmotion === "thinking") {
      content = "πŸ€”";
    }
}

Now we just need to reply to the user! This is done by adding a section below:

Emoji.ts
run: async (client: Client, interaction: BaseCommandInteraction) => {
    const inputEmotion = interaction.options.get("emotion")!.value;
    let content = "Sorry, I don't understand.";

    if (inputEmotion === "happy") {
      content = "πŸ˜„";
    } else if (inputEmotion === "sad") {
      content = "πŸ˜”";
    } else if (inputEmotion === "angry") {
      content = "😠";
    } else if (inputEmotion === "wink") {
      content = "πŸ˜‰";
    } else if (inputEmotion === "thinking") {
      content = "πŸ€”";
    }
    await interaction.followUp({
      ephemeral: true,
      content
    });
}

The await… section replies the content to the user. As you can see in the curly brackets, content, which holds the reply, is within that return value object.

And that is all the nitty gritty of writing the functionality. But there is one last thing to do!

You may have noticed in the tutorial that we created the file Commands.ts. This is actually how we help make things more scalable. As soon as me make a new command, we will need to import and add it to the list of commands. This is seen below:

Commands.ts
import { Command } from "./Command";
import { Emoji } from "./commands/Emoji";
import { Hello } from "./commands/Hello";


export const Commands: Command[] = [Hello, Emoji];

And that is it for the project! Make sure to have a go testing and playing around with your bot :)

Extension activities

Here are some fun things to look at further if you want to explore the Discord Bot sphere more:

  • Instead of returning an emoji in the above project, return GIFs instead. You may use external APIs like GIPHY
  • You may have noticed that you need to run the commands npm run dev every time so that the bot is up and running. To have the bot always be online, you can try deploying it, and this tutorial might help: https://sabe.io/tutorials/how-to-deploy-node-app-docker
  • You might want to explore the GUILDS setting - this just makes it a bit more scalable as it makes it easier for your bot to keep track of multiple servers