license

Discord: Create Bots – How to in a few steps

Discord is undoubtedly one of the most popular chat platforms nowadays. Apart from excellent set of features provided by the application itself, it allows developers to make chatting experience even better by providing an API for creating bots! But what are bots?

What are "Discord Bots?"

Bots are special accounts meant to be automated by some specific set of instructions sent through the API. We can call them artificial companions of our chat. Therefore they can help moderate our server, act as entertainment tool or be the connection layer between external platform and your safe place to talk (e.g. CreatorsHub and RocksBot integration). The possibilities are truly infinite. Apart from ones mentioned earlier, there is a thought in my mind - how about adding Web3 stuff to Discord bot? Lets check it!

Creating the bot

Prerequisites:

Setting up the working directory

mkdir sample-discord-bot && cd sample-discord-bot && npm init -y

First of all, you have to create a project. For this you need to open the terminal and navigate to the desktop directory to run it.

Now, you should be in your newly created project with the package.json file present there. Further, open your editor (for Visual Studio Code users - execute code . in the same terminal).

Installing dependencies

You’ll need three things:

  • Discord.js library (superb library providing easy manipulation of Discord’s API in Node.js)
  • nodemon (an tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected)
  • dotenv (zero dependency module that loads .env file into process.env)

Again, open up a terminal and execute:

npm i discord.js dotenv && npm i nodemon -D

If everything went successful, from now on these two packages are installed in your project.

Adding auto restart to your bot

With the use of nodemon, we can set up automatic reload of our code. This excellent tool watches changes in your files and then performs the restart.

Simply add this code

"scripts": {
"dev": "nodemon index.js"
}

to the package.json.

Creating the application in Discord Developers Portal

You can't just create bot on your own. Rather you have to register an application on the Discord's side.

  1. Open the Discord developer portal and log in
  2. Create new application and choose a name for it
  3. Navigate to the Bot tab on the left pane of the site
  4. Click the Add Bot button and confirm your action

Great. Now your bot user is created and ready to take action. In addition there's just one more (and probably the most crucial!) thing to do - copy the bot token.

But what is 'bot token'?
It can simply be treated as a core password to your bot account. Someone with access to the token can do everything with it and its your responsibility to securely store this thing. The easiest solution is to load it as an environment variable.
Storing the token

In the following, copy the token from your bot's site button, create an .env file in your project and put it there. For example we'll call this variable DISCORD_TOKEN.

There should be an Copy Token, if you can't find them - look for Regenerate Token button instead, click it and then you shall see the token)

The file content should look like this:

DISCORD_TOKEN=<your-token>

<your-token> replaced with your actual token

NOTE: If you are going to use a version control system (e.g. git) remember to add .env file to .gitignore! This file should not be present on your repository in order to prevent any possible accidential leaks of the token.

Writing the code

When everything is set up, you can proceed to writing the actual bot. First, create a file named index.js in root of our project and paste the following code into it:

// Load variables from .env
require('dotenv').config()

const { Client, Intents, Permissions } = require("discord.js");

// Discord.js Client instance with specific intents
const client = new Client({
intents: [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MEMBERS,
],
});

// Executed once the bot has estabilished initial connection to the Discord's Gateway
client.once("ready", () => {
console.log("Ready!")
});

client.login(process.env.DISCORD_TOKEN);

Now, run your bot with npm run dev. It shall display online on the Discord and in console you shall be able to see "Ready!" message. You have finalized your code and it shall work from now, but there is still an open issue: it is not present on any server, so that you can't test any interaction with it.

The second step is to add the bot to one of the servers (via invite URL) where you have administrator permissions. Actually, the invite URL can be generated directly from the bot application!
Add this code to ready event callback:

const inviteUrl = client.generateInvite({
permissions: [
Permissions.FLAGS.SEND_MESSAGES,
Permissions.FLAGS.MANAGE_GUILD,
Permissions.FLAGS.MENTION_EVERYONE,
 ],
scopes: ["bot", "applications.commands"],
});
console.log(`Bot invite url: ${inviteUrl}`);

Then again open up a terminal, where you shall see an URL: Click on it and invite the bot to one of your servers.

NOTE: You can create separate Discord server for testing the bot and invite it there. It is an pretty common practice among bot developers

After successful invite, the artificial user should be visible on the active members list on the default channel.

There is still no movement, so you must add some interactions in the next step.

Bot interactions and chat messages

The ability to create commands has changed from simple string manipulation (bot fetches the user message and checks if it starts with a certain prefix).

The Discord Team introducted marvellous interactions API. These are special events (with several user experience benefits on the regular user side) that allows us, creators of bots, to interact with the chat and the users in a modern, standardized and easy way.

Starting with implementation of interactions

To start the implementation, you'll need to install additional 3 packages:

  • @discordjs/builders - a set of very useful builders for things like message embeds or slash commands.
  • @discordjs/rest -  an utils for interacting with the Discord REST API. This will be primarly used for registering the slash commands in our application.
  • discord-api-types - for types of the API.

Open up a terminal and paste:

npm install @discordjs/builders @discordjs/rest discord-api-types

If everything was installed correctly, you can go on with the implementation details!

Registering slash commands

Unlike regular user's message parsing, slash commands have to be registered on the Discord side. Just like the bot itself. You'll create a little helper script for that, using just installed packages and run it every time you add or remove any command.

NOTE: There is no need to re-run this script every time you modify your code. Just do it when you add or remove them!

In the next step, you have to create scripts directory and register-commands.js file in it. (it should look like scripts/register-commands.js)  with the following code

// Load variables from .env
require("dotenv").config();

const { SlashCommandBuilder } = require("@discordjs/builders");
const { REST } = require("@discordjs/rest");
const { Routes } = require("discord-api-types/v9");

// Rest client for interacting with Discord's REST API
const restClient = new REST({ version: "9" }).setToken(process.env.DISCORD_TOKEN);

// List of our commands
const commands = [
 new SlashCommandBuilder()
   .setName("ping")
   .setDescription("Replies with pong and current latency!"),
].map((command) => command.toJSON());

// Unfortunately, Node.js does not allow top-level
// await, so we have to wrap it around the async function
async function register() { 
 try {

// Register our commands on specific guild
   await restClient.put(
     Routes.applicationGuildCommands(
       process.env.DISCORD_CLIENT_ID,
       process.env.DISCORD_TEST_GUILD_ID
      ),
     { body: commands }
      );

  console.log(`Registered ${commands.length} commands.`)
 } catch (error) {
  console.error(error);
  }
 }

register()

You may have already noticed two new environment variables used in this code snippet, the DISCORD_CLIENT_ID and DISCORD_TEST_GUILD_ID - what are they?

Discord Client ID is nothing else than client/application ID of our bot in the developers portal. To obtain it, navigate to your application's page, click General Information on the left pane and look for Application ID somewhere in the page. There should also be a blue Copy ID button.
Discord test guild ID is neither anything complicated! Remember a little note about creating test guild in one of previous sections of the article? Thats just the ID of our server for testing the bot! We can get it by opening a context menu on the list (right mouse button click on the server) and clicking Copy ID.

NOTE: Before copying test server's ID, you have to enable Developer's Mode in your regular Discord application. Visit User Settings -> Advanced -> Developer mode

After retrieving these two things, your .env file should look like that:

DISCORD_TOKEN=<your-token>

DISCORD_TEST_GUILD_ID=<your-test-server-id>

DISCORD_CLIENT_ID=<your-application-id>

and for a better developer experience, you can add npm script executing our register-commands.js to your package.json

"scripts": {
 // [...]
"register-commands": "node scripts/register-commands.js"
}

The only thing that is left to do regarding registering the commands is executing your script! Simply run npm run register-commands, wait for it to finish running and navigate to your test server and type /ping somewhere in the chatbox. If an auto-completion popup is visible on the screen - everything's working!

Responding to the slash commands

You have already registered a /ping command but there is no handler for that interaction in your code. Now integrate the following code snippet, probably somewhere between ready event handler and client.login() in the global scope of index.js file (don't forget about the import!)

const {/* ... other imports */ MessageEmbed } = require('discord.js')
// [...]
// Listen to interaction events
client.on("interactionCreate", async (interaction) => { 
// Do nothing if interaction is not a command
 if (!interaction.isCommand()) {
    return;
 }
  const { commandName } = interaction;
// Check if it is command you want to process
 if (commandName === "ping") {
   await interaction.reply({
     embeds: [
     // Create embed message using MessageEmbed builder
     new MessageEmbed()
         .setTitle("🏓 Pong!")
         .setDescription(
           `${Math.abs(
             Date.now() - interaction.createdTimestamp
           )}ms latency between bot and the API`
         ),
     ],
     // Make reply to the command visible only to member who executed it
     ephemeral: true,
   });
 }
});
// [...]

Now, after restarting the bot (if nodemon is not running in one of the terminal sessions), navigating to your test server and tying /ping, there'll be beautiful response message from your bot!

Example use cases

In this article we've created really basic and simple Discord bot without any Web3 functionality or features based on blockchain technology, however there are some ideas about integrating these things together:

  • Claiming NFT as reward for Discord activity
  • Allow to send ERC20/NFTs using slash commands
  • Restrict access to certain channels for NFT owners

We'll certainly continue posting on this subject, you can look forward to future publishings.

No Comments Yet

Let us know what you think