Browse Source

Split command service guide into various articles

+ Each section has gone through a thorough proof-reading and cleanup
+ This should help with readability.
pull/988/head
Hsu Still 7 years ago
parent
commit
84c42f5e28
No known key found for this signature in database GPG Key ID: 8601A145FDA95209
10 changed files with 186 additions and 142 deletions
  1. +1
    -2
      docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md
  2. +1
    -1
      docs/faq/commands/Commands.md
  3. +6
    -2
      docs/guides/commands/dependency-injection.md
  4. +8
    -114
      docs/guides/commands/intro.md
  5. +53
    -0
      docs/guides/commands/preconditions.md
  6. +12
    -22
      docs/guides/commands/samples/dependency_module.cs
  7. +2
    -0
      docs/guides/commands/samples/empty-module.cs
  8. +29
    -0
      docs/guides/commands/samples/typereader-register.cs
  9. +69
    -0
      docs/guides/commands/typereaders.md
  10. +5
    -1
      docs/guides/toc.yml

+ 1
- 2
docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md View File

@@ -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


+ 1
- 1
docs/faq/commands/Commands.md View File

@@ -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.




+ 6
- 2
docs/guides/commands/dependency-injection.md View File

@@ -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

docs/guides/commands/commands.md → docs/guides/commands/intro.md View File

@@ -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)]

+ 53
- 0
docs/guides/commands/preconditions.md View File

@@ -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*

+ 12
- 22
docs/guides/commands/samples/dependency_module.cs View File

@@ -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;
}

}
}

+ 2
- 0
docs/guides/commands/samples/empty-module.cs View File

@@ -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>
{ {

+ 29
- 0
docs/guides/commands/samples/typereader-register.cs View File

@@ -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)
{
// ...
}
}

+ 69
- 0
docs/guides/commands/typereaders.md View File

@@ -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)]

+ 5
- 1
docs/guides/toc.yml View File

@@ -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


Loading…
Cancel
Save