From 5bb0cea91e1ccebcc74dfe92d0c13b8b073b5729 Mon Sep 17 00:00:00 2001 From: Hsu Still <341464@gmail.com> Date: Sat, 7 Apr 2018 14:30:52 +0800 Subject: [PATCH] Improve readability of command guide --- docs/guides/commands/commands.md | 141 +++++++++--------- .../commands/samples/command_handler.cs | 38 +---- docs/guides/commands/samples/module.cs | 12 +- 3 files changed, 81 insertions(+), 110 deletions(-) diff --git a/docs/guides/commands/commands.md b/docs/guides/commands/commands.md index b02cd62a5..8f71fbb96 100644 --- a/docs/guides/commands/commands.md +++ b/docs/guides/commands/commands.md @@ -10,17 +10,18 @@ command parser. ## Get Started -To use Commands, you must create a [Command Service] and a command -Handler. +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 +Included below is a 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. +> [!NOTE] +> 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)] @@ -29,20 +30,20 @@ values. ## With Attributes -Starting from 1.0, Commands can be defined ahead of time with +Starting from 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 -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 +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. @@ -74,32 +75,32 @@ By now, your module should look like this: The next step to creating commands is actually creating the commands. -To create a command, add a method to your module of type `Task` or -`Task` depending on your use. +To create a command, add a method to your module of type `Task` or +`Task` depending on your use (see: [Post-execution](xref:Guides.Commands.PostExecution)). 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`. 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 +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`. 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). Parameters, by default, are always required. To make a parameter -optional, give it a default value. To accept a comma-separated list, +optional, give it a default value (i.e. `int num = 0`). To accept a comma-separated list, set the parameter to `params Type[]`. -Should a parameter include spaces, the parameter **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 +Should a parameter include spaces, the parameter **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 +Finally, flag your command with the [CommandAttribute]. Note that you must specify a name for this command, except for when it is part of a [Module Group](#module-groups). @@ -108,10 +109,10 @@ specify a name for this command, except for when it is part of a ### Command Overloads -You may add overloads to your Commands, and the Command parser will +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 +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. @@ -122,10 +123,10 @@ priority will be called first. Every command can access the execution context through the [Context] property on [ModuleBase]. `ICommandContext` allows you to access the -message, channel, guild, user, and the underlying Discord client +message, channel, guild, user, and the underlying Discord client that the command was invoked from. -Different types of Contexts may be specified using the generic variant +Different types of `Context` 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. @@ -134,8 +135,8 @@ 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 `CommandContext` and another that uses `SocketCommandContext`. +> Contexts should **NOT** be mixed! You cannot have one module that +> uses `CommandContext` and another that uses `SocketCommandContext`. [Context]: xref:Discord.Commands.ModuleBase`1.Context [SocketCommandContext]: xref:Discord.Commands.SocketCommandContext @@ -144,12 +145,13 @@ accessing the channel through the [Context] and sending a message. ### Example Module At this point, your module should look comparable to this example: + [!code-csharp[Example Module](samples/module.cs)] #### Loading Modules Automatically The Command Service can automatically discover all classes in an -Assembly that inherit [ModuleBase] and load them. Invoke +`Assembly` that inherit [ModuleBase] and load them. Invoke [CommandService.AddModulesAsync] to discover modules and install them. @@ -171,7 +173,7 @@ service provider. Modules are constructed using Dependency Injection. Any parameters that are placed in the Module's constructor must be injected into an -@System.IServiceProvider first. +@System.IServiceProvider first. > [!TIP] > Alternatively, you may accept an @@ -185,17 +187,17 @@ injected after the construction of the Module. ### Module Groups -Module Groups allow you to create a module where Commands are +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 +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 "modules" that reside within another one. Typically, submodules are used to create nested groups (although not required to create nested groups). @@ -209,32 +211,30 @@ DI when writing your modules. ## Setup -First, you need to create an @System.IServiceProvider. - -Next, add the dependencies to the service collection that you wish -to use in the Modules. - -Finally, pass the service collection into `AddModulesAsync`. +1. Create an @System.IServiceProvider. +2. Add the dependencies to the service collection that you wish + to use in the modules. +3. Pass the service collection into `AddModulesAsync`. [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] ## Usage in Modules -In the constructor of your Module, any parameters will be filled in by +In the constructor of your module, any parameters will be filled in by the @System.IServiceProvider that you've passed. Any publicly settable properties will also be filled in the same manner. ->[!NOTE] -> Annotating a property with a [DontInjectAttribute] attribute will +> [!NOTE] +> Annotating a property with a [DontInjectAttribute] attribute will > prevent the property 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)] @@ -246,17 +246,17 @@ 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. +> [!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 +commands ship with four bundled Preconditions; you may view their usages on their respective API pages. - @Discord.Commands.RequireContextAttribute @@ -276,9 +276,9 @@ 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 +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)] @@ -303,11 +303,8 @@ By default, the following Types are supported arguments: - `float`, `double`, `decimal` - `string` - `DateTime`/`DateTimeOffset`/`TimeSpan` -- `IMessage`/`IUserMessage` -- `IChannel`/`IGuildChannel`/`ITextChannel`/`IVoiceChannel`/`ICategoryChannel`/`IMessageChannel`/`IGroupChannel` -- `IUser`/`IGuildUser`/`IGroupUser` -- `IRole` - `Nullable` where applicible +- Any implementation of `IChannel`/`IMessage`/`IUser`/`IRole` ## Creating a Type Readers @@ -317,16 +314,16 @@ To create a `TypeReader`, create a new class that imports @Discord and 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. +> [!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 +Inside this Task, add whatever logic you need to parse the input string. -If you are able to successfully parse the input, return +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 +[TypeReaderResult.FromError] and include an error message if necessary. [TypeReaderResult]: xref:Discord.Commands.TypeReaderResult diff --git a/docs/guides/commands/samples/command_handler.cs b/docs/guides/commands/samples/command_handler.cs index 1e68d70ec..470be2707 100644 --- a/docs/guides/commands/samples/command_handler.cs +++ b/docs/guides/commands/samples/command_handler.cs @@ -1,40 +1,14 @@ -using System; -using System.Threading.Tasks; -using System.Reflection; -using Discord; -using Discord.WebSocket; -using Discord.Commands; -using Microsoft.Extensions.DependencyInjection; - -public class Program +public class CommandHandle { - private CommandService _commands; - private DiscordSocketClient _client; - private IServiceProvider _services; - - private static void Main(string[] args) => new Program().StartAsync().GetAwaiter().GetResult(); + private readonly DiscordSocketClient _client; + private readonly CommandService _commands; - public async Task StartAsync() + public CommandHandle(DiscordSocketClient client) { - _client = new DiscordSocketClient(); + _client = client; _commands = new CommandService(); - - // Avoid hard coding your token. Use an external source instead in your code. - string token = "bot token here"; - - _services = new ServiceCollection() - .AddSingleton(_client) - .AddSingleton(_commands) - .BuildServiceProvider(); - - await InstallCommandsAsync(); - - await _client.LoginAsync(TokenType.Bot, token); - await _client.StartAsync(); - - await Task.Delay(-1); } - + public async Task InstallCommandsAsync() { // Hook the MessageReceived Event into our Command Handler diff --git a/docs/guides/commands/samples/module.cs b/docs/guides/commands/samples/module.cs index ad96b6051..e5fe70534 100644 --- a/docs/guides/commands/samples/module.cs +++ b/docs/guides/commands/samples/module.cs @@ -1,5 +1,5 @@ // Create a module with no prefix -public class Info : ModuleBase +public class InfoModule : ModuleBase { // ~say hello world -> hello world [Command("say")] @@ -12,14 +12,14 @@ public class Info : ModuleBase // Create a module with the 'sample' prefix [Group("sample")] -public class Sample : ModuleBase +public class SampleModule : ModuleBase { // ~sample square 20 -> 400 [Command("square")] [Summary("Squares a number.")] public async Task SquareAsync( - [Summary("The number to square.")] - int num) + [Summary("The number to square.")] + int num) { // We can also access the channel from the Command Context. await Context.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}"); @@ -36,8 +36,8 @@ public class Sample : ModuleBase ("Returns info about the current user, or the user parameter, if one passed.")] [Alias("user", "whois")] public async Task UserInfoAsync( - [Summary("The (optional) user to get info for")] - SocketUser user = null) + [Summary("The (optional) user to get info from")] + SocketUser user = null) { var userInfo = user ?? Context.Client.CurrentUser; await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}");