Browse Source

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
tags/2.0
Still Hsu Christopher F 6 years ago
parent
commit
ff0fea98a6
100 changed files with 4823 additions and 696 deletions
  1. +3
    -0
      .gitignore
  2. +19
    -1
      CONTRIBUTING.md
  3. +23
    -0
      Discord.Net.code-workspace
  4. +16
    -0
      Discord.Net.sln.DotSettings
  5. +14
    -25
      docs/CONTRIBUTING.md
  6. +21
    -0
      docs/Discord.Net.Docs.code-workspace
  7. +9
    -10
      docs/README.md
  8. +31
    -0
      docs/_overwrites/Commands/CommandException.Overwrite.md
  9. +22
    -0
      docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md
  10. +27
    -0
      docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md
  11. +5
    -0
      docs/_overwrites/Commands/ICommandContext.Inclusion.md
  12. +27
    -0
      docs/_overwrites/Commands/ICommandContext.Overwrite.md
  13. +103
    -0
      docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md
  14. +6
    -0
      docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md
  15. +68
    -0
      docs/_overwrites/Common/EmbedBuilder.Overwrites.md
  16. +25
    -0
      docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md
  17. +20
    -0
      docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md
  18. +26
    -0
      docs/_overwrites/Common/IEmote.Inclusion.md
  19. +81
    -0
      docs/_overwrites/Common/IEmote.Overwrites.md
  20. +174
    -0
      docs/_overwrites/Common/ObjectProperties.Overwrites.md
  21. +24
    -0
      docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md
  22. BIN
      docs/_overwrites/Common/images/embed-example.png
  23. BIN
      docs/_overwrites/Common/images/react-example.png
  24. BIN
      docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll
  25. +21
    -0
      docs/_template/description-generator/plugins/LICENSE
  26. +21
    -0
      docs/_template/last-modified/plugins/LICENSE
  27. BIN
      docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll
  28. +33
    -0
      docs/_template/light-dark-theme/partials/affix.tmpl.partial
  29. +27
    -0
      docs/_template/light-dark-theme/partials/head.tmpl.partial
  30. +10
    -0
      docs/_template/light-dark-theme/partials/scripts.tmpl.partial
  31. +3
    -0
      docs/_template/light-dark-theme/styles/cornerify.js
  32. +304
    -0
      docs/_template/light-dark-theme/styles/dark.css
  33. +1022
    -0
      docs/_template/light-dark-theme/styles/docfx.vendor.minify.css
  34. +311
    -0
      docs/_template/light-dark-theme/styles/gray.css
  35. +113
    -0
      docs/_template/light-dark-theme/styles/light.css
  36. +156
    -0
      docs/_template/light-dark-theme/styles/master.css
  37. +37
    -0
      docs/_template/light-dark-theme/styles/plugin-featherlight.js
  38. +26
    -0
      docs/_template/light-dark-theme/styles/styleswitcher.js
  39. +9
    -0
      docs/_template/light-dark-theme/styles/theme-switcher.css
  40. +72
    -0
      docs/_template/light-dark-theme/styles/tomorrow.css
  41. +115
    -0
      docs/_template/light-dark-theme/styles/vs2015.css
  42. +11
    -8
      docs/api/index.md
  43. +38
    -58
      docs/docfx.json
  44. +123
    -0
      docs/faq/basics/basic-operations.md
  45. +66
    -0
      docs/faq/basics/client-basics.md
  46. +79
    -0
      docs/faq/basics/getting-started.md
  47. BIN
      docs/faq/basics/images/dev-mode.png
  48. BIN
      docs/faq/basics/images/mention-escape.png
  49. BIN
      docs/faq/basics/images/snowflake.png
  50. +15
    -0
      docs/faq/basics/samples/cast.cs
  51. +18
    -0
      docs/faq/basics/samples/emoji-others.cs
  52. +17
    -0
      docs/faq/basics/samples/emoji-self.cs
  53. +54
    -0
      docs/faq/commands/dependency-injection.md
  54. +142
    -0
      docs/faq/commands/general.md
  55. +28
    -0
      docs/faq/commands/samples/DI.cs
  56. +20
    -0
      docs/faq/commands/samples/Remainder.cs
  57. +29
    -0
      docs/faq/commands/samples/missing-dep.cs
  58. +7
    -0
      docs/faq/commands/samples/runmode-cmdattrib.cs
  59. +10
    -0
      docs/faq/commands/samples/runmode-cmdconfig.cs
  60. +82
    -0
      docs/faq/misc/glossary.md
  61. +29
    -0
      docs/faq/misc/legacy.md
  62. +18
    -0
      docs/faq/toc.yml
  63. +4
    -8
      docs/filterConfig.yml
  64. +0
    -343
      docs/guides/commands/commands.md
  65. +47
    -0
      docs/guides/commands/dependency-injection.md
  66. +221
    -0
      docs/guides/commands/intro.md
  67. +122
    -0
      docs/guides/commands/post-execution.md
  68. +83
    -0
      docs/guides/commands/preconditions.md
  69. +0
    -63
      docs/guides/commands/samples/command_handler.cs
  70. +62
    -0
      docs/guides/commands/samples/dependency-injection/dependency_map_setup.cs
  71. +37
    -0
      docs/guides/commands/samples/dependency-injection/dependency_module.cs
  72. +29
    -0
      docs/guides/commands/samples/dependency-injection/dependency_module_noinject.cs
  73. +0
    -18
      docs/guides/commands/samples/dependency_map_setup.cs
  74. +0
    -40
      docs/guides/commands/samples/dependency_module.cs
  75. +0
    -6
      docs/guides/commands/samples/empty-module.cs
  76. +63
    -0
      docs/guides/commands/samples/intro/command_handler.cs
  77. +8
    -0
      docs/guides/commands/samples/intro/empty-module.cs
  78. +11
    -4
      docs/guides/commands/samples/intro/groups.cs
  79. +15
    -11
      docs/guides/commands/samples/intro/module.cs
  80. +13
    -0
      docs/guides/commands/samples/post-execution/command_exception_log.cs
  81. +13
    -0
      docs/guides/commands/samples/post-execution/command_executed_adv_demo.cs
  82. +38
    -0
      docs/guides/commands/samples/post-execution/command_executed_demo.cs
  83. +6
    -0
      docs/guides/commands/samples/post-execution/customresult_base.cs
  84. +10
    -0
      docs/guides/commands/samples/post-execution/customresult_extended.cs
  85. +10
    -0
      docs/guides/commands/samples/post-execution/customresult_usage.cs
  86. +11
    -0
      docs/guides/commands/samples/post-execution/post-execution_basic.cs
  87. +9
    -0
      docs/guides/commands/samples/preconditions/group_precondition.cs
  88. +3
    -0
      docs/guides/commands/samples/preconditions/precondition_usage.cs
  89. +7
    -3
      docs/guides/commands/samples/preconditions/require_owner.cs
  90. +29
    -0
      docs/guides/commands/samples/typereaders/typereader-register.cs
  91. +3
    -2
      docs/guides/commands/samples/typereaders/typereader.cs
  92. +70
    -0
      docs/guides/commands/typereaders.md
  93. +21
    -22
      docs/guides/concepts/connections.md
  94. +24
    -29
      docs/guides/concepts/entities.md
  95. +18
    -18
      docs/guides/concepts/events.md
  96. +20
    -19
      docs/guides/concepts/logging.md
  97. +1
    -1
      docs/guides/concepts/samples/connections.cs
  98. +3
    -5
      docs/guides/concepts/samples/entities.cs
  99. +1
    -1
      docs/guides/concepts/samples/events.cs
  100. +1
    -1
      docs/guides/concepts/samples/logging.cs

+ 3
- 0
.gitignore View File

@@ -206,3 +206,6 @@ project.lock.json
docs/api/\.manifest

\.idea/

# Codealike UID
codealike.json

+ 19
- 1
CONTRIBUTING.md View File

@@ -41,4 +41,22 @@ We attempt to conform to the .NET Foundation's [Coding Style](https://github.com
where possible.

As a general rule, follow the coding style already set in the file you
are editing, or look at a similar file if you are adding a new one.
are editing, or look at a similar file if you are adding a new one.

### Documentation Style for Members

When creating a new public member, the member must be annotated with sufficient documentation. This should include the
following, but not limited to:

* `<summary>` summarizing the purpose of the method.
* `<param>` or `<typeparam>` explaining the parameter.
* `<return>` explaining the type of the returned member and what it is.
* `<exception>` if the method directly throws an exception.

The length of the documentation should also follow the ruler as suggested by our
[Visual Studio Code workspace](Discord.Net.code-workspace).

#### Recommended Reads

* [Official Microsoft Documentation](https://docs.microsoft.com)
* [Sandcastle User Manual](https://ewsoftware.github.io/XMLCommentsGuide/html/4268757F-CE8D-4E6D-8502-4F7F2E22DDA3.htm)

+ 23
- 0
Discord.Net.code-workspace View File

@@ -0,0 +1,23 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"editor.rulers": [
120
],
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"docs/": true,
"**/obj": true,
"**/bin": true,
"samples/": true,
}
}
}

+ 16
- 0
Discord.Net.sln.DotSettings View File

@@ -0,0 +1,16 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Cacheable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Downloader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Emoji/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=libsodium/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=NSFW/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ratelimit/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=seeked/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sharded/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Spotify/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unban/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uncache/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=webhook/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=webhooks/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=webhook_0027s/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ZWSP/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 14
- 25
docs/CONTRIBUTING.md View File

@@ -1,27 +1,21 @@
# Contributing to Docs

I don't really have any strict conditions for writing documentation,
but just keep these few guidelines in mind:
## General Guidelines

We do not have any strict conditions for writing documentation,
but keep these guidelines in mind:

* Keep code samples in the `guides/samples` folder
* When referencing an object in the API, link to it's page in the
API documentation.
* When referencing an object in the API, link to its page in the
API documentation
* Documentation should be written in an FAQ/Wiki-style format
* Documentation should be written in clear and proper English*

\* If anyone is interested in translating documentation into other
languages, please open an issue or contact me on
\* If anyone is interested in translating documentation into other
languages, please open an issue or contact me on
Discord (`foxbot#0282`).

### Layout

Documentation should be written in a FAQ/Wiki style format.

Recommended reads:

* http://docs.microsoft.com
* http://flask.pocoo.org/docs/0.12/

Style consistencies:
## Style Consistencies

* Use a ruler set at 70 characters
* Links should use long syntax
@@ -29,18 +23,13 @@ Style consistencies:

Example of long link syntax:

```
```md
Please consult the [API Documentation] for more information.

[API Documentation]: xref:System.String
```

### Compiling

Documentation is compiled into a static site using [DocFx].
We currently use the most recent build off the dev branch.
## Recommended Reads

After making changes, compile your changes into the static site with
`docfx`. You can also view your changes live with `docfx --serve`.

[DocFx]: https://dotnet.github.io/docfx/
* http://docs.microsoft.com
* http://flask.pocoo.org/docs/0.12/

+ 21
- 0
docs/Discord.Net.Docs.code-workspace View File

@@ -0,0 +1,21 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"editor.rulers": [
70
],
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"obj/": true,
"_site/": true,
}
}
}

+ 9
- 10
docs/README.md View File

@@ -1,16 +1,15 @@
# Instructions for Building Documentation

The documentation for the Discord.NET library uses [DocFX][docfx-main]. [Instructions for installing this tool can be found here.][docfx-installing]
The documentation for the Discord.Net library uses [DocFX][docfx-main].
Instructions for installing this tool can be found [here][docfx-installing].

1. Navigate to the root of the repository.
2. (Optional) If you intend to target a specific version, ensure that you
have the correct version checked out.
3. Build the library. Run `dotnet build` in the root of this repository.
Ensure that the build passes without errors.
4. Build the docs using `docfx .\docs\docfx.json`. Add the `--serve` parameter
to preview the site locally. Some elements of the page may appear incorrect
when not hosted by a server.
- Remarks: According to the docfx website, this tool does work on Linux under mono.
2. Build the docs using `docfx docs/docfx.json`. Add the `--serve`
parameter to preview the site locally. Some elements of the page
may appear incorrectly when hosted offline.

Please note that if you intend to target a specific version, ensure
that you have the correct version checked out.

[docfx-main]: https://dotnet.github.io/docfx/
[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html
[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html

+ 31
- 0
docs/_overwrites/Commands/CommandException.Overwrite.md View File

@@ -0,0 +1,31 @@
---
uid: Discord.Commands.CommandException
remarks: *content
---

This @System.Exception class is typically used when diagnosing
an error thrown during the execution of a command. You will find the
thrown exception passed into
[LogMessage.Exception](xref:Discord.LogMessage.Exception), which is
sent to your [CommandService.Log](xref:Discord.Commands.CommandService.Log)
event handler.

---
uid: Discord.Commands.CommandException
example: [*content]
---

You may use this information to handle runtime exceptions after
execution. Below is an example of how you may use this:

```cs
public Task LogHandlerAsync(LogMessage logMessage)
{
// Note that this casting method requires C#7 and up.
if (logMessage?.Exception is CommandException cmdEx)
{
Console.WriteLine($"{cmdEx.GetBaseException().GetType()} was thrown while executing {cmdEx.Command.Aliases.First()} in {cmdEx.Context.Channel} by {cmdEx.Context.User}.");
}
return Task.CompletedTask;
}
```

+ 22
- 0
docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md View File

@@ -0,0 +1,22 @@
---
uid: Discord.Commands.DontAutoLoadAttribute
remarks: *content
---

The attribute can be applied to a public class that inherits
@Discord.Commands.ModuleBase. By applying this attribute,
@Discord.Commands.CommandService.AddModulesAsync* will not discover and
add the marked module to the CommandService.

---
uid: Discord.Commands.DontAutoLoadAttribute
example: [*content]
---

```cs
[DontAutoLoad]
public class MyModule : ModuleBase<SocketCommandContext>
{
// ...
}
```

+ 27
- 0
docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md View File

@@ -0,0 +1,27 @@
---
uid: Discord.Commands.DontInjectAttribute
remarks: *content
---

The attribute can be applied to a public settable property inside a
@Discord.Commands.ModuleBase based class. By applying this attribute,
the marked property will not be automatically injected of the
dependency. See @Guides.Commands.DI to learn more.

---
uid: Discord.Commands.DontInjectAttribute
example: [*content]
---

```cs
public class MyModule : ModuleBase<SocketCommandContext>
{
[DontInject]
public MyService MyService { get; set; }

public MyModule()
{
MyService = new MyService();
}
}
```

+ 5
- 0
docs/_overwrites/Commands/ICommandContext.Inclusion.md View File

@@ -0,0 +1,5 @@
An example of how this class is used the command system can be seen
below:

[!code[Sample module](../../guides/commands/samples/intro/empty-module.cs)]
[!code[Command handler](../../guides/commands/samples/intro/command_handler.cs)]

+ 27
- 0
docs/_overwrites/Commands/ICommandContext.Overwrite.md View File

@@ -0,0 +1,27 @@
---
uid: Discord.Commands.ICommandContext
example: [*content]
---

[!include[Example Section](ICommandContext.Inclusion.md)]

---
uid: Discord.Commands.CommandContext
example: [*content]
---

[!include[Example Section](ICommandContext.Inclusion.md)]

---
uid: Discord.Commands.SocketCommandContext
example: [*content]
---

[!include[Example Section](ICommandContext.Inclusion.md)]

---
uid: Discord.Commands.ShardCommandContext
example: [*content]
---

[!include[Example Section](ICommandContext.Inclusion.md)]

+ 103
- 0
docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md View File

@@ -0,0 +1,103 @@
---
uid: Discord.Commands.PreconditionAttribute
remarks: *content
---

This precondition attribute can be applied on module-level or
method-level for a command.

[!include[Additional Remarks](PreconditionAttribute.Remarks.Inclusion.md)]

---
uid: Discord.Commands.ParameterPreconditionAttribute
remarks: *content
---

This precondition attribute can be applied on parameter-level for a
command.

[!include[Additional Remarks](PreconditionAttribute.Remarks.Inclusion.md)]

---
uid: Discord.Commands.PreconditionAttribute
example: [*content]
---

The following example creates a precondition to see if the user has
sufficient role required to access the command.

```cs
public class RequireRoleAttribute : PreconditionAttribute
{
private readonly ulong _roleId;

public RequireRoleAttribute(ulong roleId)
{
_roleId = roleId;
}

public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context,
CommandInfo command, IServiceProvider services)
{
var guildUser = context.User as IGuildUser;
if (guildUser == null)
return PreconditionResult.FromError("This command cannot be executed outside of a guild.");

var guild = guildUser.Guild;
if (guild.Roles.All(r => r.Id != _roleId))
return PreconditionResult.FromError(
$"The guild does not have the role ({_roleId}) required to access this command.");

return guildUser.RoleIds.Any(rId => rId == _roleId)
? PreconditionResult.FromSuccess()
: PreconditionResult.FromError("You do not have the sufficient role required to access this command.");
}
}
```

---
uid: Discord.Commands.ParameterPreconditionAttribute
example: [*content]
---

The following example creates a precondition on a parameter-level to
see if the targeted user has a lower hierarchy than the user who
executed the command.

```cs
public class RequireHierarchyAttribute : ParameterPreconditionAttribute
{
public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context,
ParameterInfo parameter, object value, IServiceProvider services)
{
// Hierarchy is only available under the socket variant of the user.
if (!(context.User is SocketGuildUser guildUser))
return PreconditionResult.FromError("This command cannot be used outside of a guild.");

SocketGuildUser targetUser;
switch (value)
{
case SocketGuildUser targetGuildUser:
targetUser = targetGuildUser;
break;
case ulong userId:
targetUser = await context.Guild.GetUserAsync(userId).ConfigureAwait(false) as SocketGuildUser;
break;
default:
throw new ArgumentOutOfRangeException();
}

if (targetUser == null)
return PreconditionResult.FromError("Target user not found.");

if (guildUser.Hierarchy < targetUser.Hierarchy)
return PreconditionResult.FromError("You cannot target anyone else whose roles are higher than yours.");

var currentUser = await context.Guild.GetCurrentUserAsync().ConfigureAwait(false) as SocketGuildUser;
if (currentUser?.Hierarchy < targetUser.Hierarchy)
return PreconditionResult.FromError("The bot's role is lower than the targeted user.");

return PreconditionResult.FromSuccess();
}
}
```

+ 6
- 0
docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md View File

@@ -0,0 +1,6 @@
A "precondidtion" in the command system is used to determine if a
condition is met before entering the command task. Using a
precondidtion may aid in keeping a well-organized command logic.

The most common use case being whether a user has sufficient
permission to execute the command.

+ 68
- 0
docs/_overwrites/Common/EmbedBuilder.Overwrites.md View File

@@ -0,0 +1,68 @@
---
uid: Discord.EmbedBuilder
seealso:
- linkId: Discord.EmbedFooterBuilder
- linkId: Discord.EmbedAuthorBuilder
- linkId: Discord.EmbedFieldBuilder
remarks: *content
---

This builder class is used to build an @Discord.Embed (rich embed)
object that will be ready to be sent via @Discord.IMessageChannel.SendMessageAsync*
after @Discord.EmbedBuilder.Build* is called.

---
uid: Discord.EmbedBuilder
example: [*content]
---

#### Basic Usage

The example below builds an embed and sends it to the chat using the
command system.

```cs
[Command("embed")]
public async Task SendRichEmbedAsync()
{
var embed = new EmbedBuilder
{
// Embed property can be set within object initializer
Title = "Hello world!"
Description = "I am a description set by initializer."
};
// Or with methods
embed.AddField("Field title",
"Field value. I also support [hyperlink markdown](https://example.com)!")
.WithAuthor(Context.Client.CurrentUser)
.WithFooter(footer => footer.Text = "I am a footer.")
.WithColor(Color.Blue)
.WithTitle("I overwrote \"Hello world!\"")
.WithDescription("I am a description.")
.WithUrl("https://example.com")
.WithCurrentTimestamp()
.Build();
await ReplyAsync(embed: embed);
}
```

![Embed Example](images/embed-example.png)

#### Usage with Local Images

The example below sends an image and has the image embedded in the rich
embed. See @Discord.IMessageChannel.SendFileAsync* for more information
about uploading a file or image.

```cs
[Command("embedimage")]
public async Task SendEmbedWithImageAsync()
{
var fileName = "image.png";
var embed = new EmbedBuilder()
{
ImageUrl = $"attachment://{fileName}"
}.Build();
await Context.Channel.SendFileAsync(fileName, embed: embed);
}
```

+ 25
- 0
docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md View File

@@ -0,0 +1,25 @@
The example will build a rich embed with an author field, a footer
field, and 2 normal fields using an @Discord.EmbedBuilder:

```cs
var exampleAuthor = new EmbedAuthorBuilder()
.WithName("I am a bot")
.WithIconUrl("https://discordapp.com/assets/e05ead6e6ebc08df9291738d0aa6986d.png");
var exampleFooter = new EmbedFooterBuilder()
.WithText("I am a nice footer")
.WithIconUrl("https://discordapp.com/assets/28174a34e77bb5e5310ced9f95cb480b.png");
var exampleField = new EmbedFieldBuilder()
.WithName("Title of Another Field")
.WithValue("I am an [example](https://example.com).")
.WithInline(true);
var otherField = new EmbedFieldBuilder()
.WithName("Title of a Field")
.WithValue("Notice how I'm inline with that other field next to me.")
.WithInline(true);
var embed = new EmbedBuilder()
.AddField(exampleField)
.AddField(otherField)
.WithAuthor(exampleAuthor)
.WithFooter(exampleFooter)
.Build();
```

+ 20
- 0
docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md View File

@@ -0,0 +1,20 @@
---
uid: Discord.EmbedAuthorBuilder
example: [*content]
---

[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)]

---
uid: Discord.EmbedFooterBuilder
example: [*content]
---

[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)]

---
uid: Discord.EmbedFieldBuilder
example: [*content]
---

[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)]

+ 26
- 0
docs/_overwrites/Common/IEmote.Inclusion.md View File

@@ -0,0 +1,26 @@
The sample below sends a message and adds an @Discord.Emoji and a custom
@Discord.Emote to the message.

```cs
public async Task SendAndReactAsync(ISocketMessageChannel channel)
{
var message = await channel.SendMessageAsync("I am a message.");

// Creates a Unicode-based emoji based on the Unicode string.
// This is effectively the same as new Emoji("💕").
var heartEmoji = new Emoji("\U0001f495");
// Reacts to the message with the Emoji.
await message.AddReactionAsync(heartEmoji);

// Parses a custom emote based on the provided Discord emote format.
// Please note that this does not guarantee the existence of
// the emote.
var emote = Emote.Parse("<:thonkang:282745590985523200>");
// Reacts to the message with the Emote.
await message.AddReactionAsync(emote);
}
```

#### Result

![React Example](images/react-example.png)

+ 81
- 0
docs/_overwrites/Common/IEmote.Overwrites.md View File

@@ -0,0 +1,81 @@
---
uid: Discord.IEmote
seealso:
- linkId: Discord.Emote
- linkId: Discord.Emoji
- linkId: Discord.GuildEmote
- linkId: Discord.IUserMessage
remarks: *content
---

This interface is often used with reactions. It can represent an
unicode-based @Discord.Emoji, or a custom @Discord.Emote.

---
uid: Discord.Emote
seealso:
- linkId: Discord.IEmote
- linkId: Discord.GuildEmote
- linkId: Discord.Emoji
- linkId: Discord.IUserMessage
remarks: *content
---

> [!NOTE]
> A valid @Discord.Emote format is `<:emoteName:emoteId>`. This can be
> obtained by escaping with a `\` in front of the emote using the
> Discord chat client.

This class represents a custom emoji. This type of emoji can be
created via the @Discord.Emote.Parse* or @Discord.Emote.TryParse*
method.

---
uid: Discord.Emoji
seealso:
- linkId: Discord.Emote
- linkId: Discord.GuildEmote
- linkId: Discord.Emoji
- linkId: Discord.IUserMessage
remarks: *content
---

> [!NOTE]
> A valid @Discord.Emoji format is Unicode-based. This means only
> something like `🙃` or `\U0001f643` would work, instead of
> `:upside_down:`.
>
> A Unicode-based emoji can be obtained by escaping with a `\` in
> front of the emote using the Discord chat client or by looking up on
> [Emojipedia](https://emojipedia.org).

This class represents a standard Unicode-based emoji. This type of emoji
can be created by passing the Unicode into the constructor.

---
uid: Discord.IEmote
example: [*content]
---

[!include[Example Section](IEmote.Inclusion.md)]

---
uid: Discord.Emoji
example: [*content]
---

[!include[Example Section](IEmote.Inclusion.md)]

---
uid: Discord.Emote
example: [*content]
---

[!include[Example Section](IEmote.Inclusion.md)]

---
uid: Discord.GuildEmote
example: [*content]
---

[!include[Example Section](IEmote.Inclusion.md)]

+ 174
- 0
docs/_overwrites/Common/ObjectProperties.Overwrites.md View File

@@ -0,0 +1,174 @@
---
uid: Discord.GuildChannelProperties
example: [*content]
---

The following example uses @Discord.IGuildChannel.ModifyAsync* to
apply changes specified in the properties,

```cs
var channel = _client.GetChannel(id) as IGuildChannel;
if (channel == null) return;

await channel.ModifyAsync(x =>
{
x.Name = "new-name";
x.Position = channel.Position - 1;
});
```

---
uid: Discord.TextChannelProperties
example: [*content]
---

The following example uses @Discord.ITextChannel.ModifyAsync* to
apply changes specified in the properties,

```cs
var channel = _client.GetChannel(id) as ITextChannel;
if (channel == null) return;

await channel.ModifyAsync(x =>
{
x.Name = "cool-guys-only";
x.Topic = "This channel is only for cool guys and adults!!!";
x.Position = channel.Position - 1;
x.IsNsfw = true;
});
```

---
uid: Discord.VoiceChannelProperties
example: [*content]
---

The following example uses @Discord.IVoiceChannel.ModifyAsync* to
apply changes specified in the properties,

```cs
var channel = _client.GetChannel(id) as IVoiceChannel;
if (channel == null) return;

await channel.ModifyAsync(x =>
{
x.UserLimit = 5;
});
```

---
uid: Discord.EmoteProperties
example: [*content]
---

The following example uses @Discord.IGuild.ModifyEmoteAsync* to
apply changes specified in the properties,

```cs
await guild.ModifyEmoteAsync(x =>
{
x.Name = "blobo";
});
```

---
uid: Discord.MessageProperties
example: [*content]
---

The following example uses @Discord.IUserMessage.ModifyAsync* to
apply changes specified in the properties,

```cs
var message = await channel.SendMessageAsync("boo");
await Task.Delay(TimeSpan.FromSeconds(1));
await message.ModifyAsync(x => x.Content = "boi");
```

---
uid: Discord.GuildProperties
example: [*content]
---

The following example uses @Discord.IGuild.ModifyAsync* to
apply changes specified in the properties,

```cs
var guild = _client.GetGuild(id);
if (guild == null) return;

await guild.ModifyAsync(x =>
{
x.Name = "VERY Fast Discord Running at Incredible Hihg Speed";
});
```

---
uid: Discord.RoleProperties
example: [*content]
---

The following example uses @Discord.IRole.ModifyAsync* to
apply changes specified in the properties,

```cs
var role = guild.GetRole(id);
if (role == null) return;

await role.ModifyAsync(x =>
{
x.Name = "cool boi";
x.Color = Color.Gold;
x.Hoist = true;
x.Mentionable = true;
});
```

---
uid: Discord.GuildUserProperties
example: [*content]
---

The following example uses @Discord.IGuildUser.ModifyAsync* to
apply changes specified in the properties,

```cs
var user = guild.GetUser(id);
if (user == null) return;

await user.ModifyAsync(x =>
{
x.Nickname = "I need healing";
});
```

---
uid: Discord.SelfUserProperties
example: [*content]
---

The following example uses @Discord.ISelfUser.ModifyAsync* to
apply changes specified in the properties,

```cs
await selfUser.ModifyAsync(x =>
{
x.Username = "Mercy";
});
```

---
uid: Discord.WebhookProperties
example: [*content]
---

The following example uses @Discord.IWebhook.ModifyAsync* to
apply changes specified in the properties,

```cs
await webhook.ModifyAsync(x =>
{
x.Name = "very fast fox";
x.ChannelId = newChannelId;
});
```

+ 24
- 0
docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md View File

@@ -0,0 +1,24 @@
---
uid: Discord.Commands.OverrideTypeReaderAttribute
remarks: *content
---

This attribute is used to override a command parameter's type reading
behaviour. This may be useful when you have multiple custom
@Discord.Commands.TypeReader and would like to specify one.

---
uid: Discord.Commands.OverrideTypeReaderAttribute
examples: [*content]
---

The following example will override the @Discord.Commands.TypeReader
of @Discord.IUser to `MyUserTypeReader`.

```cs
public async Task PrintUserAsync(
[OverrideTypeReader(typeof(MyUserTypeReader))] IUser user)
{
//...
}
```

BIN
docs/_overwrites/Common/images/embed-example.png View File

Before After
Width: 334  |  Height: 165  |  Size: 15 KiB

BIN
docs/_overwrites/Common/images/react-example.png View File

Before After
Width: 307  |  Height: 95  |  Size: 8.8 KiB

BIN
docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll View File


+ 21
- 0
docs/_template/description-generator/plugins/LICENSE View File

@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018 Still Hsu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

+ 21
- 0
docs/_template/last-modified/plugins/LICENSE View File

@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018 Still Hsu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

BIN
docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll View File


+ 33
- 0
docs/_template/light-dark-theme/partials/affix.tmpl.partial View File

@@ -0,0 +1,33 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}

<div class="hidden-sm col-md-2" role="complementary">
<div class="sideaffix">
<div class="theme-switch-field">
<p>Theme</p>
<select id="theme-switcher">
<option value="dark">Dark</option>
<option value="gray">Gray</option>
<option value="light">Light</option>
</select>
</div>
{{^_disableContribution}}
<div class="contribution">
<ul class="nav">
{{#docurl}}
<li>
<a href="{{docurl}}" class="contribution-link">{{__global.improveThisDoc}}</a>
</li>
{{/docurl}}
{{#sourceurl}}
<li>
<a href="{{sourceurl}}" class="contribution-link">{{__global.viewSource}}</a>
</li>
{{/sourceurl}}
</ul>
</div>
{{/_disableContribution}}
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
<!-- <p><a class="back-to-top" href="#top">Back to top</a><p> -->
</nav>
</div>
</div>

+ 27
- 0
docs/_template/light-dark-theme/partials/head.tmpl.partial View File

@@ -0,0 +1,27 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
<meta name="viewport" content="width=device-width">
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
<meta name="generator" content="docfx {{_docfxVersion}}">
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
<link rel="preload" href="{{_rel}}styles/search-worker.js" as="script">
<link rel="preload" href="{{_rel}}fonts/glyphicons-halflings-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="shortcut icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.minify.css">
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
<link rel="stylesheet" href="{{_rel}}styles/master.css">
<link rel="stylesheet" href="{{_rel}}styles/main.css">
<link rel="stylesheet" href="{{_rel}}styles/theme-switcher.css">
<link href="https://cdn.rawgit.com/noelboss/featherlight/1.7.6/release/featherlight.min.css" type="text/css" rel="stylesheet" />
<meta name="theme-color" content="#99AAB5"/>
<meta property="docfx:navrel" content="{{_navRel}}">
<meta property="docfx:tocrel" content="{{_tocRel}}">
<meta id="docfx-style:rel" content="{{_rel}}">
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
{{#_enableSearch}}<meta property="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
{{#_enableNewTab}}<meta property="docfx:newtab" content="true">{{/_enableNewTab}}
</head>

+ 10
- 0
docs/_template/light-dark-theme/partials/scripts.tmpl.partial View File

@@ -0,0 +1,10 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}

<script type="text/javascript" src="{{_rel}}styles/docfx.vendor.js"></script>
<script type="text/javascript" src="{{_rel}}styles/docfx.js"></script>
<script type="text/javascript" src="{{_rel}}styles/main.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/noelboss/featherlight/master/release/featherlight.min.js" charset="utf-8"></script>
<script type="text/javascript" src="{{_rel}}styles/plugin-featherlight.js"></script>
<script type="text/javascript" src="{{_rel}}styles/styleswitcher.js"></script>
<script type="text/javascript" src="https://malsup.github.io/jquery.corner.js"></script>
<script type="text/javascript" src="{{_rel}}styles/cornerify.js"></script>

+ 3
- 0
docs/_template/light-dark-theme/styles/cornerify.js View File

@@ -0,0 +1,3 @@
window.onload = function (e) {
$('img').corner();
}

+ 304
- 0
docs/_template/light-dark-theme/styles/dark.css View File

@@ -0,0 +1,304 @@
/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */

@import url('vs2015.css');
html,
body {
background: #212121;
color: #C0C0C0;
}

button,
a {
color: #64B5F6;
}

.sidenav{
background-color: rgb(30, 30, 30);
}

button:hover,
button:focus,
a:hover,
a:focus,
.btn:focus,
.btn:hover{
color: #2196F3;
}

a.disable,
a.disable:hover {
color: #EEEEEE;
}

.divider {
color: #37474F;
}

hr {
border-color: #37474F;
}

.subnav {
background: #383838
}

.inheritance h5,
.inheritedMembers h5 {
border-bottom: 1px solid #37474F;
}

article h4 {
border-bottom: 1px solid #37474F;
}

.docs-search {
background: #424242;
}

.search-results-group-heading {
color: #424242;
}

.search-close {
color: #424242;
}

.sidetoc {
background-color: #1b1b1b;
border-left: 0px solid #37474F;
border-right: 0px solid #37474F;
}

.sideaffix {
overflow: visible;
}

.sideaffix>div.contribution>ul>li>a.contribution-link:hover {
background-color: #333333;
}

/* toc */

.toc .nav>li>a {
color: rgb(218, 218, 218);
}

.toc .nav>li>a:hover,
.toc .nav>li>a:focus {
color: #E0E0E0;
}

.toc .nav>li.active>a {
color: #90CAF9;
}

.toc .nav>li.active>a:hover,
.toc .nav>li.active>a:focus {
background-color: #37474F;
color: #4FC3F7;
}

.sidefilter {
background-color: #1b1b1b;
border-left: 0px solid #37474F;
border-right: 0px solid #37474F;
}

.affix ul>li>a:hover {
background: none;
color: #EEEEEE;
}

.affix ul>li.active>a,
.affix ul>li.active>a:before {
color: #B3E5FC;
}

.affix ul>li>a {
color: #EEEEEE;
}

.affix>ul>li.active>a,
.affix>ul>li.active>a:before {
color: #B3E5FC;
}

.tryspan {
border-color: #37474F;
}

.footer {
border-top: 1px solid #5F5F5F;
background: #616161;
}

/* alert */
.alert-info {
color: #d9edf7;
background: #004458;
border-color: #005873;
}

.alert-warning {
color: #fffaf2;
background: #80551a;
border-color: #99661f;
}

.alert-danger {
color: #fff2f2;
background: #4d0000;
border-color: #660000;
}

/* For tabbed content */

.tabGroup {
margin-top: 1rem;
}

.tabGroup ul[role="tablist"] {
margin: 0;
padding: 0;
list-style: none;
}

.tabGroup ul[role="tablist"]>li {
list-style: none;
display: inline-block;
}

.tabGroup a[role="tab"] {
color: white;
box-sizing: border-box;
display: inline-block;
padding: 5px 7.5px;
text-decoration: none;
border-bottom: 2px solid #fff;
}

.tabGroup a[role="tab"]:hover,
.tabGroup a[role="tab"]:focus,
.tabGroup a[role="tab"][aria-selected="true"] {
border-bottom: 2px solid #607D8B;
}

.tabGroup a[role="tab"][aria-selected="true"] {
color: #81D4FA;
}

.tabGroup a[role="tab"]:hover,
.tabGroup a[role="tab"]:focus {
color: #29B6F6;
}

.tabGroup a[role="tab"]:focus {
outline: 1px solid #607D8B;
outline-offset: -1px;
}

@media (min-width: 768px) {
.tabGroup a[role="tab"] {
padding: 5px 15px;
}
}

.tabGroup section[role="tabpanel"] {
border: 1px solid #607D8B;
padding: 15px;
margin: 0;
overflow: hidden;
}

.tabGroup section[role="tabpanel"]>.codeHeader,
.tabGroup section[role="tabpanel"]>pre {
margin-left: -16px;
margin-right: -16px;
}

.tabGroup section[role="tabpanel"]> :first-child {
margin-top: 0;
}

.tabGroup section[role="tabpanel"]>pre:last-child {
display: block;
margin-bottom: -16px;
}

.mainContainer[dir='rtl'] main ul[role="tablist"] {
margin: 0;
}

/* code */

code {
color: white;
background-color: #4a4c52;
border-radius: 4px;
padding: 3px 7px;
}

pre {
background-color: #282a36;
}

/* table */

.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #333333;
color: #d3d3d3
}

tbody>tr {
background-color: #424242;
color: #c0c0c0
}

.table>tbody+tbody {
border-top: 2px solid rgb(173, 173, 173)
}

/* select */

select {
background-color: #3b3b3b;
border-color: #2e2e2e;
}

/*
Following code regarding collapse container are fetched
or modified from the Materialize project.

The MIT License (MIT)
Copyright (c) 2014-2018 Materialize
https://github.com/Dogfalo/materialize
*/

/* all collapse container */
.collapse-container.last-modified {
-webkit-box-shadow: 0 2px 2px 0 rgba(50, 50, 50, 0.64), 0 3px 1px -2px rgba(50, 50, 50, 0.62), 0 1px 5px 0 rgba(50, 50, 50, 0.7);
box-shadow: 0 2px 2px 0 rgba(50, 50, 50, 0.64), 0 3px 1px -2px rgba(50, 50, 50, 0.62), 0 1px 5px 0 rgba(50, 50, 50, 0.7);
border-top: 1px solid rgba(96, 96, 96, 0.7);
border-right: 1px solid rgba(96, 96, 96, 0.7);
border-left: 1px solid rgba(96, 96, 96, 0.7);
}

/* header */
.collapse-container.last-modified>:nth-child(odd) {
background-color: #3f3f3f;
border-bottom: 1px solid rgba(96, 96, 96, 0.7);
}

/* body */
.collapse-container.last-modified>:nth-child(even) {
border-bottom: 1px solid rgba(96, 96, 96, 0.7);
background-color: inherit;
}

span.arrow-d{
border-top: 5px solid white
}

span.arrow-r{
border-left: 5px solid white
}

+ 1022
- 0
docs/_template/light-dark-theme/styles/docfx.vendor.minify.css
File diff suppressed because it is too large
View File


+ 311
- 0
docs/_template/light-dark-theme/styles/gray.css View File

@@ -0,0 +1,311 @@
/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */

@import url('vs2015.css');
html,
body {
background: #23272A;
color: #dddddd;
}

button,
a {
color: #64B5F6;
}

.sidenav{
background-color: rgb(30, 30, 30);
}

button:hover,
button:focus,
a:hover,
a:focus,
.btn:focus,
.btn:hover{
color: #2196F3;
}

a.disable,
a.disable:hover {
color: #EEEEEE;
}

.divider {
color: #37474F;
}

hr {
border-color: #37474F;
}

/* top navbar */
.navbar-inverse[role="navigation"] {
background-color: #2C2F33;
}

/* sub navbar (below top) */
.subnav {
background: #282B2F
}


.inheritance h5,
.inheritedMembers h5 {
border-bottom: 1px solid #37474F;
}

article h4 {
border-bottom: 1px solid #37474F;
}

.docs-search {
background: #424242;
}

.search-results-group-heading {
color: #424242;
}

.search-close {
color: #424242;
}

.sidetoc {
background-color: #1b1b1b;
border-left: 0px solid #37474F;
border-right: 0px solid #37474F;
}

.sideaffix {
overflow: visible;
}

.sideaffix>div.contribution>ul>li>a.contribution-link:hover {
background-color: #333333;
}

/* toc */

.toc .nav>li>a {
color: rgb(218, 218, 218);
}

.toc .nav>li>a:hover,
.toc .nav>li>a:focus {
color: #E0E0E0;
}

.toc .nav>li.active>a {
color: #90CAF9;
}

.toc .nav>li.active>a:hover,
.toc .nav>li.active>a:focus {
background-color: #37474F;
color: #4FC3F7;
}

.sidefilter {
background-color: #1b1b1b;
border-left: 0px solid #37474F;
border-right: 0px solid #37474F;
}

.affix ul>li>a:hover {
background: none;
color: #EEEEEE;
}

.affix ul>li.active>a,
.affix ul>li.active>a:before {
color: #B3E5FC;
}

.affix ul>li>a {
color: #EEEEEE;
}

.affix>ul>li.active>a,
.affix>ul>li.active>a:before {
color: #B3E5FC;
}

.tryspan {
border-color: #37474F;
}

.footer {
border-top: 1px solid #5F5F5F;
background: #2C2F33;
}

/* alert */
.alert-info {
color: #f3fdff;
background: #40788A;
border-color: #2F7A95;
}

.alert-warning {
color: #fffaf2;
background: #936C36;
border-color: #AE8443;
}

.alert-danger {
color: #fff4f4;
background: #834040;
border-color: #8C2F2F
}

/* For tabbed content */

.tabGroup {
margin-top: 1rem;
}

.tabGroup ul[role="tablist"] {
margin: 0;
padding: 0;
list-style: none;
}

.tabGroup ul[role="tablist"]>li {
list-style: none;
display: inline-block;
}

.tabGroup a[role="tab"] {
color: white;
box-sizing: border-box;
display: inline-block;
padding: 5px 7.5px;
text-decoration: none;
border-bottom: 2px solid #fff;
}

.tabGroup a[role="tab"]:hover,
.tabGroup a[role="tab"]:focus,
.tabGroup a[role="tab"][aria-selected="true"] {
border-bottom: 2px solid #607D8B;
}

.tabGroup a[role="tab"][aria-selected="true"] {
color: #81D4FA;
}

.tabGroup a[role="tab"]:hover,
.tabGroup a[role="tab"]:focus {
color: #29B6F6;
}

.tabGroup a[role="tab"]:focus {
outline: 1px solid #607D8B;
outline-offset: -1px;
}

@media (min-width: 768px) {
.tabGroup a[role="tab"] {
padding: 5px 15px;
}
}

.tabGroup section[role="tabpanel"] {
border: 1px solid #607D8B;
padding: 15px;
margin: 0;
overflow: hidden;
}

.tabGroup section[role="tabpanel"]>.codeHeader,
.tabGroup section[role="tabpanel"]>pre {
margin-left: -16px;
margin-right: -16px;
}

.tabGroup section[role="tabpanel"]> :first-child {
margin-top: 0;
}

.tabGroup section[role="tabpanel"]>pre:last-child {
display: block;
margin-bottom: -16px;
}

.mainContainer[dir='rtl'] main ul[role="tablist"] {
margin: 0;
}

/* code */

code {
color: white;
background-color: #5B646B;
border-radius: 4px;
padding: 3px 7px;
}

pre {
background-color: #282a36;
}

/* table */

.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #333333;
color: #d3d3d3
}

tbody>tr {
background-color: #424242;
color: #c0c0c0
}

.table>tbody+tbody {
border-top: 2px solid rgb(173, 173, 173)
}

/* select */

select {
background-color: #3b3b3b;
border-color: #2e2e2e;
}

/*
Following code regarding collapse container are fetched
or modified from the Materialize project.

The MIT License (MIT)
Copyright (c) 2014-2018 Materialize
https://github.com/Dogfalo/materialize
*/

/* all collapse container */
.collapse-container.last-modified {
-webkit-box-shadow: 0 2px 2px 0 rgba(50, 50, 50, 0.64), 0 3px 1px -2px rgba(50, 50, 50, 0.62), 0 1px 5px 0 rgba(50, 50, 50, 0.7);
box-shadow: 0 2px 2px 0 rgba(50, 50, 50, 0.64), 0 3px 1px -2px rgba(50, 50, 50, 0.62), 0 1px 5px 0 rgba(50, 50, 50, 0.7);
border-top: 1px solid rgba(96, 96, 96, 0.7);
border-right: 1px solid rgba(96, 96, 96, 0.7);
border-left: 1px solid rgba(96, 96, 96, 0.7);
}

/* header */
.collapse-container.last-modified>:nth-child(odd) {
background-color: #3f3f3f;
border-bottom: 1px solid rgba(96, 96, 96, 0.7);
}

/* body */
.collapse-container.last-modified>:nth-child(even) {
border-bottom: 1px solid rgba(96, 96, 96, 0.7);
background-color: inherit;
}

span.arrow-d{
border-top: 5px solid white
}

span.arrow-r{
border-left: 5px solid white
}

+ 113
- 0
docs/_template/light-dark-theme/styles/light.css View File

@@ -0,0 +1,113 @@
/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */

@import url('tomorrow.css');
html,
body {
background: #fff;
color: #000;
}

.sideaffix {
overflow: visible;
}

/* links */

a:active, a:hover, a:visited {
color: #0078d7;
}

a {
color: #0050c5;
cursor: pointer;
text-decoration: none;
word-wrap: break-word;
}

/* alert */
.alert-info {
color: #165e82;
background-color: #c1e0ef;
border-color: #8cbfd8;
}

.alert-warning {
color: #825e16;
background-color: #efe0c1;
border-color: #d8bf8c;
}

.alert-danger {
color: #821616;
background-color: #efc1c1;
border-color: #d88c8c;
}

/* code */

code {
color: #9c3a3f;
background-color: #ececec;
border-radius: 4px;
padding: 3px 7px;
}

/* table */

.table-striped>tbody>tr:nth-of-type(odd) {
color: #333333;
background-color: #d3d3d3
}

tbody>tr {
color: #424242;
background-color: #c0c0c0
}

.table>tbody+tbody {
border-top: 2px solid rgb(173, 173, 173)
}

/* select */

select {
background-color: #fcfcfc;
border-color: #aeb1b5;
}

/*
Following code regarding collapse container are fetched
or modified from the Materialize project.

The MIT License (MIT)
Copyright (c) 2014-2018 Materialize
https://github.com/Dogfalo/materialize
*/

/* all collapse container */
.collapse-container.last-modified {
-webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
border-top: 1px solid #ddd;
border-right: 1px solid #ddd;
border-left: 1px solid #ddd;
}

/* header */
.collapse-container.last-modified>:nth-child(odd) {
background-color: #fff;
border-bottom: 1px solid #ddd;
}

/* body */
.collapse-container.last-modified>:nth-child(even) {
border-bottom: 1px solid #ddd;
}

span.arrow-d{
border-top: 5px solid black
}

span.arrow-r{
border-left: 5px solid black
}

+ 156
- 0
docs/_template/light-dark-theme/styles/master.css View File

@@ -0,0 +1,156 @@
@import url('https://fonts.googleapis.com/css?family=Titillium+Web|Noto+Sans');

html,
body {
font-family: 'Titillium Web', 'Segoe UI', Tahoma, Helvetica, sans-serif;
font-display: optional;
height: 100%;
font-size: 15px;
scroll-behavior: smooth;
}

p,
li,
.toc {
text-rendering: optimizeLegibility;
line-height: 160%;
}

img {
box-shadow: 0px 0px 3px 0px rgb(66, 66, 66);
max-width: 95% !important;
margin-top: 15px;
margin-bottom: 15px;
}

article.content p{
-webkit-transition: all .75s ease-in-out;
transition: all .75s ease-in-out;
}

article.content h1,
article.content h2,
article.content h3,
article.content h4,
article.content h5,
article.content h6{
-webkit-transition: all .25s ease-in-out;
transition: all .25s ease-in-out;
}

h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Noto Sans', Verdana, Geneva, Tahoma, sans-serif;
line-height: 130%;
}

.sideaffix {
line-height: 140%;
}

header .navbar {
border-width: 0 0 0px;
border-radius: 0;
}

body .toc {
background-color: inherit;
overflow: visible;
}

select {
display: inline-block;
overflow: auto;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0 30px 0 6px;
vertical-align: middle;
height: 28px;
border: 1px solid #e3e3e3;
line-height: 16px;
outline: 0;
text-overflow: ellipsis;
-webkit-appearance: none;
-moz-appearance: none;
cursor: pointer;
background-image: linear-gradient(45deg, transparent 50%, #707070 0), linear-gradient(135deg, #707070 50%, transparent 0);
background-position: calc(100% - 13px) 11px, calc(100% - 8px) 11px;
background-size: 5px 5px, 5px 6px;
background-repeat: no-repeat;
}

/*
Following code are fetched or modified from
the Materialize project.

The MIT License (MIT)
Copyright (c) 2014-2018 Materialize
https://github.com/Dogfalo/materialize
*/

/* all collapse container */

.collapse-container.last-modified {
margin: 0.5rem 0 1rem 0;
}

/* header */

.collapse-container.last-modified>:nth-child(odd):focus {
outline: 0;
}

.collapse-container.last-modified>:nth-child(odd) {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
line-height: 1.5;
padding: 0.75rem;
background-image: none;
border: 0px;
}

/* body */

.collapse-container.last-modified>:nth-child(even) {
display: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
padding: 1rem;
border: 0px;
}

/* nav bar */

.nav {
margin: 0;
}

.nav li {
-webkit-transition: background-color .3s, color .3s;
transition: background-color .3s, color .3s;
}

.nav a {
-webkit-transition: background-color .3s, color .3s;
transition: background-color .3s, color .3s;
cursor: pointer;
}

/* arrow */

span.arrow-d{
top: 6px; position: relative;
}

span.arrow-r{
top: 6px; position: relative;
}

+ 37
- 0
docs/_template/light-dark-theme/styles/plugin-featherlight.js View File

@@ -0,0 +1,37 @@
// MIT License

// Copyright (c) 2017 Roel Fauconnier

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

$(document).ready(function() {
//find all images, but not the logo, and add the lightbox
$('img').not('#logo').each(function(){
var $img = $(this);
var filename = $img.attr('src')
//add cursor
$img.css('cursor','zoom-in');
$img.css('cursor','-moz-zoom-in');
$img.css('cursor','-webkit-zoom-in');

//add featherlight
$img.attr('alt', filename);
$img.featherlight(filename);
});
});

+ 26
- 0
docs/_template/light-dark-theme/styles/styleswitcher.js View File

@@ -0,0 +1,26 @@
const baseUrl = document.getElementById("docfx-style:rel").content;

function onThemeSelect(event) {
const theme = event.target.value;
window.localStorage.setItem("theme", theme);
window.themeElement.href = getUrl(theme);
}

function getUrl(slug) {
return baseUrl + "styles/" + slug + ".css";
}

const themeElement = document.createElement("link");
themeElement.rel = "stylesheet";

const theme = window.localStorage.getItem("theme") || "light";
themeElement.href = getUrl(theme);

document.head.appendChild(themeElement);
window.themeElement = themeElement;

document.addEventListener("DOMContentLoaded", function() {
const themeSwitcher = document.getElementById("theme-switcher");
themeSwitcher.onchange = onThemeSelect;
themeSwitcher.value = theme;
}, false);

+ 9
- 0
docs/_template/light-dark-theme/styles/theme-switcher.css View File

@@ -0,0 +1,9 @@
div.theme-switch-field {
padding-left: 10px;
padding-bottom: 15px
}

div.theme-switch-field > p{
font-weight: bold;
font-size: 1.2em;
}

+ 72
- 0
docs/_template/light-dark-theme/styles/tomorrow.css View File

@@ -0,0 +1,72 @@
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */

/* Tomorrow Comment */
.hljs-comment,
.hljs-quote {
color: #8e908c;
}

/* Tomorrow Red */
.hljs-variable,
.hljs-template-variable,
.hljs-tag,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class,
.hljs-regexp,
.hljs-deletion {
color: #c82829;
}

/* Tomorrow Orange */
.hljs-number,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params,
.hljs-meta,
.hljs-link {
color: #f5871f;
}

/* Tomorrow Yellow */
.hljs-attribute {
color: #eab700;
}

/* Tomorrow Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
color: #718c00;
}

/* Tomorrow Blue */
.hljs-title,
.hljs-section {
color: #4271ae;
}

/* Tomorrow Purple */
.hljs-keyword,
.hljs-selector-tag {
color: #8959a8;
}

.hljs {
display: block;
overflow-x: auto;
background: white;
color: #4d4d4c;
padding: 0.5em;
}

.hljs-emphasis {
font-style: italic;
}

.hljs-strong {
font-weight: bold;
}

+ 115
- 0
docs/_template/light-dark-theme/styles/vs2015.css View File

@@ -0,0 +1,115 @@
/*
* Visual Studio 2015 dark style
* Author: Nicolas LLOBERA <nllobera@gmail.com>
*/

.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #282a36;
color: #DCDCDC;
}

.hljs-keyword,
.hljs-literal,
.hljs-symbol,
.hljs-name {
color: #569CD6;
}
.hljs-link {
color: #569CD6;
text-decoration: underline;
}

.hljs-built_in,
.hljs-type {
color: #4EC9B0;
}

.hljs-number,
.hljs-class {
color: #B8D7A3;
}

.hljs-string,
.hljs-meta-string {
color: #D69D85;
}

.hljs-regexp,
.hljs-template-tag {
color: #9A5334;
}

.hljs-subst,
.hljs-function,
.hljs-title,
.hljs-params,
.hljs-formula {
color: #DCDCDC;
}

.hljs-comment,
.hljs-quote {
color: #57A64A;
font-style: italic;
}

.hljs-doctag {
color: #608B4E;
}

.hljs-meta,
.hljs-meta-keyword,
.hljs-tag {
color: #9B9B9B;
}

.hljs-variable,
.hljs-template-variable {
color: #BD63C5;
}

.hljs-attr,
.hljs-attribute,
.hljs-builtin-name {
color: #9CDCFE;
}

.hljs-section {
color: gold;
}

.hljs-emphasis {
font-style: italic;
}

.hljs-strong {
font-weight: bold;
}

/*.hljs-code {
font-family:'Monospace';
}*/

.hljs-bullet,
.hljs-selector-tag,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #D7BA7D;
}

.hljs-addition {
background-color: #144212;
display: inline-block;
width: 100%;
}

.hljs-deletion {
background-color: #600;
display: inline-block;
width: 100%;
}

+ 11
- 8
docs/api/index.md View File

@@ -1,13 +1,16 @@
---
uid: API.Docs
---

# API Documentation

This is where you will find documentation for all members and objects in Discord.Net
This is where you will find documentation for all members and objects in Discord.Net.

__Commonly Used Entities__
# Commonly Used Entities

* @Discord.WebSocket
* @Discord.WebSocket.DiscordSocketClient
* @Discord.WebSocket.SocketGuildChannel
* @Discord.WebSocket.SocketGuildUser
* @Discord.WebSocket.SocketMessage
* @Discord.WebSocket.SocketRole
* @Discord.WebSocket
* @Discord.WebSocket.DiscordSocketClient
* @Discord.WebSocket.SocketGuildChannel
* @Discord.WebSocket.SocketGuildUser
* @Discord.WebSocket.SocketMessage
* @Discord.WebSocket.SocketRole

+ 38
- 58
docs/docfx.json View File

@@ -1,74 +1,54 @@
{
"metadata": [
{
"src": [
{
"src": "..",
"files": [
"src/**/*.cs"
],
"exclude": [
"**/obj/**",
"**/bin/**",
"_site/**"
]
}
],
"dest": "api",
"filter": "filterConfig.yml"
"metadata": [{
"src": [{
"src": "../src",
"files": [
"**.csproj"
]
}],
"dest": "api",
"filter": "filterConfig.yml",
"properties": {
"TargetFramework": "netstandard1.3"
}
],
}],
"build": {
"content": [
{
"files": [
"api/**.yml",
"api/index.md"
]
"content": [{
"files": ["api/**.yml", "api/index.md"]
},
{
"files": [
"guides/**.md",
"guides/**/toc.yml",
"toc.yml",
"*.md"
],
"exclude": [
"obj/**",
"_site/**"
]
}
],
"resource": [
"files": ["toc.yml", "index.md"]
},
{
"files": [
"**/images/**",
"**/samples/**"
],
"exclude": [
"obj/**",
"_site/**"
]
}
],
"overwrite": [
"files": ["faq/**.md", "faq/**/toc.yml"]
},
{
"files": [
"apidoc/**.md"
],
"exclude": [
"obj/**",
"_site/**"
]
"files": ["guides/**.md", "guides/**/toc.yml"]
}
],
"resource": [{
"files": [
"**/images/**",
"**/samples/**",
"langwordMapping.yml"
]
}],
"dest": "_site",
"template": [
"default"
"default",
"_template/light-dark-theme",
"_template/last-modified",
"_template/description-generator"
],
"postProcessors": ["ExtractSearchIndex", "LastModifiedPostProcessor", "DescriptionPostProcessor"],
"overwrite": "_overwrites/**/**.md",
"globalMetadata": {
"_appFooter": "Discord.Net (c) 2015-2018 2.0.0-beta"
"_appTitle": "Discord.Net Documentation",
"_appFooter": "Discord.Net (c) 2015-2018 2.0.0-beta",
"_enableSearch": true
},
"noLangKeyword": false
"xrefService": [
"https://xref.docs.microsoft.com/query?uid={uid}"
]
}
}

+ 123
- 0
docs/faq/basics/basic-operations.md View File

@@ -0,0 +1,123 @@
---
uid: FAQ.Basics.BasicOp
title: Questions about Basic Operations
---

# Basic Operations Questions

In the following section, you will find commonly asked questions and
answers regarding basic usage of the library, as well as
language-specific tips when using this library.

## How should I safely check a type?

> [!WARNING]
> Direct casting (e.g., `(Type)type`) is **the least recommended**
> way of casting, as it *can* throw an [InvalidCastException]
> when the object isn't the desired type.
>
> Please refer to [this post] for more details.

In Discord.Net, the idea of polymorphism is used throughout. You may
need to cast the object as a certain type before you can perform any
action.

A good and safe casting example:

[!code-csharp[Casting](samples/cast.cs)]

[InvalidCastException]: https://docs.microsoft.com/en-us/dotnet/api/system.invalidcastexception
[this post]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/how-to-safely-cast-by-using-as-and-is-operators

## How do I send a message?

> [!TIP]
> The [GetChannel] method by default returns an [IChannel], allowing
> channel types such as [IVoiceChannel], [ICategoryChannel]
> to be returned; consequently, you cannot send a message
> to channels like those.

Any implementation of [IMessageChannel] has a [SendMessageAsync]
method. You can get the channel via [GetChannel] under the client.
Remember, when using Discord.Net, polymorphism is a common recurring
theme. This means an object may take in many shapes or form, which
means casting is your friend. You should attempt to cast the channel
as an [IMessageChannel] or any other entity that implements it to be
able to message.

[SendMessageAsync]: xref:Discord.IMessageChannel.SendMessageAsync*
[GetChannel]: xref:Discord.WebSocket.DiscordSocketClient.GetChannel*

## How can I tell if a message is from X, Y, Z channel?

You may check the message channel type. Visit [Glossary] to see the
various types of channels.

[Glossary]: xref:FAQ.Glossary#message-channels

## How can I get the guild from a message?

There are 2 ways to do this. You can do either of the following,

1. Cast the user as an [IGuildUser] and use its [IGuild] property.
2. Cast the channel as an [IGuildChannel] and use its [IGuild] property.

## How do I add hyperlink text to an embed?

Embeds can use standard [markdown] in the description field as well
as in field values. With that in mind, links can be added with
`[text](link)`.

[markdown]: https://support.discordapp.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-

## How do I add reactions to a message?

Any entity that implements [IUserMessage] has an [AddReactionAsync]
method. This method expects an [IEmote] as a parameter.
In Discord.Net, an Emote represents a custom-image emote, while an
Emoji is a Unicode emoji (standard emoji). Both [Emoji] and [Emote]
implement [IEmote] and are valid options.

# [Adding a reaction to another message](#tab/emoji-others)

[!code-csharp[Emoji](samples/emoji-others.cs)]

# [Adding a reaction to a sent message](#tab/emoji-self)

[!code-csharp[Emoji](samples/emoji-self.cs)]

***

[AddReactionAsync]: xref:Discord.IUserMessage.AddReactionAsync*

## What is a "preemptive rate limit?"

A preemptive rate limit is Discord.Net's way of telling you to slow
down before you get hit by the real rate limit. Hitting a real rate
limit might prevent your entire client from sending any requests for
a period of time. This is calculated based on the HTTP header
returned by a Discord response.

## Why am I getting so many preemptive rate limits when I try to add more than one reactions?

This is due to how HTML header works, mistreating
0.25sec/action to 1sec. This causes the lib to throw preemptive rate
limit more frequently than it should for methods such as adding
reactions.

## Can I opt-out of preemptive rate limits?

Unfortunately, not at the moment. See [#401](https://github.com/RogueException/Discord.Net/issues/401).

[IChannel]: xref:Discord.IChannel
[ICategoryChannel]: xref:Discord.ICategoryChannel
[IGuildChannel]: xref:Discord.IGuildChannel
[ITextChannel]: xref:Discord.ITextChannel
[IGuild]: xref:Discord.IGuild
[IVoiceChannel]: xref:Discord.IVoiceChannel
[IGuildUser]: xref:Discord.IGuildUser
[IMessageChannel]: xref:Discord.IMessageChannel
[IUserMessage]: xref:Discord.IUserMessage
[IEmote]: xref:Discord.IEmote
[Emote]: xref:Discord.Emote
[Emoji]: xref:Discord.Emoji

+ 66
- 0
docs/faq/basics/client-basics.md View File

@@ -0,0 +1,66 @@
---
uid: FAQ.Basics.ClientBasics
title: Basic Questions about Client
---

# Client Basics Questions

In the following section, you will find commonly asked questions and
answers about common issues that you may face when utilizing the
various clients offered by the library.

## My client keeps returning 401 upon logging in!

> [!WARNING]
> Userbot/selfbot (logging in with a user token) is no
> longer supported with this library starting from 2.0, as
> logging in under a user account may result in account termination.
>
> For more information, see issue [827] & [958], as well as the official
> [Discord API Terms of Service].

There are few possible reasons why this may occur.

1. You are not using the appropriate [TokenType]. If you are using a
bot account created from the Discord Developer portal, you should
be using `TokenType.Bot`.
2. You are not using the correct login credentials. Please keep in
mind that a token is **different** from a *client secret*.

[TokenType]: xref:Discord.TokenType
[827]: https://github.com/RogueException/Discord.Net/issues/827
[958]: https://github.com/RogueException/Discord.Net/issues/958
[Discord API Terms of Service]: https://discordapp.com/developers/docs/legal

## How do I do X, Y, Z when my bot connects/logs on? Why do I get a `NullReferenceException` upon calling any client methods after connect?

Your bot should **not** attempt to interact in any way with
guilds/servers until the [Ready] event fires. When the bot
connects, it first has to download guild information from
Discord for you to get access to any server
information; the client is not ready at this point.

Technically, the [GuildAvailable] event fires once the data for a
particular guild has downloaded; however, it is best to wait for all
guilds to be downloaded. Once all downloads are complete, the [Ready]
event is triggered, then you can proceed to do whatever you like.

[Ready]: xref:Discord.WebSocket.DiscordSocketClient.Ready
[GuildAvailable]: xref:Discord.WebSocket.BaseSocketClient.GuildAvailable

## How do I get a message's previous content when that message is edited?

If you need to do anything with messages (e.g., checking Reactions,
checking the content of edited/deleted messages), you must set the
[MessageCacheSize] in your [DiscordSocketConfig] settings in order to
use the cached message entity. Read more about it [here](xref:Guides.Concepts.Events#cacheable).

1. Message Cache must be enabled.
2. Hook the MessageUpdated event. This event provides a *before* and
*after* object.
3. Only messages received *after* the bot comes online will be
available in the cache.

[MessageCacheSize]: xref:Discord.WebSocket.DiscordSocketConfig.MessageCacheSize
[DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig
[MessageUpdated]: xref:Discord.WebSocket.BaseSocketClient.MessageUpdated

+ 79
- 0
docs/faq/basics/getting-started.md View File

@@ -0,0 +1,79 @@
---
uid: FAQ.Basics.GetStarted
title: Beginner Questions / How to Get Started
---

# Basic Concepts / Getting Started

In this following section, you will find commonly asked questions and
answers about how to get started with Discord.Net, as well as basic
introduction to the Discord API ecosystem.

## How do I add my bot to my server/guild?

You can do so by using the [permission calculator] provided
by [FiniteReality].
This tool allows you to set permissions that the bot will be assigned
with, and invite the bot into your guild. With this method, bots will
also be assigned a unique role that a regular user cannot use; this
is what we call a `Managed` role. Because you cannot assign this
role to any other users, it is much safer than creating a single
role which, intentionally or not, can be applied to other users
to escalate their privilege.

[FiniteReality]: https://github.com/FiniteReality/permissions-calculator
[permission calculator]: https://finitereality.github.io/permissions-calculator

## What is a token?

A token is a credential used to log into an account. This information
should be kept **private** and for your eyes only. Anyone with your
token can log into your account. This risk applies to both user
and bot accounts. That also means that you should **never** hardcode
your token or add it into source control, as your identity may be
stolen by scrape bots on the internet that scours through
constantly to obtain a token.

## What is a client/user/object ID?

Each user and object on Discord has its own snowflake ID generated
based on various conditions.

![Snowflake Generation](images/snowflake.png)

Anyone can see the ID; it is public. It is merely used to
identify an object in the Discord ecosystem. Many things in the
Discord ecosystem require an ID to retrieve or identify the said
object.

There are 2 common ways to obtain the said ID.

### [Discord Developer Mode](#tab/dev-mode)

By enabling the developer mode you can right click on most objects
to obtain their snowflake IDs (please note that this may not apply to
all objects, such as role IDs, or DM channel IDs).

![Developer Mode](images/dev-mode.png)

### [Escape Character](#tab/escape-char)

You can escape an object by using `\` in front the object in the
Discord client. For example, when you do `\@Example#1234` in chat,
it will return the user ID of the aforementioned user.

![Escaping mentions](images/mention-escape.png)

***

## How do I get the role ID?

> [!WARNING]
> Right-clicking on the role and copying the ID will **not** work.
> This will only copy the message ID.

Several common ways to do this:

1. Make the role mentionable and mention the role, and escape it
using the `\` character in front.
2. Inspect the roles collection within the guild via your debugger.

BIN
docs/faq/basics/images/dev-mode.png View File

Before After
Width: 1366  |  Height: 727  |  Size: 79 KiB

BIN
docs/faq/basics/images/mention-escape.png View File

Before After
Width: 1603  |  Height: 400  |  Size: 19 KiB

BIN
docs/faq/basics/images/snowflake.png View File

Before After
Width: 700  |  Height: 365  |  Size: 71 KiB

+ 15
- 0
docs/faq/basics/samples/cast.cs View File

@@ -0,0 +1,15 @@
public async Task MessageReceivedHandler(SocketMessage msg)
{
// Option 1:
// Using the `as` keyword, which will return `null` if the object isn't the desired type.
var usermsg = msg as SocketUserMessage;
// We bail when the message isn't the desired type.
if (msg == null) return;
// Option 2:
// Using the `is` keyword to cast (C#7 or above only)
if (msg is SocketUserMessage usermsg)
{
// Do things
}
}

+ 18
- 0
docs/faq/basics/samples/emoji-others.cs View File

@@ -0,0 +1,18 @@
// bail if the message is not a user one (system messages cannot have reactions)
var usermsg = msg as IUserMessage;
if (usermsg == null) return;

// standard Unicode emojis
Emoji emoji = new Emoji("👍");
// or
// Emoji emoji = new Emoji("\uD83D\uDC4D");

// custom guild emotes
Emote emote = Emote.Parse("<:dotnet:232902710280716288>");
// using Emote.TryParse may be safer in regards to errors being thrown;
// please note that the method does not verify if the emote exists,
// it simply creates the Emote object for you.

// add the reaction to the message
await usermsg.AddReactionAsync(emoji);
await usermsg.AddReactionAsync(emote);

+ 17
- 0
docs/faq/basics/samples/emoji-self.cs View File

@@ -0,0 +1,17 @@
// capture the message you're sending in a variable
var msg = await channel.SendMessageAsync("This will have reactions added.");

// standard Unicode emojis
Emoji emoji = new Emoji("👍");
// or
// Emoji emoji = new Emoji("\uD83D\uDC4D");

// custom guild emotes
Emote emote = Emote.Parse("<:dotnet:232902710280716288>");
// using Emote.TryParse may be safer in regards to errors being thrown;
// please note that the method does not verify if the emote exists,
// it simply creates the Emote object for you.

// add the reaction to the message
await msg.AddReactionAsync(emoji);
await msg.AddReactionAsync(emote);

+ 54
- 0
docs/faq/commands/dependency-injection.md View File

@@ -0,0 +1,54 @@
---
uid: FAQ.Commands.DI
title: Questions about Dependency Injection with Commands
---

# Dependency-injection-related Questions

In the following section, you will find common questions and answers
to utilizing dependency injection with @Discord.Commands, as well as
common troubleshooting steps regarding DI.

## What is a service? Why does my module not hold any data after execution?

In Discord.Net, modules are created similarly to ASP.NET, meaning
that they have a transient nature; modules are spawned whenever a
request is received, and are killed from memory when the execution
finishes. In other words, you cannot store persistent
data inside a module. Consider using a service if you wish to
workaround this.

Service is often used to hold data externally so that they persist
throughout execution. Think of it like a chest that holds
whatever you throw at it that won't be affected by anything unless
you want it to. Note that you should also learn Microsoft's
implementation of [Dependency Injection] \([video]) before proceeding,
as well as how it works in [Discord.Net](xref:Guides.Commands.DI#usage-in-modules).

A brief example of service and dependency injection can be seen below.

[!code-csharp[DI](samples/DI.cs)]

[Dependency Injection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection
[video]: https://www.youtube.com/watch?v=QtDTfn8YxXg

## Why is my `CommandService` complaining about a missing dependency?

If you encounter an error similar to `Failed to create MyModule,
dependency MyExternalDependency was not found.`, you may have
forgotten to add the external dependency to the dependency container.

Starting from Discord.Net 2.0, all dependencies required by each
module must be present when the module is loaded into the
[CommandService]. This means when loading the module, you must pass a
valid [IServiceProvider] with the dependency loaded before the module
can be successfully registered.

For example, if your module, `MyModule`, requests a `DatabaseService`
in its constructor, the `DatabaseService` must be present in the
[IServiceProvider] when registering `MyModule`.

[!code-csharp[Missing Dependencies](samples/missing-dep.cs)]

[IServiceProvider]: xref:System.IServiceProvider
[CommandService]: xref:Discord.Commands.CommandService

+ 142
- 0
docs/faq/commands/general.md View File

@@ -0,0 +1,142 @@
---
uid: FAQ.Commands.General
title: General Questions about Commands
---

# Command-related Questions

In the following section, you will find commonly asked questions and
answered regarding general command usage when using @Discord.Commands.

## How can I restrict some of my commands so only specific users can execute them?

Based on how you want to implement the restrictions, you can use the
built-in [RequireUserPermission] precondition, which allows you to
restrict the command based on the user's current permissions in the
guild or channel (*e.g., `GuildPermission.Administrator`,
`ChannelPermission.ManageMessages`*).

If, however, you wish to restrict the commands based on the user's
role, you can either create your custom precondition or use
Joe4evr's [Preconditions Addons] that provides a few custom
preconditions that aren't provided in the stock library.
Its source can also be used as an example for creating your
custom preconditions.

[RequireUserPermission]: xref:Discord.Commands.RequireUserPermissionAttribute
[Preconditions Addons]: https://github.com/Joe4evr/Discord.Addons/tree/master/src/Discord.Addons.Preconditions

## Why am I getting an error about `Assembly.GetEntryAssembly`?

You may be confusing @Discord.Commands.CommandService.AddModulesAsync*
with @Discord.Commands.CommandService.AddModuleAsync*. The former
is used to add modules via the assembly, while the latter is used to
add a single module.

## What does [Remainder] do in the command signature?

The [RemainderAttribute] leaves the string unparsed, meaning you
do not have to add quotes around the text for the text to be
recognized as a single object. Please note that if your method has
multiple parameters, the remainder attribute can only be applied to
the last parameter.

[!code-csharp[Remainder](samples/Remainder.cs)]

[RemainderAttribute]: xref:Discord.Commands.RemainderAttribute

## Discord.Net keeps saying that a `MessageReceived` handler is blocking the gateway, what should I do?

By default, the library warns the user about any long-running event
handler that persists for **more than 3 seconds**. Any event
handlers that are run on the same thread as the gateway task, the task
in charge of keeping the connection alive, may block the processing of
heartbeat, and thus terminating the connection.

In this case, the library detects that a `MessageReceived`
event handler is blocking the gateway thread. This warning is
typically associated with the command handler as it listens for that
particular event. If the command handler is blocking the thread, then
this **might** mean that you have a long-running command.

> [!NOTE]
> In rare cases, runtime errors can also cause blockage, usually
> associated with Mono, which is not supported by this library.

To prevent a long-running command from blocking the gateway
thread, a flag called [RunMode] is explicitly designed to resolve
this issue.

There are 2 main `RunMode`s.

1. `RunMode.Sync`
2. `RunMode.Async`

`Sync` is the default behavior and makes the command to be run on the
same thread as the gateway one. `Async` will spin the task off to a
different thread from the gateway one.

> [!IMPORTANT]
> While specifying `RunMode.Async` allows the command to be spun off
> to a different thread, keep in mind that by doing so, there will be
> **potentially unwanted consequences**. Before applying this flag,
> please consider whether it is necessary to do so.
>
> Further details regarding `RunMode.Async` can be found below.

You can set the `RunMode` either by specifying it individually via
the `CommandAttribute` or by setting the global default with
the [DefaultRunMode] flag under `CommandServiceConfig`.

# [CommandAttribute](#tab/cmdattrib)

[!code-csharp[Command Attribute](samples/runmode-cmdattrib.cs)]

# [CommandServiceConfig](#tab/cmdconfig)

[!code-csharp[Command Service Config](samples/runmode-cmdconfig.cs)]

***

***

[RunMode]: xref:Discord.Commands.RunMode
[CommandAttribute]: xref:Discord.Commands.CommandAttribute
[DefaultRunMode]: xref:Discord.Commands.CommandServiceConfig.DefaultRunMode

## How does `RunMode.Async` work, and why is Discord.Net *not* using it by default?

`RunMode.Async` works by spawning a new `Task` with an unawaited
[Task.Run], essentially making the task that is used to invoke the
command task to be finished on a different thread. This design means
that [ExecuteAsync] will be forced to return a successful
[ExecuteResult] regardless of the actual execution result.

The following are the known caveats with `RunMode.Async`,

1. You can potentially introduce a race condition.
2. Unnecessary overhead caused by the [async state machine].
3. [ExecuteAsync] will immediately return [ExecuteResult] instead of
other result types (this is particularly important for those who wish
to utilize [RuntimeResult] in 2.0).
4. Exceptions are swallowed.

However, there are ways to remedy some of these.

For #3, in Discord.Net 2.0, the library introduces a new event called
[CommandExecuted], which is raised whenever the command is
**successfully executed**. This event will be raised regardless of
the `RunMode` type and will return the appropriate execution result.

For #4, exceptions are caught in [CommandService.Log] event under
[LogMessage.Exception] as [CommandException].

[Task.Run]: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run
[async state machine]: https://www.red-gate.com/simple-talk/dotnet/net-tools/c-async-what-is-it-and-how-does-it-work/
[ExecuteAsync]: xref:Discord.Commands.CommandService.ExecuteAsync*
[ExecuteResult]: xref:Discord.Commands.ExecuteResult
[RuntimeResult]: xref:Discord.Commands.RuntimeResult
[CommandExecuted]: xref:Discord.Commands.CommandService.CommandExecuted
[CommandService.Log]: xref:Discord.Commands.CommandService.Log
[LogMessage.Exception]: xref:Discord.LogMessage.Exception*
[CommandException]: xref:Discord.Commands.CommandException

+ 28
- 0
docs/faq/commands/samples/DI.cs View File

@@ -0,0 +1,28 @@
public class MyService
{
public string MyCoolString { get; set; }
}
public class Setup
{
public IServiceProvider BuildProvider() =>
new ServiceCollection()
.AddSingleton<MyService>()
.BuildServiceProvider();
}
public class MyModule : ModuleBase<SocketCommandContext>
{
// Inject via public settable prop
public MyService MyService { get; set; }
// ...or via the module's constructor

// private readonly MyService _myService;
// public MyModule (MyService myService) => _myService = myService;
[Command("string")]
public Task GetOrSetStringAsync(string input)
{
if (string.IsNullOrEmpty(_myService.MyCoolString)) _myService.MyCoolString = input;
return ReplyAsync(_myService.MyCoolString);
}
}

+ 20
- 0
docs/faq/commands/samples/Remainder.cs View File

@@ -0,0 +1,20 @@
// Input:
// !echo Coffee Cake

// Output:
// Coffee Cake
[Command("echo")]
public Task EchoRemainderAsync([Remainder]string text) => ReplyAsync(text);

// Output:
// CommandError.BadArgCount
[Command("echo-hassle")]
public Task EchoAsync(string text) => ReplyAsync(text);

// The message would be seen as having multiple parameters,
// while the method only accepts one.
// Wrapping the message in quotes solves this.
// This way, the system knows the entire message is to be parsed as a
// single String.
// e.g.,
// !echo "Coffee Cake"

+ 29
- 0
docs/faq/commands/samples/missing-dep.cs View File

@@ -0,0 +1,29 @@
public class MyModule : ModuleBase<SocketCommandContext>
{
private readonly DatabaseService _dbService;
public MyModule(DatabaseService dbService)
=> _dbService = dbService;
}
public class CommandHandler
{
private readonly CommandService _commands;
private readonly IServiceProvider _services;
public CommandHandler(DiscordSocketClient client)
{
_services = new ServiceCollection()
.AddService<CommandService>()
.AddService(client)
// We are missing DatabaseService!
.BuildServiceProvider();
}
public async Task RegisterCommandsAsync()
{
// ...
// The method fails here because DatabaseService is a required
// dependency and cannot be resolved by the dependency
// injection service at runtime since the service is not
// registered in this instance of _services.
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
// ...
}
}

+ 7
- 0
docs/faq/commands/samples/runmode-cmdattrib.cs View File

@@ -0,0 +1,7 @@
[Command("process", RunMode = RunMode.Async)]
public async Task ProcessAsync(string input)
{
// Does heavy calculation here.
await Task.Delay(TimeSpan.FromMinute(1));
await ReplyAsync(input);
}

+ 10
- 0
docs/faq/commands/samples/runmode-cmdconfig.cs View File

@@ -0,0 +1,10 @@
public class Setup
{
private readonly CommandService _command;
public Setup()
{
var config = new CommandServiceConfig{ DefaultRunMode = RunMode.Async };
_command = new CommandService(config);
}
}

+ 82
- 0
docs/faq/misc/glossary.md View File

@@ -0,0 +1,82 @@
---
uid: FAQ.Glossary
title: Common Terminologies / Glossary
---

# Glossary

This is an additional chapter for quick references to various common
types that you may see within Discord.Net. To see more information
regarding each type of object, click on the object to navigate
to our API documentation page where you might find more explanation
about it.

## Common Types

* A **Guild** ([IGuild]) is an isolated collection of users and
channels, and are often referred to as "servers".
- Example: [Discord API](https://discord.gg/jkrBmQR)
* A **Channel** ([IChannel]) represents a generic channel.
- Example: #dotnet_discord-net
- See [Channel Types](#channel-types)
[IGuild]: xref:Discord.IGuild
[IChannel]: xref:Discord.IChannel

## Channel Types

### Message Channels
* A **Text Channel** ([ITextChannel]) is a message channel from a
Guild.
* A **DM Channel** ([IDMChannel]) is a message channel from a DM.
* A **Group Channel** ([IGroupChannel]) is a message channel from a
Group.
- This is rarely used due to the bot's inability to join groups.
* A **Private Channel** ([IPrivateChannel]) is a DM or a Group.
* A **Message Channel** ([IMessageChannel]) can be any of the above.

### Misc Channels
* A **Guild Channel** ([IGuildChannel]) is a guild channel in a guild.
- This can be any channels that may exist in a guild.
* A **Voice Channel** ([IVoiceChannel]) is a voice channel in a guild.
* A **Category Channel** ([ICategoryChannel]) (2.0+) is a category that
holds one or more sub-channels.
* A **Nested Channel** ([INestedChannel]) (2.0+) is a channel that can
exist under a category.

[INestedChannel]: xref:Discord.INestedChannel
[IGuildChannel]: xref:Discord.IGuildChannel
[IMessageChannel]: xref:Discord.IMessageChannel
[ITextChannel]: xref:Discord.ITextChannel
[IGroupChannel]: xref:Discord.IGroupChannel
[IDMChannel]: xref:Discord.IDMChannel
[IPrivateChannel]: xref:Discord.IPrivateChannel
[IVoiceChannel]: xref:Discord.IVoiceChannel
[ICategoryChannel]: xref:Discord.ICategoryChannel

## Emoji Types

* An **Emote** ([Emote]) is a custom emote from a guild.
- Example: `<:dotnet:232902710280716288>`
* An **Emoji** ([Emoji]) is a Unicode emoji.
- Example: `👍`

[Emote]: xref:Discord.Emote
[Emoji]: xref:Discord.Emoji

## Activity Types

* A **Game** ([Game]) refers to a user's game activity.
* A **Rich Presence** ([RichGame]) refers to a user's detailed
gameplay status.
- Visit [Rich Presence Intro] on Discord docs for more info.
* A **Streaming Status** ([StreamingGame]) refers to user's activity
for streaming on services such as Twitch.
* A **Spotify Status** ([SpotifyGame]) (2.0+) refers to a user's
activity for listening to a song on Spotify.

[Game]: xref:Discord.Game
[RichGame]: xref:Discord.RichGame
[StreamingGame]: xref:Discord.StreamingGame
[SpotifyGame]: xref:Discord.SpotifyGame
[Rich Presence Intro]: https://discordapp.com/developers/docs/rich-presence/best-practices

+ 29
- 0
docs/faq/misc/legacy.md View File

@@ -0,0 +1,29 @@
---
uid: FAQ.Legacy
title: Questions about Legacy Versions
---

# Legacy Questions

This section refers to legacy library-related questions that do not
apply to the latest or recent version of the Discord.Net library.

## X, Y, Z does not work! It doesn't return a valid value anymore.

If you are currently using an older version of the stable branch,
please upgrade to the latest pre-release version to ensure maximum
compatibility. Several features may be broken in older
versions and will likely not be fixed in the version branch due to
their breaking nature.

Visit the repo's [release tag] to see the latest public pre-release.

[release tag]: https://github.com/RogueException/Discord.Net/releases

## I came from an earlier version of Discord.Net 1.0, and DependencyMap doesn't seem to exist anymore in the later revision? What happened to it?

The `DependencyMap` has been replaced with Microsoft's
[DependencyInjection] Abstractions. An example usage can be seen
[here](https://github.com/foxbot/DiscordBotBase/blob/csharp/src/DiscordBot/Program.cs#L36).

[DependencyInjection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection

+ 18
- 0
docs/faq/toc.yml View File

@@ -0,0 +1,18 @@
- name: Basic Concepts
items:
- name: Getting Started
topicUid: FAQ.Basics.GetStarted
- name: Basic Operations
topicUid: FAQ.Basics.BasicOp
- name: Client Basics
topicUid: FAQ.Basics.ClientBasics
- name: Commands
items:
- name: General
topicUid: FAQ.Commands.General
- name: Dependency Injection
topicUid: FAQ.Commands.DI
- name: Glossary
topicUid: FAQ.Glossary
- name: Legacy or Upgrade
topicUid: FAQ.Legacy

+ 4
- 8
docs/filterConfig.yml View File

@@ -1,11 +1,7 @@
apiRules:
- exclude:
uidRegex: ^Discord\.API$
uidRegex: ^Discord\.Net\..*$
type: Namespace
- exclude:
uidRegex: ^Discord\.API.*$
- exclude:
uidRegex: ^Discord\.Net\.Converters$
- exclude:
uidRegex: ^Discord\.Net.*$
- exclude:
uidRegex: ^RegexAnalyzer$
uidRegex: ^Discord\.Analyzers$
type: Namespace

+ 0
- 343
docs/guides/commands/commands.md 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_

+ 47
- 0
docs/guides/commands/dependency-injection.md 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

+ 221
- 0
docs/guides/commands/intro.md 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)]

+ 122
- 0
docs/guides/commands/post-execution.md 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

+ 83
- 0
docs/guides/commands/preconditions.md 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*

+ 0
- 63
docs/guides/commands/samples/command_handler.cs 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);
}
}

+ 62
- 0
docs/guides/commands/samples/dependency-injection/dependency_map_setup.cs 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);
// ...
}
}

+ 37
- 0
docs/guides/commands/samples/dependency-injection/dependency_module.cs 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());
}
}

+ 29
- 0
docs/guides/commands/samples/dependency-injection/dependency_module_noinject.cs 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();
}
}

+ 0
- 18
docs/guides/commands/samples/dependency_map_setup.cs 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());
}

+ 0
- 40
docs/guides/commands/samples/dependency_module.cs 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;
}

}

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

@@ -1,6 +0,0 @@
using Discord.Commands;

public class InfoModule : ModuleBase<SocketCommandContext>
{
}

+ 63
- 0
docs/guides/commands/samples/intro/command_handler.cs 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);
}
}

+ 8
- 0
docs/guides/commands/samples/intro/empty-module.cs 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>
{
}

docs/guides/commands/samples/groups.cs → docs/guides/commands/samples/intro/groups.cs 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);
}

docs/guides/commands/samples/module.cs → docs/guides/commands/samples/intro/module.cs 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}");

+ 13
- 0
docs/guides/commands/samples/post-execution/command_exception_log.cs 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());
}
}

+ 13
- 0
docs/guides/commands/samples/post-execution/command_executed_adv_demo.cs 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;
}
}

+ 38
- 0
docs/guides/commands/samples/post-execution/command_executed_demo.cs 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);
}

+ 6
- 0
docs/guides/commands/samples/post-execution/customresult_base.cs View File

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

+ 10
- 0
docs/guides/commands/samples/post-execution/customresult_extended.cs 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);
}

+ 10
- 0
docs/guides/commands/samples/post-execution/customresult_usage.cs 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}!").
}
}

+ 11
- 0
docs/guides/commands/samples/post-execution/post-execution_basic.cs 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;
}

+ 9
- 0
docs/guides/commands/samples/preconditions/group_precondition.cs 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);
}

+ 3
- 0
docs/guides/commands/samples/preconditions/precondition_usage.cs View File

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

docs/guides/commands/samples/require_owner.cs → docs/guides/commands/samples/preconditions/require_owner.cs 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();

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

@@ -0,0 +1,29 @@
public class CommandHandler
{
private readonly CommandService _commands;
private readonly DiscordSocketClient _client;
private readonly IServiceProvider _services;

public CommandHandler(CommandService commands, DiscordSocketClient client, IServiceProvider services)
{
_commands = commands;
_client = client;
_services = services;
}

public async Task SetupAsync()
{
_client.MessageReceived += CommandHandleAsync;

// Add BooleanTypeReader to type read for the type "bool"
_commands.AddTypeReader(typeof(bool), new BooleanTypeReader());

// Then register the modules
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
}

public async Task CommandHandleAsync(SocketMessage msg)
{
// ...
}
}

docs/guides/commands/samples/typereader.cs → docs/guides/commands/samples/typereaders/typereader.cs 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))

+ 70
- 0
docs/guides/commands/typereaders.md 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)]

+ 21
- 22
docs/guides/concepts/connections.md 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

+ 24
- 29
docs/guides/concepts/entities.md 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

[!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`.
## Sample

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

+ 18
- 18
docs/guides/concepts/events.md 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

[!code-csharp[Event Sample](samples/events.cs)]
> [!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.

### Tips
## Sample

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
[!code-csharp[Event Sample](samples/events.cs)]

+ 20
- 19
docs/guides/concepts/logging.md 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

> [!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.

### Usage
## Usage in Client(s)

To receive log events, simply hook the discord client's log method
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.

+ 1
- 1
docs/guides/concepts/samples/connections.cs 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...");


+ 3
- 5
docs/guides/concepts/samples/entities.cs 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;
}

+ 1
- 1
docs/guides/concepts/samples/events.cs 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;


+ 1
- 1
docs/guides/concepts/samples/logging.cs 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);


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save