* Improve the Command Service documentation
The following changes have been added to this PR:
• Fix minor grammatical errors.
• Capitalize terms such as Commands, Modules and such, as the context is specific to the lib.
• Wrap methods and properties in code blocks.
The docs page currently has several issues that remains to be fixed.
1.
```md
>[!WARNING]
>This article is out of date and has not been rewritten yet.
Information is not guaranteed to be accurate.
```
The docs doesn't necessarily seem "out of date" as the warning claims. The basics seem pretty relevant to the latest version of the lib.
2.
>“To manually load a module, invoke [CommandService.AddModuleAsync], by passing in the generic type of your module and optionally, a dependency map.”
The latter part of the sentence seems off. Where should the user pass the dependency map to? It seems to suggest that `AddModuleAsync` has an argument to pass the dependency to. If it is referring to `AddModuleAsync(Type type)`, then I feel like it should be clarified here - or perhaps change the wording of the sentence.
3.
>“First, you need to create an @System.IServiceProvider You may create your own IServiceProvider if you wish.”
Any mention of @System.IServiceProvider is currently broken on the docs.
4.
>“Submodules are Modules that reside within another one. Typically, submodules are used to create nested groups (although not required to create nested groups).”
Clarification on the part after "although?"
5.
>“Finally, pass the map into the LoadAssembly method. Your modules will automatically be loaded with this dependency map.”
Where is this `LoadAssembly` method?
6.
```md
>[!NOTE]
>Preconditions can be applied to Modules, Groups, or Commands.
```
The docs should mention `ParameterPreconditionAttribute`'s existence.
* Update line breaks to comply with docs standard
* Change "you should..." to "instead, ..."
* Trim trailing spaces
* Change "inherits" to "inherit"
* Fix Context warning note and add ReplyAsync xref
* Fix broken xrefs
* Fix [Command Service] xref
* Fix consistency between TypeReaders and Preconditions returns
* Add missing semi-colons in ServiceProvider sample
* Change CommandContext to SocketCommandContext & change variable naming
* Cleanup TypeReader section
* Wrap [DontInject] in code block
* Fix commands docs linking in intro
* Improve Getting Started - Installation
- Fix character misalignment to comply with docs standard.
- Fix image numbering issues by moving the tooltips above some of the steps.
- Add codeblocks to search terms like `Discord.Net`.
- Remove broken `addons` reference.
- Specify `.NET 4.6.1` as `.NET Framework 4.6.1`.
- Minor cross-reference cleanup.
* Fix Getting Started - Intro
- Minor grammartical fixes.
- Wrap mentions of the methods, properties, and events in code block.
- Replace `Discord.Net` to `Discord.NET`.
- Fix steps numbering under `Creating a Discord Bot` and `Adding your bot to a server`.
- Change `Task-based Asynchronous Pattern ([TAP])` linking to mark the entire term instead.
- Change code block of `Pong!` to quotation mark instead.
* Fix cross references in Sending Voice
* Mention parameter precondition attribute
* Change `Discord.NET` to `Discord.Net` for consistency
* Wrap project names in code blocks & minor fixes in Terminology
* Change `add-ons` to `addons` for consistency
* Fix cross references in Logging
* Fix minor grammatical issues in "Working with Events"
* Missed a tilda
* Remove out-of-date warning in Commands
* Minor grammatical fixes for Entities
* Fix broken xref in Logging
* Adjust service collection sample
...according to f89aecb7bf (r141530227)
* Update Command Handler sample
- Update Main for C# 7.1.
- Inject CommandService and DiscordSocketClient into the service collection.
- Add Async suffix to asynchronous methods.
* Minor grammatical fixes in Events
* Revert 2 incorrect grammar corrections
* Revert async Main sample
* Add hardcode token notice in sample
* Fix missing method for Command Handler
* Modify module samples to use SocketCommandContext instead
* Emphasize CommandContext and SocketCommandContext
* Fix formatting for module sample
* Add SocketCommandContext for Groups sample
* Remove comma
* Fix DepMap sample formatting
* Replace [DontInject] with DontInjectAttribute with cross reference
* Remove connection logic note
There is no reason that this note should still be here since Ready event exists.
* Add a new warning message informing the users the existence of CommandService
* Make command handler private
excellent change
12 KiB
The Command Service
Discord.Commands provides an Attribute-based command parser.
Setup
To use Commands, you must create a Command Service and a Command Handler.
Included below is a very barebone Command Handler. You can extend your Command Handler as much as you like; however, the below is the bare minimum.
The CommandService will optionally accept a CommandServiceConfig,
which does set a few default values for you. It is recommended to
look over the properties in CommandServiceConfig and their default
values.
[!code-csharpCommand Handler]
With Attributes
In 1.0, Commands can be defined ahead of time with attributes, or at runtime with builders.
For most bots, ahead-of-time Commands should be all you need, and this is the recommended method of defining Commands.
Modules
The first step to creating Commands is to create a module.
A Module is an organizational pattern that allows 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 is being invoked.
Avoid using long-running code in your modules wherever possible. You should not be implementing very much logic into your modules, instead, 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 begin, create a new class somewhere in your project and inherit the
class from ModuleBase. This class must be public.
Note
ModuleBase is an abstract class, meaning that you may extend it or override it as you see fit. Your module may inherit from any extension of ModuleBase.
By now, your module should look like this:
[!code-csharpEmpty Module]
Adding Commands
The next step to creating Commands is actually creating the Commands.
To create a Command, add a method to your module of type Task.
Typically, you will want to mark this method as async, although it
is not required.
Adding parameters to a Command is done by adding parameters to the parent Task.
For example, to take an integer as an argument from the user, add int arg; to take a user as an argument from the user, add IUser user.
In 1.0, a Command can accept nearly any type of argument; a full list
of types that are parsed by default can be found in the below section
on Type Readers.
Parameters, by default, are always required. To make a parameter
optional, give it a default value. To accept a comma-separated list,
set the parameter to params Type[].
Should a parameter include spaces, it must be wrapped in quotes.
For example, for a Command with a parameter string food, you would
execute it with !favoritefood "Key Lime Pie".
If you would like a parameter to parse until the end of a Command, flag the parameter with the RemainderAttribute. This will allow a user to invoke a Command without wrapping a parameter in quotes.
Finally, flag your Command with the CommandAttribute (you must specify a name for this Command, except for when it is part of a Module Group - see below).
Command Overloads
You may add overloads to your Commands, and the Command parser will automatically pick up on it.
If for whatever reason, you have two Commands which are ambiguous to each other, you may use the @Discord.Commands.PriorityAttribute to specify which should be tested before the other.
The Priority attributes are sorted in ascending order; the higher
priority will be called first.
Command Context
Every Command can access the execution context through the Context
property on ModuleBase. ICommandContext allows you to access the
message, channel, guild, and user that the Command was invoked from,
as well as the underlying Discord client that the Command was invoked
from.
Different types of Contexts may be specified using the generic variant of ModuleBase. When using a SocketCommandContext, for example, the properties on this context will already be Socket entities, so you will not need to cast them.
To reply to messages, you may also invoke ReplyAsync, instead of accessing the channel through the Context and sending a message.
Warning
Contexts should NOT be mixed! You cannot have one module that uses
CommandContextand another that usesSocketCommandContext.
Example Module
At this point, your module should look comparable to this example: [!code-csharpExample Module]
Loading Modules Automatically
The Command Service can automatically discover all classes in an Assembly that inherit ModuleBase and load them.
To opt a module out of auto-loading, flag it with DontAutoLoadAttribute.
Invoke CommandService.AddModulesAsync to discover modules and install them.
Loading Modules Manually
To manually load a module, invoke CommandService.AddModuleAsync 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 Module's constructor must be injected into an
@System.IServiceProvider first. Alternatively, you may accept an
IServiceProvider as an argument and extract services yourself.
Module Properties
Modules with public settable properties will have the dependencies
injected after the construction of the Module.
Module Groups
Module Groups allow you to create a module where Commands are prefixed. To create a group, flag a module with the @Discord.Commands.GroupAttribute.
Module groups also allow you to create nameless Commands, where the CommandAttribute is configured with no name. In this case, the Command will inherit the name of the group it belongs to.
Submodules
Submodules are Modules that reside within another one. Typically, submodules are used to create nested groups (although not required to create nested groups).
[!code-csharpGroups and Submodules]
With Builders
TODO
Dependency Injection
The Command Service is bundled with a very barebone Dependency Injection service for your convenience. It is recommended that you use DI when writing your modules.
Setup
First, you need to create an @System.IServiceProvider; you may create your own one if you wish.
Next, add the dependencies that your modules will use to the map.
Finally, pass the map into the LoadAssembly method. Your modules
will be automatically loaded with this dependency map.
[!code-csharpIServiceProvider Setup]
Usage in Modules
In the constructor of your Module, any parameters will be filled in by
the @System.IServiceProvider that you've passed into LoadAssembly.
Any publicly settable properties will also be filled in the same manner.
Note
Annotating a property with a DontInjectAttribute attribute will prevent the property from being injected.
Note
If you accept
CommandServiceorIServiceProvideras a parameter in your constructor or as an injectable property, these entries will be filled by theCommandServicethat the Module is loaded from and theServiceProviderthat is passed into it respectively.
[!code-csharpServiceProvider in Modules]
Preconditions
Precondition 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
There are two types of Preconditions. PreconditionAttribute can be applied to Modules, Groups, or Commands; ParameterPreconditionAttribute can be applied to Parameters.
Bundled Preconditions
Commands ship with four bundled Preconditions; you may view their usages on their respective API pages.
- @Discord.Commands.RequireContextAttribute
- @Discord.Commands.RequireOwnerAttribute
- @Discord.Commands.RequireBotPermissionAttribute
- @Discord.Commands.RequireUserPermissionAttribute
Custom Preconditions
To write your own Precondition, create a new class that inherits from either PreconditionAttribute or ParameterPreconditionAttribute depending on your use.
In order for your Precondition to function, you will need to override the CheckPermissions method.
Your IDE should provide an option to fill this in for you.
If the context meets the required parameters, return PreconditionResult.FromSuccess, otherwise return PreconditionResult.FromError and include an error message if necessary.
[!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:
- bool
- char
- sbyte/byte
- ushort/short
- uint/int
- ulong/long
- float, double, decimal
- string
- DateTime/DateTimeOffset/TimeSpan
- IMessage/IUserMessage
- IChannel/IGuildChannel/ITextChannel/IVoiceChannel/IGroupChannel
- IUser/IGuildUser/IGroupUser
- IRole
Creating a Type Readers
To create a TypeReader, create a new class that imports @Discord and
@Discord.Commands and ensure the class inherits from
@Discord.Commands.TypeReader.
Next, satisfy the TypeReader class by overriding the Read method.
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.
If you are able to successfully parse the input, return TypeReaderResult.FromSuccess with the parsed input, otherwise return TypeReaderResult.FromError and include an error message if necessary.
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.