Documentation Overhaul (#1161)

* Add XML docs

* Clean up style switcher

* Squash commits on branch docs/faq-n-patches

* Fix broken theme selector

* Add local image embed instruction

* Add a bunch of XML docs

* Add a bunch of XML docs

* Fix broken search
+ DocFX by default ships with an older version of jQuery, switching to a newer version confuses parts of the DocFX Javascript.

* Minor fixes for CONTRIBUTING.md and README.md

* Clean up filterConfig.yml

+ New config exposes Discord.Net namespace since it has several common public exceptions that may be helpful to users

* Add XML docs

* Read token from Environment Variable instead of hardcode

* Add XMLDocs

* Compress some assets & add OAuth2 URL generator

* Fix sample link & add missing pictures

* Add tag examples

* Fix embed docs consistency

* Add details regarding userbot support

* Add XML Docs

* Add XML Docs

* Add XML Docs

* Minor fixes in documentations
+ Fix unescaped '<'
+ Fix typo

* Fix seealso for preconditions and add missing descriptions

* Add missing exceptions

* Document exposed TypeReaders

* Fix letter-casing for files

* Add 'last modified' plugin

Source: https://github.com/Still34/DocFx.Plugin.LastModified
Licensed under MIT License

* XML Docs

* Fix minor consistencies & redundant impl

* Add properties examples to overwrite

* Fix missing Username prop

* Add warning for bulk-delete endpoint

* Replace note block

* Add BaseSocketClient docs

* Add XML docs

* Replace langword null to code block null instead

- Because DocFX sucks at rendering langword

* Replace all langword placements with code block

* Add more IGuild docs

* Add details to SpotifyGame

* Initial proofread of the articles

* Add explanation for RunMode

* Add event docs

- MessageReceived
- ChannelUpdated/Destroyed/Created

* Fix light theme link color

* Fix xml docs error

* Add partial documentation for audit log impl

* Add documentation for some REST-based objects

* Add partial documentation for audit log objects

* Add more XML comments to quotation mark alias map stuff, including an example

* Add reference to CommandServiceConfig from the util docs'

* Add explanation that if " is removed then it wont work

* Fix missing service provider in example

* Add documentation for new INestedChannel

* Add documentation

* Add documentation for new API version & few events

* Revise guide paragraphs/samples

+ Fix various formatting.
+ Provide a more detailed walkthrough for dependency injection.
+ Add C# note at intro.

* Fix typos & formatting

* Improve group module example

* Small amount to see if I'm doing it right

* Remove/cleanup redundant variables

* Fix EnterTypingState impl for doc inheritance

* Fix Test to resolve changes made in 15b58e

* Improve precondition documentation

+ Add precondition usage sample
+ Add precondition group usage sample
+ Move precondition samples to its own sample folder

* Move samples to individual folders

* Clarify token source

* Cleanup styling of README.md for docs

* Replace InvalidPathChars for NS1.3

* InvalidPathChars does not exist in NS1.3; replaced with GetInvalidPathChars instead.

* Add a missing change for 2c7cc738

* Update LastModified to v1.1.0 & add license

* Rewrite installation page for Core 2.1

* Fix anchor link

* Bump post-processor to v1.1.1

* Add fixes to partial file & add license

* Moved theme-switcher code to scripts partial file
+ Add author's MIT license to featherlight javascript

* Remove unused bootstrap plugin

* Bump LastModified plugin

* Changed the path from 'lastmodified' to 'last-modified' for consistency

* Cleanup README & Contribution guide

* Changes to last pr

* Fix GetCategoryAsync docs

* Proofread and cleanup articles

* Change passive voice in "Get Started" to active
* Fix improper preposition in Commands Introduction page
* Fix minor grammar mistakes in "Your First Bot" (future tense -> present tense/subjunctive mood -> indicative mood/proper noun casing/incorrect noun/add missing article)
* Fix minor grammar mistakes in "Installation" (missing article)

* no hablo ingles

* Try try try again

* I'm sure you're having as much fun as I am

* Cleanup TOC & fix titles

* Improve styling

+ Change title font to Noto Sans
+ Add materialized design for commit message box

* Add DescriptionGenerator plugin

* Add nightly section for clarification

* Fix typos in Nightlies & Post-execution

* Bump DescriptionGenerator to v1.1.0

+ This build adds the functionality of generating managed references' summary into the description tag.

* Initial emoji article draft

* Add 'additional information' section for emoji article

* Add cosmetic changes to the master css

* Alter info box color
+ Add transition to article content

* Add clarification in the emoji article

* Emphasize that normal emoji string will not translate to its Unicode representation.
* Clean up or add some of the samples featured in the article.
+ Add emoji/emote declaration section for clarification.
+ Add WebSocket emote sample.
- Remove inconsistent styling ('wacky memes' proves to be too out of place).

* Improve readability for nightlies article

* Move 'Bundled Preconditions' section

* Bump LastModified to fix UTC DateTime parsing

* Add langwordMapping.yml

* Add XML docs

* Add VSC workspace rule

* The root workspace limits the ruler to 120 characters for member documentations and excludes folders such as 'samples' and 'docs'.
* The docs workspace limits the ruler to 70 characters for standard conceptual article to comply with documentation's CONTRIBUTING.md rule, and excludes temprorary folders created by DocFX.

* Update CONTRIBUTING.md

* Add documentation style rule

* Fix styling of several member documentation

* Fix ' />' caused by Agent Smith oddities
* Fix styling to be more specific about the mention of IDs

* Fix exception summary to comply with official Microsoft Docs style

* References
https://docs.microsoft.com/en-us/dotnet/api/system.argumentnullexception?view=netframework-4.7.2
https://docs.microsoft.com/en-us/dotnet/api/system.platformnotsupportedexception?view=netframework-4.7.2
https://docs.microsoft.com/en-us/dotnet/api/system.badimageformatexception?view=netframework-4.7.2

* Add XML documentations

* Shift color return docs

* Fix minor docs

* Added documentation for SocketDMChannel, SocketGuildChannel, and SocketTextChannel

* Add XML docs

* Corrections to SocketGuildChannel

* Corrections to SocketTextChannel

* Corrections to SocketDMChannel

* Swapped out 'id' for 'snowflake identifier

* Swapped out 'id' for 'snowflake identifier'

* SocketDMChannel amendments

* SocketGuildChannel amendments

* SocketTextChannel amendments

* Add XML docs & patch return types
+ Starting from this commit, all return types for tasks will use style similar to most documentations featured on docs.microsoft.com

References:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.-ctor?view=efcore-2.1
https://docs.microsoft.com/en-us/dotnet/api/system.io.filestream.readasync?view=netcore-2.1
https://docs.microsoft.com/en-us/dotnet/api/system.io.textwriter.writelineasync?view=netcore-2.1#System_IO_TextWriter_WriteLineAsync_System_Char___
And many more other asynchronous method documentations featured in the latest BCL.

* Added documentation for many audit log data types, fixed vowel indefinite articles

* Change audit log data types to start with 'Contains' (verb) instead of an article

* Fix some documentation issues and document some more audit log data types

* Fix English posession

* Add XML doc

* Documented two more types

* Documented RoleCreateAuditLogData

* Document remaining audit log data types

* Added RestDMChannel documentation

* Added RestGuildChannel documentation

* Added RestTextChannel documentation

* Added RestVoiceChannel documentation

* Added RestUser documentation

* Added RestRole documentation

* Added RestMessage documentation

* Slightly better wording

* Contains -> Contains a piece of (describe article)

* [EN] Present perf. -> past perf.

* Add XML docs

* Fix arrow alignment

* Clarify supported nullable type

* Fixed a typo in ISnowflakeEntity

* Added RestUser Documentation

* Added RestInvite documentation

* Add XML docs & minor optimizations

* Minor optimization for doc rendering

* Rollback font optimization changes

* Amendments to RestUser

* Added SocketDMChannel documentation

* Added RestDMChannel documentation

* Added RestGuild documentation

* Adjustment to SocketDMChannel

* Added minimal descriptions from the API documentation for Integration types

* Added obsolete mention to the ReadMessages flag.

* Added remarks about 2FA requirement for guild permissions

* Added xmldoc for GuildPermission methods

* Added xml doc for ToAllowList and ToDenyList

* Added specification of how the bits of the color raw value are packed

* Added discord API documentation to IConnection interface

* I can spell :^)

* Fix whitespace in ChannelPermission

* fix spacing of values in guildpermission

* Made changes to get field descriptions from feedback, added returns tag to IConnection

* Added property get standard for IntegrationAccount

* Added property get pattern to xml docs and identical returns tag.

* Change all color class references to struct
...because it isn't a class.

* Add XML docs

* Rewrote the returns tags in IGuildIntegration, removed the ones I was unsure about.

* Rewrote the rest of the returns tags

* Amendments

* Cleanup doc for c1d78189

* Added types to <returns> tags where missing

* Added second sample for adding reactions

* Added some class summaries

* Missed a period

* Amendments

* restored the removed line break

* Removed unnecessary see tag

* Use consistent quotation marks around subscribers, the name for these users are dependant on the source of where they are integrated from (youtube or twitch), so we should not use a name that is specific to one platform

* Add <remarks> tag to the IGuildIntegration xmldocs

* Fix grammar issue

* Update DescriptionGenerator

* Cleanup of https://github.com/Still34/Discord.Net/pull/8

* Cleanup previous PR

* Fix for misleading behaviour in the emoji guide
+ Original lines stated that sending a emoji wrapped in colon will not be parsed, but that was incorrect; replaced with reactions instead of sending messages as the example

* Add strings for dictionary in DotSettings

* Add XML docs

* Fix lots of typos in comments
+ Geez, I didn't know there were so many.

* Add XML docs & rewrite GetMessagesAsync docs

This commit rewrites the remarks section of GetMessagesAsync, as well as adding examples to several methods.

* Update 'Your First Bot'
+ This commit reflects the new changes made to the Discord Application Developer Portal after its major update

* Initial optimization for DocFX render & add missing files

* Add examples in message methods

* Cleanup https://github.com/RogueException/Discord.Net/pull/1128

* Fix first bot note

* Cleanup FAQ structure

* Add XML docs

* Update docfx plugins

* Fix navbar collapsing issue

* Fix broken xref

* Cleanup FAQ section
+ Add introductory paragraphs to each FAQ section.
+ Add 'missing dependency' entry to commands FAQ.
* Split commands FAQ to 'General' and 'DI' sections.

* Cleanup https://github.com/RogueException/Discord.Net/pull/1139

* Fix missing namespace

* Add missing highlighting css for the light theme

* Add additional clarification for installing packages

* Add indentation to example for clarity

* Cleanup several articles to be more human-friendly and easier to read

* Remove RPC-related notes

* Cleanup slow-mode-related documentation strings

* Add an additional note about cross-guild emote usage

* Add CreateTextChannel sample

* Add XMLDocs
This commit is contained in:
Still Hsu
2018-10-01 05:44:33 +08:00
committed by Christopher F
parent 6b21b11f7d
commit ff0fea98a6
498 changed files with 16064 additions and 2633 deletions

View File

@@ -1,343 +0,0 @@
# The Command Service
[Discord.Commands](xref:Discord.Commands) provides an Attribute-based
command parser.
## Setup
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
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.
[!code-csharp[Command Handler](samples/command_handler.cs)]
[Command Service]: xref:Discord.Commands.CommandService
[CommandServiceConfig]: xref:Discord.Commands.CommandServiceConfig
## With Attributes
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.
### Modules
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.
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,
instead, 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].
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.
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
### Adding 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.
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_.
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"`.
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)
[RemainderAttribute]: xref:Discord.Commands.RemainderAttribute
[CommandAttribute]: xref:Discord.Commands.CommandAttribute
### Command Overloads
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.
The `Priority` attributes are sorted in ascending order; the higher
priority will be called first.
### 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.
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.
> [!WARNING]
>Contexts should **NOT** be mixed! You cannot have one module that
>uses `CommandContext` and another that uses `SocketCommandContext`.
[Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context
[SocketCommandContext]: xref:Discord.Commands.SocketCommandContext
[ReplyAsync]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_ReplyAsync_System_String_System_Boolean_Discord_Embed_Discord_RequestOptions_
### 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.
To opt a module out of auto-loading, flag it with
[DontAutoLoadAttribute].
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_
#### 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.
[CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1
### 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.
### Module Properties
Modules with `public` settable properties will have the dependencies
injected after the construction of the Module.
### 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 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 used to create nested groups (although not required to
create nested groups).
[!code-csharp[Groups and Submodules](samples/groups.cs)]
## With Builders
**TODO**
## 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.
### Setup
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.
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)]
### 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`.
Any publicly settable properties will also be filled in the same
manner.
>[!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.
[!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)]
[DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute
# 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
## 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 [CheckPermissions] 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)]
[CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_ICommandContext_Discord_Commands_CommandInfo_IServiceProvider_
[PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromSuccess
[PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromError_System_String_
# 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
- IMessage/IUserMessage
- IChannel/IGuildChannel/ITextChannel/IVoiceChannel/IGroupChannel
- IUser/IGuildUser/IGroupUser
- 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 [Read] 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#Discord_Commands_TypeReaderResult_FromSuccess_Discord_Commands_TypeReaderValue_
[TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult#Discord_Commands_TypeReaderResult_FromError_Discord_Commands_CommandError_System_String_
[Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_ICommandContext_System_String_IServiceProvider_
#### Sample
[!code-csharp[TypeReaders](samples/typereader.cs)]
### Installing TypeReaders
TypeReaders are not automatically discovered by the Command Service
and must be explicitly added.
To install a TypeReader, invoke [CommandService.AddTypeReader].
[CommandService.AddTypeReader]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_

View File

@@ -0,0 +1,47 @@
---
uid: Guides.Commands.DI
title: 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.
## Setup
1. Create a @Microsoft.Extensions.DependencyInjection.ServiceCollection.
2. Add the dependencies to the service collection that you wish
to use in the modules.
3. Build the service collection into a service provider.
4. Pass the service collection into @Discord.Commands.CommandService.AddModulesAsync* / @Discord.Commands.CommandService.AddModuleAsync* , @Discord.Commands.CommandService.ExecuteAsync* .
### Example - Setting up Injection
[!code-csharp[IServiceProvider Setup](samples/dependency-injection/dependency_map_setup.cs)]
## Usage in Modules
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
> 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 `IServiceProvider` that is passed into it respectively.
### Example - Injection in Modules
[!code-csharp[Injection Modules](samples/dependency-injection/dependency_module.cs)]
[!code-csharp[Disallow Dependency Injection](samples/dependency-injection/dependency_module_noinject.cs)]
[DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute

View File

@@ -0,0 +1,221 @@
---
uid: Guides.Commands.Intro
title: Introduction to Command Service
---
# The Command Service
[Discord.Commands](xref:Discord.Commands) provides an attribute-based
command parser.
## Get Started
To use commands, you must create a [Command Service] and a command
handler.
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.
> [!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/intro/command_handler.cs)]
[Command Service]: xref:Discord.Commands.CommandService
[CommandServiceConfig]: xref:Discord.Commands.CommandServiceConfig
## With Attributes
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.
### Modules
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.
Discord.Net's implementation of "modules" is influenced heavily by the
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.
Before we create a module, it is **crucial** for you to remember that
in order to create a module and have it automatically discovered,
your module must:
* Be public
* Inherit [ModuleBase]
By now, your module should look like this:
[!code-csharp[Empty Module](samples/intro/empty-module.cs)]
> [!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.
[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/Creating Commands
> [!WARNING]
> **Avoid using long-running code** in your modules wherever possible.
> You should **not** be implementing very much logic into your
> modules, instead, 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].
The next step to creating commands is actually creating the commands.
For a command to be valid, it **must** have a return type of `Task`
or `Task<RuntimeResult>`. Typically, you might want to mark this
method as `async`, although it is not required.
Then, 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).
### Command Parameters
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 num`.
* To take a user as an argument from the user, add `IUser user`.
* ...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 @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`).
#### 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
`!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.
[RemainderAttribute]: xref:Discord.Commands.RemainderAttribute
### Command Overloads
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.
The `Priority` attributes are sorted in ascending order; the higher
priority will be called first.
### Command Context
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
that the command was invoked from.
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.
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`.
[Context]: xref:Discord.Commands.ModuleBase`1.Context
[SocketCommandContext]: xref:Discord.Commands.SocketCommandContext
[ReplyAsync]: xref:Discord.Commands.ModuleBase`1.ReplyAsync*
> [!TIP]
> At this point, your module should look comparable to this example:
> [!code-csharp[Example Module](samples/intro/module.cs)]
#### Loading Modules Automatically
The Command Service can automatically discover all classes in an
`Assembly` that inherit [ModuleBase] and load them. Invoke
[CommandService.AddModulesAsync] to discover modules and
install them.
To opt a module out of auto-loading, flag it with
[DontAutoLoadAttribute].
[DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute
[CommandService.AddModulesAsync]: xref:Discord.Commands.CommandService.AddModulesAsync*
#### Loading Modules Manually
To manually load a module, invoke [CommandService.AddModuleAsync] by
passing in the generic type of your module and optionally, a
service provider.
[CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService.AddModuleAsync*
### Module Constructors
Modules are constructed using @Guides.Commands.DI. Any parameters
that are placed in the Module's constructor must be injected into an
@System.IServiceProvider first.
> [!TIP]
> Alternatively, you may accept an
> `IServiceProvider` as an argument and extract services yourself,
> although this is discouraged.
### Module Properties
Modules with `public` settable properties will have the dependencies
injected after the construction of the module. See @Guides.Commands.DI
to learn more.
### 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 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 used to create nested groups (although not required to
create nested groups).
[!code-csharp[Groups and Submodules](samples/intro/groups.cs)]

View File

@@ -0,0 +1,122 @@
---
uid: Guides.Commands.PostExecution
title: Post-command Execution Handling
---
# Post-execution Handling for Commands
When developing commands, you may want to consider building a
post-execution handling system so you can have finer control
over commands. Discord.Net offers several post-execution workflows
for you to work with.
If you recall, in the [Command Guide], we have shown the following
example for executing and handling commands,
[!code[Command Handler](samples/intro/command_handler.cs)]
You may notice that after we perform [ExecuteAsync], we store the
result and print it to the chat, essentially creating the most
fundamental form of a post-execution handler.
With this in mind, we could start doing things like the following,
[!code[Basic Command Handler](samples/post-execution/post-execution_basic.cs)]
However, this may not always be preferred, because you are
creating your post-execution logic *with* the essential command
handler. This design could lead to messy code and could potentially
be a violation of the SRP (Single Responsibility Principle).
Another major issue is if your command is marked with
`RunMode.Async`, [ExecuteAsync] will **always** return a successful
[ExecuteResult] instead of the actual result. You can learn more
about the impact in the [FAQ](xref:FAQ.Commands.General).
## CommandExecuted Event
Enter [CommandExecuted], an event that was introduced in
Discord.Net 2.0. This event is raised whenever a command is
successfully executed **without any run-time exceptions** or **any
parsing or precondition failure**. This means this event can be
used to streamline your post-execution design, and the best thing
about this event is that it is not prone to `RunMode.Async`'s
[ExecuteAsync] drawbacks.
Thus, we can begin working on code such as:
[!code[CommandExecuted demo](samples/post-execution/command_executed_demo.cs)]
So now we have a streamlined post-execution pipeline, great! What's
next? We can take this further by using [RuntimeResult].
### RuntimeResult
`RuntimeResult` was initially introduced in 1.0 to allow
developers to centralize their command result logic.
In other words, it is a result type that is designed to be
returned when the command has finished its execution.
However, it wasn't widely adopted due to the aforementioned
[ExecuteAsync] drawback. Since we now have access to a proper
result-handler via the [CommandExecuted] event, we can start
making use of this class.
The best way to make use of it is to create your version of
`RuntimeResult`. You can achieve this by inheriting the `RuntimeResult`
class.
The following creates a bare-minimum required for a sub-class
of `RuntimeResult`,
[!code[Base Use](samples/post-execution/customresult_base.cs)]
The sky is the limit from here. You can add any additional information
you would like regarding the execution result.
For example, you may want to add your result type or other
helpful information regarding the execution, or something
simple like static methods to help you create return types easily.
[!code[Extended Use](samples/post-execution/customresult_extended.cs)]
After you're done creating your [RuntimeResult], you can
implement it in your command by marking the command return type to
`Task<RuntimeResult>`.
> [!NOTE]
> You must mark the return type as `Task<RuntimeResult>` instead of
> `Task<MyCustomResult>`. Only the former will be picked up when
> building the module.
Here's an example of a command that utilizes such logic:
[!code[Usage](samples/post-execution/customresult_usage.cs)]
And now we can check for it in our [CommandExecuted] handler:
[!code[Usage](samples/post-execution/command_executed_adv_demo.cs)]
## CommandService.Log Event
We have so far covered the handling of various result types, but we
have not talked about what to do if the command enters a catastrophic
failure (i.e., exceptions). To resolve this, we can make use of the
[CommandService.Log] event.
All exceptions thrown during a command execution are caught and sent
to the Log event under the [LogMessage.Exception] property
as a [CommandException] type. The [CommandException] class allows
us to access the exception thrown, as well as the context
of the command.
[!code[Logger Sample](samples/post-execution/command_exception_log.cs)]
[CommandException]: xref:Discord.Commands.CommandException
[LogMessage.Exception]: xref:Discord.LogMessage.Exception
[CommandService.Log]: xref:Discord.Commands.CommandService.Log
[RuntimeResult]: xref:Discord.Commands.RuntimeResult
[CommandExecuted]: xref:Discord.Commands.CommandService.CommandExecuted
[ExecuteAsync]: xref:Discord.Commands.CommandService.ExecuteAsync*
[ExecuteResult]: xref:Discord.Commands.ExecuteResult
[Command Guide]: xref:Guides.Commands.Intro

View File

@@ -0,0 +1,83 @@
---
uid: Guides.Commands.Preconditions
title: Preconditions
---
# Preconditions
Preconditions 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 ships with several bundled Preconditions for you
to use.
* @Discord.Commands.RequireContextAttribute
* @Discord.Commands.RequireOwnerAttribute
* @Discord.Commands.RequireBotPermissionAttribute
* @Discord.Commands.RequireUserPermissionAttribute
* @Discord.Commands.RequireNsfwAttribute
## Using Preconditions
To use a precondition, simply apply any valid precondition candidate to
a command method signature as an attribute.
### Example - Using a Precondition
[!code-csharp[Precondition usage](samples/preconditions/precondition_usage.cs)]
## ORing Preconditions
When writing commands, you may want to allow some of them to be
executed when only some of the precondition checks are passed.
This is where the [Group] property of a precondition attribute comes in
handy. By assigning two or more preconditions to a group, the command
system will allow the command to be executed when one of the
precondition passes.
### Example - ORing Preconditions
[!code-csharp[OR Precondition](samples/preconditions/group_precondition.cs)]
[Group]: xref:Discord.Commands.PreconditionAttribute.Group
## 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.
If the context meets the required parameters, return
[PreconditionResult.FromSuccess], otherwise return
[PreconditionResult.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.
### Example - Creating a Custom Precondition
[!code-csharp[Custom Precondition](samples/preconditions/require_owner.cs)]
[CheckPermissionsAsync]: xref:Discord.Commands.PreconditionAttribute.CheckPermissionsAsync*
[PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult.FromSuccess*
[PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult.FromError*

View File

@@ -1,63 +0,0 @@
using System;
using System.Threading.Tasks;
using System.Reflection;
using Discord;
using Discord.WebSocket;
using Discord.Commands;
using Microsoft.Extensions.DependencyInjection;
public class Program
{
private CommandService _commands;
private DiscordSocketClient _client;
private IServiceProvider _services;
private static void Main(string[] args) => new Program().StartAsync().GetAwaiter().GetResult();
public async Task StartAsync()
{
_client = new DiscordSocketClient();
_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
_client.MessageReceived += HandleCommandAsync;
// Discover all of the commands in this assembly and load them.
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
}
private async Task HandleCommandAsync(SocketMessage messageParam)
{
// Don't process the command if it was a System Message
var message = messageParam as SocketUserMessage;
if (message == null) return;
// Create a number to track where the prefix ends and the command begins
int argPos = 0;
// Determine if the message is a command, based on if it starts with '!' or a mention prefix
if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return;
// Create a Command Context
var context = new SocketCommandContext(_client, message);
// Execute the command. (result does not indicate a return value,
// rather an object stating if the command executed successfully)
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess)
await context.Channel.SendMessageAsync(result.ErrorReason);
}
}

View File

@@ -0,0 +1,62 @@
public class Initialize
{
private readonly CommandService _commands;
private readonly DiscordSocketClient _client;
public Initialize(CommandService commands = null, DiscordSocketClient client = null)
{
_commands = commands ?? new CommandService();
_client = client ?? new DiscordSocketClient();
}
public IServiceProvider BuildServiceProvider() => new ServiceCollection()
.AddSingleton(_client)
.AddSingleton(_commands)
// You can pass in an instance of the desired type
.AddSingleton(new NotificationService())
// ...or by using the generic method.
//
// The benefit of using the generic method is that
// ASP.NET DI will attempt to inject the required
// dependencies that are specified under the constructor
// for us.
.AddSingleton<DatabaseService>()
.AddSingleton<CommandHandler>()
.BuildServiceProvider();
}
public class CommandHandler
{
private readonly DiscordSocketClient _client;
private readonly CommandService _commands;
private readonly IServiceProvider _services;
public CommandHandler(IServiceProvider services, CommandService commands, DiscordSocketClient client)
{
_commands = commands;
_services = services;
_client = client;
}
public async Task InitializeAsync()
{
// Pass the service provider to the second parameter of
// AddModulesAsync to inject dependencies to all modules
// that may require them.
await _commands.AddModulesAsync(
assembly: Assembly.GetEntryAssembly(),
services: _services);
_client.MessageReceived += HandleCommandAsync;
}
public async Task HandleCommandAsync(SocketMessage msg)
{
// ...
// Pass the service provider to the ExecuteAsync method for
// precondition checks.
await _commands.ExecuteAsync(
context: context,
argPos: argPos,
services: _services);
// ...
}
}

View File

@@ -0,0 +1,37 @@
// After setting up dependency injection, modules will need to request
// the dependencies to let the library know to pass
// them along during execution.
// Dependency can be injected in two ways with Discord.Net.
// You may inject any required dependencies via...
// the module constructor
// -or-
// public settable properties
// Injection via constructor
public class DatabaseModule : ModuleBase<SocketCommandContext>
{
private readonly DatabaseService _database;
public DatabaseModule(DatabaseService database)
{
_database = database;
}
[Command("read")]
public async Task ReadFromDbAsync()
{
await ReplyAsync(_database.GetData());
}
}
// Injection via public settable properties
public class DatabaseModule : ModuleBase<SocketCommandContext>
{
public DatabaseService DbService { get; set; }
[Command("read")]
public async Task ReadFromDbAsync()
{
await ReplyAsync(_database.GetData());
}
}

View File

@@ -0,0 +1,29 @@
// Sometimes injecting dependencies automatically with the provided
// methods in the prior example may not be desired.
// You may explicitly tell Discord.Net to **not** inject the properties
// by either...
// restricting the access modifier
// -or-
// applying DontInjectAttribute to the property
// Restricting the access modifier of the property
public class ImageModule : ModuleBase<SocketCommandContext>
{
public ImageService ImageService { get; }
public ImageModule()
{
ImageService = new ImageService();
}
}
// Applying DontInjectAttribute
public class ImageModule : ModuleBase<SocketCommandContext>
{
[DontInject]
public ImageService ImageService { get; set; }
public ImageModule()
{
ImageService = new ImageService();
}
}

View File

@@ -1,18 +0,0 @@
private IServiceProvider _services;
private CommandService _commands;
public async Task InstallAsync(DiscordSocketClient client)
{
// Here, we will inject the ServiceProvider with
// all of the services our client will use.
_services = new ServiceCollection()
.AddSingleton(client)
.AddSingleton(_commands)
// You can pass in an instance of the desired type
.AddSingleton(new NotificationService())
// ...or by using the generic method.
.AddSingleton<DatabaseService>()
.BuildServiceProvider();
// ...
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
}

View File

@@ -1,40 +0,0 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
public class ModuleA : ModuleBase
{
private readonly DatabaseService _database;
// Dependencies can be injected via the constructor
public ModuleA(DatabaseService database)
{
_database = database;
}
public async Task ReadFromDb()
{
var x = _database.getX();
await ReplyAsync(x);
}
}
public class ModuleB
{
// Public settable properties will be injected
public AnnounceService { get; set; }
// Public properties without setters will not
public CommandService Commands { get; }
// Public properties annotated with [DontInject] will not
[DontInject]
public NotificationService { get; set; }
public ModuleB(CommandService commands)
{
Commands = commands;
}
}

View File

@@ -1,6 +0,0 @@
using Discord.Commands;
public class InfoModule : ModuleBase<SocketCommandContext>
{
}

View File

@@ -0,0 +1,63 @@
public class CommandHandler
{
private readonly DiscordSocketClient _client;
private readonly CommandService _commands;
public CommandHandler(DiscordSocketClient client, CommandService commands)
{
_commands = commands;
_client = client;
}
public async Task InstallCommandsAsync()
{
// Hook the MessageReceived event into our command handler
_client.MessageReceived += HandleCommandAsync;
// Here we discover all of the command modules in the entry
// assembly and load them. Starting from Discord.NET 2.0, a
// service provider is required to be passed into the
// module registration method to inject the
// required dependencies.
//
// If you do not use Dependency Injection, pass null.
// See Dependency Injection guide for more information.
await _commands.AddModulesAsync(assembly: Assembly.GetEntryAssembly(),
services: null);
}
private async Task HandleCommandAsync(SocketMessage messageParam)
{
// Don't process the command if it was a system message
var message = messageParam as SocketUserMessage;
if (message == null) return;
// Create a number to track where the prefix ends and the command begins
int argPos = 0;
// Determine if the message is a command based on the prefix
if (!(message.HasCharPrefix('!', ref argPos) ||
message.HasMentionPrefix(_client.CurrentUser, ref argPos)))
return;
// Create a WebSocket-based command context based on the message
var context = new SocketCommandContext(_client, message);
// Execute the command with the command context we just
// created, along with the service provider for precondition checks.
// Keep in mind that result does not indicate a return value
// rather an object stating if the command executed successfully.
var result = await _command.ExecuteAsync(
context: context,
argPos: argPos,
services: null);
// Optionally, we may inform the user if the command fails
// to be executed; however, this may not always be desired,
// as it may clog up the request queue should a user spam a
// command.
// if (!result.IsSuccess)
// await context.Channel.SendMessageAsync(result.ErrorReason);
}
}

View File

@@ -0,0 +1,8 @@
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>
{
}

View File

@@ -4,15 +4,22 @@ public class AdminModule : ModuleBase<SocketCommandContext>
[Group("clean")]
public class CleanModule : ModuleBase<SocketCommandContext>
{
// ~admin clean 15
// ~admin clean
[Command]
public async Task Default(int count = 10) => Messages(count);
public async Task DefaultCleanAsync()
{
// ...
}
// ~admin clean messages 15
[Command("messages")]
public async Task Messages(int count = 10) { }
public async Task CleanAsync(int count)
{
// ...
}
}
// ~admin ban foxbot#0282
[Command("ban")]
public async Task Ban(IGuildUser user) { }
public Task BanAsync(IGuildUser user) =>
Context.Guild.AddBanAsync(user);
}

View File

@@ -1,24 +1,25 @@
// Create a module with no prefix
public class Info : ModuleBase<SocketCommandContext>
public class InfoModule : ModuleBase<SocketCommandContext>
{
// ~say hello -> hello
// ~say hello world -> hello world
[Command("say")]
[Summary("Echoes a message.")]
public async Task SayAsync([Remainder] [Summary("The text to echo")] string echo)
{
// ReplyAsync is a method on ModuleBase
await ReplyAsync(echo);
}
public Task SayAsync([Remainder] [Summary("The text to echo")] string echo)
=> ReplyAsync(echo);
// ReplyAsync is a method on ModuleBase
}
// Create a module with the 'sample' prefix
[Group("sample")]
public class Sample : ModuleBase<SocketCommandContext>
public class SampleModule : ModuleBase<SocketCommandContext>
{
// ~sample square 20 -> 400
[Command("square")]
[Summary("Squares a number.")]
public async Task SquareAsync([Summary("The number to square.")] int num)
public async Task SquareAsync(
[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)}");
@@ -31,9 +32,12 @@ public class Sample : ModuleBase<SocketCommandContext>
// ~sample userinfo 96642168176807936 --> Khionu#8708
// ~sample whois 96642168176807936 --> Khionu#8708
[Command("userinfo")]
[Summary("Returns info about the current user, or the user parameter, if one passed.")]
[Summary
("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)
public async Task UserInfoAsync(
[Summary("The (optional) user to get info from")]
SocketUser user = null)
{
var userInfo = user ?? Context.Client.CurrentUser;
await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}");

View File

@@ -0,0 +1,13 @@
public async Task LogAsync(LogMessage logMessage)
{
// This casting type requries C#7
if (logMessage.Exception is CommandException cmdException)
{
// We can tell the user that something unexpected has happened
await cmdException.Context.Channel.SendMessageAsync("Something went catastrophically wrong!");
// We can also log this incident
Console.WriteLine($"{cmdException.Context.User} failed to execute '{cmdException.Command.Name}' in {cmdException.Context.Channel}.");
Console.WriteLine(cmdException.ToString());
}
}

View File

@@ -0,0 +1,13 @@
public async Task OnCommandExecutedAsync(CommandInfo command, ICommandContext context, IResult result)
{
switch(result)
{
case MyCustomResult customResult:
// do something extra with it
break;
default:
if (!string.IsNullOrEmpty(result.ErrorReason))
await context.Channel.SendMessageAsync(result.ErrorReason);
break;
}
}

View File

@@ -0,0 +1,38 @@
public async Task SetupAsync()
{
await _command.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
// Hook the execution event
_command.CommandExecuted += OnCommandExecutedAsync;
// Hook the command handler
_client.MessageReceived += HandleCommandAsync;
}
public async Task OnCommandExecutedAsync(CommandInfo command, ICommandContext context, IResult result)
{
// We have access to the information of the command executed,
// the context of the command, and the result returned from the
// execution in this event.
// We can tell the user what went wrong
if (!string.IsNullOrEmpty(result?.ErrorReason))
{
await context.Channel.SendMessageAsync(result.ErrorReason);
}
// ...or even log the result (the method used should fit into
// your existing log handler)
await _log.LogAsync(new LogMessage(LogSeverity.Info, "CommandExecution", $"{command?.Name} was executed at {DateTime.UtcNow}."));
}
public async Task HandleCommandAsync(SocketMessage msg)
{
var message = messageParam as SocketUserMessage;
if (message == null) return;
int argPos = 0;
if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return;
var context = new SocketCommandContext(_client, message);
var result = await _commands.ExecuteAsync(context, argPos, _services);
// Optionally, you may pass the result manually into your
// CommandExecuted event handler if you wish to handle parsing or
// precondition failures in the same method.
// await OnCommandExecutedAsync(null, context, result);
}

View File

@@ -0,0 +1,6 @@
public class MyCustomResult : RuntimeResult
{
public MyCustomResult(CommandError? error, string reason) : base(error, reason)
{
}
}

View File

@@ -0,0 +1,10 @@
public class MyCustomResult : RuntimeResult
{
public MyCustomResult(CommandError? error, string reason) : base(error, reason)
{
}
public static MyCustomResult FromError(string reason) =>
new MyCustomResult(CommandError.Unsuccessful, reason);
public static MyCustomResult FromSuccess(string reason = null) =>
new MyCustomResult(null, reason);
}

View File

@@ -0,0 +1,10 @@
public class MyModule : ModuleBase<SocketCommandContext>
{
[Command("eat")]
public async Task<RuntimeResult> ChooseAsync(string food)
{
if (food == "salad")
return MyCustomResult.FromError("No, I don't want that!");
return MyCustomResult.FromSuccess($"Give me the {food}!").
}
}

View File

@@ -0,0 +1,11 @@
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (result.CommandError != null)
switch(result.CommandError)
{
case CommandError.BadArgCount:
await context.Channel.SendMessageAsync("Parameter count does not match any command's.");
break;
default:
await context.Channel.SendMessageAsync($"An error has occurred {result.ErrorReason}");
break;
}

View File

@@ -0,0 +1,9 @@
// The following example only requires the user to either have the
// Administrator permission in this guild or own the bot application.
[RequireUserPermission(GuildPermission.Administrator, Group = "Permission")]
[RequireOwner(Group = "Permission")]
public class AdminModule : ModuleBase<SocketCommandContext>
{
[Command("ban")]
public Task BanAsync(IUser user) => Context.Guild.AddBanAsync(user);
}

View File

@@ -0,0 +1,3 @@
[RequireOwner]
[Command("echo")]
public Task EchoAsync(string input) => ReplyAsync(input);

View File

@@ -1,4 +1,5 @@
// (Note: This precondition is obsolete, it is recommended to use the RequireOwnerAttribute that is bundled with Discord.Commands)
// (Note: This precondition is obsolete, it is recommended to use the
// RequireOwnerAttribute that is bundled with Discord.Commands)
using Discord.Commands;
using Discord.WebSocket;
@@ -10,10 +11,13 @@ using System.Threading.Tasks;
public class RequireOwnerAttribute : PreconditionAttribute
{
// Override the CheckPermissions method
public async override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
public async override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
{
// Get the client via Depedency Injection
var client = services.GetRequiredService<DiscordSocketClient>();
// Get the ID of the bot's owner
var ownerId = (await services.GetService<DiscordSocketClient>().GetApplicationInfoAsync()).Owner.Id;
var appInfo = await client.GetApplicationInfoAsync().ConfigureAwait(false);
var ownerId = appInfo.Owner.Id;
// If this command was executed by that user, return a success
if (context.User.Id == ownerId)
return PreconditionResult.FromSuccess();

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

View File

@@ -1,10 +1,11 @@
// Note: This example is obsolete, a boolean type reader is bundled with Discord.Commands
// Note: This example is obsolete, a boolean type reader is bundled
// with Discord.Commands
using Discord;
using Discord.Commands;
public class BooleanTypeReader : TypeReader
{
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{
bool result;
if (bool.TryParse(input, out result))

View File

@@ -0,0 +1,70 @@
---
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`
* `enum`
* `DateTime`/`DateTimeOffset`/`TimeSpan`
* Any nullable value-type (e.g. `int?`, `bool?`)
* 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/typereaders/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/typereaders/typereader-register.cs)]