Added docs
10
docs/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
###############
|
||||||
|
# folder #
|
||||||
|
###############
|
||||||
|
/**/DROP/
|
||||||
|
/**/TEMP/
|
||||||
|
/**/packages/
|
||||||
|
/**/bin/
|
||||||
|
/**/obj/
|
||||||
|
_site
|
60
docs/CONTRIBUTING.md
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
# Contributing to Docs
|
||||||
|
|
||||||
|
First of all, thank you for your interest in contributing to our
|
||||||
|
documentation work. We really appreciate it! That being said,
|
||||||
|
there are several guidelines you should attempt to follow when adding
|
||||||
|
to/changing the documentation.
|
||||||
|
|
||||||
|
## General Guidelines
|
||||||
|
|
||||||
|
* Keep code samples in each section's `samples` folder
|
||||||
|
* 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 `foxbot#0282` on
|
||||||
|
Discord.
|
||||||
|
|
||||||
|
## XML Docstrings Guidelines
|
||||||
|
|
||||||
|
* When using the `<summary>` tag, use concise verbs. For example:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
/// <summary> Gets or sets the guild user in this object. </summary>
|
||||||
|
public IGuildUser GuildUser { get; set; }
|
||||||
|
```
|
||||||
|
|
||||||
|
* The `<summary>` tag should not be more than 3 lines long. Consider
|
||||||
|
simplifying the terms or using the `<remarks>` tag instead.
|
||||||
|
* When using the `<code>` tag, put the code sample within the
|
||||||
|
`src/Discord.Net.Examples` project under the corresponding path of
|
||||||
|
the object and surround the code with a `#region` tag.
|
||||||
|
* If the remarks you are looking to write are too long, consider
|
||||||
|
writing a shorter version in the XML docs while keeping the longer
|
||||||
|
version in the `overwrites` folder using the DocFX overwrites syntax.
|
||||||
|
* You may find an example of this in the samples provided within
|
||||||
|
the folder.
|
||||||
|
|
||||||
|
## Docs Guide Guidelines
|
||||||
|
|
||||||
|
* Use a ruler set at 70 characters (use the docs workspace provided
|
||||||
|
if you are using Visual Studio Code)
|
||||||
|
* Links should use long syntax
|
||||||
|
* Pages should be short and concise, not broad and long
|
||||||
|
|
||||||
|
Example of long link syntax:
|
||||||
|
|
||||||
|
```md
|
||||||
|
Please consult the [API Documentation] for more information.
|
||||||
|
|
||||||
|
[API Documentation]: xref:System.String
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recommended Reads
|
||||||
|
|
||||||
|
* [Microsoft Docs](https://docs.microsoft.com)
|
||||||
|
* [Flask Docs](https://flask.pocoo.org/docs/1.0/)
|
||||||
|
* [DocFX Manual](https://dotnet.github.io/docfx/)
|
||||||
|
* [Sandcastle XML Guide](http://ewsoftware.github.io/XMLCommentsGuide)
|
21
docs/Discord.Net.Docs.code-workspace
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
docs/README.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# 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].
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> You must use DocFX version **2.76.0** for everything to work correctly.
|
||||||
|
|
||||||
|
1. Navigate to the root of the repository.
|
||||||
|
2. Build the docs using `docfx docs/docfx.json`. Add the `--serve`
|
||||||
|
parameter to preview the site locally.
|
||||||
|
|
||||||
|
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/index.html
|
31
docs/_overwrites/Commands/CommandException.Overwrite.md
Normal 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
docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md
Normal 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
docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md
Normal 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
docs/_overwrites/Commands/ICommandContext.Inclusion.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
An example of how this class is used the command system can be seen
|
||||||
|
below:
|
||||||
|
|
||||||
|
[!code[Sample module](../../guides/text_commands/samples/intro/empty-module.cs)]
|
||||||
|
[!code[Command handler](../../guides/text_commands/samples/intro/command_handler.cs)]
|
27
docs/_overwrites/Commands/ICommandContext.Overwrite.md
Normal 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
docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,6 @@
|
||||||
|
A "precondition" in the command system is used to determine if a
|
||||||
|
condition is met before entering the command task. Using a
|
||||||
|
precondition 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.
|
29
docs/_overwrites/Common/DiscordComparers.Overwrites.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
uid: Discord.DiscordComparers.ChannelComparer
|
||||||
|
summary: *content
|
||||||
|
---
|
||||||
|
Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IChannel>> to be used to compare channels.
|
||||||
|
|
||||||
|
---
|
||||||
|
uid: Discord.DiscordComparers.GuildComparer
|
||||||
|
summary: *content
|
||||||
|
---
|
||||||
|
Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IGuild>> to be used to compare guilds.
|
||||||
|
|
||||||
|
---
|
||||||
|
uid: Discord.DiscordComparers.MessageComparer
|
||||||
|
summary: *content
|
||||||
|
---
|
||||||
|
Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IMessage>> to be used to compare messages.
|
||||||
|
|
||||||
|
---
|
||||||
|
uid: Discord.DiscordComparers.RoleComparer
|
||||||
|
summary: *content
|
||||||
|
---
|
||||||
|
Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IRole>> to be used to compare roles.
|
||||||
|
|
||||||
|
---
|
||||||
|
uid: Discord.DiscordComparers.UserComparer
|
||||||
|
summary: *content
|
||||||
|
---
|
||||||
|
Gets an [IEqualityComparer](xref:System.Collections.Generic.IEqualityComparer`1)<<xref:Discord.IUser>> to be used to compare users.
|
69
docs/_overwrites/Common/EmbedBuilder.Overwrites.md
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
---
|
||||||
|
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();
|
||||||
|
|
||||||
|
//Your embed needs to be built before it is able to be sent
|
||||||
|
await ReplyAsync(embed: embed.Build());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
![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
docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md
Normal 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://discord.com/assets/e05ead6e6ebc08df9291738d0aa6986d.png");
|
||||||
|
var exampleFooter = new EmbedFooterBuilder()
|
||||||
|
.WithText("I am a nice footer")
|
||||||
|
.WithIconUrl("https://discord.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
docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md
Normal 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
docs/_overwrites/Common/IEmote.Inclusion.md
Normal 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
docs/_overwrites/Common/IEmote.Overwrites.md
Normal 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
docs/_overwrites/Common/ObjectProperties.Overwrites.md
Normal 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;
|
||||||
|
});
|
||||||
|
```
|
|
@ -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
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/_overwrites/Common/images/react-example.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll
vendored
Normal file
29
docs/_template/description-generator/plugins/LICENSE
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
Humanizer (https://github.com/Humanizr/Humanizer)
|
||||||
|
The MIT License (MIT)
|
||||||
|
Copyright (c) .NET Foundation and Contributors
|
||||||
|
|
||||||
|
==============================================================================
|
239
docs/_template/material/public/main.css
vendored
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;400;700&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bs-font-sans-serif: 'Roboto';
|
||||||
|
--bs-border-radius: 10px;
|
||||||
|
|
||||||
|
--border-radius-button: 40px;
|
||||||
|
--card-box-shadow: 0 1px 2px 0 #3d41440f, 0 1px 3px 1px #3d414429;
|
||||||
|
|
||||||
|
--material-yellow-light: #e6dfbf;
|
||||||
|
--material-yellow-dark: #5a5338;
|
||||||
|
|
||||||
|
--material-blue-light: #c4d9f1;
|
||||||
|
--material-blue-dark: #383e5a;
|
||||||
|
|
||||||
|
--material-red-light: #f1c4c4;
|
||||||
|
--material-red-dark: #5a3838;
|
||||||
|
|
||||||
|
--material-warning-header: #f57f171a;
|
||||||
|
--material-warning-background: #f6e8bd;
|
||||||
|
--material-warning-background-dark: #57502c;
|
||||||
|
|
||||||
|
--material-info-header: #1976d21a;
|
||||||
|
--material-info-background: #e3f2fd;
|
||||||
|
--material-info-background-dark: #2c4557;
|
||||||
|
|
||||||
|
--material-danger-header: #d32f2f1a;
|
||||||
|
--material-danger-background: #ffebee;
|
||||||
|
--material-danger-background-dark: #572c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HEADINGS */
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
article h2,
|
||||||
|
article h3,
|
||||||
|
article h4 {
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAKES PROPERTIES BE SEPARATED CLEARLY */
|
||||||
|
article h3 {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 2px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** IMAGES **/
|
||||||
|
img {
|
||||||
|
border-radius: var(--bs-border-radius);
|
||||||
|
box-shadow: var(--card-box-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** NAVBAR **/
|
||||||
|
.navbar-brand > img {
|
||||||
|
box-shadow: none;
|
||||||
|
color: var(--bs-nav-link-color);
|
||||||
|
margin-right: var(--bs-navbar-brand-margin-end);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme='light'] nav.navbar {
|
||||||
|
background-color: var(--bs-primary-bg-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme='dark'] nav.navbar {
|
||||||
|
background-color: var(--bs-tertiary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav > li > a {
|
||||||
|
border-radius: var(--border-radius-button);
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a.nav-link:focus,
|
||||||
|
.navbar-nav a.nav-link:hover {
|
||||||
|
background-color: var(--bs-primary-border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link.active,
|
||||||
|
.navbar-nav .nav-link.show {
|
||||||
|
color: var(--bs-link-hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** SEARCH AND FILTER **/
|
||||||
|
input.form-control {
|
||||||
|
border-radius: var(--border-radius-button);
|
||||||
|
}
|
||||||
|
|
||||||
|
form.filter {
|
||||||
|
margin: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ALERTS **/
|
||||||
|
.alert {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
box-shadow: var(--card-box-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert > p {
|
||||||
|
padding: 0.2rem 0.7rem 0.7rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert > ul {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 5px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert > h5 {
|
||||||
|
padding: 0.5rem 0.7rem 0.7rem 1rem;
|
||||||
|
border-radius: var(--bs-border-radius) var(--bs-border-radius) 0 0;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
color: var(--material-blue-dark);
|
||||||
|
background-color: var(--material-info-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme='dark'] .alert-info {
|
||||||
|
color: var(--material-blue-light);
|
||||||
|
background-color: var(--material-info-background-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info > h5 {
|
||||||
|
background-color: var(--material-info-header);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
color: var(--material-yellow-dark);
|
||||||
|
background-color: var(--material-warning-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme='dark'] .alert-warning {
|
||||||
|
color: var(--material-yellow-light);
|
||||||
|
background-color: var(--material-warning-background-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning > h5 {
|
||||||
|
background-color: var(--material-warning-header);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
color: var(--material-red-dark);
|
||||||
|
background-color: var(--material-danger-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme='dark'] .alert-danger {
|
||||||
|
color: var(--material-red-light);
|
||||||
|
background-color: var(--material-danger-background-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger > h5 {
|
||||||
|
background-color: var(--material-danger-header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CODE HIGHLIGHT */
|
||||||
|
code {
|
||||||
|
border-radius: var(--bs-border-radius);
|
||||||
|
margin: 4px 2px;
|
||||||
|
box-shadow: var(--card-box-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAKES PARAMETERS MORE SPACIOUS */
|
||||||
|
:not(pre) > code {
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAKES LIST ITEMS BE SLIGHTLY MORE SEPARATED */
|
||||||
|
/* THIS AVOIDS CODE BLOCK OVERLAP */
|
||||||
|
ul:not(.navbar-nav) > li:not(:last-child) {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAKES NAVBAR LINKS LOOK BETTER IN MOBILE */
|
||||||
|
.navbar-expand-md .navbar-nav .nav-link {
|
||||||
|
padding-right: var(--bs-navbar-nav-link-padding-x);
|
||||||
|
padding-left: var(--bs-navbar-nav-link-padding-x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAKES INHERITANCE LIST READABLE */
|
||||||
|
:is(dl.typelist.inheritedMembers, dl.typelist.extensionMethods) > dd > div::after {
|
||||||
|
content: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(dl.typelist.inheritedMembers, dl.typelist.extensionMethods) > dd > div {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAKES "IN THIS ARTICLE" MORE READABLE */
|
||||||
|
.affix h5, .affix .h5 {
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAKES INDEX LOGO VISIBLE ON DIFFERENT THEMES */
|
||||||
|
article[data-uid="Home.Landing"] img[alt="logo"] {
|
||||||
|
height: 100pt !important;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] article[data-uid="Home.Landing"] img[alt="logo"] {
|
||||||
|
content: url('/marketing/logo/SVG/Combinationmark.svg') !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] article[data-uid="Home.Landing"] img[alt="logo"] {
|
||||||
|
content: url('/marketing/logo/SVG/Combinationmark White.svg') !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
article[data-uid="Home.Landing"] img {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAKES SIDEBAR LINKS A BIT MORE DISTINGUISHABLE */
|
||||||
|
.affix ul li a:not(.link-body-emphasis) {
|
||||||
|
display: block !important;
|
||||||
|
margin-left: 8px !important;
|
||||||
|
}
|
72
docs/_template/material/public/main.js
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
export default
|
||||||
|
{
|
||||||
|
iconLinks:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
icon: 'github',
|
||||||
|
href: 'https://github.com/discord-net/Discord.Net',
|
||||||
|
title: 'GitHub'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'box-seam-fill',
|
||||||
|
href: 'https://www.nuget.org/packages/Discord.Net/',
|
||||||
|
title: 'NuGet'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'discord',
|
||||||
|
href: 'https://discord.gg/dnet',
|
||||||
|
title: 'Discord'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
start: () =>
|
||||||
|
{
|
||||||
|
// Ugly hack to improve toc filter.
|
||||||
|
let target = document.getElementById("toc");
|
||||||
|
|
||||||
|
if(!target) return;
|
||||||
|
|
||||||
|
let config = { attributes: false, childList: true, subtree: true };
|
||||||
|
let observer = new MutationObserver((list) =>
|
||||||
|
{
|
||||||
|
for(const mutation of list)
|
||||||
|
{
|
||||||
|
if(mutation.type === "childList" && mutation.target == target)
|
||||||
|
{
|
||||||
|
let filter = target.getElementsByClassName("form-control")[0];
|
||||||
|
|
||||||
|
let filterValue = localStorage.getItem("tocFilter");
|
||||||
|
let scrollValue = localStorage.getItem("tocScroll");
|
||||||
|
|
||||||
|
if(filterValue && filterValue !== "")
|
||||||
|
{
|
||||||
|
filter.value = filterValue;
|
||||||
|
|
||||||
|
let inputEvent = new Event("input");
|
||||||
|
filter.dispatchEvent(inputEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event to store scroll pos.
|
||||||
|
let tocDiv = target.getElementsByClassName("flex-fill")[0];
|
||||||
|
|
||||||
|
tocDiv.addEventListener("scroll", (event) =>
|
||||||
|
{
|
||||||
|
if (event.target.scrollTop >= 0)
|
||||||
|
{
|
||||||
|
localStorage.setItem("tocScroll", event.target.scrollTop);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(scrollValue && scrollValue >= 0)
|
||||||
|
{
|
||||||
|
tocDiv.scroll(0, scrollValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
observer.disconnect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(target, config);
|
||||||
|
}
|
||||||
|
}
|
6
docs/api/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
###############
|
||||||
|
# temp file #
|
||||||
|
###############
|
||||||
|
*.yml
|
||||||
|
.manifest
|
17
docs/api/index.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
uid: API.Docs
|
||||||
|
---
|
||||||
|
|
||||||
|
# API Documentation
|
||||||
|
|
||||||
|
This is where you will find documentation for all members and objects in Discord.Net.
|
||||||
|
This is automatically generated based on the [dev](https://github.com/discord-net/Discord.Net/tree/dev) branch.
|
||||||
|
|
||||||
|
# Commonly Used Entities
|
||||||
|
|
||||||
|
* @Discord.WebSocket
|
||||||
|
* @Discord.WebSocket.DiscordSocketClient
|
||||||
|
* @Discord.WebSocket.SocketGuildChannel
|
||||||
|
* @Discord.WebSocket.SocketGuildUser
|
||||||
|
* @Discord.WebSocket.SocketMessage
|
||||||
|
* @Discord.WebSocket.SocketRole
|
69
docs/docfx.json
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{
|
||||||
|
"metadata": [
|
||||||
|
{
|
||||||
|
"src": [
|
||||||
|
{
|
||||||
|
"src": "../src",
|
||||||
|
"files": ["**/*.csproj"],
|
||||||
|
"exclude": ["Discord.Net.DebugTools/*.csproj"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dest": "api",
|
||||||
|
"filter": "filterConfig.yml"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"build": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"files": ["api/**.yml", "api/index.md"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["toc.yml", "index.md"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["faq/**.md", "faq/**/toc.yml"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["guides/**.md", "guides/**/toc.yml"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "../",
|
||||||
|
"files": ["CHANGELOG.md"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resource": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/images/**",
|
||||||
|
"**/samples/**",
|
||||||
|
"langwordMapping.yml",
|
||||||
|
"marketing/logo/**.svg",
|
||||||
|
"marketing/logo/**.png",
|
||||||
|
"favicon.png",
|
||||||
|
"../src/Discord.Net.Examples/**.cs"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": "_site",
|
||||||
|
"template": [
|
||||||
|
"default",
|
||||||
|
"modern",
|
||||||
|
"_template/material",
|
||||||
|
"_template/description-generator"
|
||||||
|
],
|
||||||
|
"postProcessors": [
|
||||||
|
"ExtractSearchIndex",
|
||||||
|
"DescriptionPostProcessor"
|
||||||
|
],
|
||||||
|
"overwrite": "_overwrites/**/**.md",
|
||||||
|
"globalMetadata": {
|
||||||
|
"_appTitle": "Discord.Net Documentation",
|
||||||
|
"_appName": "Discord.Net",
|
||||||
|
"_appFooter": "Discord.Net © 2015-2024 3.14.1",
|
||||||
|
"_enableSearch": true,
|
||||||
|
"_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg",
|
||||||
|
"_appFaviconPath": "favicon.png"
|
||||||
|
},
|
||||||
|
"xref": ["https://github.com/dotnet/docfx/raw/main/.xrefmap.json"]
|
||||||
|
}
|
||||||
|
}
|
123
docs/faq/basics/basic-operations.md
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.Basics.BasicOp
|
||||||
|
title: Basic Operations Questions
|
||||||
|
---
|
||||||
|
|
||||||
|
# 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/how-to/safely-cast-using-pattern-matching-is-and-as-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:Guides.Entities.Glossary#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.IMessage.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/discord-net/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
|
148
docs/faq/basics/client-basics.md
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.Basics.ClientBasics
|
||||||
|
title: Client Basics Questions
|
||||||
|
---
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
## I keep having trouble with intents!
|
||||||
|
|
||||||
|
As Discord.NET has upgraded from Discord API v6 to API v9,
|
||||||
|
`GatewayIntents` must now be specified in the socket config, as well as on the [developer portal].
|
||||||
|
|
||||||
|
```cs
|
||||||
|
|
||||||
|
// Where ever you declared your websocket client.
|
||||||
|
DiscordSocketClient _client;
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
var config = new DiscordSocketConfig()
|
||||||
|
{
|
||||||
|
.. // Other config options can be presented here.
|
||||||
|
GatewayIntents = GatewayIntents.All
|
||||||
|
}
|
||||||
|
|
||||||
|
_client = new DiscordSocketClient(config);
|
||||||
|
|
||||||
|
```
|
||||||
|
### Common intents:
|
||||||
|
|
||||||
|
- AllUnprivileged: This is a group of most common intents, that do NOT require any [developer portal] intents to be enabled.
|
||||||
|
This includes intents that receive messages such as: `GatewayIntents.GuildMessages, GatewayIntents.DirectMessages`
|
||||||
|
- GuildMembers: An intent disabled by default, as you need to enable it in the [developer portal].
|
||||||
|
- GuildPresences: Also disabled by default, this intent together with `GuildMembers` are the only intents not included in `AllUnprivileged`.
|
||||||
|
- All: All intents, it is ill advised to use this without care, as it _can_ cause a memory leak from presence.
|
||||||
|
The library will give responsive warnings if you specify unnecessary intents.
|
||||||
|
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> All gateway intents, their Discord API counterpart and their enum value are listed
|
||||||
|
> [HERE](xref:Discord.GatewayIntents)
|
||||||
|
|
||||||
|
### Stacking intents:
|
||||||
|
|
||||||
|
It is common that you require several intents together.
|
||||||
|
The example below shows how this can be done.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
|
||||||
|
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.GuildMembers | ..
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Further documentation on the ` | ` operator can be found
|
||||||
|
> [HERE](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators)
|
||||||
|
|
||||||
|
[developer portal]: https://discord.com/developers/
|
||||||
|
|
||||||
|
## 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/discord-net/Discord.Net/issues/827
|
||||||
|
[958]: https://github.com/discord-net/Discord.Net/issues/958
|
||||||
|
[Discord API Terms of Service]: https://discord.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
|
||||||
|
|
||||||
|
## What is a shard/sharded client, and how is it different from the `DiscordSocketClient`?
|
||||||
|
As your bot grows in popularity, it is recommended that you should section your bot off into separate processes.
|
||||||
|
The [DiscordShardedClient] is essentially a class that allows you to easily create and manage multiple [DiscordSocketClient]
|
||||||
|
instances, with each one serving a different amount of guilds.
|
||||||
|
|
||||||
|
There are very few differences from the [DiscordSocketClient] class, and it is very straightforward
|
||||||
|
to modify your existing code to use a [DiscordShardedClient] when necessary.
|
||||||
|
|
||||||
|
1. You can specify the total amount of shards, or shard ids, via [DiscordShardedClient]'s constructors.
|
||||||
|
If the total shards are not specified then the library will get the recommended shard count via the
|
||||||
|
[Get Gateway Bot](https://discord.com/developers/docs/topics/gateway#get-gateway-bot) route.
|
||||||
|
2. The [Connected], [Disconnected], [Ready], and [LatencyUpdated] events
|
||||||
|
are replaced with [ShardConnected], [ShardDisconnected], [ShardReady], and [ShardLatencyUpdated].
|
||||||
|
3. Every event handler you apply/remove to the [DiscordShardedClient] is applied/removed to each shard.
|
||||||
|
If you wish to control a specific shard's events, you can access an individual shard through the `Shards` property.
|
||||||
|
|
||||||
|
If you do not wish to use the [DiscordShardedClient] and instead reuse the same [DiscordSocketClient] code and manually shard them,
|
||||||
|
you can do so by specifying the [ShardId] for the [DiscordSocketConfig] and pass that to the [DiscordSocketClient]'s constructor.
|
||||||
|
|
||||||
|
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient
|
||||||
|
[DiscordShardedClient]: xref:Discord.WebSocket.DiscordShardedClient
|
||||||
|
[Connected]: xref:Discord.WebSocket.DiscordSocketClient.Connected
|
||||||
|
[Disconnected]: xref:Discord.WebSocket.DiscordSocketClient.Disconnected
|
||||||
|
[LatencyUpdated]: xref:Discord.WebSocket.DiscordSocketClient.LatencyUpdated
|
||||||
|
[ShardConnected]: xref:Discord.WebSocket.DiscordShardedClient.ShardConnected
|
||||||
|
[ShardDisconnected]: xref:Discord.WebSocket.DiscordShardedClient.ShardDisconnected
|
||||||
|
[ShardReady]: xref:Discord.WebSocket.DiscordShardedClient.ShardReady
|
||||||
|
[ShardLatencyUpdated]: xref:Discord.WebSocket.DiscordShardedClient.ShardLatencyUpdated
|
||||||
|
[ShardId]: xref:Discord.WebSocket.DiscordSocketConfig.ShardId
|
46
docs/faq/basics/dependency-injection.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.Basics.DI
|
||||||
|
title: Questions about Dependency Injection
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dependency Injection-related Questions
|
||||||
|
|
||||||
|
In the following section, you will find common questions and answers
|
||||||
|
to utilizing dependency injection with @Discord.Commands and @Discord.Interactions, 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.
|
||||||
|
|
||||||
|
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 Command/Interaction Service 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.
|
||||||
|
|
||||||
|
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
|
96
docs/faq/basics/getting-started.md
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.Basics.GetStarted
|
||||||
|
title: Getting Started
|
||||||
|
---
|
||||||
|
|
||||||
|
# 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 guild?
|
||||||
|
|
||||||
|
Inviting your bot can be done by using the OAuth2 url generator provided by the [Discord Developer Portal].
|
||||||
|
|
||||||
|
Permissions can be granted by selecting the `bot` scope in the scopes section.
|
||||||
|
|
||||||
|
![Scopes](images/scopes.png)
|
||||||
|
|
||||||
|
A permissions tab will appear below the scope selection,
|
||||||
|
from which you can pick any permissions your bot may require to function.
|
||||||
|
When invited, the role this bot is granted will include these permissions.
|
||||||
|
If you grant no permissions, no role will be created for your bot upon invitation as there is no need for one.
|
||||||
|
|
||||||
|
![Permissions](images/permissions.png)
|
||||||
|
|
||||||
|
When done selecting permissions, you can use the link below in your browser to invite the bot
|
||||||
|
to servers where you have the `Manage Server` permission.
|
||||||
|
|
||||||
|
![Invite](images/link.png)
|
||||||
|
|
||||||
|
If you are planning to play around with slash/context commands,
|
||||||
|
make sure to check the `application commands` scope before inviting your bot!
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> You do not have to kick and reinvite your bot to update permissions/scopes later on.
|
||||||
|
> Simply reusing the invite link with provided scopes/perms will update it accordingly.
|
||||||
|
|
||||||
|
[Discord Developer Portal]: https://discord.com/developers/applications/
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
There are several common ways to do this:
|
||||||
|
|
||||||
|
1. Right click on the role either in the Server Settings
|
||||||
|
or in the user's role list (recommended).
|
||||||
|
![Roles](images/role-copy.png)
|
||||||
|
2. Make the role mentionable and mention the role, and escape it
|
||||||
|
using the `\` character in front.
|
||||||
|
3. Inspect the roles collection within the guild via your debugger.
|
BIN
docs/faq/basics/images/dev-mode.png
Normal file
After Width: | Height: | Size: 127 KiB |
BIN
docs/faq/basics/images/link.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
docs/faq/basics/images/mention-escape.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
docs/faq/basics/images/permissions.png
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
docs/faq/basics/images/role-copy.png
Normal file
After Width: | Height: | Size: 124 KiB |
BIN
docs/faq/basics/images/scopes.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
docs/faq/basics/images/snowflake.png
Normal file
After Width: | Height: | Size: 71 KiB |
28
docs/faq/basics/samples/DI.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
15
docs/faq/basics/samples/cast.cs
Normal 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
docs/faq/basics/samples/emoji-others.cs
Normal 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
docs/faq/basics/samples/emoji-self.cs
Normal 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);
|
32
docs/faq/basics/samples/missing-dep.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
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()
|
||||||
|
.AddSingleton<CommandService>()
|
||||||
|
.AddSingleton(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);
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// The same approach applies to the interaction service.
|
||||||
|
// Make sure to resolve these issues!
|
||||||
|
}
|
||||||
|
}
|
41
docs/faq/build_overrides/what-are-they.md
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.BuildOverrides.WhatAreThey
|
||||||
|
title: Build Overrides
|
||||||
|
---
|
||||||
|
|
||||||
|
# Build Overrides
|
||||||
|
|
||||||
|
Build overrides are a way for library developers to override the default behavior of the library on the fly. Adding them to your code is really simple.
|
||||||
|
|
||||||
|
## Installing the package
|
||||||
|
|
||||||
|
The build override package can be installed on nuget [here](https://www.nuget.org/packages/Discord.Net.BuildOverrides) or by using the package manager
|
||||||
|
|
||||||
|
```
|
||||||
|
PM> Install-Package Discord.Net.BuildOverrides
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding an override
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public async Task MainAsync()
|
||||||
|
{
|
||||||
|
// hook into the log function
|
||||||
|
BuildOverrides.Log += (buildOverride, message) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{buildOverride.Name}: {message}");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
// add your overrides
|
||||||
|
await BuildOverrides.AddOverrideAsync("example-override-name");
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Overrides are normally built for specific problems, for example if someone is having an issue and we think we might have a fix then we can create a build override for them to test out the fix.
|
||||||
|
|
||||||
|
## Security and Transparency
|
||||||
|
|
||||||
|
Overrides can only be created and updated by library developers, you should only apply an override if a library developer asks you to.
|
||||||
|
Code for the overrides server and the overrides themselves can be found [here](https://github.com/discord-net/Discord.Net.BuildOverrides).
|
71
docs/faq/int_framework/framework.md
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.Interactions.Framework
|
||||||
|
title: Interaction Framework
|
||||||
|
---
|
||||||
|
|
||||||
|
# The Interaction Framework
|
||||||
|
|
||||||
|
Common misconceptions and questions about the Interaction Framework.
|
||||||
|
|
||||||
|
## 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`*).
|
||||||
|
|
||||||
|
[RequireUserPermission]: xref:Discord.Commands.RequireUserPermissionAttribute
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> There are many more preconditions to use, including being able to make some yourself.
|
||||||
|
> Examples on self-made preconditions can be found
|
||||||
|
> [here](https://github.com/discord-net/Discord.Net/blob/dev/samples/InteractionFramework/Attributes/RequireOwnerAttribute.cs)
|
||||||
|
|
||||||
|
## Why do preconditions not hide my commands?
|
||||||
|
|
||||||
|
In the current permission design by Discord,
|
||||||
|
it is not very straight forward to limit vision of slash/context commands to users.
|
||||||
|
If you want to hide commands, you should take a look at the commands' `DefaultPermissions` parameter.
|
||||||
|
|
||||||
|
## Module dependencies aren't getting populated by Property Injection?
|
||||||
|
|
||||||
|
Make sure the properties are publicly accessible and publicly settable.
|
||||||
|
|
||||||
|
[!code-csharp[Property Injection](samples/propertyinjection.cs)]
|
||||||
|
|
||||||
|
## `InteractionService.ExecuteAsync()` always returns a successful result, how do i access the failed command execution results?
|
||||||
|
|
||||||
|
If you are using `RunMode.Async` you need to setup your post-execution pipeline around
|
||||||
|
`..Executed` events exposed by the Interaction Service.
|
||||||
|
|
||||||
|
## How do I check if the executing user has * permission?
|
||||||
|
|
||||||
|
Refer to the [documentation about preconditions]
|
||||||
|
|
||||||
|
[documentation about preconditions]: xref:Guides.IntFw.Preconditions
|
||||||
|
|
||||||
|
## How do I send the HTTP Response from inside the command modules.
|
||||||
|
|
||||||
|
Set the `RestResponseCallback` property of [InteractionServiceConfig] with a delegate for handling HTTP Responses and use
|
||||||
|
`RestInteractionModuleBase` to create your command modules. `RespondWithModalAsync()`, `RespondAsync()` and `DeferAsync()` methods of this module base will use the
|
||||||
|
`RestResponseCallback` to create interaction responses.
|
||||||
|
|
||||||
|
## Is there a cleaner way of creating parameter choices other than using `[Choice]`?
|
||||||
|
|
||||||
|
The default `enum` [TypeConverter] of the Interaction Service will
|
||||||
|
automatically register `enum`s as multiple choice options.
|
||||||
|
|
||||||
|
## How do I add an optional `enum` parameter but make the default value not visible to the user?
|
||||||
|
|
||||||
|
The default `enum` [TypeConverter] of the Interaction Service comes with `[Hide]` attribute that
|
||||||
|
can be used to prevent certain enum values from getting registered.
|
||||||
|
|
||||||
|
## How does the InteractionService determine the generic TypeConverter to use for a parameter type?
|
||||||
|
|
||||||
|
It compares the _target base type_ key of the
|
||||||
|
[TypeConverter] and chooses the one that sits highest on the inheritance hierarchy.
|
||||||
|
|
||||||
|
[TypeConverter]: xref:Discord.Interactions.TypeConverter
|
||||||
|
[Interactions FAQ]: xref: FAQ.Basics.Interactions
|
||||||
|
[InteractionServiceConfig]: xref:Discord.Interactions.InteractionServiceConfig
|
70
docs/faq/int_framework/general.md
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.Interactions.General
|
||||||
|
title: Interaction Basics
|
||||||
|
---
|
||||||
|
|
||||||
|
# Interaction Basics
|
||||||
|
|
||||||
|
This chapter mostly refers to interactions in general,
|
||||||
|
and will include questions that are common among users of the Interaction Framework
|
||||||
|
as well as users that register and handle commands manually.
|
||||||
|
|
||||||
|
## What's the difference between RespondAsync, DeferAsync and FollowupAsync?
|
||||||
|
|
||||||
|
The difference between these 3 functions is in how you handle the command response.
|
||||||
|
[RespondAsync] and
|
||||||
|
[DeferAsync] let the API know you have successfully received the command. This is also called 'acknowledging' a command.
|
||||||
|
DeferAsync will not send out a response, RespondAsync will.
|
||||||
|
[FollowupAsync] follows up on successful acknowledgement.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> If you have not acknowledged the command FollowupAsync will not work! the interaction has not been responded to, so you cannot follow it up!
|
||||||
|
|
||||||
|
[RespondAsync]: xref:Discord.IDiscordInteraction
|
||||||
|
[DeferAsync]: xref:Discord.IDiscordInteraction
|
||||||
|
[FollowUpAsync]: xref:Discord.IDiscordInteraction
|
||||||
|
|
||||||
|
## Im getting System.TimeoutException: 'Cannot respond to an interaction after 3 seconds!'
|
||||||
|
|
||||||
|
This happens because your computer's clock is out of sync or you're trying to respond after 3 seconds.
|
||||||
|
If your clock is out of sync and you can't fix it, you can set the `UseInteractionSnowflakeDate` to false in the [DiscordSocketConfig].
|
||||||
|
|
||||||
|
[!code-csharp[Interaction Sync](samples/interactionsyncing.cs)]
|
||||||
|
|
||||||
|
[DiscordClientConfig]: xref:Discord.WebSocket.DiscordSocketConfig
|
||||||
|
|
||||||
|
## How do I use this * interaction specific method/property?
|
||||||
|
|
||||||
|
If your interaction context holds a down-casted version of the interaction object, you need to up-cast it.
|
||||||
|
Ideally, use pattern matching to make sure its the type of interaction you are expecting it to be.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Further documentation on pattern matching can be found [here](xref:Guides.Entities.Casting).
|
||||||
|
|
||||||
|
## My interaction commands are not showing up?
|
||||||
|
|
||||||
|
- Try to check for any errors in the console, there is a good chance something might have been thrown.
|
||||||
|
- - Make sure you have setup logging. If you use `InteractionService` hook into [`InteractionService.Log`]) event
|
||||||
|
|
||||||
|
- Register your commands after the Ready event in the client. The client is not configured to register commands before this moment.
|
||||||
|
|
||||||
|
- Check if no bad form exception is thrown;
|
||||||
|
|
||||||
|
- Do you have the application commands scope checked when adding your bot to guilds?
|
||||||
|
|
||||||
|
- Try reloading your Discord client. On desktop it's done with `Ctrl+R` key combo.
|
||||||
|
|
||||||
|
## Do I need to create commands on startup?
|
||||||
|
|
||||||
|
If you are registering your commands for the first time, it is required to create them once.
|
||||||
|
After this, commands will exist indefinitely until you overwrite them.
|
||||||
|
Overwriting is only required if you make changes to existing commands, or add new ones.
|
||||||
|
|
||||||
|
## I can't see all of my user/message commands, why?
|
||||||
|
|
||||||
|
Message and user commands have a limit of 5 per guild, and another 5 globally.
|
||||||
|
If you have more than 5 guild-only message commands being registered, no more than 5 will actually show up.
|
||||||
|
You can get up to 10 entries to show if you register 5 per guild, and another 5 globally.
|
||||||
|
|
||||||
|
|
||||||
|
[`InteractionService.Log`]: xref:Discord.Interactions.InteractionService.Log
|
BIN
docs/faq/int_framework/images/response-scheme-component.png
Normal file
After Width: | Height: | Size: 620 KiB |
BIN
docs/faq/int_framework/images/response-scheme-modal.png
Normal file
After Width: | Height: | Size: 615 KiB |
BIN
docs/faq/int_framework/images/response-scheme-slash.png
Normal file
After Width: | Height: | Size: 432 KiB |
BIN
docs/faq/int_framework/images/scope.png
Normal file
After Width: | Height: | Size: 16 KiB |
45
docs/faq/int_framework/manual.md
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.Interactions.Manual
|
||||||
|
title: Manual Handling
|
||||||
|
---
|
||||||
|
|
||||||
|
# Manually Handling Interactions
|
||||||
|
|
||||||
|
This section talks about the manual building and responding to interactions.
|
||||||
|
If you are using the interaction framework (highly recommended) this section does not apply to you.
|
||||||
|
|
||||||
|
## Bad form Exception when I try to create my commands, why do I get this?
|
||||||
|
|
||||||
|
Bad form exceptions are thrown if the slash, user or message command builder has invalid values.
|
||||||
|
The following options could resolve your error.
|
||||||
|
|
||||||
|
#### Is your command name lowercase?
|
||||||
|
|
||||||
|
If your command name is not lowercase, it is not seen as a valid command entry.
|
||||||
|
`Avatar` is invalid; `avatar` is valid.
|
||||||
|
|
||||||
|
#### Are your values below or above the required amount? (This also applies to message components)
|
||||||
|
|
||||||
|
Discord expects all values to be below maximum allowed.
|
||||||
|
Going over this maximum amount of characters causes an exception.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> All maximum and minimum value requirements can be found in the [Discord Developer Docs].
|
||||||
|
> For components, structure documentation is found [here].
|
||||||
|
|
||||||
|
[Discord Developer Docs]: https://discord.com/developers/docs/interactions/application-commands#application-commands
|
||||||
|
[here]: https://discord.com/developers/docs/interactions/message-components#message-components
|
||||||
|
|
||||||
|
#### Is your subcommand branching correct?
|
||||||
|
|
||||||
|
Branching structure is covered properly here: xref:Guides.SlashCommands.SubCommand
|
||||||
|
|
||||||
|
![Scope check](images/scope.png)
|
||||||
|
|
||||||
|
## There are many options for creating commands, which do I use?
|
||||||
|
|
||||||
|
[!code-csharp[Register examples](samples/registerint.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> You can use bulkoverwrite even if there are no commands in guild, nor globally.
|
||||||
|
> The bulkoverwrite method disposes the old set of commands and replaces it with the new.
|
32
docs/faq/int_framework/respondings-schemes.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.Interactions.RespondingSchemes
|
||||||
|
title: Interaction Response Schemes
|
||||||
|
---
|
||||||
|
|
||||||
|
# Interaction Response Schemes
|
||||||
|
|
||||||
|
Working with interactions can appear hard and confusing - you might accidentally miss a cast or use a wrong method. These schemes should help you create efficient interaction response flows.
|
||||||
|
|
||||||
|
## Responding to a slash command interaction
|
||||||
|
|
||||||
|
Slash command interactions support the most commonly used response methods.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Same scheme applies to context command interactions.
|
||||||
|
|
||||||
|
![Slash command interaction](images/response-scheme-slash.png)
|
||||||
|
|
||||||
|
## Responding to a component interaction
|
||||||
|
|
||||||
|
Component interactions share a lot of response mwthods with [slash command interactions](#responding-to-a-slash-command-interaction), but they also provide a way to update the message components were attached to.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Some followup methods change their behavior depending on what initial response you've sent.
|
||||||
|
|
||||||
|
![Slash command interaction](images/response-scheme-component.png)
|
||||||
|
|
||||||
|
## Responding to a modal interaction
|
||||||
|
|
||||||
|
While being similar to [Component Interaction Scheme](#responding-to-a-modal-interaction), modal interactions lack the option of responding with a modal.
|
||||||
|
|
||||||
|
![Slash command interaction](images/response-scheme-modal.png)
|
6
docs/faq/int_framework/samples/interactionsyncing.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
DiscordSocketConfig config = new()
|
||||||
|
{
|
||||||
|
UseInteractionSnowflakeDate = false
|
||||||
|
};
|
||||||
|
|
||||||
|
DiscordSocketclient client = new(config);
|
8
docs/faq/int_framework/samples/propertyinjection.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
public class MyModule
|
||||||
|
{
|
||||||
|
// Intended.
|
||||||
|
public InteractionService Service { get; set; }
|
||||||
|
|
||||||
|
// Will not work. A private setter cannot be accessed by the serviceprovider.
|
||||||
|
private InteractionService Service { get; private set; }
|
||||||
|
}
|
21
docs/faq/int_framework/samples/registerint.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
private async Task ReadyAsync()
|
||||||
|
{
|
||||||
|
// pull your commands from some array, everyone has a different approach for this.
|
||||||
|
var commands = _builders.ToArray();
|
||||||
|
|
||||||
|
// write your list of commands globally in one go.
|
||||||
|
await _client.Rest.BulkOverwriteGlobalCommands(commands);
|
||||||
|
|
||||||
|
// write your array of commands to one guild in one go.
|
||||||
|
// You can do a foreach (... in _client.Guilds) approach to write to all guilds.
|
||||||
|
await _client.Rest.BulkOverwriteGuildCommands(commands, /* some guild ID */);
|
||||||
|
|
||||||
|
foreach (var c in commands)
|
||||||
|
{
|
||||||
|
// Create a global command, repeating usage for multiple commands.
|
||||||
|
await _client.Rest.CreateGlobalCommand(c);
|
||||||
|
|
||||||
|
// Create a guild command, repeating usage for multiple commands.
|
||||||
|
await _client.Rest.CreateGuildCommand(c, guildId);
|
||||||
|
}
|
||||||
|
}
|
46
docs/faq/misc/legacy.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.Legacy
|
||||||
|
title: Legacy Questions
|
||||||
|
---
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
## Migrating your commands to application commands.
|
||||||
|
|
||||||
|
The new interaction service was designed to act like the previous service for text-based commands.
|
||||||
|
Your pre-existing code will continue to work, but you will need to migrate your modules and response functions to use the new
|
||||||
|
interaction service methods. Documentation on this can be found in the [Guides](xref:Guides.IntFw.Intro).
|
||||||
|
|
||||||
|
## Gateway event parameters changed, why?
|
||||||
|
|
||||||
|
With 3.0, a higher focus on [Cacheable]'s was introduced.
|
||||||
|
[Cacheable]'s get an entity from cache, rather than making an API call to retrieve it's data.
|
||||||
|
The entity can be retrieved from cache by calling `GetOrDownloadAsync()` on the [Cacheable] type.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> GetOrDownloadAsync will download the entity if its not available directly from the cache.
|
||||||
|
|
||||||
|
[Cacheable]: xref:Discord.Cacheable`2
|
||||||
|
|
||||||
|
## 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 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 release.
|
||||||
|
|
||||||
|
[release tag]: https://github.com/discord-net/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/Discord-Net-Labs/Discord.Net-Labs/blob/release/3.x/samples/InteractionFramework/Program.cs#L66).
|
||||||
|
|
||||||
|
[DependencyInjection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection
|
142
docs/faq/text_commands/general.md
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
---
|
||||||
|
uid: FAQ.TextCommands.General
|
||||||
|
title: General Questions about Text Commands
|
||||||
|
---
|
||||||
|
|
||||||
|
# Chat 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?
|
||||||
|
|
||||||
|
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`*).
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> There are many more preconditions to use, including being able to make some yourself.
|
||||||
|
> Precondition documentation is covered [here](xref:Guides.TextCommands.Preconditions)
|
||||||
|
|
||||||
|
[RequireUserPermission]: xref:Discord.Commands.RequireUserPermissionAttribute
|
||||||
|
|
||||||
|
## 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 in the `ExecuteAsync` result.
|
||||||
|
|
||||||
|
However, there are ways to remedy some of these.
|
||||||
|
|
||||||
|
For #3, in Discord.Net 2.0, the library introduces a new event called
|
||||||
|
[CommandService.CommandExecuted], which is raised whenever the command is executed.
|
||||||
|
This event will be raised regardless of
|
||||||
|
the `RunMode` type and will return the appropriate execution result
|
||||||
|
and the associated @Discord.Commands.CommandInfo if applicable.
|
||||||
|
|
||||||
|
For #4, exceptions are caught in [CommandService.Log] event under
|
||||||
|
[LogMessage.Exception] as [CommandException] and in the
|
||||||
|
[CommandService.CommandExecuted] event under the [IResult] as
|
||||||
|
[ExecuteResult.Exception].
|
||||||
|
|
||||||
|
[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
|
||||||
|
[CommandService.CommandExecuted]: xref:Discord.Commands.CommandService.CommandExecuted
|
||||||
|
[CommandService.Log]: xref:Discord.Commands.CommandService.Log
|
||||||
|
[LogMessage.Exception]: xref:Discord.LogMessage.Exception*
|
||||||
|
[ExecuteResult.Exception]: xref:Discord.Commands.ExecuteResult.Exception*
|
||||||
|
[CommandException]: xref:Discord.Commands.CommandException
|
||||||
|
[IResult]: xref:Discord.Commands.IResult
|
20
docs/faq/text_commands/samples/Remainder.cs
Normal 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"
|
7
docs/faq/text_commands/samples/runmode-cmdattrib.cs
Normal 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
docs/faq/text_commands/samples/runmode-cmdconfig.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
28
docs/faq/toc.yml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
- 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: Dependency Injection
|
||||||
|
topicUid: FAQ.Basics.DI
|
||||||
|
- name: Interactions
|
||||||
|
items:
|
||||||
|
- name: Starting out
|
||||||
|
topicUid: FAQ.Interactions.General
|
||||||
|
- name: Interaction Service/Framework
|
||||||
|
topicUid: FAQ.Interactions.Framework
|
||||||
|
- name: Manual handling
|
||||||
|
topicUid: FAQ.Interactions.Manual
|
||||||
|
- name: Interaction response schemes
|
||||||
|
topicUid: FAQ.Interactions.RespondingSchemes
|
||||||
|
- name: Text Commands
|
||||||
|
items:
|
||||||
|
- name: Text Command basics
|
||||||
|
topicUid: FAQ.TextCommands.General
|
||||||
|
- name: Legacy Questions
|
||||||
|
topicUid: FAQ.Legacy
|
||||||
|
- name: Build Overrides
|
||||||
|
topicUid: FAQ.BuildOverrides.WhatAreThey
|
BIN
docs/favicon.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
10
docs/filterConfig.yml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
apiRules:
|
||||||
|
- exclude:
|
||||||
|
uidRegex: ^Discord\.Net\..*$
|
||||||
|
type: Namespace
|
||||||
|
- exclude:
|
||||||
|
uidRegex: ^Discord\.Analyzers$
|
||||||
|
type: Namespace
|
||||||
|
- exclude:
|
||||||
|
uidRegex: ^Discord\.API$
|
||||||
|
type: Namespace
|
68
docs/guides/bearer_token/bearer_token_guide.md
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
uid: Guides.BearerToken
|
||||||
|
title: Working with Bearer token
|
||||||
|
---
|
||||||
|
|
||||||
|
# Working with Bearer token
|
||||||
|
|
||||||
|
Some endpoints in Discord API require a Bearer token, which can be obtained through [OAuth2 flow](https://discord.com/developers/docs/topics/oauth2). Discord.Net allows you to interact with these endpoints using the [DiscordRestClient].
|
||||||
|
|
||||||
|
## Initializing a new instance of the client
|
||||||
|
[!code-csharp[Initialize DiscordRestClient](samples/rest_client_init.cs)]
|
||||||
|
|
||||||
|
## Getting current user
|
||||||
|
|
||||||
|
The [DiscordRestClient] gets the current user when `LoginAsync()` is called. The user object can be found in the `CurrentUser` property.
|
||||||
|
|
||||||
|
If you need to fetch the user again, the `GetCurrentUserAsync()` method can be used.
|
||||||
|
|
||||||
|
[!code-csharp[Get current user](samples/current_user.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Some properties might be `null` depending on which scopes users authorized your app with.
|
||||||
|
> For example: `email` scope is required to fetch current user's email address.
|
||||||
|
|
||||||
|
## Fetching current user's guilds
|
||||||
|
|
||||||
|
The `GetGuildSummariesAsync()` method is used to fetch current user's guilds. Since it returns an `IAsyncEnumerable` you need to call `FlattenAsync()` to get a plain `IEnumerable` containing [RestUserGuild] objects.
|
||||||
|
|
||||||
|
[!code-csharp[Get current user's guilds](samples/current_user_guilds.cs)]
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This method requires `guilds` scope
|
||||||
|
|
||||||
|
## Fetching current user's guild member object
|
||||||
|
|
||||||
|
To fetch the current user's guild member object, the `GetCurrentUserGuildMemberAsync()` method can be used.
|
||||||
|
|
||||||
|
[!code-csharp[Get current user's guild member](samples/current_user_guild_member.cs)]
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This method requires `guilds.members.read` scope
|
||||||
|
|
||||||
|
## Get user connections
|
||||||
|
|
||||||
|
The `GetConnectionsAsync` method can be used to fetch current user's connections to other platforms.
|
||||||
|
|
||||||
|
[!code-csharp[Get current user's connections](samples/current_user_connections.cs)]
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This method requires `connections` scope
|
||||||
|
|
||||||
|
## Application role connection
|
||||||
|
|
||||||
|
In addition to previous features, Discord.Net supports fetching & updating user's application role connection metadata values. `GetUserApplicationRoleConnectionAsync()` returns a [RoleConnection] object of the current user for the given application id.
|
||||||
|
|
||||||
|
The `ModifyUserApplicationRoleConnectionAsync()` method is used to update current user's role connection metadata values. A new set of values can be created with [RoleConnectionProperties] object.
|
||||||
|
|
||||||
|
[!code-csharp[Get current user's connections](samples/app_role_connection.cs)]
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This method requires `role_connections.write` scope
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[DiscordRestClient]: xref:Discord.Rest.DiscordRestClient
|
||||||
|
[RestUserGuild]: xref:Discord.Rest.RestUserGuild
|
||||||
|
[RoleConnection]: xref:Discord.RoleConnection
|
||||||
|
[RoleConnectionProperties]: xref:Discord.RoleConnectionProperties
|
11
docs/guides/bearer_token/samples/app_role_connection.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// fetch application role connection of the current user for the app with provided id.
|
||||||
|
var roleConnection = await client.GetUserApplicationRoleConnectionAsync(applicationid);
|
||||||
|
|
||||||
|
// create a new role connection metadata properties object & set some values.
|
||||||
|
var properties = new RoleConnectionProperties("Discord.Net Docs", "Cool Coding Guy")
|
||||||
|
.WithNumber("eaten_cookies", 69)
|
||||||
|
.WithBool("loves_cookies", true)
|
||||||
|
.WithDate("last_eaten_cookie", DateTimeOffset.UtcNow);
|
||||||
|
|
||||||
|
// update current user's values with the given properties.
|
||||||
|
await client.ModifyUserApplicationRoleConnectionAsync(applicationId, properties);
|
5
docs/guides/bearer_token/samples/current_user.cs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// gets the user object stored in the DiscordRestClient.
|
||||||
|
var user = client.CurrentUser;
|
||||||
|
|
||||||
|
// fetches the current user with a REST call & updates the CurrentUser property.
|
||||||
|
var refreshedUser = await client.GetCurrentUserAsync();
|
|
@ -0,0 +1,2 @@
|
||||||
|
// fetches the current user's connections.
|
||||||
|
var connections = await client.GetConnectionsAsync();
|
|
@ -0,0 +1,6 @@
|
||||||
|
// fetches the current user's guild member object in a guild with provided id.
|
||||||
|
var member = await client.GetCurrentUserGuildMemberAsync(guildId);
|
||||||
|
|
||||||
|
// fetches the current user's guild member object in a RestUserGuild.
|
||||||
|
var guild = await client.GetGuildSummariesAsync().FlattenAsync().First();
|
||||||
|
var member = await guild.GetCurrentUserGuildMemberAsync();
|
2
docs/guides/bearer_token/samples/current_user_guilds.cs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// fetches the guilds the current user participate in.
|
||||||
|
var guilds = await client.GetGuildSummariesAsync().FlattenAsync();
|
5
docs/guides/bearer_token/samples/rest_client_init.cs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Discord;
|
||||||
|
using Discord.Rest;
|
||||||
|
|
||||||
|
await using var client = new DiscordRestClient();
|
||||||
|
await client.LoginAsync(TokenType.Bearer, "bearer token obtained through oauth2 flow");
|
53
docs/guides/concepts/connections.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
stopped.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To start a connection, invoke the `StartAsync` method on a client that
|
||||||
|
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 made.**
|
||||||
|
|
||||||
|
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
|
||||||
|
ready to be used.
|
||||||
|
|
||||||
|
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 completed guild cache.
|
||||||
|
|
||||||
|
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient
|
||||||
|
|
||||||
|
## Reconnection
|
||||||
|
|
||||||
|
> [!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
|
||||||
|
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 does not manage to reconnect, you have found a bug!
|
||||||
|
|
||||||
|
[events]: xref:Guides.Concepts.Events
|
84
docs/guides/concepts/events.md
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
---
|
||||||
|
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 @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
|
||||||
|
|
||||||
|
To receive data from an event, hook into it using C#'s delegate
|
||||||
|
event pattern.
|
||||||
|
|
||||||
|
You may either opt to hook an event to an anonymous function (lambda)
|
||||||
|
or a named function.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
All events are designed to be thread-safe; events are executed
|
||||||
|
synchronously off the gateway task in the same context as the gateway
|
||||||
|
task.
|
||||||
|
|
||||||
|
As a side effect, this makes it possible to deadlock the gateway task
|
||||||
|
and kill a connection. As a general rule of thumb, any task that takes
|
||||||
|
longer than three seconds should **not** be awaited directly in the
|
||||||
|
context of an event, but should be wrapped in a `Task.Run` or
|
||||||
|
offloaded to another task.
|
||||||
|
|
||||||
|
This also means that you should not await a task that requests data
|
||||||
|
from Discord's gateway in the same context of an event. Since the
|
||||||
|
gateway will wait on all invoked event handlers to finish before
|
||||||
|
processing any additional data from the gateway, this will create
|
||||||
|
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
|
||||||
|
|
||||||
|
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,
|
||||||
|
so you must either consult IntelliSense, or view the API documentation
|
||||||
|
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
|
||||||
|
|
||||||
|
An event handler with a signature of `Func<Entity, Entity, Task>`
|
||||||
|
typically means that the first object will be a clone of the entity
|
||||||
|
_before_ a change was made, and the latter object will be an attached
|
||||||
|
model of the entity _after_ the change was made.
|
||||||
|
|
||||||
|
This pattern is typically only found on `EntityUpdated` events.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
API, so it can either be pulled from the client's cache or
|
||||||
|
downloaded from the API.
|
||||||
|
|
||||||
|
See the documentation for [Cacheable] for more information on this
|
||||||
|
object.
|
||||||
|
|
||||||
|
[Cacheable]: xref:Discord.Cacheable`2
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Many events relating to a Message entity (i.e., `MessageUpdated` and
|
||||||
|
> `ReactionAdded`) rely on the client's message cache, which is
|
||||||
|
> **not** enabled by default. Set the `MessageCacheSize` flag in
|
||||||
|
> @Discord.WebSocket.DiscordSocketConfig to enable it.
|
||||||
|
|
||||||
|
## Sample
|
||||||
|
|
||||||
|
[!code-csharp[Event Sample](samples/events.cs)]
|
48
docs/guides/concepts/logging.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
uid: Guides.Concepts.Logging
|
||||||
|
title: Logging Events/Data
|
||||||
|
---
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
[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 in Client(s)
|
||||||
|
|
||||||
|
To receive log events, simply hook the Discord client's @Discord.Rest.BaseDiscordClient.Log
|
||||||
|
to a `Task` with a single parameter of type [LogMessage].
|
||||||
|
|
||||||
|
It is recommended that you use an established function instead of a
|
||||||
|
lambda for handling logs, because most addons accept a reference
|
||||||
|
to a logging function to write their own messages.
|
||||||
|
|
||||||
|
[LogMessage]: xref:Discord.LogMessage
|
||||||
|
|
||||||
|
## Usage in Commands
|
||||||
|
|
||||||
|
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
|
||||||
|
and error can be found and handled.
|
||||||
|
|
||||||
|
[CommandService]: xref:Discord.Commands.CommandService
|
||||||
|
[CommandException]: xref:Discord.Commands.CommandException
|
||||||
|
|
||||||
|
## Sample
|
||||||
|
|
||||||
|
[!code-csharp[Logging Sample](samples/logging.cs)]
|
49
docs/guides/concepts/ratelimits.md
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Ratelimits
|
||||||
|
|
||||||
|
Ratelimits are a core concept of any API - Discords API is no exception. Each verified library must follow the ratelimit guidelines.
|
||||||
|
|
||||||
|
### Using the ratelimit callback
|
||||||
|
|
||||||
|
There is a new property within `RequestOptions` called RatelimitCallback. This callback is called when a request is made via the rest api. The callback is called with a `IRateLimitInfo` parameter:
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| IsGlobal | bool | Whether or not this ratelimit info is global. |
|
||||||
|
| Limit | int? | The number of requests that can be made. |
|
||||||
|
| Remaining | int? | The number of remaining requests that can be made. |
|
||||||
|
| RetryAfter | int? | The total time (in seconds) of when the current rate limit bucket will reset. Can have decimals to match previous millisecond ratelimit precision. |
|
||||||
|
| Reset | DateTimeOffset? | The time at which the rate limit resets. |
|
||||||
|
| ResetAfter | TimeSpan? | The absolute time when this ratelimit resets. |
|
||||||
|
| Bucket | string | A unique string denoting the rate limit being encountered (non-inclusive of major parameters in the route path). |
|
||||||
|
| Lag | TimeSpan? | The amount of lag for the request. This is used to denote the precise time of when the ratelimit expires. |
|
||||||
|
| Endpoint | string | The endpoint that this ratelimit info came from. |
|
||||||
|
|
||||||
|
Let's set up a ratelimit callback that will print out the ratelimit info to the console.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public async Task MyRatelimitCallback(IRateLimitInfo info)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{info.IsGlobal} {info.Limit} {info.Remaining} {info.RetryAfter} {info.Reset} {info.ResetAfter} {info.Bucket} {info.Lag} {info.Endpoint}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's use this callback in a send message function
|
||||||
|
|
||||||
|
```cs
|
||||||
|
[Command("ping")]
|
||||||
|
public async Task ping()
|
||||||
|
{
|
||||||
|
var options = new RequestOptions()
|
||||||
|
{
|
||||||
|
RatelimitCallback = MyRatelimitCallback
|
||||||
|
};
|
||||||
|
|
||||||
|
await Context.Channel.SendMessageAsync("Pong!", options: options);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Running this produces the following output:
|
||||||
|
|
||||||
|
```
|
||||||
|
False 5 4 2021-09-09 3:48:14 AM +00:00 00:00:05 a06de0de4a08126315431cc0c55ee3dc 00:00:00.9891364 channels/848511736872828929/messages
|
||||||
|
```
|
34
docs/guides/concepts/samples/events.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
private static DiscordSocketClient _client;
|
||||||
|
public static async Task MainAsync()
|
||||||
|
{
|
||||||
|
// When working with events that have Cacheable<IMessage, ulong> parameters,
|
||||||
|
// you must enable the message cache in your config settings if you plan to
|
||||||
|
// use the cached message entity.
|
||||||
|
var _config = new DiscordSocketConfig { MessageCacheSize = 100 };
|
||||||
|
_client = new DiscordSocketClient(_config);
|
||||||
|
|
||||||
|
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken"));
|
||||||
|
await _client.StartAsync();
|
||||||
|
|
||||||
|
_client.MessageUpdated += MessageUpdated;
|
||||||
|
_client.Ready += () =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("Bot is connected!");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await Task.Delay(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task MessageUpdated(Cacheable<IMessage, ulong> before, SocketMessage after, ISocketMessageChannel channel)
|
||||||
|
{
|
||||||
|
// If the message was not in the cache, downloading it will result in getting a copy of `after`.
|
||||||
|
var message = await before.GetOrDownloadAsync();
|
||||||
|
Console.WriteLine($"{message} -> {after}");
|
||||||
|
}
|
||||||
|
}
|
24
docs/guides/concepts/samples/logging.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
|
||||||
|
public class LoggingService
|
||||||
|
{
|
||||||
|
public LoggingService(DiscordSocketClient client, CommandService command)
|
||||||
|
{
|
||||||
|
client.Log += LogAsync;
|
||||||
|
command.Log += LogAsync;
|
||||||
|
}
|
||||||
|
private Task LogAsync(LogMessage message)
|
||||||
|
{
|
||||||
|
if (message.Exception is CommandException cmdException)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Command/{message.Severity}] {cmdException.Command.Aliases.First()}"
|
||||||
|
+ $" failed to execute in {cmdException.Context.Channel}.");
|
||||||
|
Console.WriteLine(cmdException);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Console.WriteLine($"[General/{message.Severity}] {message}");
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
69
docs/guides/dependency_injection/basics.md
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
---
|
||||||
|
uid: Guides.DI.Intro
|
||||||
|
title: Introduction
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dependency Injection
|
||||||
|
|
||||||
|
Dependency injection is a feature not required in Discord.Net, but makes it a lot easier to use.
|
||||||
|
It can be combined with a large number of other libraries, and gives you better control over your application.
|
||||||
|
|
||||||
|
> Further into the documentation, Dependency Injection will be referred to as 'DI'.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
DI is not native to .NET. You need to install the extension packages to your project in order to use it:
|
||||||
|
|
||||||
|
- [Meta](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection/).
|
||||||
|
- [Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection.Abstractions/).
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Downloading the abstractions package alone will not give you access to required classes to use DI properly.
|
||||||
|
> Please install both packages, or choose to only install the meta package to implicitly install both.
|
||||||
|
|
||||||
|
### Visual Package Manager:
|
||||||
|
|
||||||
|
![Installing](images/manager.png)
|
||||||
|
|
||||||
|
### Command Line:
|
||||||
|
|
||||||
|
`PM> Install-Package Microsoft.Extensions.DependencyInjection`.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> ASP.NET already comes packed with all the necessary assemblies in its framework.
|
||||||
|
> You do not require to install any additional NuGet packages to make full use of all features of DI in ASP.NET projects.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
First of all, you will need to create an application based around dependency injection,
|
||||||
|
which in order will be able to access and inject them across the project.
|
||||||
|
|
||||||
|
[!code-csharp[Building the Program](samples/program.cs)]
|
||||||
|
|
||||||
|
In order to freely pass around your dependencies in different classes,
|
||||||
|
you will need to register them to a new `ServiceCollection` and build them into an `IServiceProvider` as seen above.
|
||||||
|
The IServiceProvider then needs to be accessible by the startup file, so you can access your provider and manage them.
|
||||||
|
|
||||||
|
[!code-csharp[Building the Collection](samples/collection.cs)]
|
||||||
|
|
||||||
|
As shown above, an instance of `DiscordSocketConfig` is created, and added **before** the client itself is.
|
||||||
|
Because the collection will prefer to create the highest populated constructor available with the services already present,
|
||||||
|
it will prefer the constructor with the configuration, because you already added it.
|
||||||
|
|
||||||
|
## Using your dependencies
|
||||||
|
|
||||||
|
After building your provider in the Program class constructor, the provider is now available inside the instance you're actively using.
|
||||||
|
Through the provider, we can ask for the DiscordSocketClient we registered earlier.
|
||||||
|
|
||||||
|
[!code-csharp[Applying DI in RunAsync](samples/runasync.cs)]
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Service constructors are not activated until the service is **first requested**.
|
||||||
|
> An 'endpoint' service will have to be requested from the provider before it is activated.
|
||||||
|
> If a service is requested with dependencies, its dependencies (if not already active) will be activated before the service itself is.
|
||||||
|
|
||||||
|
## Injecting dependencies
|
||||||
|
|
||||||
|
You can not only directly access the provider from a field or property, but you can also pass around instances to classes registered in the provider.
|
||||||
|
There are multiple ways to do this. Please refer to the
|
||||||
|
[Injection Documentation](xref:Guides.DI.Injection) for further information.
|
BIN
docs/guides/dependency_injection/images/manager.png
Normal file
After Width: | Height: | Size: 12 KiB |
44
docs/guides/dependency_injection/injection.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
uid: Guides.DI.Injection
|
||||||
|
title: Injection
|
||||||
|
---
|
||||||
|
|
||||||
|
# Injecting instances within the provider
|
||||||
|
|
||||||
|
You can inject registered services into any class that is registered to the `IServiceProvider`.
|
||||||
|
This can be done through property or constructor.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> As mentioned above, the dependency *and* the target class have to be registered in order for the serviceprovider to resolve it.
|
||||||
|
|
||||||
|
## Injecting through a constructor
|
||||||
|
|
||||||
|
Services can be injected from the constructor of the class.
|
||||||
|
This is the preferred approach, because it automatically locks the readonly field in place with the provided service and isn't accessible outside of the class.
|
||||||
|
|
||||||
|
[!code-csharp[Constructor Injection](samples/ctor-injecting.cs)]
|
||||||
|
|
||||||
|
## Injecting through properties
|
||||||
|
|
||||||
|
Injecting through properties is also allowed as follows.
|
||||||
|
|
||||||
|
[!code-csharp[Property Injection](samples/property-injecting.cs)]
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Dependency Injection will not resolve missing services in property injection, and it will not pick a constructor instead.
|
||||||
|
> If a publicly accessible property is attempted to be injected and its service is missing, the application will throw an error.
|
||||||
|
|
||||||
|
## Using the provider itself
|
||||||
|
|
||||||
|
You can also access the provider reference itself from injecting it into a class. There are multiple use cases for this:
|
||||||
|
|
||||||
|
- Allowing libraries (Like Discord.Net) to access your provider internally.
|
||||||
|
- Injecting optional dependencies.
|
||||||
|
- Calling methods on the provider itself if necessary, this is often done for creating scopes.
|
||||||
|
|
||||||
|
[!code-csharp[Provider Injection](samples/provider.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> It is important to keep in mind that the provider will pick the 'biggest' available constructor.
|
||||||
|
> If you choose to introduce multiple constructors,
|
||||||
|
> keep in mind that services missing from one constructor may have the provider pick another one that *is* available instead of throwing an exception.
|
|
@ -0,0 +1,9 @@
|
||||||
|
async Task RunAsync()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
|
||||||
|
await _serviceProvider.GetRequiredService<ServiceActivator>()
|
||||||
|
.ActivateAsync();
|
||||||
|
|
||||||
|
//...
|
||||||
|
}
|
13
docs/guides/dependency_injection/samples/collection.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
static IServiceProvider CreateServices()
|
||||||
|
{
|
||||||
|
var config = new DiscordSocketConfig()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
};
|
||||||
|
|
||||||
|
var collection = new ServiceCollection()
|
||||||
|
.AddSingleton(config)
|
||||||
|
.AddSingleton<DiscordSocketClient>();
|
||||||
|
|
||||||
|
return collection.BuildServiceProvider();
|
||||||
|
}
|
14
docs/guides/dependency_injection/samples/ctor-injecting.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
public class ClientHandler
|
||||||
|
{
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
|
public ClientHandler(DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ConfigureAsync()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
}
|
18
docs/guides/dependency_injection/samples/enumeration.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
public class ServiceActivator
|
||||||
|
{
|
||||||
|
// This contains *all* registered services of serviceType IService
|
||||||
|
private readonly IEnumerable<IService> _services;
|
||||||
|
|
||||||
|
public ServiceActivator(IEnumerable<IService> services)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ActivateAsync()
|
||||||
|
{
|
||||||
|
foreach(var service in _services)
|
||||||
|
{
|
||||||
|
await service.StartAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
public static ServiceCollection RegisterImplicitServices(this ServiceCollection collection, Type interfaceType, Type activatorType)
|
||||||
|
{
|
||||||
|
// Get all types in the executing assembly. There are many ways to do this, but this is fastest.
|
||||||
|
foreach (var type in typeof(Program).Assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (interfaceType.IsAssignableFrom(type) && !type.IsAbstract)
|
||||||
|
collection.AddSingleton(interfaceType, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the activator so you can activate the instances.
|
||||||
|
collection.AddSingleton(activatorType);
|
||||||
|
}
|
16
docs/guides/dependency_injection/samples/modules.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
public class MyModule : InteractionModuleBase
|
||||||
|
{
|
||||||
|
private readonly MyService _service;
|
||||||
|
|
||||||
|
public MyModule(MyService service)
|
||||||
|
{
|
||||||
|
_service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("things", "Shows things")]
|
||||||
|
public async Task ThingsAsync()
|
||||||
|
{
|
||||||
|
var str = string.Join("\n", _service.Things)
|
||||||
|
await RespondAsync(str);
|
||||||
|
}
|
||||||
|
}
|
16
docs/guides/dependency_injection/samples/program.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
private static IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
static IServiceProvider CreateProvider()
|
||||||
|
{
|
||||||
|
var collection = new ServiceCollection();
|
||||||
|
//...
|
||||||
|
return collection.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
_serviceProvider = CreateProvider();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
public class ClientHandler
|
||||||
|
{
|
||||||
|
public DiscordSocketClient Client { get; set; }
|
||||||
|
|
||||||
|
public async Task ConfigureAsync()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
}
|
26
docs/guides/dependency_injection/samples/provider.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
public class UtilizingProvider
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _provider;
|
||||||
|
private readonly AnyService _service;
|
||||||
|
|
||||||
|
// This service is allowed to be null because it is only populated if the service is actually available in the provider.
|
||||||
|
private readonly AnyOtherService? _otherService;
|
||||||
|
|
||||||
|
// This constructor injects only the service provider,
|
||||||
|
// and uses it to populate the other dependencies.
|
||||||
|
public UtilizingProvider(IServiceProvider provider)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
_service = provider.GetRequiredService<AnyService>();
|
||||||
|
_otherService = provider.GetService<AnyOtherService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This constructor injects the service provider, and AnyService,
|
||||||
|
// making sure that AnyService is not null without having to call GetRequiredService
|
||||||
|
public UtilizingProvider(IServiceProvider provider, AnyService service)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
_service = service;
|
||||||
|
_otherService = provider.GetService<AnyOtherService>();
|
||||||
|
}
|
||||||
|
}
|
17
docs/guides/dependency_injection/samples/runasync.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
async Task RunAsync(string[] args)
|
||||||
|
{
|
||||||
|
// Request the instance from the client.
|
||||||
|
// Because we're requesting it here first, its targetted constructor will be called and we will receive an active instance.
|
||||||
|
var client = _services.GetRequiredService<DiscordSocketClient>();
|
||||||
|
|
||||||
|
client.Log += async (msg) =>
|
||||||
|
{
|
||||||
|
await Task.CompletedTask;
|
||||||
|
Console.WriteLine(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.LoginAsync(TokenType.Bot, "");
|
||||||
|
await client.StartAsync();
|
||||||
|
|
||||||
|
await Task.Delay(Timeout.Infinite);
|
||||||
|
}
|
6
docs/guides/dependency_injection/samples/scoped.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
// With serviceType:
|
||||||
|
collection.AddScoped<IScopedService, ScopedService>();
|
||||||
|
|
||||||
|
// Without serviceType:
|
||||||
|
collection.AddScoped<ScopedService>();
|
|
@ -0,0 +1,21 @@
|
||||||
|
static IServiceProvider CreateServices()
|
||||||
|
{
|
||||||
|
var config = new DiscordSocketConfig()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
};
|
||||||
|
|
||||||
|
// X represents either Interaction or Command, as it functions the exact same for both types.
|
||||||
|
var servConfig = new XServiceConfig()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
var collection = new ServiceCollection()
|
||||||
|
.AddSingleton(config)
|
||||||
|
.AddSingleton<DiscordSocketClient>()
|
||||||
|
.AddSingleton(servConfig)
|
||||||
|
.AddSingleton<XService>();
|
||||||
|
|
||||||
|
return collection.BuildServiceProvider();
|
||||||
|
}
|
9
docs/guides/dependency_injection/samples/services.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
public class MyService
|
||||||
|
{
|
||||||
|
public List<string> Things { get; }
|
||||||
|
|
||||||
|
public MyService()
|
||||||
|
{
|
||||||
|
Things = new();
|
||||||
|
}
|
||||||
|
}
|
6
docs/guides/dependency_injection/samples/singleton.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
// With serviceType:
|
||||||
|
collection.AddSingleton<ISingletonService, SingletonService>();
|
||||||
|
|
||||||
|
// Without serviceType:
|
||||||
|
collection.AddSingleton<SingletonService>();
|
6
docs/guides/dependency_injection/samples/transient.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
// With serviceType:
|
||||||
|
collection.AddTransient<ITransientService, TransientService>();
|
||||||
|
|
||||||
|
// Without serviceType:
|
||||||
|
collection.AddTransient<TransientService>();
|
39
docs/guides/dependency_injection/scaling.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
uid: Guides.DI.Scaling
|
||||||
|
title: Scaling your DI
|
||||||
|
---
|
||||||
|
|
||||||
|
# Scaling your DI
|
||||||
|
|
||||||
|
Dependency injection has a lot of use cases, and is very suitable for scaled applications.
|
||||||
|
There are a few ways to make registering & using services easier in large amounts.
|
||||||
|
|
||||||
|
## Using a range of services.
|
||||||
|
|
||||||
|
If you have a lot of services that all have the same use such as handling an event or serving a module,
|
||||||
|
you can register and inject them all at once by some requirements:
|
||||||
|
|
||||||
|
- All classes need to inherit a single interface or abstract type.
|
||||||
|
- While not required, it is preferred if the interface and types share a method to call on request.
|
||||||
|
- You need to register a class that all the types can be injected into.
|
||||||
|
|
||||||
|
### Registering implicitly
|
||||||
|
|
||||||
|
Registering all the types is done through getting all types in the assembly and checking if they inherit the target interface.
|
||||||
|
|
||||||
|
[!code-csharp[Registering](samples/implicit-registration.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> As seen above, the interfaceType and activatorType are undefined. For our usecase below, these are `IService` and `ServiceActivator` in order.
|
||||||
|
|
||||||
|
### Using implicit dependencies
|
||||||
|
|
||||||
|
In order to use the implicit dependencies, you have to get access to the activator you registered earlier.
|
||||||
|
|
||||||
|
[!code-csharp[Accessing the activator](samples/access-activator.cs)]
|
||||||
|
|
||||||
|
When the activator is accessed and the `ActivateAsync()` method is called, the following code will be executed:
|
||||||
|
|
||||||
|
[!code-csharp[Executing the activator](samples/enumeration.cs)]
|
||||||
|
|
||||||
|
As a result of this, all the services that were registered with `IService` as its implementation type will execute their starting code, and start up.
|
48
docs/guides/dependency_injection/services.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
uid: Guides.DI.Services
|
||||||
|
title: Using DI in Interaction & Command Frameworks
|
||||||
|
---
|
||||||
|
|
||||||
|
# DI in the Interaction- & Command Service
|
||||||
|
|
||||||
|
For both the Interaction- and Command Service modules, DI is quite straight-forward to use.
|
||||||
|
|
||||||
|
You can inject any service into modules without the modules having to be registered to the provider.
|
||||||
|
Discord.Net resolves your dependencies internally.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> The way DI is used in the Interaction- & Command Service are nearly identical, except for one detail:
|
||||||
|
> [Resolving Module Dependencies](xref:Guides.IntFw.Intro#resolving-module-dependencies)
|
||||||
|
|
||||||
|
## Registering the Service
|
||||||
|
|
||||||
|
Thanks to earlier described behavior of allowing already registered members as parameters of the available ctors,
|
||||||
|
The socket client & configuration will automatically be acknowledged and the XService(client, config) overload will be used.
|
||||||
|
|
||||||
|
[!code-csharp[Service Registration](samples/service-registration.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.
|
||||||
|
|
||||||
|
[!code-csharp[Module Injection](samples/modules.cs)]
|
||||||
|
|
||||||
|
If you accept `Command/InteractionService` or `IServiceProvider` as a parameter in your constructor or as an injectable property,
|
||||||
|
these entries will be filled by the `Command/InteractionService` that the module is loaded from and the `IServiceProvider` that is passed into it respectively.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Annotating a property with a [DontInjectAttribute] attribute will
|
||||||
|
> prevent the property from being injected.
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
Because modules are transient of nature and will reinstantiate on every request,
|
||||||
|
it is suggested to create a singleton service behind it to hold values across multiple command executions.
|
||||||
|
|
||||||
|
[!code-csharp[Services](samples/services.cs)]
|
||||||
|
|
||||||
|
|
52
docs/guides/dependency_injection/types.md
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
uid: Guides.DI.Dependencies
|
||||||
|
title: Types of Dependencies
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dependency Types
|
||||||
|
|
||||||
|
There are 3 types of dependencies to learn to use. Several different usecases apply for each.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> When registering types with a serviceType & implementationType,
|
||||||
|
> only the serviceType will be available for injection, and the implementationType will be used for the underlying instance.
|
||||||
|
|
||||||
|
## Singleton
|
||||||
|
|
||||||
|
A singleton service creates a single instance when first requested, and maintains that instance across the lifetime of the application.
|
||||||
|
Any values that are changed within a singleton will be changed across all instances that depend on it, as they all have the same reference to it.
|
||||||
|
|
||||||
|
### Registration:
|
||||||
|
|
||||||
|
[!code-csharp[Singleton Example](samples/singleton.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Types like the Discord client and Interaction/Command services are intended to be singleton,
|
||||||
|
> as they should last across the entire app and share their state with all references to the object.
|
||||||
|
|
||||||
|
## Scoped
|
||||||
|
|
||||||
|
A scoped service creates a new instance every time a new service is requested, but is kept across the 'scope'.
|
||||||
|
As long as the service is in view for the created scope, the same instance is used for all references to the type.
|
||||||
|
This means that you can reuse the same instance during execution, and keep the services' state for as long as the request is active.
|
||||||
|
|
||||||
|
### Registration:
|
||||||
|
|
||||||
|
[!code-csharp[Scoped Example](samples/scoped.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Without using HTTP or libraries like EFCORE, scopes are often unused in Discord bots.
|
||||||
|
> They are most commonly used for handling HTTP and database requests.
|
||||||
|
|
||||||
|
## Transient
|
||||||
|
|
||||||
|
A transient service is created every time it is requested, and does not share its state between references within the target service.
|
||||||
|
It is intended for lightweight types that require little state, to be disposed quickly after execution.
|
||||||
|
|
||||||
|
### Registration:
|
||||||
|
|
||||||
|
[!code-csharp[Transient Example](samples/transient.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Discord.Net modules behave exactly as transient types, and are intended to only last as long as the command execution takes.
|
||||||
|
> This is why it is suggested for apps to use singleton services to keep track of cross-execution data.
|
109
docs/guides/deployment/deployment.md
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
---
|
||||||
|
uid: Guides.Deployment
|
||||||
|
title: Deploying the Bot
|
||||||
|
---
|
||||||
|
|
||||||
|
# Deploying a Discord.Net Bot
|
||||||
|
|
||||||
|
After finishing your application, you may want to deploy your bot to a
|
||||||
|
remote location such as a Virtual Private Server (VPS) or another
|
||||||
|
computer so you can keep the bot up and running 24/7.
|
||||||
|
|
||||||
|
## Recommended VPS
|
||||||
|
|
||||||
|
For small-medium scaled bots, a cheap VPS (~$5) might be sufficient
|
||||||
|
enough. Here is a list of recommended VPS provider.
|
||||||
|
|
||||||
|
* [DigitalOcean](https://www.digitalocean.com/)
|
||||||
|
* Description: American cloud infrastructure provider headquartered
|
||||||
|
in New York City with data centers worldwide.
|
||||||
|
* Location(s):
|
||||||
|
* Asia: Singapore, India
|
||||||
|
* America: Canada, United States
|
||||||
|
* Europe: Netherlands, Germany, United Kingdom
|
||||||
|
* Based in: United States
|
||||||
|
* [Vultr](https://www.vultr.com/)
|
||||||
|
* Description: DigitalOcean-like
|
||||||
|
* Location(s):
|
||||||
|
* Asia: Japan, Australia, Singapore
|
||||||
|
* America: United States
|
||||||
|
* Europe: United Kingdom, France, Netherlands, Germany
|
||||||
|
* Based in: United States
|
||||||
|
* [OVH](https://www.ovh.com/)
|
||||||
|
* Description: French cloud computing company that offers VPS,
|
||||||
|
dedicated servers and other web services.
|
||||||
|
* Location(s):
|
||||||
|
* Asia: Australia, Singapore
|
||||||
|
* America: United States, Canada
|
||||||
|
* Europe: United Kingdom, Poland, Germany
|
||||||
|
* Based in: Europe
|
||||||
|
* [Scaleway](https://www.scaleway.com/)
|
||||||
|
* Description: Cheap but powerful VPS owned by [Online.net](https://online.net/).
|
||||||
|
* Location(s):
|
||||||
|
* Europe: France, Netherlands
|
||||||
|
* Based in: Europe
|
||||||
|
* [Time4VPS](https://www.time4vps.eu/)
|
||||||
|
* Description: Affordable and powerful VPS Hosting in Europe.
|
||||||
|
* Location(s):
|
||||||
|
* Europe: Lithuania
|
||||||
|
* Based in: Europe
|
||||||
|
* [ServerStarter.Host](https://serverstarter.host/clients/store/discord-bots)
|
||||||
|
* Description: Bot hosting with a panel for quick deployment and
|
||||||
|
no Linux knowledge required.
|
||||||
|
* Location(s):
|
||||||
|
* America: United States
|
||||||
|
* Based in: United States
|
||||||
|
|
||||||
|
## .NET Core Deployment
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> This section only covers the very basics of .NET Core deployment.
|
||||||
|
> To learn more about .NET Core deployment,
|
||||||
|
> visit [.NET Core application deployment] by Microsoft.
|
||||||
|
|
||||||
|
When redistributing the application - whether for deployment on a
|
||||||
|
remote machine or for sharing with another user - you may want to
|
||||||
|
publish the application; in other words, to create a
|
||||||
|
self-contained package without installing the dependencies
|
||||||
|
and the runtime on the target platform.
|
||||||
|
|
||||||
|
### Framework-dependent Deployment
|
||||||
|
|
||||||
|
To deploy a framework-dependent package (i.e. files to be used on a
|
||||||
|
remote machine with the `dotnet` command), simply publish
|
||||||
|
the package with:
|
||||||
|
|
||||||
|
* `dotnet publish -c Release`
|
||||||
|
|
||||||
|
This will create a package with the **least dependencies**
|
||||||
|
included with the application; however, the remote machine
|
||||||
|
must have `dotnet` runtime installed before the remote could run the
|
||||||
|
program.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Do not know how to run a .NET Core application with
|
||||||
|
> the `dotnet` runtime? Navigate to the folder of the program
|
||||||
|
> (typically under `$projFolder/bin/Release`) and
|
||||||
|
> enter `dotnet program.dll` where `program.dll` is your compiled
|
||||||
|
> binaries.
|
||||||
|
|
||||||
|
### Self-contained Deployment
|
||||||
|
|
||||||
|
To deploy a self-contained package (i.e. files to be used on a remote
|
||||||
|
machine without the `dotnet` runtime), publish with a specific
|
||||||
|
[Runtime ID] with the `-r` switch.
|
||||||
|
|
||||||
|
This will create a package with dependencies compiled for the target
|
||||||
|
platform, meaning that all the required dependencies will be included
|
||||||
|
with the program. This will result in **larger package size**;
|
||||||
|
however, that means the copy of the runtime that can be run
|
||||||
|
natively on the target platform.
|
||||||
|
|
||||||
|
For example, the following command will create a Windows
|
||||||
|
executable (`.exe`) that is ready to be executed on any
|
||||||
|
Windows 10 x64 based machine:
|
||||||
|
|
||||||
|
* `dotnet publish -c Release -r win10-x64`
|
||||||
|
|
||||||
|
[.NET Core application deployment]: https://docs.microsoft.com/en-us/dotnet/core/deploying/
|
||||||
|
[Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
|
102
docs/guides/emoji/emoji.md
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
---
|
||||||
|
uid: Guides.Emoji
|
||||||
|
title: Emoji
|
||||||
|
---
|
||||||
|
|
||||||
|
# Emoji in Discord.Net
|
||||||
|
|
||||||
|
Before we delve into the difference between an @Discord.Emoji and an
|
||||||
|
@Discord.Emote in Discord.Net, it is **crucial** to understand what
|
||||||
|
they both look like behind the scene. When the end-users are sending
|
||||||
|
or receiving an emoji or emote, they are typically in the form of
|
||||||
|
`:ok_hand:` or `:reeee:`; however, what goes under the hood is that,
|
||||||
|
depending on the type of emoji, they are sent in an entirely
|
||||||
|
different format.
|
||||||
|
|
||||||
|
What does this all mean? It means that you should know that by
|
||||||
|
reacting with a string like `“:ok_hand:”` will **NOT** automatically
|
||||||
|
translate to `👌`; rather, it will be treated as-is,
|
||||||
|
like `:ok_hand:`, thus the server will return a `400 Bad Request`.
|
||||||
|
|
||||||
|
## Emoji
|
||||||
|
|
||||||
|
An emoji is a standard emoji that can be found anywhere else outside
|
||||||
|
of Discord, which means strings like `👌`, `♥`, `👀` are all
|
||||||
|
considered an emoji in Discord. However, from the
|
||||||
|
introduction paragraph we have learned that we cannot
|
||||||
|
simply send `:ok_hand:` and have Discord take
|
||||||
|
care of it, but what do we need to send exactly?
|
||||||
|
|
||||||
|
To send an emoji correctly, one must send the emoji in its Unicode
|
||||||
|
form; this can be obtained in several different ways.
|
||||||
|
|
||||||
|
1. (Easiest) Escape the emoji by using the escape character, `\`, in
|
||||||
|
your Discord chat client; this will reveal the emoji’s pure Unicode
|
||||||
|
form, which will allow you to copy-paste into your code.
|
||||||
|
2. Look it up on Emojipedia, from which you can copy the emoji
|
||||||
|
easily into your code.
|
||||||
|
![Emojipedia](images/emojipedia.png)
|
||||||
|
3. (Recommended) Look it up in the Emoji list from [FileFormat.Info];
|
||||||
|
this will give you the .NET-compatible code that
|
||||||
|
represents the emoji.
|
||||||
|
* This is the most recommended method because some systems or
|
||||||
|
IDE sometimes do not render the Unicode emoji correctly.
|
||||||
|
![Fileformat Emoji Source Code](images/fileformat-emoji-src.png)
|
||||||
|
|
||||||
|
### Emoji Declaration
|
||||||
|
|
||||||
|
After obtaining the Unicode representation of the emoji, you may
|
||||||
|
create the @Discord.Emoji object by passing the string with unicode into its
|
||||||
|
constructor (e.g. `new Emoji("👌");` or `new Emoji("\uD83D\uDC4C");`).
|
||||||
|
|
||||||
|
Your method of declaring an @Discord.Emoji should look similar to
|
||||||
|
this:
|
||||||
|
[!code-csharp[Emoji Sample](samples/emoji-sample.cs)]
|
||||||
|
|
||||||
|
Also you can use `Emoji.Parse()` or `Emoji.TryParse()` methods
|
||||||
|
for parsing emojis from strings like `:heart:`, `<3` or `❤`.
|
||||||
|
|
||||||
|
[FileFormat.Info]: https://www.fileformat.info/info/emoji/list.htm
|
||||||
|
|
||||||
|
## Emote
|
||||||
|
|
||||||
|
The meat of the debate is here; what is an emote and how does it
|
||||||
|
differ from an emoji? An emote refers to a **custom emoji**
|
||||||
|
created on Discord.
|
||||||
|
|
||||||
|
The underlying structure of an emote also differs drastically; an
|
||||||
|
emote looks sort-of like a mention on Discord. It consists of two
|
||||||
|
main elements as illustrated below:
|
||||||
|
|
||||||
|
![Emote illustration](images/emote-format.png)
|
||||||
|
|
||||||
|
As you can see, emote uses a completely different format. To obtain
|
||||||
|
the raw string as shown above for your emote, you would need to
|
||||||
|
escape the emote using the escape character `\` in chat somewhere.
|
||||||
|
|
||||||
|
### Emote Declaration
|
||||||
|
|
||||||
|
After obtaining the raw emote string, you would need to use
|
||||||
|
@Discord.Emote.Parse* or @Discord.Emote.TryParse* to create a valid
|
||||||
|
emote object.
|
||||||
|
|
||||||
|
Your method of declaring an @Discord.Emote should look similar to
|
||||||
|
this:
|
||||||
|
|
||||||
|
[!code[Emote Sample](samples/emote-sample.cs)]
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> For WebSocket users, you may also consider fetching the Emote
|
||||||
|
> via the @Discord.WebSocket.SocketGuild.Emotes collection.
|
||||||
|
> [!code-csharp[Socket emote sample](samples/socket-emote-sample.cs)]
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> On Discord, any user with Discord Nitro subscription may use
|
||||||
|
> custom emotes from any guilds they are currently in. This is also
|
||||||
|
> true for _any_ standard bot accounts; this does not require
|
||||||
|
> the bot owner to have a Nitro subscription.
|
||||||
|
|
||||||
|
## Additional Information
|
||||||
|
|
||||||
|
To learn more about emote and emojis and how they could be used,
|
||||||
|
see the documentation of @Discord.IEmote.
|