The Command Service
Discord.Commands provides an Attribute-based
Command Parser.
Setup
To use Commands, you must create a
Commands Service
and a Command Handler.
Included below is a very bare-bones Command Handler. You can extend
your Command Handler as much as you like, however the below is the
bare minimum.
[!code-csharpBarebones Command Handler]
Commands
In 1.0, Commands are no longer implemented at runtime with a builder
pattern. While a builder pattern may be provided later, commands are
created primarily with attributes.
Basic Structure
All commands belong to a Module. (See the below section for creating
modules).
All commands in a module must be defined as a Task
.
To add parameters to your command, you simply need to add parameters
to the Task that represents the command. You are not required to
accept all arguments as String
, they will be automatically parsed
into the type you specify for the arument. See the Example Module
for an example of command parameters.
Modules
Modules are an organizational pattern that allow you to write your
commands in different classes, and have them automatically loaded.
Discord.Net's implementation of Modules is influenced heavily from
ASP.Net Core's Controller pattern. This means that the lifetime of a
module instance is only as long as the command being ran in it.
Avoid using long-running code in your modules whereever possible.
You should not be implementing very much logic into your modules;
outsource to a service for that.
If you are unfamiliar with Inversion of Control, it is recommended to
read the MSDN article on IoC and Dependency Injection.
To create a module, create a class that inherits from
@Discord.Commands.ModuleBase.
Example Module
[!code-csharpModules]
Loading Modules Automatically
The Command Service can automatically discover all classes in an
Assembly that inherit @Discord.Commands.ModuleBase, and load them.
To have a module opt-out of auto-loading, pass autoload: false
in
the Module attribute.
Invoke CommandService.AddModules to discover modules and install them.
Loading Modules Manually
To manually load a module, invoke CommandService.AddModule,
by passing in the generic type of your module, and optionally
a dependency map.
Module Constructors
Modules are constructed using Dependency Injection. Any parameters
that are placed in the constructor must be injected into an
@Discord.Commands.IDependencyMap. Alternatively, you may accept an
IDependencyMap as an argument and extract services yourself.
Command Groups
Command Groups allow you to create a module where commands are prefixed.
To create a group, create a new module and flag it with the
@Discord.Commands.GroupAttribute.
[!NOTE]
Groups do not need to be modules. Only classes with commands should
inherit from ModuleBase. If you plan on using a group for strictly
organizational purposes, there is no reason to make it a module.
[!code-csharpGroups Sample]
Dependency Injection
The commands service is bundled with a very barebones Dependency
Injection service for your convienence. It is recommended that
you use DI when writing your modules.
Setup
First, you need to create an @Discord.Commands.IDependencyMap.
The library includes @Discord.Commands.DependencyMap to help with
this, however you may create your own IDependencyMap if you wish.
Next, add the dependencies your modules will use to the map.
Finally, pass the map into the LoadAssembly
method.
Your modules will automatically be loaded with this dependency map.
[!code-csharpDependencyMap Setup]
Usage in Modules
In the constructor of your module, any parameters will be filled in by
the @Discord.Commands.IDependencyMap you pass into LoadAssembly
.
[!NOTE]
If you accept CommandService
or IDependencyMap
as a parameter in
your constructor, these parameters will be filled by the
CommandService the module was loaded from, and the DependencyMap passed
into it, respectively.
[!code-csharpDependencyMap in Modules]
Preconditions
Preconditions serve as a permissions system for your commands. Keep in
mind, however, that they are not limited to just permissions, and
can be as complex as you want them to be.
[!NOTE]
Preconditions can be applied to Modules, Groups, or Commands.
Bundled Preconditions
@Discord.Commands ships with two built-in preconditions,
@Discord.Commands.RequireContextAttribute and
@Discord.Commands.RequirePermissionAttribute.
RequireContext
@Discord.Commands.RequireContextAttribute is a precondition that
requires your command to be executed in the specified context.
You may require three different types of context:
Since these are Flags
, you may OR them together.
[!code-csharpRequireContext]
RequirePermission
@Discord.Commands.RequirePermissionAttribute is a precondition that
allows you to quickly specfiy that a user must poesess a permission
to execute a command.
You may require either a @Discord.GuildPermission or
@Discord.ChannelPermission
[!code-csharpRequireContext]
Custom Preconditions
To write your own preconditions, create a new class that inherits from
@Discord.Commands.PreconditionAttribute
In order for your precondition to function, you will need to override
CheckPermissions
, which is a Task<PreconditionResult>
.
Your IDE should provide an option to fill this in for you.
Return PreconditionResult.FromSuccess()
if the context met the
required parameters, otherwise return PreconditionResult.FromError()
,
optionally including an error message.
[!code-csharpCustom Precondition]
Type Readers
Type Readers allow you to parse different types of arguments in
your commands.
By default, the following Types are supported arguments:
- string
- sbyte/byte
- ushort/short
- uint/int
- ulong/long
- float, double, decimal
- DateTime/DateTimeOffset
- IUser/IGuildUser
- IChannel/IGuildChannel/ITextChannel/IVoiceChannel/IGroupChannel
- IRole
- IMessage/IUserMessage
Creating a Type Readers
To create a TypeReader, create a new class that imports @Discord and
@Discord.Commands. Ensure your class inherits from @Discord.Commands.TypeReader
Next, satisfy the TypeReader
class by overriding Task<TypeReaderResult> Read(CommandContext context, string input)
.
[!NOTE]
In many cases, Visual Studio can fill this in for you, using the
"Implement Abstract Class" IntelliSense hint.
Inside this task, add whatever logic you need to parse the input string.
Finally, return a TypeReaderResult
. If you were able to successfully
parse the input, return TypeReaderResult.FromSuccess(parsedInput)
.
Otherwise, return TypeReaderResult.FromError
.
Sample
[!code-csharpTypeReaders]
Installing TypeReaders
TypeReaders are not automatically discovered by the Command Service,
and must be explicitly added. To install a TypeReader, invoke CommandService.AddTypeReader.