| @@ -1,209 +1,285 @@ | |||||
| # The Command Service | |||||
| # The Command Service | |||||
| >[!WARNING] | >[!WARNING] | ||||
| >This article is out of date and has not been rewritten yet. | |||||
| Information is not guaranteed to be accurate. | |||||
| >This article is out of date and has not been rewritten yet. | |||||
| Information is not guaranteed to be accurate. | |||||
| [Discord.Commands](xref:Discord.Commands) provides an Attribute-based command parser. | |||||
| [Discord.Commands](xref:Discord.Commands) provides an Attribute-based | |||||
| command parser. | |||||
| ## Setup | |||||
| ## Setup | |||||
| To use Commands, you must create a [Commands Service] and a Command Handler. | |||||
| To use Commands, you must create a [Commands 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. | |||||
| 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. | |||||
| 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-csharp[Command Handler](samples/command_handler.cs)] | |||||
| [!code-csharp[Command Handler](samples/command_handler.cs)] | |||||
| [Command Service]: xref:Discord.Commands.CommandService | |||||
| [CommandServiceConfig]: xref:Discord.Commands.CommandServiceConfig | |||||
| [Command Service]: xref:Discord.Commands.CommandService | |||||
| [CommandServiceConfig]: xref:Discord.Commands.CommandServiceConfig | |||||
| ## With Attributes | |||||
| ## With Attributes | |||||
| In 1.0, Commands can be defined ahead of time with attributes, or at runtime with builders. | |||||
| 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. | |||||
| For most bots, ahead-of-time Commands should be all you need, and this | |||||
| is the recommended method of defining Commands. | |||||
| ### Modules | |||||
| ### Modules | |||||
| The first step to creating Commands is to create a _module_. | |||||
| 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. | |||||
| 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. | |||||
| 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; you should outsource to a service for that. | |||||
| **Avoid using long-running code** in your modules wherever possible. | |||||
| You should **not** be implementing very much logic into your modules; | |||||
| you should 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]. | |||||
| 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`. | |||||
| 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. | |||||
| >[!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-csharp[Empty Module](samples/empty-module.cs)] | |||||
| By now, your module should look like this: | |||||
| [!code-csharp[Empty Module](samples/empty-module.cs)] | |||||
| [IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx | |||||
| [Dependency Injection]: https://msdn.microsoft.com/en-us/library/ff921152.aspx | |||||
| [ModuleBase]: xref:Discord.Commands.ModuleBase`1 | |||||
| [IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx | |||||
| [Dependency Injection]: https://msdn.microsoft.com/en-us/library/ff921152.aspx | |||||
| [ModuleBase]: xref:Discord.Commands.ModuleBase`1 | |||||
| ### Adding Commands | |||||
| ### Adding Commands | |||||
| The next step to creating Commands is actually creating the 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. | |||||
| 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. | |||||
| 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_. | |||||
| 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[]`. | |||||
| 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"`. | |||||
| 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. | |||||
| 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). | |||||
| 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). | |||||
| [RemainderAttribute]: xref:Discord.Commands.RemainderAttribute | |||||
| [CommandAttribute]: xref:Discord.Commands.CommandAttribute | |||||
| [RemainderAttribute]: xref:Discord.Commands.RemainderAttribute | |||||
| [CommandAttribute]: xref:Discord.Commands.CommandAttribute | |||||
| ### Command Overloads | |||||
| ### Command Overloads | |||||
| You may add overloads to your Commands, and the Command parser will automatically pick up on it. | |||||
| 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. | |||||
| 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. | |||||
| The `Priority` attributes are sorted in ascending order; the higher | |||||
| priority will be called first. | |||||
| ### Command Context | |||||
| ### 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. | |||||
| 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. | |||||
| 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. | |||||
| To reply to messages, you may also invoke [ReplyAsync], instead of | |||||
| accessing the channel through the [Context] and sending a message. | |||||
| [Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context | [Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context | ||||
| [SocketCommandContext]: xref:Discord.Commands.SocketCommandContext | |||||
| [SocketCommandContext]: xref:Discord.Commands.SocketCommandContext | |||||
| >![WARNING] | |||||
| >Contexts should **NOT** be mixed! You cannot have one module that | |||||
| >uses CommandContext and another that uses SocketCommandContext. | |||||
| >![WARNING] | |||||
| >Contexts should **NOT** be mixed! You cannot have one module that | |||||
| >uses CommandContext and another that uses SocketCommandContext. | |||||
| ### Example Module | |||||
| ### Example Module | |||||
| At this point, your module should look comparable to this example: | |||||
| [!code-csharp[Example Module](samples/module.cs)] | |||||
| At this point, your module should look comparable to this example: | |||||
| [!code-csharp[Example Module](samples/module.cs)] | |||||
| #### Loading Modules Automatically | |||||
| #### Loading Modules Automatically | |||||
| The Command Service can automatically discover all classes in an Assembly that inherits [ModuleBase] and load them. | |||||
| The Command Service can automatically discover all classes in an | |||||
| Assembly that inherits [ModuleBase] and load them. | |||||
| To opt a module out of auto-loading, flag it with [DontAutoLoadAttribute]. | |||||
| To opt a module out of auto-loading, flag it with | |||||
| [DontAutoLoadAttribute]. | |||||
| Invoke [CommandService.AddModulesAsync] to discover modules and install them. | |||||
| Invoke [CommandService.AddModulesAsync] to discover modules and | |||||
| install them. | |||||
| [DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute | |||||
| [CommandService.AddModulesAsync]: xref:Discord_Commands_CommandService#Discord_Commands_CommandService_AddModulesAsync_Assembly_ | |||||
| [DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute | |||||
| [CommandService.AddModulesAsync]: xref:Discord_Commands_CommandService#Discord_Commands_CommandService_AddModulesAsync_Assembly_ | |||||
| #### Loading Modules Manually | |||||
| #### 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. | |||||
| To manually load a module, invoke [CommandService.AddModuleAsync], by | |||||
| passing in the generic type of your module and optionally, a | |||||
| dependency map. | |||||
| [CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1 | [CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1 | ||||
| ### Module Constructors | |||||
| ### 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. | |||||
| 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 | |||||
| ### Module Properties | |||||
| Modules with `public` settable properties will have the dependencies injected after the construction of the Module. | |||||
| Modules with `public` settable properties will have the dependencies | |||||
| injected after the construction of the Module. | |||||
| ### Module Groups | |||||
| ### 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 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. | |||||
| 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 | |||||
| Submodules are Modules that reside within another one. Typically, submodules are used to create nested groups (although not required to create nested groups). | |||||
| Submodules are Modules that reside within another one. Typically, | |||||
| submodules are used to create nested groups (although not required to | |||||
| create nested groups). | |||||
| [!code-csharp[Groups and Submodules](samples/groups.cs)] | |||||
| [!code-csharp[Groups and Submodules](samples/groups.cs)] | |||||
| ## With Builders | |||||
| ## With Builders | |||||
| **TODO** | |||||
| **TODO** | |||||
| ## Dependency Injection | |||||
| ## 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. | |||||
| 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 | |||||
| ### Setup | |||||
| First, you need to create an @System.IServiceProvider; you may create your own one if you wish. | |||||
| 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. | |||||
| 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. | |||||
| Finally, pass the map into the `LoadAssembly` method. Your modules | |||||
| will be automatically loaded with this dependency map. | |||||
| [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] | [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] | ||||
| ### Usage in Modules | |||||
| ### 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`. | |||||
| 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. | |||||
| Any publicly settable properties will also be filled in the same | |||||
| manner. | |||||
| >[!NOTE] | |||||
| > Annotating a property with the [DontInject] attribute will prevent it from being injected. | |||||
| >[!NOTE] | |||||
| > Annotating a property with the [DontInject] attribute will | |||||
| prevent it from being injected. | |||||
| >[!NOTE] | |||||
| >If you accept `CommandService` or `IServiceProvider` as a parameter in your constructor, or as an injectable property, these entries will be filled by the `CommandService` that the Module is loaded from, and the `ServiceProvider` that is passed into it respectively. | |||||
| >[!NOTE] | |||||
| >If you accept `CommandService` or `IServiceProvider` as a parameter | |||||
| in your constructor, or as an injectable property, these entries will | |||||
| be filled by the `CommandService` that the Module is loaded from, and | |||||
| the `ServiceProvider` that is passed into it respectively. | |||||
| [!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] | |||||
| [!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] | |||||
| # Preconditions | |||||
| # 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. | |||||
| 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] | |||||
| >Preconditions can be applied to Modules, Groups, or Commands. | |||||
| >[!NOTE] | |||||
| >Preconditions can be applied to Modules, Groups, or Commands. | |||||
| ## Bundled Preconditions | |||||
| ## Bundled Preconditions | |||||
| Commands ship with four bundled preconditions; you may view their usages on their respective API pages. | |||||
| Commands ship with four bundled preconditions; you may view their | |||||
| usages on their respective API pages. | |||||
| - @Discord.Commands.RequireContextAttribute | - @Discord.Commands.RequireContextAttribute | ||||
| - @Discord.Commands.RequireOwnerAttribute | - @Discord.Commands.RequireOwnerAttribute | ||||
| - @Discord.Commands.RequireBotPermissionAttribute | - @Discord.Commands.RequireBotPermissionAttribute | ||||
| - @Discord.Commands.RequireUserPermissionAttribute | - @Discord.Commands.RequireUserPermissionAttribute | ||||
| ## Custom Preconditions | |||||
| ## Custom Preconditions | |||||
| To write your own precondition, create a new class that inherits from @Discord.Commands.PreconditionAttribute. | |||||
| To write your own precondition, create a new class that inherits from | |||||
| @Discord.Commands.PreconditionAttribute. | |||||
| In order for your precondition to function, you will need to override the [CheckPermissions] method. | |||||
| 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. | |||||
| Your IDE should provide an option to fill this in for you. | |||||
| Return [PreconditionResult.FromSuccess] if the context meets the required parameters, otherwise return [PreconditionResult.FromError] and optionally include an error message. | |||||
| Return [PreconditionResult.FromSuccess] if the context meets the | |||||
| required parameters, otherwise return [PreconditionResult.FromError] | |||||
| and optionally include an error message. | |||||
| [!code-csharp[Custom Precondition](samples/require_owner.cs)] | |||||
| [!code-csharp[Custom Precondition](samples/require_owner.cs)] | |||||
| [CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_CommandContext_Discord_Commands_CommandInfo_Discord_Commands_IDependencyMap_ | [CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_CommandContext_Discord_Commands_CommandInfo_Discord_Commands_IDependencyMap_ | ||||
| [PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromSuccess | [PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromSuccess | ||||
| [PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromError_System_String_ | [PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromError_System_String_ | ||||
| # Type Readers | |||||
| # Type Readers | |||||
| Type Readers allow you to parse different types of arguments in your Commands. | |||||
| Type Readers allow you to parse different types of arguments in | |||||
| your commands. | |||||
| By default, the following Types are supported arguments: | By default, the following Types are supported arguments: | ||||
| @@ -221,29 +297,35 @@ By default, the following Types are supported arguments: | |||||
| - IUser/IGuildUser/IGroupUser | - IUser/IGuildUser/IGroupUser | ||||
| - IRole | - IRole | ||||
| ### Creating a Type Readers | |||||
| ### 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. | |||||
| 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. | |||||
| Next, satisfy the `TypeReader` class by overriding the [Read] method. | |||||
| >[!NOTE] | >[!NOTE] | ||||
| >In many cases, Visual Studio can fill this in for you using the | |||||
| >In many cases, Visual Studio can fill this in for you, using the | |||||
| >"Implement Abstract Class" IntelliSense hint. | >"Implement Abstract Class" IntelliSense hint. | ||||
| Inside this task, add whatever logic you need to parse the input string. | |||||
| 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` and optionally include an error message. | |||||
| Finally, return a `TypeReaderResult`. If you were able to successfully | |||||
| parse the input, return `TypeReaderResult.FromSuccess(parsedInput)`, | |||||
| otherwise, return `TypeReaderResult.FromError` and optionally include | |||||
| an error message. | |||||
| [Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_CommandContext_System_String_ | [Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_CommandContext_System_String_ | ||||
| #### Sample | |||||
| #### Sample | |||||
| [!code-csharp[TypeReaders](samples/typereader.cs)] | |||||
| [!code-csharp[TypeReaders](samples/typereader.cs)] | |||||
| ### Installing TypeReaders | |||||
| ### Installing TypeReaders | |||||
| TypeReaders are not automatically discovered by the Command Service and must be explicitly added. | |||||
| TypeReaders are not automatically discovered by the Command Service | |||||
| and must be explicitly added. | |||||
| To install a TypeReader, invoke [CommandService.AddTypeReader](xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_). | To install a TypeReader, invoke [CommandService.AddTypeReader](xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_). | ||||