alchemy v0.6.0 Alchemy.Cogs
This module provides quite a bit of sugar for registering commands.
To use the macros in this module, it must be used
. This also defines a
__using__
macro for that module, which will then allow these commands
to be loaded in the main application via use
Example Module
defmodule Example do
use Alchemy.Cogs
Cogs.def ping do
Cogs.say "pong"
end
Cogs.def echo do
Cogs.say "please give me a word to echo"
end
Cogs.def echo(word) do
Cogs.say word
end
end
This defines a basic Cog, that can now be loaded into our application via use
.
The command created from this module are “!ping”, and “!echo”,
(“!” is merely the default prefix, it could be anything from “?”, to “SHARKNADO”).
The ping
command is straight forward, but as you can see, the echo
command
takes in an argument. When you define a command, the handler will
try and get arguments up to the max arity of that command;
in this case, echo
has a max arity of one, so the parser will pass up to
one argument to the function. In the case that the parser can’t get enough
arguments, it will pass a lower amount. We explicitly handle this case
here, in this case sending a useful error message back.
Shared names across multiple modules
If I define a command ping
in module A
, and a ping
in module B
,
which ping
should become the command? In general, you should avoid doing
this, but the module used last will override previously loaded commands
with a matching name.
Parsing
The way the parser works is simple: a message is first decomposed into parts:
prefix <> command <> " " <> rest
If the prefix doesn’t match, the message is ignored. If it does match,
a new Task is started to handle this event. This task will try and find
the function corresponding to the command called, and will return preemptively
if no such function is found. After that, rest
is passed to the parser,
which will try and extract arguments to pass to the function. The default
parsing method is simply splitting by whitespace.
Thankfully,
you can define a custom parser for a command via Cogs.set_parser/2
. This
parser will act upon rest
, and parse out the relevant arguments.
The message
argument
When you define a function with Cogs.def
the function gets expanded
to take an extra message
parameter, which is the message triggering
the command. This contains a lot of useful information, and is what
enables a lot of the other macros to work. Because of this,
be wary of naming something else message
.
Loading and Unloading
Loading a cog merely requires having started the client:
use Example
If you need to remove this cog from the handler:
Cogs.unload(Example)
Or you just want to disable a single function:
Cogs.disable(:ping)
Summary
Functions
Returns a map from command name (string) to the command information
Disables a command
Sets the client’s command prefix to a specific string
Unloads a module from the handler
Macros
Registers a new command, under the name of the function
Makes all commands in this module sub commands of a group
Gets the guild struct from which a command was triggered
Gets the id of the guild from which a command was triggered
Gets the member that triggered a command
Sends a message to the same channel as the message triggering a command
Allows you to register a custom message parser for a command
Halts the current command until an event is received
Waits for a specific event satisfying a condition
Types
Functions
Specs
all_commands :: map
Returns a map from command name (string) to the command information.
Each command is either {module, arity, function_name}
, or
{module, arity, function_name, parser}
.
This can be useful for providing some kind of help command, or telling a user if a command is defined, e.g. :
Cogs.def iscommand(maybe) do
case Cogs.all_commands()[maybe] do
nil -> Cogs.say "#{maybe} is not a command"
_ -> Cogs.say "#{maybe} is a command"
end
end
Specs
disable(atom) :: :ok
Disables a command.
If you want to remove a whole module from the cogs, use Cogs.unload/1
.
This will stop a command from being triggered. The only way to reenable the
command is to reload the module with use
.
Examples
defmodule Example do
use Alchemy.Cogs
Cogs.def ping, do: Cogs.say "pong"
Cogs.def foo, do: Cogs.say "bar"
end
Client.start(@token)
use Example
Cogs.disable(:foo)
Only ping
will be triggerable now.
use Example
At runtime this will add foo
back in, given it’s still in the module.
Specs
set_prefix(String.t) :: :ok
Sets the client’s command prefix to a specific string.
This will only work after the client has been started
Example
Client.start(@token)
Cogs.set_prefix("!!")
Specs
unload(atom) :: :ok
Unloads a module from the handler.
If you just want to disable a single command, use Cogs.disable/1
Examples
Client.start(@token)
use Commands2
Turns out we want to stop using Commands2
commands in our bot, so we
can simply unload the module:
Cogs.unload(Commands2)
Now none of the commands defined in that module will be accessible. If we want to reverse that, we can merely do:
use Commands2
and reload them back in.
Macros
Registers a new command, under the name of the function.
This macro modifies the function definition, to accept an extra
message
parameter, allowing the message that triggered the command to be passed,
as a t:Alchemy.Message/0
Examples
Cogs.def ping do
Cogs.say "pong"
end
In this case, “!ping” will trigger the command, unless another prefix has been set
with set_prefix/1
Cogs.def mimic, do: Cogs.say "Please send a word for me to echo"
Cogs.def mimic(word), do: Cogs.say word
Messages will be parsed, and arguments will be extracted, however, to deal with potentially missing arguments, pattern matching should be used. So, in this case, when a 2nd argument isn’t given, an error message is sent back.
Makes all commands in this module sub commands of a group.
Examples
defmodule C do
use Alchemy.Cogs
Cogs.group("cool")
Cogs.def foo, do: Cogs.say "foo"
end
To use this foo command, one has to type !cool foo
, from there on
arguments will be passed like normal.
The relevant parsing will be done in the command task, as if there
were a command !cool
that redirected to subfunctions. Because of this,
Cogs.disable/1
will not be able to disable the subcommands, however,
Cogs.unload/1
still works as expected. Reloading a grouped module
will also disable removed commands, unlike with ungrouped modules.
Gets the guild struct from which a command was triggered.
If only the id is needed, see :guild_id/0
Examples
Cogs.def guild do
{:ok, %Alchemy.Guild{name: name}} = Cogs.guild()
Cogs.say(name)
end
Gets the id of the guild from which a command was triggered.
This is to be used when the guild_id is necessary for an operation, but the full guild struct isn’t needed.
Gets the member that triggered a command.
As opposed to message.author
, this comes with a bit more info about who
triggered the command. This is useful for when you want to use certain information
in a command, such as permissions, for example.
Sends a message to the same channel as the message triggering a command.
This can only be used in a command defined with Cogs.def
This is just a thin macro around Alchemy.Client.send_message/2
Examples
Cogs.def ping, do: Cogs.say("pong!")
Allows you to register a custom message parser for a command.
The parser will be applied to part of the message not used for command matching.
prefix <> command <> " " <> rest
Examples
Cogs.set_parser(:echo, &List.wrap/1)
Cogs.def echo(rest) do
Cogs.say(rest)
end
Halts the current command until an event is received.
The event type is an item corresponding to the events in Alchemy.Events
,
i.e. on_message_edit
-> Cogs.wait_for(:message_edit, ...)
. The fun
is the function that gets called with the relevant event arguments; see
Alchemy.Events
for more info on what events have what arguments.
The :message
event is a bit special, as it will specifically wait for
a message not triggered by a bot, in that specific channel, unlike other events,
which trigger generically across the entire bot.
The process will kill itself if it doesn’t receive any such event for 20s.
Examples
Cogs.def color do
Cogs.say "What's your favorite color?"
Cogs.wait_for :message, fn msg ->
Cogs.say "#{msg.content} is my favorite color too!"
end
end
Cogs.def typing do
Cogs.say "I'm waiting for someone to type.."
Cogs.wait_for :typing, fn _,_,_ ->
Cogs.say "Someone somewhere started typing..."
end
Waits for a specific event satisfying a condition.
Same as wait_for/2
, except this takes an extra condition that needs to be
met for the waiting to handle to trigger.
Examples
Cogs.def foo do
Cogs.say "Send me foo"
Cogs.wait_for(:message, & &1.content == "foo", fn _msg ->
Cogs.say "Nice foo man!"
end)
Note that, if no event of the given type is received after 20s, the process will kill itself, it’s possible that this will never get met, but no event satisfying the condition will ever arrive, essentially rendering the process a waste. To circumvent this, it might be smart to send a preemptive kill message:
self = self()
Task.start(fn ->
Process.sleep(20_000)
Process.exit(self, :kill)
)
Cogs.wait_for(:message, fn x -> false end, fn _msg ->
Cogs.say "If you hear this, logic itself is falling apart!!!"
end)