| @@ -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 | |||
| [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 | |||
| @@ -16,7 +16,5 @@ | |||
| topicUid: FAQ.Commands.Interactions | |||
| - name: Dependency Injection | |||
| topicUid: FAQ.Commands.DI | |||
| - name: Glossary | |||
| topicUid: FAQ.Glossary | |||
| - name: Legacy or Upgrade | |||
| 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 | |||
| * 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 | |||
| exist under a category. | |||
| > [!NOTE] | |||
| > A Channel ([IChannel]) can be all types of channels. | |||
| [INestedChannel]: xref:Discord.INestedChannel | |||
| [IGuildChannel]: xref:Discord.IGuildChannel | |||
| [IMessageChannel]: xref:Discord.IMessageChannel | |||
| @@ -62,17 +49,27 @@ exist under a category. | |||
| [IStageChannel]: xref:Discord.IStageChannel | |||
| [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. | |||
| * A **System Message** ([ISystemMessage]) is a message sent by Discord itself. | |||
| * 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 | |||
| [ISystemMessage]: xref:Discord.ISystemMessage | |||
| [IMessage]: xref:Discord.IMessage | |||
| ## User Types | |||
| ## Users | |||
|  | |||
| * A **Guild User** ([IGuildUser]) is a user available inside a guild. | |||
| * A **Group User** ([IGroupUser]) is a user available inside a group. | |||
| @@ -85,7 +82,13 @@ exist under a category. | |||
| [ISelfUser]: xref:Discord.ISelfUser | |||
| [IUser]: xref:Discord.IUser | |||
| ## Emoji Types | |||
| ## Interactions | |||
|  | |||
| ## Other types: | |||
| ### Emoji | |||
| * An **Emote** ([Emote]) is a custom emote from a guild. | |||
| - Example: `<:dotnet:232902710280716288>` | |||
| @@ -96,7 +99,7 @@ exist under a category. | |||
| [Emoji]: xref:Discord.Emoji | |||
| ## Sticker Types | |||
| ### Stickers | |||
| * A **Sticker** ([ISticker]) is a standard Discord sticker. | |||
| * A **Custom Sticker ([ICustomSticker]) is a Guild-unique sticker. | |||
| @@ -104,7 +107,7 @@ exist under a category. | |||
| [ISticker]: xref:Discord.ISticker | |||
| [ICustomSticker]: xref:Discord.ICustomSticker | |||
| ## Activity Types | |||
| ### Activity | |||
| * A **Game** ([Game]) refers to a user's game activity. | |||
| * 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 | |||
| > [!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 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 | |||
| 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 | |||
| 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 | |||
| `SocketMessageChannel`. All messages come from channels capable of | |||
| 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 | |||
| 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 | |||
| @@ -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 | |||
| an entity to navigate to its parent. | |||
| ## Accessing Entities | |||
| ## Accessing Socket Entities | |||
| The most basic forms of entities, `SocketGuild`, `SocketUser`, and | |||
| `SocketChannel` can be pulled from the DiscordSocketClient's global | |||
| cache, and can be retrieved using the respective `GetXXX` method on | |||
| DiscordSocketClient. | |||
| > [!TIP] | |||
| > It is **vital** that you use the proper IDs for an entity when using | |||
| > a `GetXXX` method. It is recommended that you enable Discord's | |||
| > _developer mode_ to allow easy access to entity IDs, found in | |||
| > Settings > Appearance > Advanced. Read more about it in the | |||
| > [FAQ](xref:FAQ.Basics.GetStarted) page. | |||
| More detailed versions of entities can be pulled from the basic | |||
| entities, e.g., `SocketGuild.GetUser`, which returns a | |||
| `SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a | |||
| `SocketGuildChannel`. Again, you may need to cast these objects to get | |||
| 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 | |||
| - name: 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 | |||
| items: | |||
| - 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.Threading.Tasks; | |||
| namespace _04_interactions_framework | |||
| namespace InteractionFramework.Attributes | |||
| { | |||
| public class RequireOwnerAttribute : PreconditionAttribute | |||
| { | |||
| @@ -8,7 +8,7 @@ using System.Reflection; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace _04_interactions_framework | |||
| namespace InteractionFramework | |||
| { | |||
| public class CommandHandler | |||
| { | |||
| @@ -27,6 +27,9 @@ namespace _04_interactions_framework | |||
| { | |||
| // Add the public modules that inherit InteractionModuleBase<T> to the InteractionService | |||
| 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 | |||
| _client.InteractionCreated += HandleInteraction; | |||
| @@ -37,6 +40,8 @@ namespace _04_interactions_framework | |||
| _commands.ComponentCommandExecuted += ComponentCommandExecuted; | |||
| } | |||
| # region Error Handling | |||
| private Task ComponentCommandExecuted (ComponentCommandInfo arg1, Discord.IInteractionContext arg2, IResult arg3) | |||
| { | |||
| if (!arg3.IsSuccess) | |||
| @@ -123,6 +128,9 @@ namespace _04_interactions_framework | |||
| return Task.CompletedTask; | |||
| } | |||
| # endregion | |||
| # region Execution | |||
| private async Task HandleInteraction (SocketInteraction arg) | |||
| { | |||
| @@ -142,5 +150,6 @@ namespace _04_interactions_framework | |||
| await arg.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync()); | |||
| } | |||
| } | |||
| # endregion | |||
| } | |||
| } | |||
| @@ -4,7 +4,7 @@ using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace _04_interactions_framework | |||
| namespace InteractionFramework | |||
| { | |||
| 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.Interactions; | |||
| using Discord.WebSocket; | |||
| using InteractionFramework.Attributes; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace _04_interactions_framework.Modules | |||
| namespace InteractionFramework.Modules | |||
| { | |||
| // 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 | |||
| public InteractionService Commands { get; set; } | |||
| @@ -18,7 +19,7 @@ namespace _04_interactions_framework.Modules | |||
| private CommandHandler _handler; | |||
| // Constructor injection is also a valid way to access the dependecies | |||
| public UtilityModule ( CommandHandler handler ) | |||
| public GeneralModule(CommandHandler 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.Tasks; | |||
| namespace _04_interactions_framework | |||
| namespace InteractionFramework | |||
| { | |||
| class Program | |||
| { | |||
| // Entry point of the program. | |||
| static void Main ( string[] args ) | |||
| { | |||
| // 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(); | |||
| } | |||
| 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. | |||
| using var services = ConfigureServices(configuration); | |||
| @@ -64,14 +65,12 @@ namespace _04_interactions_framework | |||
| } | |||
| static ServiceProvider ConfigureServices ( IConfiguration configuration ) | |||
| { | |||
| return new ServiceCollection() | |||
| => new ServiceCollection() | |||
| .AddSingleton(configuration) | |||
| .AddSingleton<DiscordSocketClient>() | |||
| .AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>())) | |||
| .AddSingleton<CommandHandler>() | |||
| .BuildServiceProvider(); | |||
| } | |||
| static bool IsDebug ( ) | |||
| { | |||
| @@ -3,7 +3,7 @@ | |||
| <PropertyGroup> | |||
| <OutputType>Exe</OutputType> | |||
| <TargetFramework>net5.0</TargetFramework> | |||
| <RootNamespace>_04_interactions_framework</RootNamespace> | |||
| <RootNamespace>InteractionFramework</RootNamespace> | |||
| <StartupObject></StartupObject> | |||
| </PropertyGroup> | |||
| @@ -1,7 +1,7 @@ | |||
| using System.Threading.Tasks; | |||
| using Discord.Commands; | |||
| namespace _03_sharded_client.Modules | |||
| namespace ShardedClient.Modules | |||
| { | |||
| // Remember to make your module reference the ShardedCommandContext | |||
| public class PublicModule : ModuleBase<ShardedCommandContext> | |||
| @@ -1,13 +1,13 @@ | |||
| using System; | |||
| using System.Threading; | |||
| using System.Threading.Tasks; | |||
| using _03_sharded_client.Services; | |||
| using ShardedClient.Services; | |||
| using Discord; | |||
| using Discord.Commands; | |||
| using Discord.WebSocket; | |||
| using Microsoft.Extensions.DependencyInjection; | |||
| namespace _03_sharded_client | |||
| namespace ShardedClient | |||
| { | |||
| // This is a minimal example of using Discord.Net's Sharded Client | |||
| // The provided DiscordShardedClient class simplifies having multiple | |||
| @@ -15,7 +15,11 @@ namespace _03_sharded_client | |||
| class Program | |||
| { | |||
| static void Main(string[] args) | |||
| => new Program().MainAsync().GetAwaiter().GetResult(); | |||
| => new Program() | |||
| .MainAsync() | |||
| .GetAwaiter() | |||
| .GetResult(); | |||
| public async Task MainAsync() | |||
| { | |||
| // 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) | |||
| { | |||
| return new ServiceCollection() | |||
| => new ServiceCollection() | |||
| .AddSingleton(new DiscordShardedClient(config)) | |||
| .AddSingleton<CommandService>() | |||
| .AddSingleton<CommandHandlingService>() | |||
| .BuildServiceProvider(); | |||
| } | |||
| private Task ReadyAsync(DiscordSocketClient shard) | |||
| @@ -6,7 +6,7 @@ using Discord; | |||
| using Discord.Commands; | |||
| using Discord.WebSocket; | |||
| namespace _03_sharded_client.Services | |||
| namespace ShardedClient.Services | |||
| { | |||
| public class CommandHandlingService | |||
| { | |||
| @@ -3,6 +3,7 @@ | |||
| <PropertyGroup> | |||
| <OutputType>Exe</OutputType> | |||
| <TargetFramework>net5.0</TargetFramework> | |||
| <RootNamespace>ShardedClient</RootNamespace> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| @@ -2,9 +2,9 @@ using System.IO; | |||
| using System.Threading.Tasks; | |||
| using Discord; | |||
| 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 | |||
| public class PublicModule : ModuleBase<SocketCommandContext> | |||
| @@ -6,9 +6,9 @@ using Microsoft.Extensions.DependencyInjection; | |||
| using Discord; | |||
| using Discord.WebSocket; | |||
| 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 | |||
| // framework - by no means does it show everything the framework | |||
| @@ -6,7 +6,7 @@ using Discord; | |||
| using Discord.Commands; | |||
| using Discord.WebSocket; | |||
| namespace _02_commands_framework.Services | |||
| namespace TextCommandFramework.Services | |||
| { | |||
| public class CommandHandlingService | |||
| { | |||
| @@ -2,7 +2,7 @@ using System.IO; | |||
| using System.Net.Http; | |||
| using System.Threading.Tasks; | |||
| namespace _02_commands_framework.Services | |||
| namespace TextCommandFramework.Services | |||
| { | |||
| public class PictureService | |||
| { | |||
| @@ -3,7 +3,7 @@ | |||
| <PropertyGroup> | |||
| <OutputType>Exe</OutputType> | |||
| <TargetFramework>net5.0</TargetFramework> | |||
| <RootNamespace>_03_sharded_client</RootNamespace> | |||
| <RootNamespace>TextCommandFramework</RootNamespace> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| @@ -2,7 +2,7 @@ using Discord; | |||
| using Discord.Webhook; | |||
| using System.Threading.Tasks; | |||
| namespace _04_webhook_client | |||
| namespace WebHookClient | |||
| { | |||
| // 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 | |||
| @@ -2,8 +2,8 @@ | |||
| <PropertyGroup> | |||
| <OutputType>Exe</OutputType> | |||
| <TargetFramework>netcoreapp2.2</TargetFramework> | |||
| <RootNamespace>_04_webhook_client</RootNamespace> | |||
| <TargetFramework>net5.0</TargetFramework> | |||
| <RootNamespace>WebHookClient</RootNamespace> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||