@@ -24,6 +24,6 @@ Visit the repo's [release tag] to see the latest public pre-release. | |||||
The `DependencyMap` has been replaced with Microsoft's | The `DependencyMap` has been replaced with Microsoft's | ||||
[DependencyInjection] Abstractions. An example usage can be seen | [DependencyInjection] Abstractions. An example usage can be seen | ||||
[here](https://github.com/foxbot/DiscordBotBase/blob/csharp/src/DiscordBot/Program.cs#L36). | |||||
[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 | [DependencyInjection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection |
@@ -16,7 +16,5 @@ | |||||
topicUid: FAQ.Commands.Interactions | topicUid: FAQ.Commands.Interactions | ||||
- name: Dependency Injection | - name: Dependency Injection | ||||
topicUid: FAQ.Commands.DI | topicUid: FAQ.Commands.DI | ||||
- name: Glossary | |||||
topicUid: FAQ.Glossary | |||||
- name: Legacy or Upgrade | - name: Legacy or Upgrade | ||||
topicUid: FAQ.Legacy | topicUid: FAQ.Legacy |
@@ -0,0 +1,68 @@ | |||||
--- | |||||
uid: Guides.Entities.Casting | |||||
title: Casting & Unboxing | |||||
--- | |||||
# Casting | |||||
Casting can be done in many ways, and is the only method to box and unbox types to/from their base definition. | |||||
Casting only works for types that inherit the base type that you want to unbox from. | |||||
`IUser` cannot be cast to `IMessage`. | |||||
> [!NOTE] | |||||
> Interfaces CAN be cast to other interfaces, as long as they inherit eachother. | |||||
> The same goes for reverse casting. As long as some entity can be simplified into what it inherits, your cast will pass. | |||||
## Boxing | |||||
A boxed object is the definition of an object that was simplified (or trimmed) by incoming traffic, | |||||
but still owns the data of the originally constructed type. Boxing is an implicit operation. | |||||
Through casting, we can **unbox** this type, and access the properties that were unaccessible before. | |||||
## Unboxing | |||||
Unboxing is the most direct way to access the real definition of an object. | |||||
If we want to return a type from its interface, we can unbox it directly. | |||||
[!code-csharp[Unboxing](images/unboxing.cs)] | |||||
## Regular casting | |||||
In 'regular' casting, we use the ` as ` keyword to assign the given type to the object. | |||||
If the boxed type can indeed be cast into given type, | |||||
it will become said type, and its properties can be accessed. | |||||
[!code-csharp[Casting](images/casting.cs)] | |||||
> [!WARNING] | |||||
> If the type you're casting to is null, a ` NullReferenceException ` will be thrown when its called. | |||||
> This makes safety casting much more interesting to use, as it prevents this exception from being thrown. | |||||
## Safety casting | |||||
Safety casting makes sure that the type you're trying to cast to can never be null, as it passes checks upon calling them. | |||||
There are 3 different ways to safety cast an object: | |||||
### Basic safety casting: | |||||
To safety cast an object, all we need to do is check if it is of the member type in a statement. | |||||
If this check fails, it will continue below, making sure we don't try to access null. | |||||
[!code-csharp[Base](images/safety-cast.cs)] | |||||
### Object declaration: | |||||
Here we declare the object we are casting to, | |||||
making it so that you can immediately work with its properties without reassigning through regular casting. | |||||
[!code-csharp[Declare](images/safety-cast-var.cs)] | |||||
### Reverse passage: | |||||
In previous examples, we want to let code continue running after the check, or if the check fails. | |||||
In this example, the cast will return the entire method (ignoring the latter) upon failure, | |||||
and declare the variable for further use into the method: | |||||
[!code-csharp[Pass](images/safety-cast-pass.cs)] | |||||
> [!NOTE] | |||||
> Usage of ` is `, ` not ` and ` as ` is required in cast assignment and/or type checks. ==, != and = are invalid assignment, | |||||
> as these operators only apply to initialized objects and not their types. |
@@ -1,29 +1,19 @@ | |||||
--- | --- | ||||
uid: FAQ.Glossary | |||||
title: Common Terminologies / Glossary | |||||
uid: Guides.Entities.Glossary | |||||
title: Glossary & Flowcharts | |||||
--- | --- | ||||
# Glossary | |||||
# Entity Types | |||||
This is an additional chapter for quick references to various common | |||||
types that you may see within Discord.Net. To see more information | |||||
regarding each type of object, click on the object to navigate | |||||
to our API documentation page where you might find more explanation | |||||
about it. | |||||
A list of all Discord.Net entities, what they can be cast to and what their properties are. | |||||
## Common Types | |||||
> [!IMPORTANT] | |||||
> All interfaces have the same inheritance tree for both `Socket` and `Rest` entities. | |||||
> Entities with that have been marked red are exclusive to the project they source from. | |||||
* A **Guild** ([IGuild]) is an isolated collection of users and | |||||
channels, and are often referred to as "servers". | |||||
- Example: [Discord API](https://discord.gg/jkrBmQR) | |||||
* A **Channel** ([IChannel]) represents a generic channel. | |||||
- Example: #dotnet_discord-net | |||||
- See [Channel Types](#channel-types) | |||||
## Channels | |||||
[IGuild]: xref:Discord.IGuild | |||||
[IChannel]: xref:Discord.IChannel | |||||
## Channel Types | |||||
 | |||||
### Message Channels | ### Message Channels | ||||
* A **Text Channel** ([ITextChannel]) is a message channel from a Guild. | * A **Text Channel** ([ITextChannel]) is a message channel from a Guild. | ||||
@@ -45,9 +35,6 @@ holds one or more sub-channels. | |||||
* A **Nested Channel** ([INestedChannel]) (2.0+) is a channel that can | * A **Nested Channel** ([INestedChannel]) (2.0+) is a channel that can | ||||
exist under a category. | exist under a category. | ||||
> [!NOTE] | |||||
> A Channel ([IChannel]) can be all types of channels. | |||||
[INestedChannel]: xref:Discord.INestedChannel | [INestedChannel]: xref:Discord.INestedChannel | ||||
[IGuildChannel]: xref:Discord.IGuildChannel | [IGuildChannel]: xref:Discord.IGuildChannel | ||||
[IMessageChannel]: xref:Discord.IMessageChannel | [IMessageChannel]: xref:Discord.IMessageChannel | ||||
@@ -62,17 +49,27 @@ exist under a category. | |||||
[IStageChannel]: xref:Discord.IStageChannel | [IStageChannel]: xref:Discord.IStageChannel | ||||
[INewsChannel]: xref:Discord.INewsChannel | [INewsChannel]: xref:Discord.INewsChannel | ||||
## Message Types | |||||
## Messages | |||||
 | |||||
* A **Rest Followup Message ([RestFollowupMessage]) is a message returned by followup on on an interaction. | |||||
* A **Rest Interaction Message ([RestInteractionMessage]) is a message returned by the interactions' original response. | |||||
* A **Rest User Message** ([RestUserMessage]) is a message sent over rest, can be any of the above. | |||||
* An **User Message** ([IUserMessage]) is a message sent by a user. | * An **User Message** ([IUserMessage]) is a message sent by a user. | ||||
* A **System Message** ([ISystemMessage]) is a message sent by Discord itself. | * A **System Message** ([ISystemMessage]) is a message sent by Discord itself. | ||||
* A **Message** ([IMessage]) can be any of the above. | * A **Message** ([IMessage]) can be any of the above. | ||||
[RestFollowupMessage]: xref:Discord.Rest.RestFollowupMessage | |||||
[RestInteractionMessage]: xref:Discord.Rest.RestInteractionMessage | |||||
[RestUserMEssage]: xref:Discord.Rest.RestUserMessage | |||||
[IUserMessage]: xref:Discord.IUserMessage | [IUserMessage]: xref:Discord.IUserMessage | ||||
[ISystemMessage]: xref:Discord.ISystemMessage | [ISystemMessage]: xref:Discord.ISystemMessage | ||||
[IMessage]: xref:Discord.IMessage | [IMessage]: xref:Discord.IMessage | ||||
## User Types | |||||
## Users | |||||
 | |||||
* A **Guild User** ([IGuildUser]) is a user available inside a guild. | * A **Guild User** ([IGuildUser]) is a user available inside a guild. | ||||
* A **Group User** ([IGroupUser]) is a user available inside a group. | * A **Group User** ([IGroupUser]) is a user available inside a group. | ||||
@@ -85,7 +82,13 @@ exist under a category. | |||||
[ISelfUser]: xref:Discord.ISelfUser | [ISelfUser]: xref:Discord.ISelfUser | ||||
[IUser]: xref:Discord.IUser | [IUser]: xref:Discord.IUser | ||||
## Emoji Types | |||||
## Interactions | |||||
 | |||||
## Other types: | |||||
### Emoji | |||||
* An **Emote** ([Emote]) is a custom emote from a guild. | * An **Emote** ([Emote]) is a custom emote from a guild. | ||||
- Example: `<:dotnet:232902710280716288>` | - Example: `<:dotnet:232902710280716288>` | ||||
@@ -96,7 +99,7 @@ exist under a category. | |||||
[Emoji]: xref:Discord.Emoji | [Emoji]: xref:Discord.Emoji | ||||
## Sticker Types | |||||
### Stickers | |||||
* A **Sticker** ([ISticker]) is a standard Discord sticker. | * A **Sticker** ([ISticker]) is a standard Discord sticker. | ||||
* A **Custom Sticker ([ICustomSticker]) is a Guild-unique sticker. | * A **Custom Sticker ([ICustomSticker]) is a Guild-unique sticker. | ||||
@@ -104,7 +107,7 @@ exist under a category. | |||||
[ISticker]: xref:Discord.ISticker | [ISticker]: xref:Discord.ISticker | ||||
[ICustomSticker]: xref:Discord.ICustomSticker | [ICustomSticker]: xref:Discord.ICustomSticker | ||||
## Activity Types | |||||
### Activity | |||||
* A **Game** ([Game]) refers to a user's game activity. | * A **Game** ([Game]) refers to a user's game activity. | ||||
* A **Rich Presence** ([RichGame]) refers to a user's detailed | * A **Rich Presence** ([RichGame]) refers to a user's detailed |
@@ -1,26 +1,41 @@ | |||||
--- | --- | ||||
uid: Guides.Concepts.Entities | |||||
title: Entities | |||||
uid: Guides.Entities.Intro | |||||
title: Introduction | |||||
--- | --- | ||||
# Entities in Discord.Net | # Entities in Discord.Net | ||||
> [!NOTE] | |||||
> This article is written with the Socket variants of entities in mind, | |||||
> not the general interfaces or Rest entities. | |||||
Discord.Net provides a versatile entity system for navigating the | Discord.Net provides a versatile entity system for navigating the | ||||
Discord API. | Discord API. | ||||
> [!TIP] | |||||
> It is **vital** that you use the proper IDs for an entity when using | |||||
> a `GetXXX` method. It is recommended that you enable Discord's | |||||
> _developer mode_ to allow easy access to entity IDs, found in | |||||
> Settings > Appearance > Advanced. Read more about it in the | |||||
> [FAQ](xref:FAQ.Basics.GetStarted) page. | |||||
## Inheritance | ## Inheritance | ||||
Due to the nature of the Discord API, some entities are designed with | Due to the nature of the Discord API, some entities are designed with | ||||
multiple variants; for example, `SocketUser` and `SocketGuildUser`. | |||||
multiple variants; for example, `IUser` and `IGuildUser`. | |||||
All models will contain the most detailed version of an entity | All models will contain the most detailed version of an entity | ||||
possible, even if the type is less detailed. | possible, even if the type is less detailed. | ||||
For example, in the case of the `MessageReceived` event, a | |||||
## Socket & REST | |||||
REST entities are retrieved over REST, and will be disposed after use. | |||||
It is suggested to limit the amount of REST calls as much as possible, | |||||
as calls over REST interact with the API, and are thus prone to rate-limits. | |||||
- [Learn more about REST](https://restfulapi.net/) | |||||
Socket entities are created through the gateway, | |||||
most commonly through `DiscordSocketClient` events. | |||||
These entities will enter the clients' global cache for later use. | |||||
In the case of the `MessageReceived` event, a | |||||
`SocketMessage` is passed in with a channel property of type | `SocketMessage` is passed in with a channel property of type | ||||
`SocketMessageChannel`. All messages come from channels capable of | `SocketMessageChannel`. All messages come from channels capable of | ||||
messaging, so this is the only variant of a channel that can cover | messaging, so this is the only variant of a channel that can cover | ||||
@@ -31,7 +46,9 @@ But that doesn't mean a message _can't_ come from a | |||||
retrieve information about a guild from a message entity, you will | retrieve information about a guild from a message entity, you will | ||||
need to cast its channel object to a `SocketTextChannel`. | need to cast its channel object to a `SocketTextChannel`. | ||||
You can find out various types of entities in the [Glossary page.](xref:FAQ.Glossary) | |||||
> [!NOTE] | |||||
> You can find out the inheritance tree & definitions of various entities | |||||
> [here](xref:Guides.Entities.Glossary) | |||||
## Navigation | ## Navigation | ||||
@@ -40,26 +57,31 @@ you to easily navigate to an entity's parent or children. As explained | |||||
above, you will sometimes need to cast to a more detailed version of | above, you will sometimes need to cast to a more detailed version of | ||||
an entity to navigate to its parent. | an entity to navigate to its parent. | ||||
## Accessing Entities | |||||
## Accessing Socket Entities | |||||
The most basic forms of entities, `SocketGuild`, `SocketUser`, and | The most basic forms of entities, `SocketGuild`, `SocketUser`, and | ||||
`SocketChannel` can be pulled from the DiscordSocketClient's global | `SocketChannel` can be pulled from the DiscordSocketClient's global | ||||
cache, and can be retrieved using the respective `GetXXX` method on | cache, and can be retrieved using the respective `GetXXX` method on | ||||
DiscordSocketClient. | DiscordSocketClient. | ||||
> [!TIP] | |||||
> It is **vital** that you use the proper IDs for an entity when using | |||||
> a `GetXXX` method. It is recommended that you enable Discord's | |||||
> _developer mode_ to allow easy access to entity IDs, found in | |||||
> Settings > Appearance > Advanced. Read more about it in the | |||||
> [FAQ](xref:FAQ.Basics.GetStarted) page. | |||||
More detailed versions of entities can be pulled from the basic | More detailed versions of entities can be pulled from the basic | ||||
entities, e.g., `SocketGuild.GetUser`, which returns a | entities, e.g., `SocketGuild.GetUser`, which returns a | ||||
`SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a | `SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a | ||||
`SocketGuildChannel`. Again, you may need to cast these objects to get | `SocketGuildChannel`. Again, you may need to cast these objects to get | ||||
a variant of the type that you need. | a variant of the type that you need. | ||||
## Sample | |||||
### Sample | |||||
[!code-csharp[Socket Sample](samples/socketentities.cs)] | |||||
## Accessing REST Entities | |||||
REST entities work almost the same as Socket entities, but are much less frequently used. | |||||
To access REST entities, the `DiscordSocketClient`'s `Rest` property is required. | |||||
Another option here is to create your own [DiscordRestClient], independent of the Socket gateway. | |||||
[DiscordRestClient]: xref:Discord.Rest.DiscordRestClient | |||||
### Sample | |||||
[!code-csharp[Entity Sample](samples/entities.cs)] | |||||
[!code-csharp[Rest Sample](samples/restentities.cs)] |
@@ -0,0 +1,7 @@ | |||||
// Say we have an entity, for the simplicity of this example, it will appear from thin air. | |||||
IChannel channel; | |||||
// If I want this to be an ITextChannel so I can access the properties of a text channel inside of a guild, an approach would be: | |||||
ITextChannel textChannel = channel as ITextChannel; | |||||
await textChannel.DoSomethingICantWithIChannelAsync(); |
@@ -0,0 +1,8 @@ | |||||
// RestUser entities expose the accentcolor and banner of a user. | |||||
// This being one of the few use-cases for requesting a RestUser instead of depending on the Socket counterpart. | |||||
public static EmbedBuilder WithUserColor(this EmbedBuilder builder, IUser user) | |||||
{ | |||||
var restUser = await _client.Rest.GetUserAsync(user.Id); | |||||
return builder.WithColor(restUser.AccentColor ?? Color.Blue); | |||||
// The accent color can still be null, so a check for this needs to be done to prevent an exception to be thrown. | |||||
} |
@@ -0,0 +1,10 @@ | |||||
private void MyFunction(IMessage message) | |||||
{ | |||||
// Here we do the reverse as in the previous examples, and let it continue the code below if it IS an IUserMessage | |||||
if (message is not IUserMessage userMessage) | |||||
return; | |||||
// Because we do the above check inline (dont give the statement a body), | |||||
// the code will still declare `userMessage` as available outside of the above statement. | |||||
Console.WriteLine(userMessage.Author); | |||||
} |
@@ -0,0 +1,9 @@ | |||||
IUser user; | |||||
// Here we can pre-define the actual declaration of said IGuildUser object, | |||||
// so we dont need to cast additionally inside of the statement. | |||||
if (user is IGuildUser guildUser) | |||||
{ | |||||
Console.WriteLine(guildUser.JoinedAt); | |||||
} | |||||
// Check failed. |
@@ -0,0 +1,14 @@ | |||||
IUser user; | |||||
// Here we check if the user is an IGuildUser, if not, let it pass. This ensures its not null. | |||||
if (user is IGuildUser) | |||||
{ | |||||
Console.WriteLine("This user is in a guild!"); | |||||
} | |||||
// Check failed. | |||||
---------------------------- | |||||
// Another situation, where we want to get the actual data of said IGuildUser. | |||||
---------------------------- | |||||
// A final situation, where we dont actually need to do anything code-wise when the check does not pass, so we want to simplify it. |
@@ -0,0 +1,9 @@ | |||||
IUser user; | |||||
// Here we use inline unboxing to make a call to its member (if available) only once. | |||||
// Note that if the entity we're trying to cast to is null, this will throw a NullReferenceException. | |||||
Console.WriteLine(((IGuildUser)user).Nickname); | |||||
// In case you are certain the entity IS said member, you can also use unboxing to declare variables. | |||||
IGuildUser guildUser = (IGuildUser)user; |
@@ -23,6 +23,14 @@ | |||||
topicUid: Guides.Concepts.ManageConnections | topicUid: Guides.Concepts.ManageConnections | ||||
- name: Entities | - name: Entities | ||||
topicUid: Guides.Concepts.Entities | topicUid: Guides.Concepts.Entities | ||||
- name: Entities | |||||
items: | |||||
- name: Introduction | |||||
topicUid: Guides.Entities.Intro | |||||
- name: Casting | |||||
topicUid: Guides.Entities.Casting | |||||
- name: Glossary & Flowcharts | |||||
topicUid: Guides.Entities.Glossary | |||||
- name: Working with Text-based Commands | - name: Working with Text-based Commands | ||||
items: | items: | ||||
- name: Introduction | - name: Introduction | ||||
@@ -1,77 +0,0 @@ | |||||
using System; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using Discord; | |||||
using Discord.WebSocket; | |||||
namespace _01_basic_ping_bot | |||||
{ | |||||
// This is a minimal, bare-bones example of using Discord.Net | |||||
// | |||||
// If writing a bot with commands, we recommend using the Discord.Net.Commands | |||||
// framework, rather than handling commands yourself, like we do in this sample. | |||||
// | |||||
// You can find samples of using the command framework: | |||||
// - Here, under the 02_commands_framework sample | |||||
// - https://github.com/foxbot/DiscordBotBase - a bare-bones bot template | |||||
// - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library | |||||
class Program | |||||
{ | |||||
private readonly DiscordSocketClient _client; | |||||
// Discord.Net heavily utilizes TAP for async, so we create | |||||
// an asynchronous context from the beginning. | |||||
static void Main(string[] args) | |||||
{ | |||||
new Program().MainAsync().GetAwaiter().GetResult(); | |||||
} | |||||
public Program() | |||||
{ | |||||
// It is recommended to Dispose of a client when you are finished | |||||
// using it, at the end of your app's lifetime. | |||||
_client = new DiscordSocketClient(); | |||||
_client.Log += LogAsync; | |||||
_client.Ready += ReadyAsync; | |||||
_client.MessageReceived += MessageReceivedAsync; | |||||
} | |||||
public async Task MainAsync() | |||||
{ | |||||
// Tokens should be considered secret data, and never hard-coded. | |||||
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); | |||||
await _client.StartAsync(); | |||||
// Block the program until it is closed. | |||||
await Task.Delay(Timeout.Infinite); | |||||
} | |||||
private Task LogAsync(LogMessage log) | |||||
{ | |||||
Console.WriteLine(log.ToString()); | |||||
return Task.CompletedTask; | |||||
} | |||||
// The Ready event indicates that the client has opened a | |||||
// connection and it is now safe to access the cache. | |||||
private Task ReadyAsync() | |||||
{ | |||||
Console.WriteLine($"{_client.CurrentUser} is connected!"); | |||||
return Task.CompletedTask; | |||||
} | |||||
// This is not the recommended way to write a bot - consider | |||||
// reading over the Commands Framework sample. | |||||
private async Task MessageReceivedAsync(SocketMessage message) | |||||
{ | |||||
// The bot should never respond to itself. | |||||
if (message.Author.Id == _client.CurrentUser.Id) | |||||
return; | |||||
if (message.Content == "!ping") | |||||
await message.Channel.SendMessageAsync("pong!"); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,112 @@ | |||||
using System; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using Discord; | |||||
using Discord.WebSocket; | |||||
namespace BasicBot | |||||
{ | |||||
// This is a minimal, bare-bones example of using Discord.Net | |||||
// | |||||
// If writing a bot with commands/interactions, we recommend using the Discord.Net.Commands/Discord.Net.Interactions | |||||
// framework, rather than handling them yourself, like we do in this sample. | |||||
// | |||||
// You can find samples of using the command framework: | |||||
// - Here, under the TextCommandFramework sample | |||||
// - At the guides: https://discordnet.dev/guides/text_commands/intro.html | |||||
// | |||||
// You can find samples of using the interaction framework: | |||||
// - Here, under the InteractionFramework sample | |||||
// - At the guides: https://discordnet.dev/guides/int_framework/intro.html | |||||
class Program | |||||
{ | |||||
// Non-static readonly fields can only be assigned in a constructor. | |||||
// If you want to assign it elsewhere, consider removing the readonly keyword. | |||||
private readonly DiscordSocketClient _client; | |||||
// Discord.Net heavily utilizes TAP for async, so we create | |||||
// an asynchronous context from the beginning. | |||||
static void Main(string[] args) | |||||
=> new Program() | |||||
.MainAsync() | |||||
.GetAwaiter() | |||||
.GetResult(); | |||||
public Program() | |||||
{ | |||||
// It is recommended to Dispose of a client when you are finished | |||||
// using it, at the end of your app's lifetime. | |||||
_client = new DiscordSocketClient(); | |||||
// Subscribing to client events, so that we may receive them whenever they're invoked. | |||||
_client.Log += LogAsync; | |||||
_client.Ready += ReadyAsync; | |||||
_client.MessageReceived += MessageReceivedAsync; | |||||
_client.InteractionCreated += InteractionCreatedAsync; | |||||
} | |||||
public async Task MainAsync() | |||||
{ | |||||
// Tokens should be considered secret data, and never hard-coded. | |||||
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); | |||||
// Different approaches to making your token a secret is by putting them in local .json, .yaml, .xml or .txt files, then reading them on startup. | |||||
await _client.StartAsync(); | |||||
// Block the program until it is closed. | |||||
await Task.Delay(Timeout.Infinite); | |||||
} | |||||
private Task LogAsync(LogMessage log) | |||||
{ | |||||
Console.WriteLine(log.ToString()); | |||||
return Task.CompletedTask; | |||||
} | |||||
// The Ready event indicates that the client has opened a | |||||
// connection and it is now safe to access the cache. | |||||
private Task ReadyAsync() | |||||
{ | |||||
Console.WriteLine($"{_client.CurrentUser} is connected!"); | |||||
return Task.CompletedTask; | |||||
} | |||||
// This is not the recommended way to write a bot - consider | |||||
// reading over the Commands Framework sample. | |||||
private async Task MessageReceivedAsync(SocketMessage message) | |||||
{ | |||||
// The bot should never respond to itself. | |||||
if (message.Author.Id == _client.CurrentUser.Id) | |||||
return; | |||||
if (message.Content == "!ping") | |||||
{ | |||||
// Create a new componentbuilder, in which dropdowns & buttons can be created. | |||||
var cb = new ComponentBuilder() | |||||
.WithButton("Click me!", "unique-id", ButtonStyle.Primary); | |||||
// Send a message with content 'pong', including a button. | |||||
// This button needs to be build by calling .Build() before being passed into the call. | |||||
await message.Channel.SendMessageAsync("pong!", components: cb.Build()); | |||||
} | |||||
} | |||||
// For better functionality & a more developer-friendly approach to handling any kind of interaction, refer to: | |||||
// https://discordnet.dev/guides/int_framework/intro.html | |||||
private async Task InteractionCreatedAsync(SocketInteraction interaction) | |||||
{ | |||||
// safety-casting is the best way to prevent something being cast from being null. | |||||
// If this check does not pass, it could not be cast to said type. | |||||
if (interaction is SocketMessageComponent component) | |||||
{ | |||||
// Check for the ID created in the button mentioned above. | |||||
if (component.Data.CustomId == "unique-id") | |||||
await interaction.RespondAsync("Thank you for clicking my button!"); | |||||
else Console.WriteLine("An ID has been received that has no handler!"); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,37 @@ | |||||
using Discord; | |||||
using Discord.Interactions; | |||||
using Discord.WebSocket; | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
namespace InteractionFramework.Attributes | |||||
{ | |||||
internal class DoUserCheck : PreconditionAttribute | |||||
{ | |||||
public override Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context, ICommandInfo commandInfo, IServiceProvider services) | |||||
{ | |||||
// Check if the component matches the target properly. | |||||
if (context.Interaction is not SocketMessageComponent componentContext) | |||||
return Task.FromResult(PreconditionResult.FromError("Context unrecognized as component context.")); | |||||
else | |||||
{ | |||||
// The approach here entirely depends on how you construct your custom ID. In this case, the format is: | |||||
// unique-name:*,* | |||||
// here the name and wildcards are split by ':' | |||||
var param = componentContext.Data.CustomId.Split(':'); | |||||
// here we determine that we should always check for the first ',' present. | |||||
// This will deal with additional wildcards by always selecting the first wildcard present. | |||||
if (param.Length > 1 && ulong.TryParse(param[1].Split(',')[0], out ulong id)) | |||||
return (context.User.Id == id) | |||||
// If the user ID | |||||
? Task.FromResult(PreconditionResult.FromSuccess()) | |||||
: Task.FromResult(PreconditionResult.FromError("User ID does not match component ID!")); | |||||
else return Task.FromResult(PreconditionResult.FromError("Parse cannot be done if no userID exists.")); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -6,7 +6,7 @@ using System.Linq; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace _04_interactions_framework | |||||
namespace InteractionFramework.Attributes | |||||
{ | { | ||||
public class RequireOwnerAttribute : PreconditionAttribute | public class RequireOwnerAttribute : PreconditionAttribute | ||||
{ | { |
@@ -8,7 +8,7 @@ using System.Reflection; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace _04_interactions_framework | |||||
namespace InteractionFramework | |||||
{ | { | ||||
public class CommandHandler | public class CommandHandler | ||||
{ | { | ||||
@@ -27,6 +27,9 @@ namespace _04_interactions_framework | |||||
{ | { | ||||
// Add the public modules that inherit InteractionModuleBase<T> to the InteractionService | // Add the public modules that inherit InteractionModuleBase<T> to the InteractionService | ||||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); | await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); | ||||
// Another approach to get the assembly of a specific type is: | |||||
// typeof(CommandHandler).Assembly | |||||
// Process the InteractionCreated payloads to execute Interactions commands | // Process the InteractionCreated payloads to execute Interactions commands | ||||
_client.InteractionCreated += HandleInteraction; | _client.InteractionCreated += HandleInteraction; | ||||
@@ -37,6 +40,8 @@ namespace _04_interactions_framework | |||||
_commands.ComponentCommandExecuted += ComponentCommandExecuted; | _commands.ComponentCommandExecuted += ComponentCommandExecuted; | ||||
} | } | ||||
# region Error Handling | |||||
private Task ComponentCommandExecuted (ComponentCommandInfo arg1, Discord.IInteractionContext arg2, IResult arg3) | private Task ComponentCommandExecuted (ComponentCommandInfo arg1, Discord.IInteractionContext arg2, IResult arg3) | ||||
{ | { | ||||
if (!arg3.IsSuccess) | if (!arg3.IsSuccess) | ||||
@@ -123,6 +128,9 @@ namespace _04_interactions_framework | |||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
# endregion | |||||
# region Execution | |||||
private async Task HandleInteraction (SocketInteraction arg) | private async Task HandleInteraction (SocketInteraction arg) | ||||
{ | { | ||||
@@ -142,5 +150,6 @@ namespace _04_interactions_framework | |||||
await arg.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync()); | await arg.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync()); | ||||
} | } | ||||
} | } | ||||
# endregion | |||||
} | } | ||||
} | } |
@@ -4,7 +4,7 @@ using System.Linq; | |||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace _04_interactions_framework | |||||
namespace InteractionFramework | |||||
{ | { | ||||
public enum ExampleEnum | public enum ExampleEnum | ||||
{ | { |
@@ -0,0 +1,19 @@ | |||||
using Discord; | |||||
using Discord.Interactions; | |||||
using Discord.WebSocket; | |||||
using InteractionFramework.Attributes; | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
namespace InteractionFramework | |||||
{ | |||||
// As with all other modules, we create the context by defining what type of interaction this module is supposed to target. | |||||
internal class ComponentModule : InteractionModuleBase<SocketInteractionContext<SocketMessageComponent>> | |||||
{ | |||||
// With the Attribute DoUserCheck you can make sure that only the user this button targets can click it. This is defined by the first wildcard: *. | |||||
// See Attributes/DoUserCheckAttribute.cs for elaboration. | |||||
[DoUserCheck] | |||||
[ComponentInteraction("myButton:*")] | |||||
public async Task ClickButtonAsync(string userId) | |||||
=> await RespondAsync(text: ":thumbsup: Clicked!"); | |||||
} |
@@ -1,16 +1,17 @@ | |||||
using Discord; | using Discord; | ||||
using Discord.Interactions; | using Discord.Interactions; | ||||
using Discord.WebSocket; | using Discord.WebSocket; | ||||
using InteractionFramework.Attributes; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace _04_interactions_framework.Modules | |||||
namespace InteractionFramework.Modules | |||||
{ | { | ||||
// Interation modules must be public and inherit from an IInterationModuleBase | // Interation modules must be public and inherit from an IInterationModuleBase | ||||
public class UtilityModule : InteractionModuleBase<SocketInteractionContext> | |||||
public class GeneralModule : InteractionModuleBase<SocketInteractionContext> | |||||
{ | { | ||||
// Dependencies can be accessed through Property injection, public properties with public setters will be set by the service provider | // Dependencies can be accessed through Property injection, public properties with public setters will be set by the service provider | ||||
public InteractionService Commands { get; set; } | public InteractionService Commands { get; set; } | ||||
@@ -18,7 +19,7 @@ namespace _04_interactions_framework.Modules | |||||
private CommandHandler _handler; | private CommandHandler _handler; | ||||
// Constructor injection is also a valid way to access the dependecies | // Constructor injection is also a valid way to access the dependecies | ||||
public UtilityModule ( CommandHandler handler ) | |||||
public GeneralModule(CommandHandler handler) | |||||
{ | { | ||||
_handler = handler; | _handler = handler; | ||||
} | } |
@@ -0,0 +1,30 @@ | |||||
using Discord; | |||||
using Discord.Interactions; | |||||
using Discord.WebSocket; | |||||
using System.Threading.Tasks; | |||||
namespace InteractionFramework.Modules | |||||
{ | |||||
// A transient module for executing commands. This module will NOT keep any information after the command is executed. | |||||
internal class MessageCommandModule : InteractionModuleBase<SocketInteractionContext<SocketMessageCommand>> | |||||
{ | |||||
// Pins a message in the channel it is in. | |||||
[MessageCommand("pin")] | |||||
public async Task PinMessageAsync(IMessage message) | |||||
{ | |||||
// make a safety cast to check if the message is ISystem- or IUserMessage | |||||
if (message is not IUserMessage userMessage) | |||||
await RespondAsync(text: ":x: You cant pin system messages!"); | |||||
// if the pins in this channel are equal to or above 50, no more messages can be pinned. | |||||
else if ((await Context.Channel.GetPinnedMessagesAsync()).Count >= 50) | |||||
await RespondAsync(text: ":x: You cant pin any more messages, the max has already been reached in this channel!"); | |||||
else | |||||
{ | |||||
await userMessage.PinAsync(); | |||||
await RespondAsync(":white_check_mark: Successfully pinned message!"); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,51 @@ | |||||
using Discord; | |||||
using Discord.Interactions; | |||||
using Discord.WebSocket; | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
namespace InteractionFramework.Modules | |||||
{ | |||||
public enum Hobby | |||||
{ | |||||
Gaming, | |||||
Art, | |||||
Reading | |||||
} | |||||
// A transient module for executing commands. This module will NOT keep any information after the command is executed. | |||||
class SlashCommandModule : InteractionModuleBase<SocketInteractionContext<SocketSlashCommand>> | |||||
{ | |||||
// Will be called before execution. Here you can populate several entities you may want to retrieve before executing a command. | |||||
// I.E. database objects | |||||
public override void BeforeExecute(ICommandInfo command) | |||||
{ | |||||
// Anything | |||||
throw new NotImplementedException(); | |||||
} | |||||
// Will be called after execution | |||||
public override void AfterExecute(ICommandInfo command) | |||||
{ | |||||
// Anything | |||||
throw new NotImplementedException(); | |||||
} | |||||
[SlashCommand("ping", "Pings the bot and returns its latency.")] | |||||
public async Task GreetUserAsync() | |||||
=> await RespondAsync(text: $":ping_pong: It took me {Context.Client.Latency}ms to respond to you!", ephemeral: true); | |||||
[SlashCommand("hobby", "Choose your hobby from the list!")] | |||||
public async Task ChooseAsync(Hobby hobby) | |||||
=> await RespondAsync(text: $":thumbsup: Your hobby is: {hobby}."); | |||||
[SlashCommand("bitrate", "Gets the bitrate of a specific voice channel.")] | |||||
public async Task GetBitrateAsync([ChannelTypes(ChannelType.Voice, ChannelType.Stage)] IChannel channel) | |||||
{ | |||||
var voiceChannel = channel as IVoiceChannel; | |||||
await RespondAsync(text: $"This voice channel has a bitrate of {voiceChannel.Bitrate}"); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
using Discord; | |||||
using Discord.Interactions; | |||||
using Discord.WebSocket; | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
namespace InteractionFramework.Modules | |||||
{ | |||||
// A transient module for executing commands. This module will NOT keep any information after the command is executed. | |||||
class UserCommandModule : InteractionModuleBase<SocketInteractionContext<SocketUserCommand>> | |||||
{ | |||||
// This command will greet target user in the channel this was executed in. | |||||
[UserCommand("greet")] | |||||
public async Task GreetUserAsync(IUser user) | |||||
=> await RespondAsync(text: $":wave: {Context.User} said hi to you, <@{user.Id}>!"); | |||||
} | |||||
} | |||||
@@ -8,10 +8,11 @@ using System.Reflection; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace _04_interactions_framework | |||||
namespace InteractionFramework | |||||
{ | { | ||||
class Program | class Program | ||||
{ | { | ||||
// Entry point of the program. | |||||
static void Main ( string[] args ) | static void Main ( string[] args ) | ||||
{ | { | ||||
// One of the more flexable ways to access the configuration data is to use the Microsoft's Configuration model, | // One of the more flexable ways to access the configuration data is to use the Microsoft's Configuration model, | ||||
@@ -24,7 +25,7 @@ namespace _04_interactions_framework | |||||
RunAsync(config).GetAwaiter().GetResult(); | RunAsync(config).GetAwaiter().GetResult(); | ||||
} | } | ||||
static async Task RunAsync (IConfiguration configuration ) | |||||
static async Task RunAsync (IConfiguration configuration) | |||||
{ | { | ||||
// Dependency injection is a key part of the Interactions framework but it needs to be disposed at the end of the app's lifetime. | // Dependency injection is a key part of the Interactions framework but it needs to be disposed at the end of the app's lifetime. | ||||
using var services = ConfigureServices(configuration); | using var services = ConfigureServices(configuration); | ||||
@@ -64,14 +65,12 @@ namespace _04_interactions_framework | |||||
} | } | ||||
static ServiceProvider ConfigureServices ( IConfiguration configuration ) | static ServiceProvider ConfigureServices ( IConfiguration configuration ) | ||||
{ | |||||
return new ServiceCollection() | |||||
=> new ServiceCollection() | |||||
.AddSingleton(configuration) | .AddSingleton(configuration) | ||||
.AddSingleton<DiscordSocketClient>() | .AddSingleton<DiscordSocketClient>() | ||||
.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>())) | .AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>())) | ||||
.AddSingleton<CommandHandler>() | .AddSingleton<CommandHandler>() | ||||
.BuildServiceProvider(); | .BuildServiceProvider(); | ||||
} | |||||
static bool IsDebug ( ) | static bool IsDebug ( ) | ||||
{ | { |
@@ -3,7 +3,7 @@ | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
<TargetFramework>net5.0</TargetFramework> | <TargetFramework>net5.0</TargetFramework> | ||||
<RootNamespace>_04_interactions_framework</RootNamespace> | |||||
<RootNamespace>InteractionFramework</RootNamespace> | |||||
<StartupObject></StartupObject> | <StartupObject></StartupObject> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
@@ -1,7 +1,7 @@ | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Discord.Commands; | using Discord.Commands; | ||||
namespace _03_sharded_client.Modules | |||||
namespace ShardedClient.Modules | |||||
{ | { | ||||
// Remember to make your module reference the ShardedCommandContext | // Remember to make your module reference the ShardedCommandContext | ||||
public class PublicModule : ModuleBase<ShardedCommandContext> | public class PublicModule : ModuleBase<ShardedCommandContext> |
@@ -1,13 +1,13 @@ | |||||
using System; | using System; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using _03_sharded_client.Services; | |||||
using ShardedClient.Services; | |||||
using Discord; | using Discord; | ||||
using Discord.Commands; | using Discord.Commands; | ||||
using Discord.WebSocket; | using Discord.WebSocket; | ||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
namespace _03_sharded_client | |||||
namespace ShardedClient | |||||
{ | { | ||||
// This is a minimal example of using Discord.Net's Sharded Client | // This is a minimal example of using Discord.Net's Sharded Client | ||||
// The provided DiscordShardedClient class simplifies having multiple | // The provided DiscordShardedClient class simplifies having multiple | ||||
@@ -15,7 +15,11 @@ namespace _03_sharded_client | |||||
class Program | class Program | ||||
{ | { | ||||
static void Main(string[] args) | static void Main(string[] args) | ||||
=> new Program().MainAsync().GetAwaiter().GetResult(); | |||||
=> new Program() | |||||
.MainAsync() | |||||
.GetAwaiter() | |||||
.GetResult(); | |||||
public async Task MainAsync() | public async Task MainAsync() | ||||
{ | { | ||||
// You specify the amount of shards you'd like to have with the | // You specify the amount of shards you'd like to have with the | ||||
@@ -51,13 +55,11 @@ namespace _03_sharded_client | |||||
} | } | ||||
private ServiceProvider ConfigureServices(DiscordSocketConfig config) | private ServiceProvider ConfigureServices(DiscordSocketConfig config) | ||||
{ | |||||
return new ServiceCollection() | |||||
=> new ServiceCollection() | |||||
.AddSingleton(new DiscordShardedClient(config)) | .AddSingleton(new DiscordShardedClient(config)) | ||||
.AddSingleton<CommandService>() | .AddSingleton<CommandService>() | ||||
.AddSingleton<CommandHandlingService>() | .AddSingleton<CommandHandlingService>() | ||||
.BuildServiceProvider(); | .BuildServiceProvider(); | ||||
} | |||||
private Task ReadyAsync(DiscordSocketClient shard) | private Task ReadyAsync(DiscordSocketClient shard) |
@@ -6,7 +6,7 @@ using Discord; | |||||
using Discord.Commands; | using Discord.Commands; | ||||
using Discord.WebSocket; | using Discord.WebSocket; | ||||
namespace _03_sharded_client.Services | |||||
namespace ShardedClient.Services | |||||
{ | { | ||||
public class CommandHandlingService | public class CommandHandlingService | ||||
{ | { |
@@ -3,6 +3,7 @@ | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
<TargetFramework>net5.0</TargetFramework> | <TargetFramework>net5.0</TargetFramework> | ||||
<RootNamespace>ShardedClient</RootNamespace> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> |
@@ -2,9 +2,9 @@ using System.IO; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Discord; | using Discord; | ||||
using Discord.Commands; | using Discord.Commands; | ||||
using _02_commands_framework.Services; | |||||
using TextCommandFramework.Services; | |||||
namespace _02_commands_framework.Modules | |||||
namespace TextCommandFramework.Modules | |||||
{ | { | ||||
// Modules must be public and inherit from an IModuleBase | // Modules must be public and inherit from an IModuleBase | ||||
public class PublicModule : ModuleBase<SocketCommandContext> | public class PublicModule : ModuleBase<SocketCommandContext> |
@@ -6,9 +6,9 @@ using Microsoft.Extensions.DependencyInjection; | |||||
using Discord; | using Discord; | ||||
using Discord.WebSocket; | using Discord.WebSocket; | ||||
using Discord.Commands; | using Discord.Commands; | ||||
using _02_commands_framework.Services; | |||||
using TextCommandFramework.Services; | |||||
namespace _02_commands_framework | |||||
namespace TextCommandFramework | |||||
{ | { | ||||
// This is a minimal example of using Discord.Net's command | // This is a minimal example of using Discord.Net's command | ||||
// framework - by no means does it show everything the framework | // framework - by no means does it show everything the framework |
@@ -6,7 +6,7 @@ using Discord; | |||||
using Discord.Commands; | using Discord.Commands; | ||||
using Discord.WebSocket; | using Discord.WebSocket; | ||||
namespace _02_commands_framework.Services | |||||
namespace TextCommandFramework.Services | |||||
{ | { | ||||
public class CommandHandlingService | public class CommandHandlingService | ||||
{ | { |
@@ -2,7 +2,7 @@ using System.IO; | |||||
using System.Net.Http; | using System.Net.Http; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace _02_commands_framework.Services | |||||
namespace TextCommandFramework.Services | |||||
{ | { | ||||
public class PictureService | public class PictureService | ||||
{ | { |
@@ -3,7 +3,7 @@ | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
<TargetFramework>net5.0</TargetFramework> | <TargetFramework>net5.0</TargetFramework> | ||||
<RootNamespace>_03_sharded_client</RootNamespace> | |||||
<RootNamespace>TextCommandFramework</RootNamespace> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> |
@@ -2,7 +2,7 @@ using Discord; | |||||
using Discord.Webhook; | using Discord.Webhook; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace _04_webhook_client | |||||
namespace WebHookClient | |||||
{ | { | ||||
// This is a minimal example of using Discord.Net's Webhook Client | // This is a minimal example of using Discord.Net's Webhook Client | ||||
// Webhooks are send-only components of Discord that allow you to make a POST request | // Webhooks are send-only components of Discord that allow you to make a POST request |
@@ -2,8 +2,8 @@ | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
<TargetFramework>netcoreapp2.2</TargetFramework> | |||||
<RootNamespace>_04_webhook_client</RootNamespace> | |||||
<TargetFramework>net5.0</TargetFramework> | |||||
<RootNamespace>WebHookClient</RootNamespace> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> |