+ Each section has gone through a thorough proof-reading and cleanup + This should help with readability.pull/988/head
| @@ -6,8 +6,7 @@ remarks: *content | |||||
| The attribute can be applied to a public settable property inside a | The attribute can be applied to a public settable property inside a | ||||
| @Discord.Commands.ModuleBase based class. By applying this attribute, | @Discord.Commands.ModuleBase based class. By applying this attribute, | ||||
| the marked property will not be automatically injected of the | the marked property will not be automatically injected of the | ||||
| dependency. See [Dependency Injection](xref:Guides.Commands.Intro#dependency-injection) | |||||
| to learn more. | |||||
| dependency. See @Guides.Commands.DI to learn more. | |||||
| --- | --- | ||||
| uid: Discord.Commands.DontInjectAttribute | uid: Discord.Commands.DontInjectAttribute | ||||
| @@ -57,7 +57,7 @@ persist throughout execution. Think of it like a chest that holds | |||||
| whatever you throw at it that won't be affected by anything unless | whatever you throw at it that won't be affected by anything unless | ||||
| you want it to. Note that you should also learn Microsoft's | you want it to. Note that you should also learn Microsoft's | ||||
| implementation of [Dependency Injection] \([video]) before proceeding, as well | implementation of [Dependency Injection] \([video]) before proceeding, as well | ||||
| as how it works in [Discord.Net](xref:Guides.Commands.Intro#usage-in-modules). | |||||
| as how it works in [Discord.Net](xref:Guides.Commands.DI#usage-in-modules). | |||||
| A brief example of service and dependency injection can be seen below. | A brief example of service and dependency injection can be seen below. | ||||
| @@ -16,6 +16,8 @@ DI when writing your modules. | |||||
| to use in the modules. | to use in the modules. | ||||
| 3. Pass the service collection into `AddModulesAsync`. | 3. Pass the service collection into `AddModulesAsync`. | ||||
| ### Example - Setting up Injection | |||||
| [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] | [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] | ||||
| ## Usage in Modules | ## Usage in Modules | ||||
| @@ -34,8 +36,10 @@ manner. | |||||
| > If you accept `CommandService` or `IServiceProvider` as a parameter | > If you accept `CommandService` or `IServiceProvider` as a parameter | ||||
| > in your constructor or as an injectable property, these entries will | > in your constructor or as an injectable property, these entries will | ||||
| > be filled by the `CommandService` that the module is loaded from and | > be filled by the `CommandService` that the module is loaded from and | ||||
| > the `ServiceProvider` that is passed into it respectively. | |||||
| > the `IServiceProvider` that is passed into it respectively. | |||||
| ### Example - Injection in Modules | |||||
| [!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] | |||||
| [!code-csharp[IServiceProvider in Modules](samples/dependency_module.cs)] | |||||
| [DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute | [DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute | ||||
| @@ -99,19 +99,20 @@ For example: | |||||
| * ...etc. | * ...etc. | ||||
| Starting from 1.0, a command can accept nearly any type of argument; | Starting from 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](#type-readers). | |||||
| a full list of types that are parsed by default can | |||||
| be found in @Guides.Commands.TypeReaders. | |||||
| [CommandAttribute]: xref:Discord.Commands.CommandAttribute | [CommandAttribute]: xref:Discord.Commands.CommandAttribute | ||||
| #### Optional Parameters | #### Optional Parameters | ||||
| Parameters, by default, are always required. To make a parameter | Parameters, by default, are always required. To make a parameter | ||||
| optional, give it a default value (i.e. `int num = 0`). To accept a comma-separated list, | |||||
| set the parameter to `params Type[]`. | |||||
| optional, give it a default value (i.e. `int num = 0`). | |||||
| #### Parameters with Spaces | #### Parameters with Spaces | ||||
| To accept a comma-separated list, set the parameter to `params Type[]`. | |||||
| Should a parameter include spaces, the parameter **must** be | Should a parameter include spaces, the parameter **must** be | ||||
| wrapped in quotes. For example, for a command with a parameter | wrapped in quotes. For example, for a command with a parameter | ||||
| `string food`, you would execute it with | `string food`, you would execute it with | ||||
| @@ -198,7 +199,8 @@ that are placed in the Module's constructor must be injected into an | |||||
| ### Module Properties | ### Module Properties | ||||
| Modules with `public` settable properties will have the dependencies | Modules with `public` settable properties will have the dependencies | ||||
| injected after the construction of the Module. | |||||
| injected after the construction of the module. See @Guides.Commands.DI | |||||
| to learn more. | |||||
| ### Module Groups | ### Module Groups | ||||
| @@ -216,112 +218,4 @@ Submodules are "modules" that reside within another one. Typically, | |||||
| submodules are used to create nested groups (although not required to | submodules are used to create nested groups (although not required to | ||||
| create nested groups). | create nested groups). | ||||
| [!code-csharp[Groups and Submodules](samples/groups.cs)] | |||||
| # 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. | |||||
| [PreconditionAttribute]: xref:Discord.Commands.PreconditionAttribute | |||||
| [ParameterPreconditionAttribute]: xref:Discord.Commands.ParameterPreconditionAttribute | |||||
| ## 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 | |||||
| * @Discord.Commands.RequireNsfwAttribute | |||||
| ## 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 [CheckPermissionsAsync] 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-csharp[Custom Precondition](samples/require_owner.cs)] | |||||
| [CheckPermissionsAsync]: xref:Discord.Commands.PreconditionAttribute.CheckPermissionsAsync* | |||||
| [PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult.FromSuccess* | |||||
| [PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult.FromError* | |||||
| # 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` | |||||
| * `Nullable<T>` where applicible | |||||
| * Any implementation of `IChannel`/`IMessage`/`IUser`/`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 [ReadAsync] 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. | |||||
| [TypeReaderResult]: xref:Discord.Commands.TypeReaderResult | |||||
| [TypeReaderResult.FromSuccess]: xref:Discord.Commands.TypeReaderResult.FromSuccess* | |||||
| [TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult.FromError* | |||||
| [ReadAsync]: xref:Discord.Commands.TypeReader.ReadAsync* | |||||
| ## Registering TypeReaders | |||||
| TypeReaders are not automatically discovered by the Command Service | |||||
| and must be explicitly added. | |||||
| To register a TypeReader, invoke [CommandService.AddTypeReader]. | |||||
| > [!WARNING] | |||||
| > TypeReaders must be added prior to module discovery, otherwise your | |||||
| > TypeReaders may not work! | |||||
| [CommandService.AddTypeReader]: xref:Discord.Commands.CommandService.AddTypeReader* | |||||
| ### Sample | |||||
| [!code-csharp[TypeReaders](samples/typereader.cs)] | |||||
| [!code-csharp[Groups and Submodules](samples/groups.cs)] | |||||
| @@ -0,0 +1,53 @@ | |||||
| --- | |||||
| uid: Guides.Commands.Preconditions | |||||
| title: 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. | |||||
| There are two types of Preconditions you can use: | |||||
| * [PreconditionAttribute] can be applied to Modules, Groups, or Commands. | |||||
| * [ParameterPreconditionAttribute] can be applied to Parameters. | |||||
| You may visit their respective API documentation to find out more. | |||||
| [PreconditionAttribute]: xref:Discord.Commands.PreconditionAttribute | |||||
| [ParameterPreconditionAttribute]: xref:Discord.Commands.ParameterPreconditionAttribute | |||||
| ## Bundled Preconditions | |||||
| @Discord.Commands ship with several bundled Preconditions; you may | |||||
| view their usages on their respective API pages. | |||||
| * @Discord.Commands.RequireContextAttribute | |||||
| * @Discord.Commands.RequireOwnerAttribute | |||||
| * @Discord.Commands.RequireBotPermissionAttribute | |||||
| * @Discord.Commands.RequireUserPermissionAttribute | |||||
| * @Discord.Commands.RequireNsfwAttribute | |||||
| ## 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 [CheckPermissionsAsync] 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-csharp[Custom Precondition](samples/require_owner.cs)] | |||||
| [CheckPermissionsAsync]: xref:Discord.Commands.PreconditionAttribute.CheckPermissionsAsync* | |||||
| [PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult.FromSuccess* | |||||
| [PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult.FromError* | |||||
| @@ -1,40 +1,30 @@ | |||||
| using Discord; | |||||
| using Discord.Commands; | |||||
| using Discord.WebSocket; | |||||
| public class ModuleA : ModuleBase<SocketCommandContext> | |||||
| public class DatabaseModule : ModuleBase<SocketCommandContext> | |||||
| { | { | ||||
| private readonly DatabaseService _database; | private readonly DatabaseService _database; | ||||
| // Dependencies can be injected via the constructor | // Dependencies can be injected via the constructor | ||||
| public ModuleA(DatabaseService database) | |||||
| public DatabaseModule(DatabaseService database) | |||||
| { | { | ||||
| _database = database; | _database = database; | ||||
| } | } | ||||
| public async Task ReadFromDb() | |||||
| [Command("read")] | |||||
| public async Task ReadFromDbAsync() | |||||
| { | { | ||||
| var x = _database.getX(); | |||||
| await ReplyAsync(x); | |||||
| await ReplyAsync(_database.GetData()); | |||||
| } | } | ||||
| } | } | ||||
| public class ModuleB : ModuleBase<SocketCommandContext> | |||||
| public class MixModule : ModuleBase<SocketCommandContext> | |||||
| { | { | ||||
| // Public settable properties will be injected. | |||||
| public AnnounceService Announce { get; set; } | |||||
| // Public settable properties will be injected | |||||
| public AnnounceService AnnounceService { get; set; } | |||||
| // Public properties without setters will not be injected. | |||||
| public CommandService Commands { get; } | |||||
| // Public properties without setters will not be injected | |||||
| public ImageService ImageService { get; } | |||||
| // Public properties annotated with [DontInject] will not | // Public properties annotated with [DontInject] will not | ||||
| // be injected. | |||||
| // be injected | |||||
| [DontInject] | [DontInject] | ||||
| public NotificationService NotificationService { get; set; } | public NotificationService NotificationService { get; set; } | ||||
| public ModuleB(CommandService commands) | |||||
| { | |||||
| Commands = commands; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,5 +1,7 @@ | |||||
| using Discord.Commands; | using Discord.Commands; | ||||
| // Keep in mind your module **must** be public and inherit ModuleBase. | |||||
| // If it isn't, it will not be discovered by AddModulesAsync! | |||||
| public class InfoModule : ModuleBase<SocketCommandContext> | public class InfoModule : ModuleBase<SocketCommandContext> | ||||
| { | { | ||||
| @@ -0,0 +1,29 @@ | |||||
| public class CommandHandler | |||||
| { | |||||
| private readonly CommandService _commands; | |||||
| private readonly DiscordSocketClient _client; | |||||
| private readonly IServiceProvider _services; | |||||
| public CommandHandler(CommandService commands, DiscordSocketClient client, IServiceProvider services) | |||||
| { | |||||
| _commands = commands; | |||||
| _client = client; | |||||
| _services = services; | |||||
| } | |||||
| public async Task SetupAsync() | |||||
| { | |||||
| _client.MessageReceived += CommandHandleAsync; | |||||
| // Add BooleanTypeReader to type read for the type "bool" | |||||
| _commands.AddTypeReader(typeof(bool), new BooleanTypeReader()); | |||||
| // Then register the modules | |||||
| await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); | |||||
| } | |||||
| public async Task CommandHandleAsync(SocketMessage msg) | |||||
| { | |||||
| // ... | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,69 @@ | |||||
| --- | |||||
| uid: Guides.Commands.TypeReaders | |||||
| title: Type Readers | |||||
| --- | |||||
| # 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` | |||||
| * `Nullable<T>` where applicible | |||||
| * Any implementation of `IChannel`/`IMessage`/`IUser`/`IRole` | |||||
| ## Creating a Type Reader | |||||
| 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 [ReadAsync] method. | |||||
| 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. | |||||
| > [!NOTE] | |||||
| > Visual Studio can help you implement missing members | |||||
| > from the abstract class by using the "Implement Abstract Class" | |||||
| > IntelliSense hint. | |||||
| [TypeReaderResult]: xref:Discord.Commands.TypeReaderResult | |||||
| [TypeReaderResult.FromSuccess]: xref:Discord.Commands.TypeReaderResult.FromSuccess* | |||||
| [TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult.FromError* | |||||
| [ReadAsync]: xref:Discord.Commands.TypeReader.ReadAsync* | |||||
| ### Example - Creating a Type Reader | |||||
| [!code-csharp[TypeReaders](samples/typereader.cs)] | |||||
| ## Registering a Type Reader | |||||
| TypeReaders are not automatically discovered by the Command Service | |||||
| and must be explicitly added. | |||||
| To register a TypeReader, invoke [CommandService.AddTypeReader]. | |||||
| > [!IMPORTANT] | |||||
| > TypeReaders must be added prior to module discovery, otherwise your | |||||
| > TypeReaders may not work! | |||||
| [CommandService.AddTypeReader]: xref:Discord.Commands.CommandService.AddTypeReader* | |||||
| ### Example - Adding a Type Reader | |||||
| [!code-csharp[Adding TypeReaders](samples/typereader-register.cs)] | |||||
| @@ -20,8 +20,12 @@ | |||||
| topicUid: Guides.Concepts.Entities | topicUid: Guides.Concepts.Entities | ||||
| - name: The Command Service | - name: The Command Service | ||||
| items: | items: | ||||
| - name: Introduction to Command Service | |||||
| - name: Introduction | |||||
| topicUid: Guides.Commands.Intro | topicUid: Guides.Commands.Intro | ||||
| - name: TypeReaders | |||||
| topicUid: Guides.Commands.TypeReaders | |||||
| - name: Preconditions | |||||
| topicUid: Guides.Commands.Preconditions | |||||
| - name: Dependency Injection | - name: Dependency Injection | ||||
| topicUid: Guides.Commands.DI | topicUid: Guides.Commands.DI | ||||
| - name: Post-execution Handling | - name: Post-execution Handling | ||||