Skip to content

Commands

Matthew Carey edited this page Nov 15, 2020 · 1 revision

discordrb has a system specifically to create bots that use commands. It provides advanced functionality such as command chaining, command nesting, and permissions.

Creating a command bot

You create a command bot almost the same way as a regular bot:

require 'discordrb'

bot = Discordrb::Commands::CommandBot.new token: '<token here>', client_id: 168123456789123456, prefix: '!'

As you can see, you instantiate Discordrb::Commands::CommandBot instead of Discordrb::Bot. Also, there's a third argument: the prefix, which is the string that has to be in front of a message for discordrb to recognize it as a command.

Attributes

There are further attributes available in the initializer that set some behaviours specific to command bots. See the documentation for information on those.

Defining commands

Now that you've made a command bot, you can start defining commands:

bot.command :random do |event, min, max|
  rand(min.to_i .. max.to_i)
end

This is a simple command that generates a random number between two other numbers. The command name is specified as a symbol sent as an argument to bot.command. The block afterwards represents the actual functionality. In this case, the command takes two arguments, min and max, and it returns a random number between those. Whatever you return from the block will be sent to the channel, so if you don't want to send anything, make sure to return nil explicitly.

Command attributes

Commands can also have attributes:

bot.command(:region, chain_usable: false, description: "Gets the region the server is stationed in.", permission_level: 1) do |event|
  event.server.region
end

Attributes are passed as a hash to bot.command, after the name. See the documentation for available attributes.

Permissions

You can set commands to only be usable by people with a certain permission level. Permission levels are defined by roles or users. Use bot.set_user_permission(id, level) or bot.set_role_permission(id, level) to set the permission level for a certain user or role. If multiple levels are available for one user (e.g. if they are in a role for which a level has been defined, but a level has also been defined for them specifically) the highest will be used.

Rate limiting

You can easily make commands only be usable every N seconds, or only K times every M seconds, using built-in rate limiting functionality. First, define a Bucket using the bucket method:

bot.bucket :memes, limit: 3, time_span: 60, delay: 10

This will create a new rate limiting bucket that allows associated commands to be executed at most 3 times every 60 seconds, and with a hard limit of 10 seconds between each usage. Then, add a command for this bucket:

bot.command(:fug, bucket: :memes) do |event|
  event.channel.send_file File.new(['rayquaza1.png', 'rayquaza2.png', 'rayquaza3.png'].sample)
end

You can also define a custom message for when a command gets rate limited using the rate_limit_message attribute:

bot.command(:fug, bucket: :memes, rate_limit_message: 'Calm down for %time% more seconds!') do |event|
  event.channel.send_file File.new(['rayquaza1.png', 'rayquaza2.png', 'rayquaza3.png'].sample)
end

As you may have guessed, %time% gets replaced with the remaining time in seconds until the command can be used again.

Note that the rate limits are per-user. If you want finer control over what's rate limited, or want to do rate limiting for events, use the SimpleRateLimiter class:

rate_limiter = Discordrb::Commands::SimpleRateLimiter.new
rate_limiter.bucket :example, delay: 5  # 5 seconds between each execution
bot.message(containing: ['(╯°□°)╯︵ ┻━┻', '(ノಥ益ಥ)ノ ┻━┻', '(ノಠ益ಠ)ノ彡┻━┻']) do |event|
  next if rate_limiter.rate_limited?(:example, event.channel)
  event.respond '┬─┬ノ( º _ ºノ)'
end

The rate_limited? method allows using anything that resolves to an ID (i. e. any data class or Integer) or a symbol as the thing to be rate limited.

*args in a command specification

Ruby allows you to use the splat operator (*) before a parameter in a block to greedily grab all arguments that aren't already covered by others. This is useful when making a command that takes a text as an argument that might also contain spaces, but you don't want to have to use quotes:

bot.command :replace do |event, str1, str2, *text|
  # Replace every occurrence of str1 in text with str2 and return the result.
  # Text is an array so we will have to join it first:
  text.join(' ').gsub str1, str2
end

If you call this command using !replace lemon meme I’m so glad that our lemon tree finally grew and sprouted fruitful lemony lemons. the text parameter will be the array ["I’m", "so", "glad", "that", "our", "lemon", "tree", "finally", "grew", "and", "sprouted", "fruitful", "lemony", "lemons."] and joining it will give the text back.

Command chain syntax

Starting with version 2.0.0, this functionality needs to be manually enabled because it was confusing to most users who didn't need it. Simply enable advanced_functionality: true in your initializer line.

The command chain syntax may be intimidating, but it's actually quite simple. I will demonstrate it using the three example commands bold, italic, and user, which make a message bold, make a message italic, and return the username that issued the command, respectively. The prefix for the examples will be !, and all other attributes will be left at the default (see above). I will designate the bot's replies with an arrow ->.

Simple command without arguments

The simplest possible command usage is just the prefix and a command, no arguments:

!user
-> meew0

Simple command with arguments

A command can have arguments:

!bold This message will become bold.
-> This message will become bold.

Simple command chain

Commands can be chained using >, which takes the previous command's results and appends them to the end of the next command's argument list:

!user > bold The user name is:
-> The user name is: meew0

You can also insert the previous result anywhere in the next argument list by using ~:

!user > bold The user name is: ~! Congratulations!
-> The user name is: meew0! Congratulations!

Note that you only have to use the prefix at the very beginning of the command chain, not for every command individually.

Nested commands

You can nest a command inside another using [ and ], which evaluates the command and replaces the nested command with its result:

!bold The user name is: [user]! Congratulations!
-> The user name is: meew0! Congratulations!

You can nest multiple commands inside one command chain, and you can nest nested chains inside each other:

!bold The user name is: [user]! [italic [user]!!!]
-> The user name is: meew0! meew0!!!

You can also nest command chains:

!bold The user name is: [user > italic]
-> The user name is: meew0

Chain arguments

Chain arguments are ways to modify command chains directly without the use of commands. Currently, there's only one such chain argument, repeat N, which repeats a chain N times. You can add a chain argument to a command chain by separating it using ::

!bold The user name, 10 times: [repeat 10: user]
-> The user name, 10 times: meew0meew0meew0meew0meew0meew0meew0meew0meew0meew0

Note that you cannot add chain arguments to the root chain, only to nested chains.

Quotes

You can quote something to make it invisible to the parser using double quotes:

!bold The user name is: "[user]"! Congratulations!
-> The user name is: [user]! Congratulations!

As you can see, the quotes get removed in the process. There is currently no way to escape quotes so you can use them verbatim, so in the meantime, just avoid using double quotes in your command chains where you don't need them (or make a command that generates them). Note that anything inside quotes counts as one argument, even if it contains spaces, so (assuming the existence of a command count_arguments):

!count_arguments 1 2 "3 4 5" 6
-> 4