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

View File

@@ -1,51 +1,50 @@
---
uid: Guides.Concepts.ManageConnections
title: Managing Connections
---
# Managing Connections with Discord.Net
In Discord.Net, once a client has been started, it will automatically
maintain a connection to Discord's gateway, until it is manually
maintain a connection to Discord's gateway until it is manually
stopped.
### Usage
## Usage
To start a connection, invoke the `StartAsync` method on a client that
supports a WebSocket connection.
These clients include the [DiscordSocketClient] and
[DiscordRpcClient], as well as Audio clients.
To end a connection, invoke the `StopAsync` method. This will
gracefully close any open WebSocket or UdpSocket connections.
supports a WebSocket connection; to end a connection, invoke the
`StopAsync` method, which gracefully closes any open WebSocket or
UdpSocket connections.
Since the Start/Stop methods only signal to an underlying connection
manager that a connection needs to be started, **they return before a
connection is actually made.**
manager that a connection needs to be started, **they return before a
connection is made.**
As a result, you will need to hook into one of the connection-state
As a result, you need to hook into one of the connection-state
based events to have an accurate representation of when a client is
ready for use.
All clients provide a `Connected` and `Disconnected` event, which is
raised respectively when a connection opens or closes. In the case of
the DiscordSocketClient, this does **not** mean that the client is
the [DiscordSocketClient], this does **not** mean that the client is
ready to be used.
A separate event, `Ready`, is provided on DiscordSocketClient, which
A separate event, `Ready`, is provided on [DiscordSocketClient], which
is raised only when the client has finished guild stream or guild
sync, and has a complete guild cache.
sync and has a completed guild cache.
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient
[DiscordRpcClient]: xref:Discord.Rpc.DiscordRpcClient
### Samples
[!code-csharp[Connection Sample](samples/events.cs)]
### Tips
## Reconnection
Avoid running long-running code on the gateway! If you deadlock the
gateway (as explained in [events]), the connection manager will be
unable to recover and reconnect.
> [!TIP]
> Avoid running long-running code on the gateway! If you deadlock the
> gateway (as explained in [events]), the connection manager will
> **NOT** be able to recover and reconnect.
Assuming the client disconnected because of a fault on Discord's end,
and not a deadlock on your end, we will always attempt to reconnect
@@ -53,6 +52,6 @@ and resume a connection.
Don't worry about trying to maintain your own connections, the
connection manager is designed to be bulletproof and never fail - if
your client doesn't manage to reconnect, you've found a bug!
your client does not manage to reconnect, you have found a bug!
[events]: events.md
[events]: xref:Guides.Concepts.Events

View File

@@ -1,23 +1,26 @@
---
uid: Guides.Concepts.Entities
title: Entities
---
>[!NOTE]
This article is written with the Socket variants of entities in mind,
not the general interfaces or Rest/Rpc entities.
# Entities in Discord.Net
> [!NOTE]
> This article is written with the Socket variants of entities in mind,
> not the general interfaces or Rest/Rpc entities.
Discord.Net provides a versatile entity system for navigating the
Discord API.
### Inheritance
## Inheritance
Due to the nature of the Discord API, some entities are designed with
multiple variants; for example, `SocketUser` and `SocketGuildUser`.
All models will contain the most detailed version of an entity
possible, even if the type is less detailed.
possible, even if the type is less detailed.
For example, in the case of the `MessageReceived` event, a
For example, in the case of the `MessageReceived` event, a
`SocketMessage` is passed in with a channel property of type
`SocketMessageChannel`. All messages come from channels capable of
messaging, so this is the only variant of a channel that can cover
@@ -28,44 +31,36 @@ But that doesn't mean a message _can't_ come from a
retrieve information about a guild from a message entity, you will
need to cast its channel object to a `SocketTextChannel`.
### Navigation
You can find out various types of entities in the @FAQ.Misc.Glossary
page.
## Navigation
All socket entities have navigation properties on them, which allow
you to easily navigate to an entity's parent or children. As explained
above, you will sometimes need to cast to a more detailed version of
an entity to navigate to its parent.
### Accessing Entities
## Accessing Entities
The most basic forms of entities, `SocketGuild`, `SocketUser`, and
`SocketChannel` can be pulled from the DiscordSocketClient's global
cache, and can be retrieved using the respective `GetXXX` method on
DiscordSocketClient.
>[!TIP]
It is **vital** that you use the proper IDs for an entity when using
a GetXXX method. It is recommended that you enable Discord's
_developer mode_ to allow easy access to entity IDs, found in
Settings > Appearance > Advanced
> [!TIP]
> It is **vital** that you use the proper IDs for an entity when using
> a `GetXXX` method. It is recommended that you enable Discord's
> _developer mode_ to allow easy access to entity IDs, found in
> Settings > Appearance > Advanced. Read more about it in the
> [FAQ](xref:FAQ.Basics.GetStarted) page.
More detailed versions of entities can be pulled from the basic
entities, e.g. `SocketGuild.GetUser`, which returns a
`SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a
entities, e.g., `SocketGuild.GetUser`, which returns a
`SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a
`SocketGuildChannel`. Again, you may need to cast these objects to get
a variant of the type that you need.
### Samples
## Sample
[!code-csharp[Entity Sample](samples/entities.cs)]
### Tips
Avoid using boxing-casts to coerce entities into a variant, use the
[`as`] keyword, and a null-conditional operator instead.
This allows you to write safer code and avoid [InvalidCastExceptions].
For example, `(message.Author as SocketGuildUser)?.Nickname`.
[`as`]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as
[InvalidCastExceptions]: https://msdn.microsoft.com/en-us/library/system.invalidcastexception(v=vs.110).aspx
[!code-csharp[Entity Sample](samples/entities.cs)]

View File

@@ -1,16 +1,19 @@
---
uid: Guides.Concepts.Events
title: Working with Events
---
# Events in Discord.Net
Events in Discord.Net are consumed in a similar manner to the standard
convention, with the exception that every event must be of the type
`System.Threading.Tasks.Task` and instead of using `EventArgs`, the
event's parameters are passed directly into the handler.
@System.Threading.Tasks.Task and instead of using @System.EventArgs,
the event's parameters are passed directly into the handler.
This allows for events to be handled in an async context directly
instead of relying on `async void`.
### Usage
## Usage
To receive data from an event, hook into it using C#'s delegate
event pattern.
@@ -18,7 +21,7 @@ event pattern.
You may either opt to hook an event to an anonymous function (lambda)
or a named function.
### Safety
## Safety
All events are designed to be thread-safe; events are executed
synchronously off the gateway task in the same context as the gateway
@@ -39,7 +42,7 @@ a deadlock that will be impossible to recover from.
Exceptions in commands will be swallowed by the gateway and logged out
through the client's log method.
### Common Patterns
## Common Patterns
As you may know, events in Discord.Net are only given a signature of
`Func<T1, ..., Task>`. There is no room for predefined argument names,
@@ -49,7 +52,7 @@ directly.
That being said, there are a variety of common patterns that allow you
to infer what the parameters in an event mean.
#### Entity, Entity
### Entity, Entity
An event handler with a signature of `Func<Entity, Entity, Task>`
typically means that the first object will be a clone of the entity
@@ -58,10 +61,10 @@ model of the entity _after_ the change was made.
This pattern is typically only found on `EntityUpdated` events.
#### Cacheable
### Cacheable
An event handler with a signature of `Func<Cacheable, Entity, Task>`
means that the `before` state of the entity was not provided by the
means that the `before` state of the entity was not provided by the
API, so it can either be pulled from the client's cache or
downloaded from the API.
@@ -70,15 +73,12 @@ object.
[Cacheable]: xref:Discord.Cacheable`2
### Samples
> [!NOTE]
> Many events relating to a Message entity (i.e., `MessageUpdated` and
> `ReactionAdded`) rely on the client's message cache, which is
> **not** enabled by default. Set the `MessageCacheSize` flag in
> @Discord.WebSocket.DiscordSocketConfig to enable it.
## Sample
[!code-csharp[Event Sample](samples/events.cs)]
### Tips
Many events relating to a Message entity (i.e. `MessageUpdated` and
`ReactionAdded`) rely on the client's message cache, which is
**not** enabled by default. Set the `MessageCacheSize` flag in
[DiscordSocketConfig] to enable it.
[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig

View File

@@ -1,19 +1,28 @@
---
title: Logging
uid: Guides.Concepts.Logging
title: Logging Events/Data
---
Discord.Net's clients provide a [Log] event that all messages will be
disbatched over.
# Logging in Discord.Net
Discord.Net's clients provide a log event that all messages will be
dispatched over.
For more information about events in Discord.Net, see the [Events]
section.
[Log]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log
[Events]: events.md
[Events]: xref:Guides.Concepts.Events
### Usage
> [!WARNING]
> Due to the nature of Discord.Net's event system, all log event
> handlers will be executed synchronously on the gateway thread. If your
> log output will be dumped to a Web API (e.g., Sentry), you are advised
> to wrap your output in a `Task.Run` so the gateway thread does not
> become blocked while waiting for logging data to be written.
To receive log events, simply hook the discord client's log method
## Usage in Client(s)
To receive log events, simply hook the Discord client's @Discord.Rest.BaseDiscordClient.Log
to a `Task` with a single parameter of type [LogMessage].
It is recommended that you use an established function instead of a
@@ -22,10 +31,10 @@ to a logging function to write their own messages.
[LogMessage]: xref:Discord.LogMessage
### Usage in Commands
## Usage in Commands
Discord.Net's [CommandService] also provides a log event, identical
in signature to other log events.
Discord.Net's [CommandService] also provides a @Discord.Commands.CommandService.Log
event, identical in signature to other log events.
Data logged through this event is typically coupled with a
[CommandException], where information about the command's context
@@ -34,14 +43,6 @@ and error can be found and handled.
[CommandService]: xref:Discord.Commands.CommandService
[CommandException]: xref:Discord.Commands.CommandException
#### Samples
## Sample
[!code-csharp[Logging Sample](samples/logging.cs)]
#### Tips
Due to the nature of Discord.Net's event system, all log event
handlers will be executed synchronously on the gateway thread. If your
log output will be dumped to a Web API (e.g. Sentry), you are advised
to wrap your output in a `Task.Run` so the gateway thread does not
become blocked while waiting for logging data to be written.

View File

@@ -10,7 +10,7 @@ public class Program
{
_client = new DiscordSocketClient();
await _client.LoginAsync(TokenType.Bot, "bot token");
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken"));
await _client.StartAsync();
Console.WriteLine("Press any key to exit...");

View File

@@ -1,13 +1,11 @@
public string GetChannelTopic(ulong id)
{
var channel = client.GetChannel(81384956881809408) as SocketTextChannel;
if (channel == null) return "";
return channel.Topic;
return channel?.Topic;
}
public string GuildOwner(SocketChannel channel)
public SocketGuildUser GetGuildOwner(SocketChannel channel)
{
var guild = (channel as SocketGuildChannel)?.Guild;
if (guild == null) return "";
return Context.Guild.Owner.Username;
return guild?.Owner;
}

View File

@@ -14,7 +14,7 @@ public class Program
var _config = new DiscordSocketConfig { MessageCacheSize = 100 };
_client = new DiscordSocketClient(_config);
await _client.LoginAsync(TokenType.Bot, "bot token");
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken"));
await _client.StartAsync();
_client.MessageUpdated += MessageUpdated;

View File

@@ -15,7 +15,7 @@ public class Program
_client.Log += Log;
await _client.LoginAsync(TokenType.Bot, "bot token");
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken"));
await _client.StartAsync();
await Task.Delay(-1);

View File

@@ -0,0 +1,86 @@
---
uid: Guides.Deployment
title: Deploying the Bot
---
# Deploying a Discord.Net Bot
After finishing your application, you may want to deploy your bot to a
remote location such as a Virtual Private Server (VPS) or another
computer so you can keep the bot up and running 24/7.
## Recommended VPS
For small-medium scaled bots, a cheap VPS (~$5) might be sufficient
enough. Here is a list of recommended VPS provider.
* [DigitalOcean](https://www.digitalocean.com/)
* Description: American cloud infrastructure provider headquartered
in New York City with data centers worldwide.
* Location(s):
* Asia: Singapore, India
* America: Canada, United States
* Europe: Netherlands, Germany, United Kingdom
* Based in: United States
* [Vultr](https://www.vultr.com/)
* Description: DigitalOcean-like
* Location(s):
* Asia: Japan, Australia, Singapore
* America: United States
* Europe: United Kingdom, France, Netherlands, Germany
* Based in: United States
* [OVH](https://www.ovh.com/)
* Description: French cloud computing company that offers VPS,
dedicated servers and other web services.
* Location(s):
* Asia: Australia, Singapore
* America: United States, Canada
* Europe: United Kingdom, Poland, Germany
* Based in: Europe
* [Scaleway](https://www.scaleway.com/)
* Description: Cheap but powerful VPS owned by [Online.net](https://online.net/).
* Location(s):
* Europe: France, Netherlands
* Based in: Europe
* [Time4VPS](https://www.time4vps.eu/)
* Description: Affordable and powerful VPS Hosting in Europe.
* Location(s):
* Europe: Lithuania
* Based in: Europe
## .NET Core Deployment
> [!NOTE]
> This section only covers the very basics of .NET Core deployment.
> To learn more about deployment, visit [.NET Core application deployment]
> by Microsoft.
By default, .NET Core compiles all projects as a DLL file, so that any
.NET Core runtime can execute the application.
You may execute the application via `dotnet myprogram.dll` assuming you
have the dotnet CLI installed.
When redistributing the application, you may want to publish the
application, or in other words, create a self-contained package
for use on another machine without installing the dependencies first.
This can be achieved by using the dotnet CLI too on the development
machine:
* `dotnet publish -c Release`
Additionally, you may want to target a specific platform when
publishing the application so you may use the application without
having to install the Core runtime on the target machine. To do this,
you may specify an [Runtime ID] upon build/publish with the `-r`
option.
For example, when targeting a Windows 10 machine, you may want to use
the following to create the application in Windows executable
format (.exe):
* `dotnet publish -c Release -r win10-x64`
[.NET Core application deployment]: https://docs.microsoft.com/en-us/dotnet/core/deploying/
[Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog

100
docs/guides/emoji/emoji.md Normal file
View File

@@ -0,0 +1,100 @@
---
uid: Guides.Emoji
title: Emoji
---
# Emoji in Discord.Net
Before we delve into the difference between an @Discord.Emoji and an
@Discord.Emote in Discord.Net, it is **crucial** to understand what
they both look like behind the scene. When the end-users are sending
or receiving an emoji or emote, they are typically in the form of
`:ok_hand:` or `:reeee:`; however, what goes under the hood is that,
depending on the type of emoji, they are sent in an entirely
different format.
What does this all mean? It means that you should know that by
reacting with a string like `“:ok_hand:”` will **NOT** automatically
translate to `👌`; rather, it will be treated as-is,
like `:ok_hand:`, thus the server will return a `400 Bad Request`.
## Emoji
An emoji is a standard emoji that can be found anywhere else outside
of Discord, which means strings like `👌`, `♥`, `👀` are all
considered an emoji in Discord. However, from the
introduction paragraph we have learned that we cannot
simply send `:ok_hand:` and have Discord take
care of it, but what do we need to send exactly?
To send an emoji correctly, one must send the emoji in its Unicode
form; this can be obtained in several different ways.
1. (Easiest) Escape the emoji by using the escape character, `\`, in
your Discord chat client; this will reveal the emojis pure Unicode
form, which will allow you to copy-paste into your code.
2. Look it up on Emojipedia, from which you can copy the emoji
easily into your code.
![Emojipedia](images/emojipedia.png)
3. (Recommended) Look it up in the Emoji list from [FileFormat.Info];
this will give you the .NET-compatible code that
represents the emoji.
* This is the most recommended method because some systems or
IDE sometimes do not render the Unicode emoji correctly.
![Fileformat Emoji Source Code](images/fileformat-emoji-src.png)
### Emoji Declaration
After obtaining the Unicode representation of the emoji, you may
create the @Discord.Emoji object by passing the string into its
constructor (e.g. `new Emoji("👌");` or `new Emoji("\uD83D\uDC4C");`).
Your method of declaring an @Discord.Emoji should look similar to
this:
[!code-csharp[Emoji Sample](samples/emoji-sample.cs)]
[FileFormat.Info]: https://www.fileformat.info/info/emoji/list.htm
## Emote
The meat of the debate is here; what is an emote and how does it
differ from an emoji? An emote refers to a **custom emoji**
created on Discord.
The underlying structure of an emote also differs drastically; an
emote looks sort-of like a mention on Discord. It consists of two
main elements as illustrated below:
![Emote illustration](images/emote-format.png)
As you can see, emote uses a completely different format. To obtain
the raw string as shown above for your emote, you would need to
escape the emote using the escape character `\` in chat somewhere.
### Emote Declaration
After obtaining the raw emote string, you would need to use
@Discord.Emote.Parse* or @Discord.Emote.TryParse* to create a valid
emote object.
Your method of declaring an @Discord.Emote should look similar to
this:
[!code[Emote Sample](samples/emote-sample.cs)]
> [!TIP]
> For WebSocket users, you may also consider fetching the Emote
> via the @Discord.WebSocket.SocketGuild.Emotes collection.
> [!code-csharp[Socket emote sample](samples/socket-emote-sample.cs)]
> [!TIP]
> On Discord, any user with Discord Nitro subscription may use
> custom emotes from any guilds they are currently in. This is also
> true for _any_ standard bot accounts; this does not require
> the bot owner to have a Nitro subscription.
## Additional Information
To learn more about emote and emojis and how they could be used,
see the documentation of @Discord.IEmote.

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,6 @@
public async Task ReactAsync(SocketUserMessage userMsg)
{
// equivalent to "👌"
var emoji = new Emoji("\uD83D\uDC4C");
await userMsg.AddReactionAsync(emoji);
}

View File

@@ -0,0 +1,7 @@
public async Task ReactWithEmoteAsync(SocketUserMessage userMsg, string escapedEmote)
{
if (Emote.TryParse(escapedEmote, out var emote))
{
await userMsg.AddReactionAsync(emote);
}
}

View File

@@ -0,0 +1,11 @@
private readonly DiscordSocketClient _client;
public async Task ReactAsync(SocketUserMessage userMsg, string emoteName)
{
var emote = _client.Guilds
.SelectMany(x => x.Emotes)
.FirstOrDefault(x => x.Name.IndexOf(
emoteName, StringComparison.OrdinalIgnoreCase) != -1);
if (emote == null) return;
await userMsg.AddReactionAsync(emote);
}

View File

@@ -0,0 +1,263 @@
---
uid: Guides.GettingStarted.FirstBot
title: Start making a bot
---
# Making Your First Bot with Discord.Net
One of the ways to get started with the Discord API is to write a
basic ping-pong bot. This bot will respond to a simple command "ping."
We will expand on this to create more diverse commands later, but for
now, it is a good starting point.
## Creating a Discord Bot
Before writing your bot, it is necessary to create a bot account via
the Discord Applications Portal first.
1. Visit the [Discord Applications Portal].
2. Create a new application.
3. Give the application a name (this will be the bot's initial username).
4. On the left-hand side, under `Settings`, click `Bot`.
![Step 4](images/intro-bot-settings.png)
5. Click on `Add Bot`.
![Step 5](images/intro-add-bot.png)
6. Confirm the popup.
7. (Optional) If this bot will be public, tick `Public Bot`.
![Step 7](images/intro-public-bot.png)
[Discord Applications Portal]: https://discordapp.com/developers/applications/
## Adding your bot to a server
Bots **cannot** use invite links; they must be explicitly invited
through the OAuth2 flow.
1. Open your bot's application on the [Discord Applications Portal].
2. On the left-hand side, under `Settings`, click `OAuth2`.
![Step 2](images/intro-oauth-settings.png)
3. Scroll down to `OAuth2 URL Generator` and under `Scopes` tick `bot`.
![Step 3](images/intro-scopes-bot.png)
4. Scroll down further to `Bot Permissions` and select the
permissions that you wish to assign your bot with.
> [!NOTE]
> This will assign the bot with a special "managed" role that no
> one else can use. The permissions can be changed later in the
> roles settings if you ever change your mind!
5. Open the generated authorization URL in your browser.
6. Select a server.
7. Click on Authorize.
> [!NOTE]
> Only servers where you have the `MANAGE_SERVER` permission will be
> present in this list.
![Step 6](images/intro-authorize.png)
## Connecting to Discord
If you have not already created a project and installed Discord.Net,
do that now.
For more information, see @Guides.GettingStarted.Installation.
### Async
Discord.Net uses .NET's [Task-based Asynchronous Pattern (TAP)]
extensively - nearly every operation is asynchronous. It is highly
recommended for these operations to be awaited in a
properly established async context whenever possible.
To establish an async context, we will be creating an async main method
in your console application, and rewriting the static main method to
invoke the new async main.
[!code-csharp[Async Context](samples/first-bot/async-context.cs)]
As a result of this, your program will now start and immediately
jump into an async context. This allows us to create a connection
to Discord later on without having to worry about setting up the
correct async implementation.
> [!WARNING]
> If your application throws any exceptions within an async context,
> they will be thrown all the way back up to the first non-async method;
> since our first non-async method is the program's `Main` method, this
> means that **all** unhandled exceptions will be thrown up there, which
> will crash your application.
>
> Discord.Net will prevent exceptions in event handlers from crashing
> your program, but any exceptions in your async main **will** cause
> the application to crash.
[Task-based Asynchronous Pattern (TAP)]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async
### Creating a logging method
Before we create and configure a Discord client, we will add a method
to handle Discord.Net's log events.
To allow agnostic support of as many log providers as possible, we
log information through a `Log` event with a proprietary `LogMessage`
parameter. See the [API Documentation] for this event.
If you are using your own logging framework, this is where you would
invoke it. For the sake of simplicity, we will only be logging to
the console.
You may learn more about this concept in @Guides.Concepts.Logging.
[!code-csharp[Async Context](samples/first-bot/logging.cs)]
[API Documentation]: xref:Discord.Rest.BaseDiscordClient.Log
### Creating a Discord Client
Finally, we can create a new connection to Discord.
Since we are writing a bot, we will be using a [DiscordSocketClient]
along with socket entities. See @Guides.GettingStarted.Terminology
if you are unsure of the differences.
To establish a new connection, we will create an instance of
[DiscordSocketClient] in the new async main. You may pass in an
optional @Discord.WebSocket.DiscordSocketConfig if necessary. For most
users, the default will work fine.
Before connecting, we should hook the client's `Log` event to the
log handler that we had just created. Events in Discord.Net work
similarly to any other events in C#.
Next, you will need to "log in to Discord" with the [LoginAsync]
method with the application's "token."
> [!NOTE]
> Pay attention to what you are copying from the developer portal!
> A token is not the same as the application's "client secret."
![Token](images/intro-token.png)
> [!IMPORTANT]
> Your bot's token can be used to gain total access to your bot, so
> **do __NOT__ share this token with anyone else!** It may behoove you
> to store this token in an external source if you plan on distributing
> the source code for your bot.
We may now invoke the client's [StartAsync] method, which will
start connection/reconnection logic. It is important to note that
**this method will return as soon as connection logic has been started!**
Any methods that rely on the client's state should go in an event
handler. This means that you should **not** directly be interacting with
the client before it is fully ready.
Finally, we will want to block the async main method from returning
when running the application. To do this, we can await an infinite delay
or any other blocking method, such as reading from the console.
The following lines can now be added:
[!code-csharp[Create client](samples/first-bot/client.cs)]
At this point, feel free to start your program and see your bot come
online in Discord.
> [!TIP]
> Getting a warning about `A supplied token was invalid.` and/or
> having trouble logging in? Double-check whether you have put in
> the correct credentials and make sure that it is _not_ a client
> secret, which is different from a token.
> [!TIP]
> Encountering a `PlatformNotSupportedException` when starting your bot?
> This means that you are targeting a platform where .NET's default
> WebSocket client is not supported. Refer to the [installation guide]
> for how to fix this.
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient
[LoginAsync]: xref:Discord.Rest.BaseDiscordClient.LoginAsync*
[StartAsync]: xref:Discord.WebSocket.DiscordSocketClient.StartAsync*
[installation guide]: xref:Guides.GettingStarted.Installation
### Handling a 'ping'
> [!WARNING]
> Please note that this is *not* a proper way to create a command.
> Use the `CommandService` provided by the library instead, as explained
> in the [Command Guide](xref:Guides.Commands.Intro) section.
Now that we have learned to open a connection to Discord, we can
begin handling messages that the users are sending. To start out, our
bot will listen for any message whose content is equal to `!ping` and
will respond back with "Pong!".
Since we want to listen for new messages, the event to hook into
is [MessageReceived].
In your program, add a method that matches the signature of the
`MessageReceived` event - it must be a method (`Func`) that returns
the type `Task` and takes a single parameter, a [SocketMessage]. Also,
since we will be sending data to Discord in this method, we will flag
it as `async`.
In this method, we will add an `if` block to determine if the message
content fits the rules of our scenario - recall that it must be equal
to `!ping`.
Inside the branch of this condition, we will want to send a message,
`Pong!`, back to the channel from which the message comes from. To
find the channel, look for the `Channel` property on the message
parameter.
Next, we will want to send a message to this channel. Since the
channel object is of type [ISocketMessageChannel], we can invoke the
[SendMessageAsync] instance method. For the message content, send back
a string, "Pong!".
You should have now added the following lines,
[!code-csharp[Message](samples/first-bot/message.cs)]
Now that your first bot is complete. You may continue to add on to this
if you desire, but for any bots that will be carrying out multiple
commands, it is strongly recommended to use the command framework as
shown below.
> [!NOTE]
> For your reference, you may view the [completed program].
[MessageReceived]: xref:Discord.WebSocket.BaseSocketClient.MessageReceived
[SocketMessage]: xref:Discord.WebSocket.SocketMessage
[ISocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel
[SendMessageAsync]: xref:Discord.WebSocket.ISocketMessageChannel.SendMessageAsync*
[completed program]: samples/first-bot/complete.cs
# Building a bot with commands
@Guides.Commands.Intro will guide you through how to setup a program
that is ready for [CommandService], a service that is ready for
advanced command usage.
For reference, view an [annotated example] of this structure.
[annotated example]: samples/first-bot/structure.cs
It is important to know that the recommended design pattern of bots
should be to separate...
1. the program (initialization and command handler)
2. the modules (handle commands)
3. the services (persistent storage, pure functions, data manipulation)
[CommandService]: xref:Discord.Commands.CommandService

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -1,146 +1,136 @@
---
uid: Guides.GettingStarted.Installation
title: Installing Discord.Net
---
Discord.Net is distributed through the NuGet package manager, and it
is recommended to use NuGet to get started.
# Discord.Net Installation
Optionally, you may compile from source and install yourself.
Discord.Net is distributed through the NuGet package manager; the most
recommended way for you to install this library. Alternatively, you
may also compile this library yourself should you so desire.
# Supported Platforms
## Supported Platforms
Currently, Discord.Net targets [.NET Standard] 1.3 and offers support
for .NET Standard 1.1. If your application will be targeting .NET
Standard 1.1, please see the [additional steps].
Discord.Net targets [.NET Standard] both 1.3 and 2.0; this also means
that creating applications using the latest version of [.NET Core] is
the most recommended. If you are bound by Windows-specific APIs or
other limitations, you may also consider targeting [.NET Framework]
4.6.1 or higher.
Since Discord.Net is built on the .NET Standard, it is also
recommended to create applications using [.NET Core], though not
required. When using .NET Framework, it is suggested to target
`.NET Framework 4.6.1` or higher.
> [!WARNING]
> Using this library with [Mono] is not supported until further
> notice. It is known to have issues with the library's WebSockets
> implementation and may crash the application upon startup.
[Mono]: https://www.mono-project.com/
[.NET Standard]: https://docs.microsoft.com/en-us/dotnet/articles/standard/library
[.NET Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/
[.NET Framework]: https://docs.microsoft.com/en-us/dotnet/framework/get-started/
[additional steps]: #installing-on-net-standard-11
# Installing with NuGet
## Installing with NuGet
Release builds of Discord.Net 1.0 will be published to the
Release builds of Discord.Net will be published to the
[official NuGet feed].
Development builds of Discord.Net 1.0, as well as addons *(TODO)* are
published to our development [MyGet feed].
Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json`
Not sure how to add a direct feed? See how [with Visual Studio] or
[without Visual Studio].
Development builds of Discord.Net, as well as add-ons, will be
published to our [MyGet feed]. See
@Guides.GettingStarted.Installation.Nightlies to learn more.
[official NuGet feed]: https://nuget.org
[MyGet feed]: https://www.myget.org/feed/Packages/discord-net
[with Visual Studio]: https://docs.microsoft.com/en-us/nuget/tools/package-manager-ui#package-sources
[without Visual Studio]: #configuring-nuget-without-visual-studio
## Using Visual Studio
### [Using Visual Studio](#tab/vs-install)
> [!TIP]
>Don't forget to change your package source if you're installing from
the developer feed.
>Also make sure to check "Enable Prereleases" if installing a dev
build!
1. Create a solution for your bot.
2. In Solution Explorer, find the "Dependencies" element under your
bot's project.
1. Create a new solution for your bot.
2. In the Solution Explorer, find the "Dependencies" element under your
bot's project.
3. Right click on "Dependencies", and select "Manage NuGet packages."
![Step 3](images/install-vs-deps.png)
![Step 3](images/install-vs-deps.png)
4. In the "Browse" tab, search for `Discord.Net`.
5. Install the `Discord.Net` package.
![Step 5](images/install-vs-nuget.png)
![Step 5](images/install-vs-nuget.png)
## Using JetBrains Rider
> [!TIP]
Make sure to check the "Prerelease" box if installing a dev build!
### [Using JetBrains Rider](#tab/rider-install)
1. Create a new solution for your bot.
2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for
Solution).
Solution).
![Step 2](images/install-rider-nuget-manager.png)
3. In the "Packages" tab, search for `Discord.Net`.
![Step 3](images/install-rider-search.png)
4. Install by adding the package to your project.
![Step 4](images/install-rider-add.png)
## Using Visual Studio Code
> [!TIP]
Don't forget to add the package source to a [NuGet.Config file] if
you're installing from the developer feed.
### [Using Visual Studio Code](#tab/vs-code)
1. Create a new project for your bot.
2. Add `Discord.Net` to your .csproj.
[!code-xml[Sample .csproj](samples/project.csproj)]
[!code[Sample .csproj](samples/project.xml)]
[NuGet.Config file]: #configuring-nuget-without-visual-studio
### [Using dotnet CLI](#tab/dotnet-cli)
# Compiling from Source
1. Open command-line and navigate to where your .csproj is located.
2. Enter `dotnet add package Discord.Net`.
In order to compile Discord.Net, you require the following:
***
## Compiling from Source
In order to compile Discord.Net, you will need the following:
### Using Visual Studio
- [Visual Studio 2017](https://www.visualstudio.com/)
- [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk)
* [Visual Studio 2017](https://www.visualstudio.com/)
* [.NET Core SDK]
The .NET Core and Docker (Preview) workload is required during Visual
Studio installation.
The .NET Core and Docker workload is required during Visual Studio
installation.
### Using Command Line
- [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk)
* [.NET Core SDK]
# Additional Information
## Additional Information
## Installing on .NET Standard 1.1
### Installing on Unsupported WebSocket Platform
For applications targeting a runtime corresponding with .NET Standard
1.1 or 1.2, the builtin WebSocket and UDP provider will not work. For
applications which utilize a WebSocket connection to Discord
(WebSocket or RPC), third-party provider packages will need to be
installed and configured.
When running any Discord.Net-powered bot on an older operating system
(e.g. Windows 7) that does not natively support WebSocket,
you may encounter a @System.PlatformNotSupportedException upon
connecting.
First, install the following packages through NuGet, or compile
yourself, if you prefer:
You may resolve this by either targeting .NET Core 2.1 or later, or
by installing one or more custom packages as listed below.
- Discord.Net.Providers.WS4Net
- Discord.Net.Providers.UDPClient
#### [Targeting .NET Core 2.1](#tab/core2-1)
Note that `Discord.Net.Providers.UDPClient` is _only_ required if your
bot will be utilizing voice chat.
1. Download the latest [.NET Core SDK].
2. Create or move your existing project to use .NET Core.
3. Modify your `<TargetFramework>` tag to at least `netcoreapp2.1`, or
by adding the `--framework netcoreapp2.1` switch when building.
Next, you will need to configure your [DiscordSocketClient] to use
these custom providers over the default ones.
#### [Custom Packages](#tab/custom-pkg)
To do this, set the `WebSocketProvider` and the optional
`UdpSocketProvider` properties on the [DiscordSocketConfig] that you
are passing into your client.
1. Install or compile the following packages:
[!code-csharp[NET Standard 1.1 Example](samples/netstd11.cs)]
* `Discord.Net.Providers.WS4Net`
* `Discord.Net.Providers.UDPClient` (Optional)
* This is _only_ required if your bot will be utilizing voice chat.
2. Configure your [DiscordSocketClient] to use these custom providers
over the default ones.
* To do this, set the `WebSocketProvider` and the optional
`UdpSocketProvider` properties on the [DiscordSocketConfig] that you
are passing into your client.
[!code-csharp[Example](samples/netstd11.cs)]
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient
[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig
## Configuring NuGet without Visual Studio
***
If you plan on deploying your bot or developing outside of Visual
Studio, you will need to create a local NuGet configuration file for
your project.
To do this, create a file named `nuget.config` alongside the root of
your application, where the project solution is located.
Paste the following snippets into this configuration file, adding any
additional feeds as necessary.
[!code-xml[NuGet Configuration](samples/nuget.config)]
[.NET Core SDK]: https://www.microsoft.com/net/download/

View File

@@ -1,237 +0,0 @@
---
title: Getting Started
---
# Making a Ping-Pong bot
One of the first steps to getting started with the Discord API is to
write a basic ping-pong bot. We will expand on this to create more
diverse commands later, but for now, it is a good starting point.
## Creating a Discord Bot
Before you can begin writing your bot, it is necessary to create a bot
account on Discord.
1. Visit the [Discord Applications Portal].
2. Create a New Application.
3. Give the application a name (this will be the bot's initial
username).
4. Create the Application.
![Step 4](images/intro-create-app.png)
5. In the application review page, click **Create a Bot User**.
![Step 5](images/intro-create-bot.png)
6. Confirm the popup.
7. If this bot will be public, check "Public Bot." **Do not tick any
other options!**
[Discord Applications Portal]: https://discordapp.com/developers/applications/me
## Adding your bot to a server
Bots **cannot** use invite links, they must be explicitly invited
through the OAuth2 flow.
1. Open your bot's application on the [Discord Applications Portal].
2. Retrieve the app's **Client ID**.
![Step 2](images/intro-client-id.png)
3. Create an OAuth2 authorization URL
`https://discordapp.com/oauth2/authorize?client_id=<CLIENT ID>&scope=bot`
4. Open the authorization URL in your browser.
5. Select a server.
6. Click on authorize.
>[!NOTE]
Only servers where you have the `MANAGE_SERVER` permission will be
present in this list.
![Step 6](images/intro-add-bot.png)
## Connecting to Discord
If you have not already created a project and installed Discord.Net,
do that now. (see the [Installing](installing.md) section)
### Async
Discord.Net uses .NET's [Task-based Asynchronous Pattern (TAP)]
extensively - nearly every operation is asynchronous.
It is highly recommended that these operations are awaited in a
properly established async context whenever possible. Establishing an
async context can be problematic, but not hard.
To do so, we will be creating an async main in your console
application, and rewriting the static main method to invoke the new
async main.
[!code-csharp[Async Context](samples/intro/async-context.cs)]
As a result of this, your program will now start and immediately
jump into an async context. This will allow us to create a connection
to Discord later on without needing to worry about setting up the
correct async implementation.
>[!TIP]
If your application throws any exceptions within an async context,
they will be thrown all the way back up to the first non-async method;
since our first non-async method is the program's `Main` method, this
means that **all** unhandled exceptions will be thrown up there, which
will crash your application. Discord.Net will prevent exceptions in
event handlers from crashing your program, but any exceptions in your
async main **will** cause the application to crash.
[Task-based Asynchronous Pattern (TAP)]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async
### Creating a logging method
Before we create and configure a Discord client, we will add a method
to handle Discord.Net's log events.
To allow agnostic support of as many log providers as possible, we
log information through a `Log` event with a proprietary `LogMessage`
parameter. See the [API Documentation] for this event.
If you are using your own logging framework, this is where you would
invoke it. For the sake of simplicity, we will only be logging to
the Console.
[!code-csharp[Async Context](samples/intro/logging.cs)]
[API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log
### Creating a Discord Client
Finally, we can create a connection to Discord. Since we are writing
a bot, we will be using a [DiscordSocketClient] along with socket
entities. See the [terminology](terminology.md) if you're unsure of
the differences.
To do so, create an instance of [DiscordSocketClient] in your async
main, passing in a configuration object only if necessary. For most
users, the default will work fine.
Before connecting, we should hook the client's `Log` event to the
log handler that was just created. Events in Discord.Net work
similarly to other events in C#, so hook this event the way that
you typically would.
Next, you will need to "login to Discord" with the `LoginAsync`
method.
You may create a variable to hold your bot's token (this can be found
on your bot's application page on the [Discord Applications Portal]).
![Token](images/intro-token.png)
>[!IMPORTANT]
Your bot's token can be used to gain total access to your bot, so
**do __NOT__ share this token with anyone else!** It may behoove you
to store this token in an external file if you plan on distributing
the source code for your bot.
We may now invoke the client's `StartAsync` method, which will
start connection/reconnection logic. It is important to note that
**this method returns as soon as connection logic has been started!**
Any methods that rely on the client's state should go in an event
handler.
Finally, we will want to block the async main method from returning
until after the application is exited. To do this, we can await an
infinite delay or any other blocking method, such as reading from
the console.
The following lines can now be added:
[!code-csharp[Create client](samples/intro/client.cs)]
At this point, feel free to start your program and see your bot come
online in Discord.
>[!TIP]
Encountering a `PlatformNotSupportedException` when starting your bot?
This means that you are targeting a platform where .NET's default
WebSocket client is not supported. Refer to the [installation guide]
for how to fix this.
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient
[installation guide]: installing.md#installing-on-net-standard-11
### Handling a 'ping'
>[!WARNING]
Please note that this is *not* a proper way to create a command.
Use the `CommandService` provided by the library instead, as explained
in the [Command Guide] section.
Now that we have learned how to open a connection to Discord, we can
begin handling messages that users are sending.
To start out, our bot will listen for any message where the content
is equal to `!ping` and respond back with "Pong!".
Since we want to listen for new messages, the event to hook into
is [MessageReceived].
In your program, add a method that matches the signature of the
`MessageReceived` event - it must be a method (`Func`) that returns
the type `Task` and takes a single parameter, a [SocketMessage]. Also,
since we will be sending data to Discord in this method, we will flag
it as `async`.
In this method, we will add an `if` block to determine if the message
content fits the rules of our scenario - recall that it must be equal
to `!ping`.
Inside the branch of this condition, we will want to send a message
back to the channel from which the message comes from - "Pong!". To
find the channel, look for the `Channel` property on the message
parameter.
Next, we will want to send a message to this channel. Since the
channel object is of type [SocketMessageChannel], we can invoke the
`SendMessageAsync` instance method. For the message content, send back
a string containing "Pong!".
You should have now added the following lines:
[!code-csharp[Message](samples/intro/message.cs)]
Now your first bot is complete. You may continue to add on to this
if you desire, but for any bots that will be carrying out multiple
commands, it is strongly recommended to use the command framework as
shown below.
For your reference, you may view the [completed program].
[MessageReceived]: xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_MessageReceived
[SocketMessage]: xref:Discord.WebSocket.SocketMessage
[SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel
[completed program]: samples/intro/complete.cs
[Command Guide]: ../commands/commands.md
# Building a bot with commands
This section will show you how to write a program that is ready for
[Commands](../commands/commands.md). Note that we will not be
explaining _how_ to write Commands or Services, it will only be
covering the general structure.
For reference, view an [annotated example] of this structure.
[annotated example]: samples/intro/structure.cs
It is important to know that the recommended design pattern of bots
should be to separate the program (initialization and command handler),
the modules (handle commands), and the services (persistent storage,
pure functions, data manipulation).
**todo:** diagram of bot structure

View File

@@ -0,0 +1,86 @@
---
uid: Guides.GettingStarted.Installation.Nightlies
title: Installing Nightly Build
---
# Installing Discord.Net Nightly Build
Before Discord.Net pushes a new set of features into the stable
version, we use nightly builds to test the features with the
community for an extensive period of time. Each nightly build is
compiled by AppVeyor whenever a new commit is made and will be pushed
to our MyGet feed.
> [!IMPORTANT]
> Although nightlies are generally stable and have more features
> and bug fixes than the current stable build on NuGet, there
> will be breaking changes during the development or
> breaking bugs; these bugs are usually fixed as soon as they
> are discovered, but you should still be aware of that.
## Installing with MyGet (Recommended)
MyGet is typically used by many development teams to publish their
latest pre-release packages before the features are finalized and
pushed to NuGet.
The following is the feed link of Discord.Net,
* `https://www.myget.org/F/discord-net/api/v3/index.json`
Depending on which IDE you use, there are many different ways of
adding the feed to your package source.
### [Visual Studio](#tab/vs)
1. Go to `Tools` > `NuGet Package Manager` > `Package Manager Settings`
![VS](images/nightlies-vs-step1.png)
2. Go to `Package Sources`
![Package Sources](images/nightlies-vs-step2.png)
3. Click on the add icon
4. Fill in the desired name and source as shown below and hit `Update`
![Add Source](images/nightlies-vs-step4.png)
> [!NOTE]
> Remember to tick the `Include prerelease` checkbox to see the
> nightly builds!
> ![Checkbox](images/nightlies-vs-note.png)
### [Local NuGet.Config](#tab/local-nuget-config)
If you plan on deploying your bot or developing outside of Visual
Studio, you will need to create a local NuGet configuration file for
your project.
To do this, create a file named `NuGet.Config` alongside the root of
your application, where the project is located.
Paste the following snippets into this configuration file, adding any
additional feeds if necessary.
[!code[NuGet Configuration](samples/nuget.config)]
After which, you may install the packages by directly modifying the
project file and specifying a version, or by using
the [Package Manager Console](https://docs.microsoft.com/en-us/nuget/tools/powershell-reference)
(`Install-Package Discord.Net -IncludePrerelease`).
***
## Installing from AppVeyor Artifacts
As mentioned in the first paragraph, we utilize AppVeyor to perform
automated tests and publish the new build. During the publishing
process, we also upload the NuGet packages onto
AppVeyor's Artifact collection.
The latest build status can be found within our [AppVeyor project].
[AppVeyor project]: https://ci.appveyor.com/project/rogueexception/discord-net
1. In the project, you may find our latest build including the
aforementioned artifacts.
![Artifacts](images/appveyor-artifacts.png)
2. In the artifacts collection, you should see the latest packages
packed in `*.nupkg` form which you could download from and use.
![NuPkgs](images/appveyor-nupkg.png)

View File

@@ -0,0 +1,9 @@
public class Program
{
public static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();
public async Task MainAsync()
{
}
}

View File

@@ -0,0 +1,21 @@
private DiscordSocketClient _client;
public async Task MainAsync()
{
_client = new DiscordSocketClient();
_client.Log += Log;
// Remember to keep token private or to read it from an
// external source! In this case, we are reading the token
// from an environment variable. If you do not know how to set-up
// environment variables, you may find more information on the
// Internet or by using other methods such as reading from
// a configuration.
await _client.LoginAsync(TokenType.Bot,
Environment.GetEnvironmentVariable("DiscordToken"));
await _client.StartAsync();
// Block this task until the program is closed.
await Task.Delay(-1);
}

View File

@@ -0,0 +1,34 @@
public class Program
{
private DiscordSocketClient _client;
public static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();
public async Task MainAsync()
{
_client = new DiscordSocketClient();
_client.Log += Log;
_client.MessageReceived += MessageReceivedAsync;
await _client.LoginAsync(TokenType.Bot,
Environment.GetEnvironmentVariable("DiscordToken"));
await _client.StartAsync();
// Block this task until the program is closed.
await Task.Delay(-1);
}
private async Task MessageReceivedAsync(SocketMessage message)
{
if (message.Content == "!ping")
{
await message.Channel.SendMessageAsync("Pong!");
}
}
private Task Log(LogMessage msg)
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,5 @@
private Task Log(LogMessage msg)
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}

View File

@@ -1,6 +1,6 @@
public async Task MainAsync()
{
// client.Log ...
// ...
_client.MessageReceived += MessageReceived;
// ...
}

View File

@@ -1,15 +0,0 @@
using System;
using System.Threading.Tasks;
namespace MyBot
{
public class Program
{
public static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();
public async Task MainAsync()
{
}
}
}

View File

@@ -1,17 +0,0 @@
// Program.cs
using Discord.WebSocket;
// ...
private DiscordSocketClient _client;
public async Task MainAsync()
{
_client = new DiscordSocketClient();
_client.Log += Log;
string token = "abcdefg..."; // Remember to keep this private!
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();
// Block this task until the program is closed.
await Task.Delay(-1);
}

View File

@@ -1,44 +0,0 @@
using Discord;
using Discord.WebSocket;
using System;
using System.Threading.Tasks;
namespace MyBot
{
public class Program
{
private DiscordSocketClient _client;
public static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();
public async Task MainAsync()
{
_client = new DiscordSocketClient();
_client.Log += Log;
_client.MessageReceived += MessageReceived;
string token = "abcdefg..."; // Remember to keep this private!
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();
// Block this task until the program is closed.
await Task.Delay(-1);
}
private async Task MessageReceived(SocketMessage message)
{
if (message.Content == "!ping")
{
await message.Channel.SendMessageAsync("Pong!");
}
}
private Task Log(LogMessage msg)
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}
}
}

View File

@@ -1,22 +0,0 @@
using Discord;
using System;
using System.Threading.Tasks;
namespace MyBot
{
public class Program
{
public static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();
public async Task MainAsync()
{
}
private Task Log(LogMessage msg)
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}
}
}

View File

@@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
<NoWin32Manifest>true</NoWin32Manifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="1.*" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<!--
The following may differ depending on the latest version of
.NET Core or Discord.Net.
-->
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="2.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,5 @@
---
uid: Terminology
uid: Guides.GettingStarted.Terminology
title: Terminology
---
@@ -7,34 +7,30 @@ title: Terminology
## Preface
Most terms for objects remain the same between 0.9 and 1.0. The major
difference is that the ``Server`` is now called ``Guild`` to stay in
line with Discord internally.
Most terms for objects remain the same between 0.9 and 1.0 and above.
The major difference is that the ``Server`` is now called ``Guild``
to stay in line with Discord internally.
## Implementation Specific Entities
Discord.Net 1.0 is split into a core library and three different
implementations - `Discord.Net.Core`, `Discord.Net.Rest`,
`Discord.Net.Rpc`, and `Discord.Net.WebSockets`.
Discord.Net is split into a core library and two different
implementations - `Discord.Net.Core`, `Discord.Net.Rest`, and
`Discord.Net.WebSockets`.
As a bot developer, you will only need to use `Discord.Net.WebSockets`,
As a bot developer, you will only need to use `Discord.Net.WebSockets`,
but you should be aware of the differences between them.
`Discord.Net.Core` provides a set of interfaces that models Discord's
API. These interfaces are consistent throughout all implementations of
Discord.Net, and if you are writing an implementation-agnostic library
or addon, you can rely on the core interfaces to ensure that your
`Discord.Net.Core` provides a set of interfaces that models Discord's
API. These interfaces are consistent throughout all implementations of
Discord.Net, and if you are writing an implementation-agnostic library
or addon, you can rely on the core interfaces to ensure that your
addon will run on all platforms.
`Discord.Net.Rest` provides a set of concrete classes to be used
**strictly** with the REST portion of Discord's API. Entities in this
implementation are prefixed with `Rest` (e.g. `RestChannel`).
`Discord.Net.Rest` provides a set of concrete classes to be used
**strictly** with the REST portion of Discord's API. Entities in this
implementation are prefixed with `Rest` (e.g., `RestChannel`).
`Discord.Net.Rpc` provides a set of concrete classes that are used
with Discord's RPC API. Entities in this implementation are prefixed
with `Rpc` (e.g. `RpcChannel`).
`Discord.Net.WebSocket` provides a set of concrete classes that are
`Discord.Net.WebSocket` provides a set of concrete classes that are
used primarily with Discord's WebSocket API or entities that are kept
in cache. When developing bots, you will be using this implementation.
All entities are prefixed with `Socket` (e.g. `SocketChannel`).
in cache. When developing bots, you will be using this implementation.
All entities are prefixed with `Socket` (e.g., `SocketChannel`).

View File

@@ -0,0 +1,54 @@
---
uid: Guides.Introduction
title: Introduction to Discord.Net
---
# Introduction
## Looking to get started?
Welcome! Before you dive into this library, however, you should have
some decent understanding of the language
you are about to use. This library touches on
[Task-based Asynchronous Pattern] \(TAP), [polymorphism], [interface]
and many more advanced topics extensively. Please make sure that you
understand these topics to some extent before proceeding. With all
that being said, feel free to visit us on Discord at the link below
if you have any questions!
Here are some examples:
1. [Official samples]
2. [Official template]
> [!NOTE]
> Please note that you should *not* try to blindly copy paste
> the code. The examples are meant to be a template or a guide.
[Official template]: https://github.com/foxbot/DiscordBotBase/tree/csharp/src/DiscordBot
[Official samples]: https://github.com/RogueException/Discord.Net/tree/dev/samples
[Task-based Asynchronous Pattern]: https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap
[polymorphism]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/polymorphism
[interface]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/
## New to .NET/C#?
All examples or snippets featured in this guide and all API
documentation will be written in C#.
If you are new to the language, using this wrapper may prove to be
difficult, but don't worry! There are many resources online that can
help you get started in the wonderful world of .NET. Here are some
resources to get you started.
- [C# Programming Guide (MSDN/Microsoft, Free)](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/)
- [C# Fundamentals For Absolute Beginners (Channel9/Microsoft, Free)](https://channel9.msdn.com/Series/C-Fundamentals-for-Absolute-Beginners)
- [C# Path (Pluralsight, Paid)](https://www.pluralsight.com/paths/csharp)
## Still have questions?
Please visit us at `#dotnet_discord-net` on the [Discord API] server.
Describe the problem in details to us, what you've done, and,
if any, the problematic code uploaded onto [Hastebin](https://hastebin.com).
[Discord API]: https://discord.gg/jkrBmQR

View File

@@ -1,61 +0,0 @@
# Migrating from 0.9
**1.0.0 is the biggest breaking change the library has gone through, due to massive
changes in the design of the library.**
>A medium to advanced understanding is recommended when working with this library.
It is recommended to familiarize yourself with the entities in 1.0 before continuing.
Feel free to look through the library's source directly, look through IntelliSense, or
look through our hosted [API Documentation](xref:Discord).
## Entities
Most API models function _similarly_ to 0.9, however their names have been changed.
You should also keep in mind that we now separate different types of Channels and Users.
Before proceeding, please read over @Terminology to understand the naming behind some objects.
Below is a table that compares most common 0.9 entities to their 1.0 counterparts.
>This should be used mostly for migration purposes. Please take some time to consider whether
>or not you are using the right "tool for the job" when working with 1.0
| 0.9 | 1.0 | Notice |
| --- | --- | ------ |
| Server | @Discord.WebSocket.SocketGuild |
| Channel | @Discord.WebSocket.SocketGuildChannel | Applies only to channels that are members of a Guild |
| Channel.IsPrivate | @Discord.WebSocket.SocketDMChannel
| ChannelType.Text | @Discord.WebSocket.SocketTextChannel | This applies only to Text Channels in Guilds
| ChannelType.Voice | @Discord.WebSocket.SocketVoiceChannel | This applies only to Voice Channels in Guilds
| User | @Discord.WebSocket.SocketGuildUser | This applies only to users belonging to a Guild*
| Profile | @Discord.WebSocket.SocketGuildUser
| Message | @Discord.WebSocket.SocketUserMessage
\* To retrieve an @Discord.WebSocket.SocketGuildUser, you must retrieve the user from an @Discord.WebSocket.SocketGuild.
## Event Registration
Prior to 1.0, events were registered using the standard c# `Handler(EventArgs)` pattern. In 1.0,
events are delegates, but are still registered the same.
For example, let's look at [DiscordSocketClient.MessageReceived](xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_MessageReceived)
To hook an event into MessageReceived, we now use the following code:
[!code-csharp[Event Registration](samples/event.cs)]
> **All Event Handlers in 1.0 MUST return Task!**
If your event handler is marked as `async`, it will automatically return `Task`. However,
if you do not need to execute asynchronus code, do _not_ mark your handler as `async`, and instead,
stick a `return Task.CompletedTask` at the bottom.
[!code-csharp[Sync Event Registration](samples/sync_event.cs)]
**Event handlers no longer require a sender.** The only arguments your event handler needs to accept
are the parameters used by the event. It is recommended to look at the event in IntelliSense or on the
API docs before implementing it.
## Async
Nearly everything in 1.0 is an async Task. You should always await any tasks you invoke.

View File

@@ -1,4 +0,0 @@
_client.MessageReceived += async (msg) =>
{
await msg.Channel.SendMessageAsync(msg.Content);
}

View File

@@ -1,5 +0,0 @@
_client.Log += (msg) =>
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}

View File

@@ -1,27 +1,41 @@
- name: Introduction
topicUid: Guides.Introduction
- name: Getting Started
items:
- name: Installation
href: getting_started/installing.md
topicUid: Guides.GettingStarted.Installation
items:
- name: Nightly Builds
topicUid: Guides.GettingStarted.Installation.Nightlies
- name: Your First Bot
href: getting_started/intro.md
topicUid: Guides.GettingStarted.FirstBot
- name: Terminology
href: getting_started/terminology.md
topicUid: Guides.GettingStarted.Terminology
- name: Basic Concepts
items:
- name: Logging Data
href: concepts/logging.md
topicUid: Guides.Concepts.Logging
- name: Working with Events
href: concepts/events.md
topicUid: Guides.Concepts.Events
- name: Managing Connections
href: concepts/connections.md
topicUid: Guides.Concepts.ManageConnections
- name: Entities
href: concepts/entities.md
- name: The Command Service
topicUid: Guides.Concepts.Entities
- name: Working with Commands
items:
- name: Command Guide
href: commands/commands.md
- 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
topicUid: Guides.Commands.PostExecution
- name: Emoji
topicUid: Guides.Emoji
- name: Voice
items:
- name: Voice Guide
href: voice/sending-voice.md
- name: Migrating from 0.9
topicUid: Guides.Voice.SendingVoice
- name: Deployment
topicUid: Guides.Deployment

View File

@@ -1,4 +1,5 @@
---
uid: Guides.Voice.SendingVoice
title: Sending Voice
---
@@ -44,7 +45,7 @@ guild. To switch channels within a guild, invoke [ConnectAsync] on
another voice channel in the guild.
[IAudioClient]: xref:Discord.Audio.IAudioClient
[ConnectAsync]: xref:Discord.IAudioChannel#Discord_IAudioChannel_ConnectAsync_Action_IAudioClient__
[ConnectAsync]: xref:Discord.IAudioChannel.ConnectAsync*
## Transmitting Audio
@@ -98,4 +99,4 @@ you will want to wait for audio to stop playing before continuing on
to the next song. You can await `AudioOutStream.FlushAsync` to wait for
the audio client's internal buffer to clear out.
[!code-csharp[Sending Audio](samples/audio_ffmpeg.cs)]
[!code-csharp[Sending Audio](samples/audio_ffmpeg.cs)]