Writing a Discord Bot in Ruby using DiscordRB

Sun 30, Aug 2020

This is Part 1 of a series about writing a Discord bot in Ruby using DiscordRB.

Update: The source code of the part can be found on GitHub!

Hi!

If you've been on Discord for quite some time, you've probably noticed how important Discord bots are (in case you haven't: trust me, they really are!)

Unfortunately, many tutorials on Discord bots only show you how to create the very minimum required for your bot to answer with "Pong!" when someone types "!ping".

I wanna go a little further with this tutorial and show you how to create an actually useful Discord bot in Ruby using DiscordRB.

Please note that this tutorial expects you to already have some knowledge about Ruby. You don't need to be an expert, but you should have at least some understanding of the language itself!


Registering your bot

Before we can actually start coding, we need to tell Discord about our bot. Navigate to Discord's Developer Portal and click on New Application, then enter your app's name. Note that this name can be, but does not have to be, your bot's username!

After your application has been successfully created, go to the Bot tab and click on Add Bot to create the actual bot user. In order for your bot to login to Discord using the bot account, you'll need its token. Click on Copy in the Token section. Make sure to keep that token private! Anyone can login with your bot account if you accidentally show it to someone!

Setting up the project

Now, that we have our bot user account, we can start to create our project's files and directories! Create a new project directory where you'll save your bot's files.

In that directory, we'll need to create a basic file structure:

The file structure

Now that's a lot of files and directories for just a simple hello, world! project. However, this is a really good starting point from which you can grow your bot easily!

Let's take a look at what we have:

This architecture as well as src/bot.rb are taken from z64's Gemstone template for DiscordRB bots

So, now that all of our files and directories have been set up, let's start writing something into those files! Let's start with our Gemfile: we'll define a single dependency for now, discordrb:

gem 'discordrb'

That's all there is to that file for now. Run bundle install to install our one, single dependecy (this requires Bundler; it should be packaged by default with your Ruby installation. If it is not, install it!)

As I mentioned earlier, run.rb will only be a shortcut to src/bot.rb. So the only thing we need to write into run.rb is this one, single line:

require_relative 'src/bot'

Easy, eh?

The next file we have to edit is src/bot.rb:

require 'discordrb'
require 'ostruct'
require 'yaml'

module Bot
    if File.file? 'data/config.yaml'
        CONFIG = OpenStruct.new YAML.load_file 'data/config.yaml'
    else
        puts 'Can\'t load config file!'
        exit!
    end

    BOT = Discordrb::Commands::CommandBot.new(client_id: CONFIG.client_id,
                                            token: CONFIG.token,
                                            prefix: CONFIG.prefix)

    # Integrations
    module Integrations; end
    Dir['src/integrations/*.rb'].each { |mod| load mod }

    # Discord Commands
    module DiscordCommands; end
    Dir['src/modules/commands/*.rb'].each { |mod| load mod }
    DiscordCommands.constants.each do |mod|
        BOT.include! DiscordCommands.const_get mod
    end

    # Discord Events
    module DiscordEvents; end
    Dir['src/modules/events/*.rb'].each { |mod| load mod }
    DiscordEvents.constants.each do |mod|
        BOT.include! DiscordEvents.const_get mod
    end

    BOT.run
end

Phew, so that's quite a bit more code. Let's go throught this snippet-by-snippet:

require 'discordrb'
require 'ostruct'
require 'yaml'

This imports all our dependencies. Note that ostruct and yaml come pre-installed, so no need to add them to our Gemfile.

module Bot

Here we define our Bot module. This module will contain all our bot's logic.

    if File.file? 'data/config.yaml'
        CONFIG = OpenStruct.new YAML.load_file 'data/config.yaml'
    else
        puts 'Can\'t load config file!'
        exit!
    end

    BOT = Discordrb::Commands::CommandBot.new(client_id: CONFIG.client_id,
                                            token: CONFIG.token,
                                            prefix: CONFIG.prefix)

This is where we load our configuration file (data/config.yaml) and initiate the bot. We check whether the config file exists. If it does, we load our config using OpenStruct (ostruct). If it does not, we simply exit with an error message.

The last 3 lines are where we create our CommandBot instance.

    # Integrations
    module Integrations; end
    Dir['src/integrations/*.rb'].each { |mod| load mod }

    # Discord Commands
    module DiscordCommands; end
    Dir['src/modules/commands/*.rb'].each { |mod| load mod }
    DiscordCommands.constants.each do |mod|
        BOT.include! DiscordCommands.const_get mod
    end

    # Discord Events
    module DiscordEvents; end
    Dir['src/modules/events/*.rb'].each { |mod| load mod }
    DiscordEvents.constants.each do |mod|
        BOT.include! DiscordEvents.const_get mod
    end

Here we load and register all of our commands, event handlers and integrations. Think of it as an autoloading mechanism.

All that's left to do to see our bot in action for the very first time is fill in the config file and create our first command, ping.

Here's the config file, data/config.yaml:

client_id: 
token: ''
prefix: '?'

You'll need to put in your bot user's client ID and token here. Simply copy&paste them from Discord's Developer Portal (the client ID is on the General tab, the token is on the Bot tab, simply click on the Copy button right under Token to copy it).

If you did it right, your config file should look something like this now:

client_id: 000000000000000000
token: 'YMNI%fAQ$vRwpEsTouFiJaJV0YoERrbkeuErhTCSFYOqcligcF8D3X9JKSD'
prefix: '?'

(Note that the credentials in the above example are not real)

Creating our first command

Finally, we have everything set up and can start writing our actual, own code! Open up src/modules/commands/ping.rb and put the following contents into it:

module Bot
    module DiscordCommands
        module Ping extend Discordrb::Commands::CommandContainer
            command :ping do |event|
                'Pong!'
            end
        end
    end
end

The first 3 lines only tell Ruby that we want our code to be placed into modules. However, on the 4th line, we define the actual command:

command :ping do |event|
    'Pong!'
 end

This tells DiscordRB that we add a command called ping and all it does is respond with Pong!.

If you now start up your bot by executing ruby run.rb in your project's root directory, you should get a message similar to this one:

[INFO : websocket @ 2020-08-30 19:02:19.378] Discord using gateway protocol version: 6, requested: 6

Congratulations, your bot works! 🥳

Now, how do you get it to join your Discord guild, though? That's where event handlers come in handy!

Events and inviting the bot to your guild

Create a new file at src/modules/events/online.rb. In this file, we'll define what our bot will do when it connects to the Discord API.

module Bot
    module DiscordEvents
        module Online extend Discordrb::EventContainer
            ready do |event|
                puts "Logged in as #{event.bot.profile.distinct}"
                puts "Invite URL: #{event.bot.invite_url}"
            end
        end
    end
end

This will print your bot's username and tag as well as a full invite URL to the console. So, if you now run ruby run.rb again, you should see a Discord invite URL in your console. Open it in your browser, select your guild and boom, your bot joins it!

Now you can finally test it out: type ?ping into a channel that the bot user can read and write to. If the permissions are set up correctly, it will respond with Pong!. If not, you'll see an error telling you what went wrong in the console. Try adjusting the bot's permissions if your permissions weren't setup correctly.


This was Part 1 of what will be a series on creating a Discord bot with DiscordRB. Thanks for reading, I hope you enjoyed this tutorial so far! Stay tuned by subscribing to my blog's feed (RSS|Atom|JSON).

You are on page 1 right now.


Back to top