nosedrum v0.1.0 Nosedrum

nosedrum is a command framework for use with the excellent nostrum library.

It contains behaviour specifications for easily implementing command handling in your bot along with other conveniences to ease creating an interactive bot.

nosedrums provided implementations are largely based off what was originally written for bolt. bolt also contains around 57 commands based off the Nosedrum.Command behaviour that you can explore if you're looking for inspiration.

The core of the framework consists of three parts:

  • Nosedrum.Command, the behaviour that all commands must implement.
  • Nosedrum.Invoker, the behaviour of command processors. Command processors take a message, look it up in the provided storage implementation, and invoke commands as required. nosedrum ships with an implementation of this based on bolt's original command parser named Nosedrum.Invoker.Split.
  • Nosedrum.Storage, the behaviour of command storages. Command storages allow for fast and simple lookups of commands and command groups and store command names along with their corresponding Nosedrum.Command implementations internally. An ETS-based command storage implementation is provided with Nosedrum.Storage.ETS.

Installation

Simply add :nosedrum to your mix.exs file:

def deps do
  [
    {:nostrum, github: "Kraigie/nostrum"},
    {:nosedrum, "~> 0.1"}
  ]
end

If mix complains due to conflicting versions, you will need to specify override: true in your dependencies:

def deps do
  [
    {:nostrum, github: "Kraigie/nostrum", override: true},
    {:nosedrum, "~> 0.1"}
  ]
end

Note that nosedrum requires at least version 0.3.0 of nostrum.

Getting started

To start off, your commands need to implement the Nosedrum.Command behaviour. As a simple example, let's reimplement ed.

defmodule MyBot.Cogs.Ed do
  @behaviour Nosedrum.Command

  alias Nostrum.Api

  @impl true
  def usage, do: ["ed [-GVhs] [-p string] [file]"]

  @impl true
  def description, do: "Ed is the standard text editor."

  @impl true
  def predicates, do: []

  @impl true
  def command(msg, _args) do
    {:ok, _msg} = Api.create_message(msg.channel_id, "?")
  end
end

With your commands defined, choose a Nosedrum.Storage implementation and add it to your application callback. We will use the included Nosedrum.Storage.ETS implementation here, but feel free to write your own:

defmodule MyBot.Application do
  use Application

  def start(_type, _args) do
    children = [
      Nosedrum.Storage.ETS,
      MyBot.Consumer
    ]
    options = [strategy: :one_for_one, name: MyBot.Supervisor]
    Supervisor.start_link(children, options)
  end
end

Finally, we hook things up in our consumer: we will load commands once the bot is ready, and invoke the command invoker on each message.

defmodule MyBot.Consumer do
  alias Nosedrum.Invoker.Split, as: CommandInvoker
  alias Nosedrum.Storage.ETS, as: CommandStorage
  use Nostrum.Consumer

  @commands %{
    "ed" => MyBot.Cogs.Ed
  }

  def handle_event({:READY, {_data}, _ws_state}) do
    Enum.each(@commands, fn {name, cog} -> CommandStorage.add_command({name}, cog) end)
  end

  def handle_event({:MESSAGE_CREATE, {msg}, _ws_state}) do
    CommandInvoker.handle_message(msg, CommandStorage)
  end

  def handle_event(_data), do: :ok
end

That's all we need to get started with. If you want to customize your bot's prefix, set the nosedrum.prefix configuration variable:

config :nosedrum,
  prefix: System.get_env("BOT_PREFIX") || "."

If no value is configured, the default prefix used depends on the chosen command invoker implementation. Nosedrum.Invoker.Split defaults to ..