diff --git a/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md index e395f2058..950d2990c 100644 --- a/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md +++ b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md @@ -6,8 +6,7 @@ remarks: *content The attribute can be applied to a public settable property inside a @Discord.Commands.ModuleBase based class. By applying this attribute, 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 diff --git a/docs/faq/commands/Commands.md b/docs/faq/commands/Commands.md index 7d0bdf8bc..e54daf0ac 100644 --- a/docs/faq/commands/Commands.md +++ b/docs/faq/commands/Commands.md @@ -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 you want it to. Note that you should also learn Microsoft's 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. diff --git a/docs/guides/commands/dependency-injection.md b/docs/guides/commands/dependency-injection.md index fc99cd6e2..db63e00c4 100644 --- a/docs/guides/commands/dependency-injection.md +++ b/docs/guides/commands/dependency-injection.md @@ -16,6 +16,8 @@ DI when writing your modules. to use in the modules. 3. Pass the service collection into `AddModulesAsync`. +### Example - Setting up Injection + [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] ## Usage in Modules @@ -34,8 +36,10 @@ manner. > 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. +> 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 \ No newline at end of file diff --git a/docs/guides/commands/commands.md b/docs/guides/commands/intro.md similarity index 64% rename from docs/guides/commands/commands.md rename to docs/guides/commands/intro.md index 322fa61cd..8036ed09e 100644 --- a/docs/guides/commands/commands.md +++ b/docs/guides/commands/intro.md @@ -99,19 +99,20 @@ For example: * ...etc. 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 #### Optional Parameters 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 +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 @@ -198,7 +199,8 @@ that are placed in the Module's constructor must be injected into an ### Module Properties 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 @@ -216,112 +218,4 @@ 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)] - -# 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` 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)] \ No newline at end of file +[!code-csharp[Groups and Submodules](samples/groups.cs)] \ No newline at end of file diff --git a/docs/guides/commands/preconditions.md b/docs/guides/commands/preconditions.md new file mode 100644 index 000000000..f3b79f505 --- /dev/null +++ b/docs/guides/commands/preconditions.md @@ -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* diff --git a/docs/guides/commands/samples/dependency_module.cs b/docs/guides/commands/samples/dependency_module.cs index 2014ed881..3c9b51d7d 100644 --- a/docs/guides/commands/samples/dependency_module.cs +++ b/docs/guides/commands/samples/dependency_module.cs @@ -1,40 +1,30 @@ -using Discord; -using Discord.Commands; -using Discord.WebSocket; - -public class ModuleA : ModuleBase +public class DatabaseModule : ModuleBase { private readonly DatabaseService _database; // Dependencies can be injected via the constructor - public ModuleA(DatabaseService database) + public DatabaseModule(DatabaseService 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 +public class MixModule : ModuleBase { - // 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 - // be injected. + // be injected [DontInject] public NotificationService NotificationService { get; set; } - - public ModuleB(CommandService commands) - { - Commands = commands; - } - -} +} \ No newline at end of file diff --git a/docs/guides/commands/samples/empty-module.cs b/docs/guides/commands/samples/empty-module.cs index 6483c7cd2..db62032c2 100644 --- a/docs/guides/commands/samples/empty-module.cs +++ b/docs/guides/commands/samples/empty-module.cs @@ -1,5 +1,7 @@ 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 { diff --git a/docs/guides/commands/samples/typereader-register.cs b/docs/guides/commands/samples/typereader-register.cs new file mode 100644 index 000000000..292caea6f --- /dev/null +++ b/docs/guides/commands/samples/typereader-register.cs @@ -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) + { + // ... + } +} \ No newline at end of file diff --git a/docs/guides/commands/typereaders.md b/docs/guides/commands/typereaders.md new file mode 100644 index 000000000..a502fb3c8 --- /dev/null +++ b/docs/guides/commands/typereaders.md @@ -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` 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)] \ No newline at end of file diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml index 9bf259da5..51755b91f 100644 --- a/docs/guides/toc.yml +++ b/docs/guides/toc.yml @@ -20,8 +20,12 @@ topicUid: Guides.Concepts.Entities - name: The Command Service items: - - name: Introduction to Command Service + - name: Introduction topicUid: Guides.Commands.Intro + - name: TypeReaders + topicUid: Guides.Commands.TypeReaders + - name: Preconditions + topicUid: Guides.Commands.Preconditions - name: Dependency Injection topicUid: Guides.Commands.DI - name: Post-execution Handling