Skip to content
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.


Amaranth is a Discord bot written Crystal using discordcr and discordcr-middleware.

Anything in src/amaranth could get moved to an external repo at any time.


Amaranth now supports plugins and namespaced commands! Available commands are:

Group Commands:

bot: rename
carc: eval, langs
chan: rename
dota: latest
github: add, list, remove
patches: check, disable, enable, games, gids, list, list_all, stop_all
rust: crate?, crates, eval, versions

Regular Commands:

clear, commands, echo, help

So to run a command, use <group>.<command> or <command>.

Writing a Plugin and Commands

Command Creation

Commands have several initialization methods. You must specify a name and desc, but min_args, max_args and permissions are optional.

  • name and desc are of type String.
  • desc shows up when a user types help <command>.
  • permissions are of the type Discord::Permissions.

Example description and output:

desc = <<-DESC
Gets latest dota match.
Usage: dota.latest [dotaID] where ID is optional.
You can save your ID using dota.add. You can find your ID at

Sample command help.

Initialization Methods

alias ExecType = Proc(Array(String), Discord::Context, CommandReturn)
def initialize(@name, @desc, &@exec : ExecType)
def initialize(@name, @desc, @permissions, &@exec : ExecType)
def initialize(@name, @desc, @min_args : Int32, &@exec : ExecType)
def initialize(@name, @desc, @min_args : Int32, @max_args : Int32, &@exec : ExecType)
def initialize(@name, @desc, @min_args : Int32, @max_args : Int32, @permissions : Discord::Permissions, &@exec : ExecType)
desc = <<-DESC
This command is an example command.
Usage: name
command "name", desc, do |args, context|
  # etc
perms = Discord::Permissions.flags(ReadMessages, SendMessages, KickPeople)
command "example", desc, 1, 2, perms do |args, context|

PreCommand Creation

PreCommands are a limited version of Commands:

  • They are not registered (unable to run manually via Discord)
  • They run before every command
  • They currently take a return type of Discord::Message | Nil
  • They do not take args
  • They do not take a description
  • They currently cannot be namespaced.
pre_command do |context|
  # something to be run before any command

PreCommands are new to Amaranth so they are prone to change.

Making the plugin

The plugin class needs to inherit from Plugin.

class ExamplePlugin < Plugin::Plugin

To make a grouped command that says Hello, world!:

class ExamplePlugin < Plugin::Plugin
  group "example" do
    desc = "My description"
    command "hello", desc do |args, context|
      "Hello, world!"

# this registers example.hello as a command!

To make a command without a group, omit group. However, make sure there aren't multiple commands with the same name.

class ExamplePlugin < Plugin::Plugin
  desc = "My description"
  command "hello", desc do |args, context|
    "Hello, world!"

# this registers hello as a command!

Command return types

Amaranth allows you to manually run context.client.create_message() or use implicit returns and automatically handle it for you.

Implicit Return Types:

  • String: Amaranth will run context.client.create_message(, <string>)
  • Discord::Embed: Amaranth will run context.client.create_message(, "", <embed>)
  • NamedTuple(msg: String, embed: Discord::Embed): Amaranth will run context.client.create_message(, <string>, <embed>)
  • Discord::Message: No message will be created.
  • Discord::Channel: No message will be created.
  • Discord::Guild: No message will be created.

Return types like Discord::Channel get returned from context.client commands and thus do not warrant a message.

Note: Returning an empty string ("") also causes no message to be created.

Saving data in a config

To get access to helper methods for saving a config, you need two things:

  1. Create a config class with a MessagePack mapping.
  2. Extend your plugin class with the config type of your Config.
class ExampleConfig
    ids: Array(UInt64),
    channels: Array(UInt64)

class ExamplePlugin < Plugin::Plugin
  extend Plugin::Config(ExampleConfig)

Now you get access to two methods:

  1. get_config(name : String) which returns an existing config or creates a new one
  2. save_config(name : String, config : ConfigType) which saves your config
class ExampleConfig
    ids: Array(UInt64),
    channels: Array(UInt64)

class ExamplePlugin < Plugin::Plugin
  extend Plugin::Config(ExampleConfig)

  group "example" do
    command "list" do |args, context|
      config = get_config("example")
      config.channels.each do |channel|
        context.client.create_message(channel, "Hello!")

Running your Plugin

You've created a plugin. Great! Now, how do you run it?

Amaranth runs on discordcr-middleware, so what you'll want to do is create a stack.

module ExampleBot
  # in src/, etc
  class_property client =
  class_property cache =
  @@client.cache = @@cache


PluginHandler can take an array of an arbitary amount of plugins. Keep in mind to namespace your commands to avoid overwriting issues with commands of the same name.

Recommended reading for help


  • Improve on PreCommands as a concept
  • Improve min_args, max_args checking
  • Implement a help property for each command, so one can run help command to get more information.


Interested in development? Message @ Andrei#8263 (91329651909074944) on Discord.


  1. Fork it ( )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request


  • azah Andrew Zah - creator, maintainer
You can’t perform that action at this time.