From c0c1cacc856a60ea74030f84407a00b83f9756b7 Mon Sep 17 00:00:00 2001 From: Misha133 Date: Thu, 11 Jan 2024 18:07:08 +0300 Subject: [PATCH 1/3] update them all --- samples/BasicBot/Program.cs | 182 +++++++++--------- samples/BasicBot/_BasicBot.csproj | 2 +- .../Attributes/DoUserCheckAttribute.cs | 43 ++--- .../Attributes/RequireOwnerAttribute.cs | 28 ++- .../InteractionFramework/Enums/ExampleEnum.cs | 25 +-- .../InteractionHandler.cs | 128 ++++++------ .../Modules/ExampleModule.cs | 155 ++++++++------- samples/InteractionFramework/Program.cs | 90 ++++----- .../_InteractionFramework.csproj | 12 +- .../Modules/InteractionModule.cs | 18 +- samples/ShardedClient/Modules/PublicModule.cs | 17 +- samples/ShardedClient/Program.cs | 112 +++++------ .../Services/CommandHandlingService.cs | 99 +++++----- .../Services/InteractionHandlingService.cs | 85 ++++---- samples/ShardedClient/_ShardedClient.csproj | 4 +- .../Modules/PublicModule.cs | 105 +++++----- samples/TextCommandFramework/Program.cs | 102 +++++----- .../Services/CommandHandlingService.cs | 109 ++++++----- .../Services/PictureService.cs | 21 +- .../_TextCommandFramework.csproj | 6 +- samples/WebhookClient/Program.cs | 41 ++-- samples/WebhookClient/_WebhookClient.csproj | 2 +- 22 files changed, 667 insertions(+), 719 deletions(-) diff --git a/samples/BasicBot/Program.cs b/samples/BasicBot/Program.cs index 01462770f3..51690f230b 100644 --- a/samples/BasicBot/Program.cs +++ b/samples/BasicBot/Program.cs @@ -4,117 +4,109 @@ using System.Threading; using System.Threading.Tasks; -namespace BasicBot +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 { - // 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 static DiscordSocketClient _client; + + // Discord.Net heavily utilizes TAP for async, so we create + // an asynchronous context from the beginning. + public static async Task Main(string[] args) { - // 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() + // Config used by DiscordSocketClient + // Define intents for the client + // Note that GatewayIntents.MessageContent is a privileged intent, and requires extra setup in the developer portal. + var config = new DiscordSocketConfig { - // Config used by DiscordSocketClient - // Define intents for the client - var config = new DiscordSocketConfig - { - GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent - }; - - // 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(config); - - // 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; - } + GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent + }; - 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. + // 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(config); - await _client.StartAsync(); + // 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; - // Block the program until it is closed. - await Task.Delay(Timeout.Infinite); - } - private Task LogAsync(LogMessage log) - { - Console.WriteLine(log.ToString()); - return Task.CompletedTask; - } + // 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. - // 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!"); + await _client.StartAsync(); - return Task.CompletedTask; - } + // Block the program until it is closed. + await Task.Delay(Timeout.Infinite); + } - // 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; + private static 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 static 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 static 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()); - } + 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) + // 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 static 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) { - // 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!"); - } + // 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!"); } } } diff --git a/samples/BasicBot/_BasicBot.csproj b/samples/BasicBot/_BasicBot.csproj index b27e326828..9f56dede6b 100644 --- a/samples/BasicBot/_BasicBot.csproj +++ b/samples/BasicBot/_BasicBot.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/InteractionFramework/Attributes/DoUserCheckAttribute.cs b/samples/InteractionFramework/Attributes/DoUserCheckAttribute.cs index c83df6e2df..c311967f45 100644 --- a/samples/InteractionFramework/Attributes/DoUserCheckAttribute.cs +++ b/samples/InteractionFramework/Attributes/DoUserCheckAttribute.cs @@ -4,35 +4,30 @@ using System; using System.Threading.Tasks; -namespace InteractionFramework.Attributes +namespace InteractionFramework.Attributes; + +internal class DoUserCheck : PreconditionAttribute { - internal class DoUserCheck : PreconditionAttribute + public override Task CheckRequirementsAsync(IInteractionContext context, ICommandInfo commandInfo, IServiceProvider services) { - public override Task 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.")); + // 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:*,* + // 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 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!")); + // 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.")); - } - } + return Task.FromResult(PreconditionResult.FromError("Parse cannot be done if no userID exists.")); } } diff --git a/samples/InteractionFramework/Attributes/RequireOwnerAttribute.cs b/samples/InteractionFramework/Attributes/RequireOwnerAttribute.cs index d82918b507..2d789cdceb 100644 --- a/samples/InteractionFramework/Attributes/RequireOwnerAttribute.cs +++ b/samples/InteractionFramework/Attributes/RequireOwnerAttribute.cs @@ -1,27 +1,23 @@ using Discord; using Discord.Interactions; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; -namespace InteractionFramework.Attributes +namespace InteractionFramework.Attributes; + +public class RequireOwnerAttribute : PreconditionAttribute { - public class RequireOwnerAttribute : PreconditionAttribute + public override async Task CheckRequirementsAsync(IInteractionContext context, ICommandInfo commandInfo, IServiceProvider services) { - public override async Task CheckRequirementsAsync(IInteractionContext context, ICommandInfo commandInfo, IServiceProvider services) + switch (context.Client.TokenType) { - switch (context.Client.TokenType) - { - case TokenType.Bot: - var application = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false); - if (context.User.Id != application.Owner.Id) - return PreconditionResult.FromError(ErrorMessage ?? "Command can only be run by the owner of the bot."); - return PreconditionResult.FromSuccess(); - default: - return PreconditionResult.FromError($"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}."); - } + case TokenType.Bot: + var application = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false); + return context.User.Id != application.Owner.Id + ? PreconditionResult.FromError(ErrorMessage ?? "Command can only be run by the owner of the bot.") + : PreconditionResult.FromSuccess(); + default: + return PreconditionResult.FromError($"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}."); } } } diff --git a/samples/InteractionFramework/Enums/ExampleEnum.cs b/samples/InteractionFramework/Enums/ExampleEnum.cs index a70dd49a92..79b3da37f2 100644 --- a/samples/InteractionFramework/Enums/ExampleEnum.cs +++ b/samples/InteractionFramework/Enums/ExampleEnum.cs @@ -1,20 +1,13 @@ using Discord.Interactions; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace InteractionFramework +namespace InteractionFramework; + +public enum ExampleEnum { - public enum ExampleEnum - { - First, - Second, - Third, - Fourth, - [ChoiceDisplay("Twenty First")] - TwentyFirst - } + First, + Second, + Third, + Fourth, + [ChoiceDisplay("Twenty First")] + TwentyFirst } diff --git a/samples/InteractionFramework/InteractionHandler.cs b/samples/InteractionFramework/InteractionHandler.cs index bc6f47285f..2d362e742b 100644 --- a/samples/InteractionFramework/InteractionHandler.cs +++ b/samples/InteractionFramework/InteractionHandler.cs @@ -6,76 +6,90 @@ using System.Reflection; using System.Threading.Tasks; -namespace InteractionFramework +namespace InteractionFramework; + +public class InteractionHandler { - public class InteractionHandler + private readonly DiscordSocketClient _client; + private readonly InteractionService _handler; + private readonly IServiceProvider _services; + private readonly IConfiguration _configuration; + + public InteractionHandler(DiscordSocketClient client, InteractionService handler, IServiceProvider services, IConfiguration config) { - private readonly DiscordSocketClient _client; - private readonly InteractionService _handler; - private readonly IServiceProvider _services; - private readonly IConfiguration _configuration; + _client = client; + _handler = handler; + _services = services; + _configuration = config; + } - public InteractionHandler(DiscordSocketClient client, InteractionService handler, IServiceProvider services, IConfiguration config) - { - _client = client; - _handler = handler; - _services = services; - _configuration = config; - } + public async Task InitializeAsync() + { + // Process when the client is ready, so we can register our commands. + _client.Ready += ReadyAsync; + _handler.Log += LogAsync; - public async Task InitializeAsync() - { - // Process when the client is ready, so we can register our commands. - _client.Ready += ReadyAsync; - _handler.Log += LogAsync; + // Add the public modules that inherit InteractionModuleBase to the InteractionService + await _handler.AddModulesAsync(Assembly.GetEntryAssembly(), _services); - // Add the public modules that inherit InteractionModuleBase to the InteractionService - await _handler.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + // Process the InteractionCreated payloads to execute Interactions commands + _client.InteractionCreated += HandleInteraction; - // Process the InteractionCreated payloads to execute Interactions commands - _client.InteractionCreated += HandleInteraction; - } + // Also process the result of the command execution. + _handler.InteractionExecuted += HandleInteractionExecute; + } - private async Task LogAsync(LogMessage log) - => Console.WriteLine(log); + private async Task LogAsync(LogMessage log) + => Console.WriteLine(log); - private async Task ReadyAsync() - { - // Context & Slash commands can be automatically registered, but this process needs to happen after the client enters the READY state. - // Since Global Commands take around 1 hour to register, we should use a test guild to instantly update and test our commands. - if (Program.IsDebug()) - await _handler.RegisterCommandsToGuildAsync(_configuration.GetValue("testGuild"), true); - else - await _handler.RegisterCommandsGloballyAsync(true); - } + private async Task ReadyAsync() + { + // Register the commands globally. + // alternatively you can use _handler.RegisterCommandsGloballyAsync() to register commands to a specific guild. + await _handler.RegisterCommandsGloballyAsync(); + } - private async Task HandleInteraction(SocketInteraction interaction) + private async Task HandleInteraction(SocketInteraction interaction) + { + try { - try - { - // Create an execution context that matches the generic type parameter of your InteractionModuleBase modules. - var context = new SocketInteractionContext(_client, interaction); + // Create an execution context that matches the generic type parameter of your InteractionModuleBase modules. + var context = new SocketInteractionContext(_client, interaction); - // Execute the incoming command. - var result = await _handler.ExecuteCommandAsync(context, _services); + // Execute the incoming command. + var result = await _handler.ExecuteCommandAsync(context, _services); - if (!result.IsSuccess) - switch (result.Error) - { - case InteractionCommandError.UnmetPrecondition: - // implement - break; - default: - break; - } - } - catch + // Due to async nature of InteractionFramework, the result here may always be success. + // That's why we also need to handle the InteractionExecuted event. + if (!result.IsSuccess) + switch (result.Error) + { + case InteractionCommandError.UnmetPrecondition: + // implement + break; + default: + break; + } + } + catch + { + // If Slash Command execution fails it is most likely that the original interaction acknowledgement will persist. It is a good idea to delete the original + // response, or at least let the user know that something went wrong during the command execution. + if (interaction.Type is InteractionType.ApplicationCommand) + await interaction.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync()); + } + } + + private async Task HandleInteractionExecute(ICommandInfo commandInfo, IInteractionContext context, IResult result) + { + if (!result.IsSuccess) + switch (result.Error) { - // If Slash Command execution fails it is most likely that the original interaction acknowledgement will persist. It is a good idea to delete the original - // response, or at least let the user know that something went wrong during the command execution. - if (interaction.Type is InteractionType.ApplicationCommand) - await interaction.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync()); + case InteractionCommandError.UnmetPrecondition: + // implement + break; + default: + break; } - } } } diff --git a/samples/InteractionFramework/Modules/ExampleModule.cs b/samples/InteractionFramework/Modules/ExampleModule.cs index 38cc4120bc..1d587b6587 100644 --- a/samples/InteractionFramework/Modules/ExampleModule.cs +++ b/samples/InteractionFramework/Modules/ExampleModule.cs @@ -4,97 +4,96 @@ using System; using System.Threading.Tasks; -namespace InteractionFramework.Modules +namespace InteractionFramework.Modules; + +// Interaction modules must be public and inherit from an IInteractionModuleBase +public class ExampleModule : InteractionModuleBase { - // Interaction modules must be public and inherit from an IInteractionModuleBase - public class ExampleModule : InteractionModuleBase + // Dependencies can be accessed through Property injection, public properties with public setters will be set by the service provider + public InteractionService Commands { get; set; } + + private InteractionHandler _handler; + + // Constructor injection is also a valid way to access the dependencies + public ExampleModule(InteractionHandler handler) { - // Dependencies can be accessed through Property injection, public properties with public setters will be set by the service provider - public InteractionService Commands { get; set; } + _handler = handler; + } - private InteractionHandler _handler; + // You can use a number of parameter types in you Slash Command handlers (string, int, double, bool, IUser, IChannel, IMentionable, IRole, Enums) by default. Optionally, + // you can implement your own TypeConverters to support a wider range of parameter types. For more information, refer to the library documentation. + // Optional method parameters(parameters with a default value) also will be displayed as optional on Discord. - // Constructor injection is also a valid way to access the dependencies - public ExampleModule(InteractionHandler handler) - { - _handler = handler; - } + // [Summary] lets you customize the name and the description of a parameter + [SlashCommand("echo", "Repeat the input")] + public async Task Echo(string echo, [Summary(description: "mention the user")] bool mention = false) + => await RespondAsync(echo + (mention ? Context.User.Mention : string.Empty)); - // You can use a number of parameter types in you Slash Command handlers (string, int, double, bool, IUser, IChannel, IMentionable, IRole, Enums) by default. Optionally, - // you can implement your own TypeConverters to support a wider range of parameter types. For more information, refer to the library documentation. - // Optional method parameters(parameters with a default value) also will be displayed as optional on Discord. + [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); - // [Summary] lets you customize the name and the description of a parameter - [SlashCommand("echo", "Repeat the input")] - public async Task Echo(string echo, [Summary(description: "mention the user")] bool mention = false) - => await RespondAsync(echo + (mention ? Context.User.Mention : string.Empty)); + [SlashCommand("bitrate", "Gets the bitrate of a specific voice channel.")] + public async Task GetBitrateAsync([ChannelTypes(ChannelType.Voice, ChannelType.Stage)] IChannel channel) + => await RespondAsync(text: $"This voice channel has a bitrate of {(channel as IVoiceChannel).Bitrate}"); - [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); + // [Group] will create a command group. [SlashCommand]s and [ComponentInteraction]s will be registered with the group prefix + [Group("test_group", "This is a command group")] + public class GroupExample : InteractionModuleBase + { + // You can create command choices either by using the [Choice] attribute or by creating an enum. Every enum with 25 or less values will be registered as a multiple + // choice option + [SlashCommand("choice_example", "Enums create choices")] + public async Task ChoiceExample(ExampleEnum input) + => await RespondAsync(input.ToString()); + } - [SlashCommand("bitrate", "Gets the bitrate of a specific voice channel.")] - public async Task GetBitrateAsync([ChannelTypes(ChannelType.Voice, ChannelType.Stage)] IChannel channel) - => await RespondAsync(text: $"This voice channel has a bitrate of {(channel as IVoiceChannel).Bitrate}"); + // Use [ComponentInteraction] to handle message component interactions. Message component interaction with the matching customId will be executed. + // Alternatively, you can create a wild card pattern using the '*' character. Interaction Service will perform a lazy regex search and capture the matching strings. + // You can then access these capture groups from the method parameters, in the order they were captured. Using the wild card pattern, you can cherry pick component interactions. + [ComponentInteraction("musicSelect:*,*")] + public async Task ButtonPress(string id, string name) + { + // ... + await RespondAsync($"Playing song: {name}/{id}"); + } - // [Group] will create a command group. [SlashCommand]s and [ComponentInteraction]s will be registered with the group prefix - [Group("test_group", "This is a command group")] - public class GroupExample : InteractionModuleBase - { - // You can create command choices either by using the [Choice] attribute or by creating an enum. Every enum with 25 or less values will be registered as a multiple - // choice option - [SlashCommand("choice_example", "Enums create choices")] - public async Task ChoiceExample(ExampleEnum input) - => await RespondAsync(input.ToString()); - } + // Select Menu interactions, contain ids of the menu options that were selected by the user. You can access the option ids from the method parameters. + // You can also use the wild card pattern with Select Menus, in that case, the wild card captures will be passed on to the method first, followed by the option ids. + [ComponentInteraction("roleSelect")] + public async Task RoleSelect(string[] selections) + { + throw new NotImplementedException(); + } - // Use [ComponentInteraction] to handle message component interactions. Message component interaction with the matching customId will be executed. - // Alternatively, you can create a wild card pattern using the '*' character. Interaction Service will perform a lazy regex search and capture the matching strings. - // You can then access these capture groups from the method parameters, in the order they were captured. Using the wild card pattern, you can cherry pick component interactions. - [ComponentInteraction("musicSelect:*,*")] - public async Task ButtonPress(string id, string name) - { - // ... - await RespondAsync($"Playing song: {name}/{id}"); - } + // 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!"); - // Select Menu interactions, contain ids of the menu options that were selected by the user. You can access the option ids from the method parameters. - // You can also use the wild card pattern with Select Menus, in that case, the wild card captures will be passed on to the method first, followed by the option ids. - [ComponentInteraction("roleSelect")] - public async Task RoleSelect(string[] selections) - { - throw new NotImplementedException(); - } + // 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}>!"); + + // 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!"); - // 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!"); - - // 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}>!"); - - // Pins a message in the channel it is in. - [MessageCommand("pin")] - public async Task PinMessageAsync(IMessage message) + else { - // 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!"); - } + await userMessage.PinAsync(); + await RespondAsync(":white_check_mark: Successfully pinned message!"); } } } diff --git a/samples/InteractionFramework/Program.cs b/samples/InteractionFramework/Program.cs index c6188e3a66..9731887d5d 100644 --- a/samples/InteractionFramework/Program.cs +++ b/samples/InteractionFramework/Program.cs @@ -7,68 +7,50 @@ using System.Threading; using System.Threading.Tasks; -namespace InteractionFramework -{ - public class Program - { - private readonly IConfiguration _configuration; - private readonly IServiceProvider _services; - - private readonly DiscordSocketConfig _socketConfig = new() - { - GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.GuildMembers, - AlwaysDownloadUsers = true, - }; - - public Program() - { - _configuration = new ConfigurationBuilder() - .AddEnvironmentVariables(prefix: "DC_") - .AddJsonFile("appsettings.json", optional: true) - .Build(); +namespace InteractionFramework; - _services = new ServiceCollection() - .AddSingleton(_configuration) - .AddSingleton(_socketConfig) - .AddSingleton() - .AddSingleton(x => new InteractionService(x.GetRequiredService())) - .AddSingleton() - .BuildServiceProvider(); - } +public class Program +{ + private static IConfiguration _configuration; + private static IServiceProvider _services; - static void Main(string[] args) - => new Program().RunAsync() - .GetAwaiter() - .GetResult(); + private static readonly DiscordSocketConfig _socketConfig = new() + { + GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.GuildMembers, + AlwaysDownloadUsers = true, + }; - public async Task RunAsync() - { - var client = _services.GetRequiredService(); + public static async Task Main(string[] args) + { + _configuration = new ConfigurationBuilder() + .AddEnvironmentVariables(prefix: "DC_") + .AddJsonFile("appsettings.json", optional: true) + .Build(); - client.Log += LogAsync; + _services = new ServiceCollection() + .AddSingleton(_configuration) + .AddSingleton(_socketConfig) + .AddSingleton() + .AddSingleton(x => new InteractionService(x.GetRequiredService())) + .AddSingleton() + .BuildServiceProvider(); - // Here we can initialize the service that will register and execute our commands - await _services.GetRequiredService() - .InitializeAsync(); + var client = _services.GetRequiredService(); - // Bot token can be provided from the Configuration object we set up earlier - await client.LoginAsync(TokenType.Bot, _configuration["token"]); - await client.StartAsync(); + client.Log += LogAsync; - // Never quit the program until manually forced to. - await Task.Delay(Timeout.Infinite); - } + // Here we can initialize the service that will register and execute our commands + await _services.GetRequiredService() + .InitializeAsync(); - private async Task LogAsync(LogMessage message) - => Console.WriteLine(message.ToString()); + // Bot token can be provided from the Configuration object we set up earlier + await client.LoginAsync(TokenType.Bot, _configuration["token"]); + await client.StartAsync(); - public static bool IsDebug() - { -#if DEBUG - return true; -#else - return false; -#endif - } + // Never quit the program until manually forced to. + await Task.Delay(Timeout.Infinite); } + + private static async Task LogAsync(LogMessage message) + => Console.WriteLine(message.ToString()); } diff --git a/samples/InteractionFramework/_InteractionFramework.csproj b/samples/InteractionFramework/_InteractionFramework.csproj index 8d28d2710d..2669b76a5b 100644 --- a/samples/InteractionFramework/_InteractionFramework.csproj +++ b/samples/InteractionFramework/_InteractionFramework.csproj @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git a/samples/ShardedClient/Modules/InteractionModule.cs b/samples/ShardedClient/Modules/InteractionModule.cs index 6c2f0e9403..50b293af7a 100644 --- a/samples/ShardedClient/Modules/InteractionModule.cs +++ b/samples/ShardedClient/Modules/InteractionModule.cs @@ -1,18 +1,16 @@ using Discord.Interactions; -using Discord.WebSocket; using System.Threading.Tasks; -namespace ShardedClient.Modules +namespace ShardedClient.Modules; + +// A display of portability, which shows how minimal the difference between the 2 frameworks is. +public class InteractionModule : InteractionModuleBase { - // A display of portability, which shows how minimal the difference between the 2 frameworks is. - public class InteractionModule : InteractionModuleBase + [SlashCommand("info", "Information about this shard.")] + public async Task InfoAsync() { - [SlashCommand("info", "Information about this shard.")] - public async Task InfoAsync() - { - var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards.Count} shards! + var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards.Count} shards! This guild is being served by shard number {Context.Client.GetShardFor(Context.Guild).ShardId}"; - await RespondAsync(msg); - } + await RespondAsync(msg); } } diff --git a/samples/ShardedClient/Modules/PublicModule.cs b/samples/ShardedClient/Modules/PublicModule.cs index 25aa88aafa..3f28133b88 100644 --- a/samples/ShardedClient/Modules/PublicModule.cs +++ b/samples/ShardedClient/Modules/PublicModule.cs @@ -1,17 +1,16 @@ using Discord.Commands; using System.Threading.Tasks; -namespace ShardedClient.Modules +namespace ShardedClient.Modules; + +// Remember to make your module reference the ShardedCommandContext +public class PublicModule : ModuleBase { - // Remember to make your module reference the ShardedCommandContext - public class PublicModule : ModuleBase + [Command("info")] + public async Task InfoAsync() { - [Command("info")] - public async Task InfoAsync() - { - var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards.Count} shards! + var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards.Count} shards! This guild is being served by shard number {Context.Client.GetShardFor(Context.Guild).ShardId}"; - await ReplyAsync(msg); - } + await ReplyAsync(msg); } } diff --git a/samples/ShardedClient/Program.cs b/samples/ShardedClient/Program.cs index cb7b0dbb3d..7c65750480 100644 --- a/samples/ShardedClient/Program.cs +++ b/samples/ShardedClient/Program.cs @@ -8,78 +8,70 @@ using System.Threading; using System.Threading.Tasks; -namespace ShardedClient +namespace ShardedClient; + +// This is a minimal example of using Discord.Net's Sharded Client +// The provided DiscordShardedClient class simplifies having multiple +// DiscordSocketClient instances (or shards) to serve a large number of guilds. +class Program { - // This is a minimal example of using Discord.Net's Sharded Client - // The provided DiscordShardedClient class simplifies having multiple - // DiscordSocketClient instances (or shards) to serve a large number of guilds. - class Program + public static async Task Main(string[] args) { - static void Main(string[] args) - => new Program() - .MainAsync() - .GetAwaiter() - .GetResult(); - - public async Task MainAsync() + // You specify the amount of shards you'd like to have with the + // DiscordSocketConfig. Generally, it's recommended to + // have 1 shard per 1500-2000 guilds your bot is in. + var config = new DiscordSocketConfig { - // You specify the amount of shards you'd like to have with the - // DiscordSocketConfig. Generally, it's recommended to - // have 1 shard per 1500-2000 guilds your bot is in. - var config = new DiscordSocketConfig - { - TotalShards = 2, - GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent - }; + TotalShards = 2, + GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent + }; - // You should dispose a service provider created using ASP.NET - // when you are finished using it, at the end of your app's lifetime. - // If you use another dependency injection framework, you should inspect - // its documentation for the best way to do this. - using (var services = ConfigureServices(config)) - { - var client = services.GetRequiredService(); + // You should dispose a service provider created using ASP.NET + // when you are finished using it, at the end of your app's lifetime. + // If you use another dependency injection framework, you should inspect + // its documentation for the best way to do this. + await using var services = ConfigureServices(config); - // The Sharded Client does not have a Ready event. - // The ShardReady event is used instead, allowing for individual - // control per shard. - client.ShardReady += ReadyAsync; - client.Log += LogAsync; + var client = services.GetRequiredService(); - await services.GetRequiredService() - .InitializeAsync(); + // The Sharded Client does not have a Ready event. + // The ShardReady event is used instead, allowing for individual + // control per shard. + client.ShardReady += ReadyAsync; + client.Log += LogAsync; - await services.GetRequiredService() - .InitializeAsync(); + await services.GetRequiredService() + .InitializeAsync(); - // Tokens should be considered secret data, and never hard-coded. - await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); - await client.StartAsync(); + await services.GetRequiredService() + .InitializeAsync(); - await Task.Delay(Timeout.Infinite); - } - } + // Tokens should be considered secret data, and never hard-coded. + await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); + await client.StartAsync(); - private ServiceProvider ConfigureServices(DiscordSocketConfig config) - => new ServiceCollection() - .AddSingleton(new DiscordShardedClient(config)) - .AddSingleton() - .AddSingleton(x => new InteractionService(x.GetRequiredService())) - .AddSingleton() - .AddSingleton() - .BuildServiceProvider(); + await Task.Delay(Timeout.Infinite); + } + private static ServiceProvider ConfigureServices(DiscordSocketConfig config) + => new ServiceCollection() + .AddSingleton(new DiscordShardedClient(config)) + .AddSingleton() + .AddSingleton(x => new InteractionService(x.GetRequiredService())) + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); - private Task ReadyAsync(DiscordSocketClient shard) - { - Console.WriteLine($"Shard Number {shard.ShardId} is connected and ready!"); - return Task.CompletedTask; - } - private Task LogAsync(LogMessage log) - { - Console.WriteLine(log.ToString()); - return Task.CompletedTask; - } + private static Task ReadyAsync(DiscordSocketClient shard) + { + Console.WriteLine($"Shard Number {shard.ShardId} is connected and ready!"); + return Task.CompletedTask; + } + + private static Task LogAsync(LogMessage log) + { + Console.WriteLine(log.ToString()); + return Task.CompletedTask; } } diff --git a/samples/ShardedClient/Services/CommandHandlingService.cs b/samples/ShardedClient/Services/CommandHandlingService.cs index 7961880500..d2c699751d 100644 --- a/samples/ShardedClient/Services/CommandHandlingService.cs +++ b/samples/ShardedClient/Services/CommandHandlingService.cs @@ -6,67 +6,66 @@ using System.Reflection; using System.Threading.Tasks; -namespace ShardedClient.Services +namespace ShardedClient.Services; + +public class CommandHandlingService { - public class CommandHandlingService - { - private readonly CommandService _commands; - private readonly DiscordShardedClient _discord; - private readonly IServiceProvider _services; + private readonly CommandService _commands; + private readonly DiscordShardedClient _discord; + private readonly IServiceProvider _services; - public CommandHandlingService(IServiceProvider services) - { - _commands = services.GetRequiredService(); - _discord = services.GetRequiredService(); - _services = services; + public CommandHandlingService(IServiceProvider services) + { + _commands = services.GetRequiredService(); + _discord = services.GetRequiredService(); + _services = services; - _commands.CommandExecuted += CommandExecutedAsync; - _commands.Log += LogAsync; - _discord.MessageReceived += MessageReceivedAsync; - } + _commands.CommandExecuted += CommandExecutedAsync; + _commands.Log += LogAsync; + _discord.MessageReceived += MessageReceivedAsync; + } - public async Task InitializeAsync() - { - await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); - } + public async Task InitializeAsync() + { + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + } - public async Task MessageReceivedAsync(SocketMessage rawMessage) - { - // Ignore system messages, or messages from other bots - if (rawMessage is not SocketUserMessage message) - return; - if (message.Source != MessageSource.User) - return; + public async Task MessageReceivedAsync(SocketMessage rawMessage) + { + // Ignore system messages, or messages from other bots + if (rawMessage is not SocketUserMessage message) + return; + if (message.Source != MessageSource.User) + return; - // This value holds the offset where the prefix ends - var argPos = 0; - if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) - return; + // This value holds the offset where the prefix ends + var argPos = 0; + if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) + return; - // A new kind of command context, ShardedCommandContext can be utilized with the commands framework - var context = new ShardedCommandContext(_discord, message); - await _commands.ExecuteAsync(context, argPos, _services); - } + // A new kind of command context, ShardedCommandContext can be utilized with the commands framework + var context = new ShardedCommandContext(_discord, message); + await _commands.ExecuteAsync(context, argPos, _services); + } - public async Task CommandExecutedAsync(Optional command, ICommandContext context, IResult result) - { - // command is unspecified when there was a search failure (command not found); we don't care about these errors - if (!command.IsSpecified) - return; + public async Task CommandExecutedAsync(Optional command, ICommandContext context, IResult result) + { + // command is unspecified when there was a search failure (command not found); we don't care about these errors + if (!command.IsSpecified) + return; - // the command was successful, we don't care about this result, unless we want to log that a command succeeded. - if (result.IsSuccess) - return; + // the command was successful, we don't care about this result, unless we want to log that a command succeeded. + if (result.IsSuccess) + return; - // the command failed, let's notify the user that something happened. - await context.Channel.SendMessageAsync($"error: {result}"); - } + // the command failed, let's notify the user that something happened. + await context.Channel.SendMessageAsync($"error: {result}"); + } - private Task LogAsync(LogMessage log) - { - Console.WriteLine(log.ToString()); + private Task LogAsync(LogMessage log) + { + Console.WriteLine(log.ToString()); - return Task.CompletedTask; - } + return Task.CompletedTask; } } diff --git a/samples/ShardedClient/Services/InteractionHandlingService.cs b/samples/ShardedClient/Services/InteractionHandlingService.cs index fc2af81503..8929445489 100644 --- a/samples/ShardedClient/Services/InteractionHandlingService.cs +++ b/samples/ShardedClient/Services/InteractionHandlingService.cs @@ -1,62 +1,65 @@ using Discord; using Discord.Interactions; using Discord.WebSocket; + using Microsoft.Extensions.DependencyInjection; + using System; -using System.Linq; using System.Threading.Tasks; -namespace ShardedClient.Services +namespace ShardedClient.Services; + +public class InteractionHandlingService { - public class InteractionHandlingService + private readonly InteractionService _service; + private readonly DiscordShardedClient _client; + private readonly IServiceProvider _provider; + + public InteractionHandlingService(IServiceProvider services) { - private readonly InteractionService _service; - private readonly DiscordShardedClient _client; - private readonly IServiceProvider _provider; + _service = services.GetRequiredService(); + _client = services.GetRequiredService(); + _provider = services; - public InteractionHandlingService(IServiceProvider services) - { - _service = services.GetRequiredService(); - _client = services.GetRequiredService(); - _provider = services; - - _service.Log += LogAsync; - _client.InteractionCreated += OnInteractionAsync; - _client.ShardReady += ReadyAsync; - // For examples on how to handle post execution, - // see the InteractionFramework samples. - } + _service.Log += LogAsync; + _client.InteractionCreated += OnInteractionAsync; + _client.ShardReady += ReadyAsync; + // For examples on how to handle post execution, + // see the InteractionFramework samples. + } - // Register all modules, and add the commands from these modules to either guild or globally depending on the build state. - public async Task InitializeAsync() - { - await _service.AddModulesAsync(typeof(InteractionHandlingService).Assembly, _provider); - } + // Register all modules, and add the commands from these modules to either guild or globally depending on the build state. + public async Task InitializeAsync() + { + await _service.AddModulesAsync(typeof(InteractionHandlingService).Assembly, _provider); + } - private async Task OnInteractionAsync(SocketInteraction interaction) + private async Task OnInteractionAsync(SocketInteraction interaction) + { + _ = Task.Run(async () => { - _ = Task.Run(async () => - { - var context = new ShardedInteractionContext(_client, interaction); - await _service.ExecuteCommandAsync(context, _provider); - }); - await Task.CompletedTask; - } + var context = new ShardedInteractionContext(_client, interaction); + await _service.ExecuteCommandAsync(context, _provider); + }); + await Task.CompletedTask; + } - private Task LogAsync(LogMessage log) - { - Console.WriteLine(log.ToString()); + private Task LogAsync(LogMessage log) + { + Console.WriteLine(log.ToString()); - return Task.CompletedTask; - } + return Task.CompletedTask; + } - private async Task ReadyAsync(DiscordSocketClient _) + private bool _hasRegistered = false; + + private async Task ReadyAsync(DiscordSocketClient _) + { + // ShardReady is called for each shard; to avoid getting ratelimited we only want to register commands once. + if (!_hasRegistered) { -#if DEBUG - await _service.RegisterCommandsToGuildAsync(1 /* implement */); -#else await _service.RegisterCommandsGloballyAsync(); -#endif + _hasRegistered = true; } } } diff --git a/samples/ShardedClient/_ShardedClient.csproj b/samples/ShardedClient/_ShardedClient.csproj index 5a30799385..d2af3be4f0 100644 --- a/samples/ShardedClient/_ShardedClient.csproj +++ b/samples/ShardedClient/_ShardedClient.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/samples/TextCommandFramework/Modules/PublicModule.cs b/samples/TextCommandFramework/Modules/PublicModule.cs index 68534e8267..863d12e295 100644 --- a/samples/TextCommandFramework/Modules/PublicModule.cs +++ b/samples/TextCommandFramework/Modules/PublicModule.cs @@ -4,66 +4,65 @@ using System.Threading.Tasks; using TextCommandFramework.Services; -namespace TextCommandFramework.Modules +namespace TextCommandFramework.Modules; + +// Modules must be public and inherit from an IModuleBase +public class PublicModule : ModuleBase { - // Modules must be public and inherit from an IModuleBase - public class PublicModule : ModuleBase - { - // Dependency Injection will fill this value in for us - public PictureService PictureService { get; set; } + // Dependency Injection will fill this value in for us + public PictureService PictureService { get; set; } - [Command("ping")] - [Alias("pong", "hello")] - public Task PingAsync() - => ReplyAsync("pong!"); + [Command("ping")] + [Alias("pong", "hello")] + public Task PingAsync() + => ReplyAsync("pong!"); - [Command("cat")] - public async Task CatAsync() - { - // Get a stream containing an image of a cat - var stream = await PictureService.GetCatPictureAsync(); - // Streams must be seeked to their beginning before being uploaded! - stream.Seek(0, SeekOrigin.Begin); - await Context.Channel.SendFileAsync(stream, "cat.png"); - } + [Command("cat")] + public async Task CatAsync() + { + // Get a stream containing an image of a cat + var stream = await PictureService.GetCatPictureAsync(); + // Streams must be seeked to their beginning before being uploaded! + stream.Seek(0, SeekOrigin.Begin); + await Context.Channel.SendFileAsync(stream, "cat.png"); + } - // Get info on a user, or the user who invoked the command if one is not specified - [Command("userinfo")] - public async Task UserInfoAsync(IUser user = null) - { - user ??= Context.User; + // Get info on a user, or the user who invoked the command if one is not specified + [Command("userinfo")] + public async Task UserInfoAsync(IUser user = null) + { + user ??= Context.User; - await ReplyAsync(user.ToString()); - } + await ReplyAsync(user.ToString()); + } - // Ban a user - [Command("ban")] - [RequireContext(ContextType.Guild)] - // make sure the user invoking the command can ban - [RequireUserPermission(GuildPermission.BanMembers)] - // make sure the bot itself can ban - [RequireBotPermission(GuildPermission.BanMembers)] - public async Task BanUserAsync(IGuildUser user, [Remainder] string reason = null) - { - await user.Guild.AddBanAsync(user, reason: reason); - await ReplyAsync("ok!"); - } + // Ban a user + [Command("ban")] + [RequireContext(ContextType.Guild)] + // make sure the user invoking the command can ban + [RequireUserPermission(GuildPermission.BanMembers)] + // make sure the bot itself can ban + [RequireBotPermission(GuildPermission.BanMembers)] + public async Task BanUserAsync(IGuildUser user, [Remainder] string reason = null) + { + await user.Guild.AddBanAsync(user, reason: reason); + await ReplyAsync("ok!"); + } - // [Remainder] takes the rest of the command's arguments as one argument, rather than splitting every space - [Command("echo")] - public Task EchoAsync([Remainder] string text) - // Insert a ZWSP before the text to prevent triggering other bots! - => ReplyAsync('\u200B' + text); + // [Remainder] takes the rest of the command's arguments as one argument, rather than splitting every space + [Command("echo")] + public Task EchoAsync([Remainder] string text) + // Insert a ZWSP before the text to prevent triggering other bots! + => ReplyAsync('\u200B' + text); - // 'params' will parse space-separated elements into a list - [Command("list")] - public Task ListAsync(params string[] objects) - => ReplyAsync("You listed: " + string.Join("; ", objects)); + // 'params' will parse space-separated elements into a list + [Command("list")] + public Task ListAsync(params string[] objects) + => ReplyAsync("You listed: " + string.Join("; ", objects)); - // Setting a custom ErrorMessage property will help clarify the precondition error - [Command("guild_only")] - [RequireContext(ContextType.Guild, ErrorMessage = "Sorry, this command must be ran from within a server, not a DM!")] - public Task GuildOnlyCommand() - => ReplyAsync("Nothing to see here!"); - } + // Setting a custom ErrorMessage property will help clarify the precondition error + [Command("guild_only")] + [RequireContext(ContextType.Guild, ErrorMessage = "Sorry, this command must be ran from within a server, not a DM!")] + public Task GuildOnlyCommand() + => ReplyAsync("Nothing to see here!"); } diff --git a/samples/TextCommandFramework/Program.cs b/samples/TextCommandFramework/Program.cs index ccd23436e7..1ad458830b 100644 --- a/samples/TextCommandFramework/Program.cs +++ b/samples/TextCommandFramework/Program.cs @@ -8,68 +8,62 @@ using System.Threading.Tasks; using TextCommandFramework.Services; -namespace TextCommandFramework +namespace TextCommandFramework; + +// This is a minimal example of using Discord.Net's command +// framework - by no means does it show everything the framework +// is capable of. +// +// 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 { - // This is a minimal example of using Discord.Net's command - // framework - by no means does it show everything the framework - // is capable of. - // - // 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 + // There is no need to implement IDisposable like before as we are + // using dependency injection, which handles calling Dispose for us. + public static async Task Main(string[] args) { - // There is no need to implement IDisposable like before as we are - // using dependency injection, which handles calling Dispose for us. - static void Main(string[] args) - => new Program().MainAsync().GetAwaiter().GetResult(); - - public async Task MainAsync() - { - // You should dispose a service provider created using ASP.NET - // when you are finished using it, at the end of your app's lifetime. - // If you use another dependency injection framework, you should inspect - // its documentation for the best way to do this. - using (var services = ConfigureServices()) - { - var client = services.GetRequiredService(); + // You should dispose a service provider created using ASP.NET + // when you are finished using it, at the end of your app's lifetime. + // If you use another dependency injection framework, you should inspect + // its documentation for the best way to do this. + await using var services = ConfigureServices(); + var client = services.GetRequiredService(); - client.Log += LogAsync; - services.GetRequiredService().Log += LogAsync; + client.Log += LogAsync; + services.GetRequiredService().Log += LogAsync; - // Tokens should be considered secret data and never hard-coded. - // We can read from the environment variable to avoid hard coding. - await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); - await client.StartAsync(); + // Tokens should be considered secret data and never hard-coded. + // We can read from the environment variable to avoid hard coding. + await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); + await client.StartAsync(); - // Here we initialize the logic required to register our commands. - await services.GetRequiredService().InitializeAsync(); + // Here we initialize the logic required to register our commands. + await services.GetRequiredService().InitializeAsync(); - await Task.Delay(Timeout.Infinite); - } - } + await Task.Delay(Timeout.Infinite); + } - private Task LogAsync(LogMessage log) - { - Console.WriteLine(log.ToString()); + private static Task LogAsync(LogMessage log) + { + Console.WriteLine(log.ToString()); - return Task.CompletedTask; - } + return Task.CompletedTask; + } - private ServiceProvider ConfigureServices() - { - return new ServiceCollection() - .AddSingleton(new DiscordSocketConfig - { - GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent - }) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .BuildServiceProvider(); - } + private static ServiceProvider ConfigureServices() + { + return new ServiceCollection() + .AddSingleton(new DiscordSocketConfig + { + GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent + }) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); } } diff --git a/samples/TextCommandFramework/Services/CommandHandlingService.cs b/samples/TextCommandFramework/Services/CommandHandlingService.cs index 5dd4804242..a87ff73b43 100644 --- a/samples/TextCommandFramework/Services/CommandHandlingService.cs +++ b/samples/TextCommandFramework/Services/CommandHandlingService.cs @@ -6,70 +6,69 @@ using System.Reflection; using System.Threading.Tasks; -namespace TextCommandFramework.Services +namespace TextCommandFramework.Services; + +public class CommandHandlingService { - public class CommandHandlingService - { - private readonly CommandService _commands; - private readonly DiscordSocketClient _discord; - private readonly IServiceProvider _services; + private readonly CommandService _commands; + private readonly DiscordSocketClient _discord; + private readonly IServiceProvider _services; - public CommandHandlingService(IServiceProvider services) - { - _commands = services.GetRequiredService(); - _discord = services.GetRequiredService(); - _services = services; + public CommandHandlingService(IServiceProvider services) + { + _commands = services.GetRequiredService(); + _discord = services.GetRequiredService(); + _services = services; - // Hook CommandExecuted to handle post-command-execution logic. - _commands.CommandExecuted += CommandExecutedAsync; - // Hook MessageReceived so we can process each message to see - // if it qualifies as a command. - _discord.MessageReceived += MessageReceivedAsync; - } + // Hook CommandExecuted to handle post-command-execution logic. + _commands.CommandExecuted += CommandExecutedAsync; + // Hook MessageReceived so we can process each message to see + // if it qualifies as a command. + _discord.MessageReceived += MessageReceivedAsync; + } - public async Task InitializeAsync() - { - // Register modules that are public and inherit ModuleBase. - await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); - } + public async Task InitializeAsync() + { + // Register modules that are public and inherit ModuleBase. + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + } - public async Task MessageReceivedAsync(SocketMessage rawMessage) - { - // Ignore system messages, or messages from other bots - if (!(rawMessage is SocketUserMessage message)) - return; - if (message.Source != MessageSource.User) - return; + public async Task MessageReceivedAsync(SocketMessage rawMessage) + { + // Ignore system messages, or messages from other bots + if (!(rawMessage is SocketUserMessage message)) + return; + if (message.Source != MessageSource.User) + return; - // This value holds the offset where the prefix ends - var argPos = 0; - // Perform prefix check. You may want to replace this with - // (!message.HasCharPrefix('!', ref argPos)) - // for a more traditional command format like !help. - if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) - return; + // This value holds the offset where the prefix ends + var argPos = 0; + // Perform prefix check. You may want to replace this with + // (!message.HasCharPrefix('!', ref argPos)) + // for a more traditional command format like !help. + if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) + return; - var context = new SocketCommandContext(_discord, message); - // Perform the execution of the command. In this method, - // the command service will perform precondition and parsing check - // then execute the command if one is matched. - await _commands.ExecuteAsync(context, argPos, _services); - // Note that normally a result will be returned by this format, but here - // we will handle the result in CommandExecutedAsync, - } + var context = new SocketCommandContext(_discord, message); + // Perform the execution of the command. In this method, + // the command service will perform precondition and parsing check + // then execute the command if one is matched. + await _commands.ExecuteAsync(context, argPos, _services); + // Note that normally a result will be returned by this format, but here + // we will handle the result in CommandExecutedAsync, + } - public async Task CommandExecutedAsync(Optional command, ICommandContext context, IResult result) - { - // command is unspecified when there was a search failure (command not found); we don't care about these errors - if (!command.IsSpecified) - return; + public async Task CommandExecutedAsync(Optional command, ICommandContext context, IResult result) + { + // command is unspecified when there was a search failure (command not found); we don't care about these errors + if (!command.IsSpecified) + return; - // the command was successful, we don't care about this result, unless we want to log that a command succeeded. - if (result.IsSuccess) - return; + // the command was successful, we don't care about this result, unless we want to log that a command succeeded. + if (result.IsSuccess) + return; - // the command failed, let's notify the user that something happened. - await context.Channel.SendMessageAsync($"error: {result}"); - } + // the command failed, let's notify the user that something happened. + await context.Channel.SendMessageAsync($"error: {result}"); } } diff --git a/samples/TextCommandFramework/Services/PictureService.cs b/samples/TextCommandFramework/Services/PictureService.cs index 5c8e1dd89c..f09d060b93 100644 --- a/samples/TextCommandFramework/Services/PictureService.cs +++ b/samples/TextCommandFramework/Services/PictureService.cs @@ -2,19 +2,18 @@ using System.Net.Http; using System.Threading.Tasks; -namespace TextCommandFramework.Services +namespace TextCommandFramework.Services; + +public class PictureService { - public class PictureService - { - private readonly HttpClient _http; + private readonly HttpClient _http; - public PictureService(HttpClient http) - => _http = http; + public PictureService(HttpClient http) + => _http = http; - public async Task GetCatPictureAsync() - { - var resp = await _http.GetAsync("https://cataas.com/cat"); - return await resp.Content.ReadAsStreamAsync(); - } + public async Task GetCatPictureAsync() + { + var resp = await _http.GetAsync("https://cataas.com/cat"); + return await resp.Content.ReadAsStreamAsync(); } } diff --git a/samples/TextCommandFramework/_TextCommandFramework.csproj b/samples/TextCommandFramework/_TextCommandFramework.csproj index d70fd9d3bd..fdf7990e37 100644 --- a/samples/TextCommandFramework/_TextCommandFramework.csproj +++ b/samples/TextCommandFramework/_TextCommandFramework.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/samples/WebhookClient/Program.cs b/samples/WebhookClient/Program.cs index 7b25393020..a547a2a1ef 100644 --- a/samples/WebhookClient/Program.cs +++ b/samples/WebhookClient/Program.cs @@ -2,33 +2,28 @@ using Discord.Webhook; using System.Threading.Tasks; -namespace WebHookClient +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 +// To a channel specific URL to send a message to that channel. +class Program { - // 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 - // To a channel specific URL to send a message to that channel. - class Program + public static async Task Main() { - static void Main(string[] args) - => new Program().MainAsync().GetAwaiter().GetResult(); + // The webhook url follows the format https://discord.com/api/webhooks/{id}/{token} + // Because anyone with the webhook URL can use your webhook + // you should NOT hard code the URL or ID + token into your application. + using var client = new DiscordWebhookClient("https://discord.com/api/webhooks/123/abc123"); - public async Task MainAsync() + var embed = new EmbedBuilder { - // The webhook url follows the format https://discord.com/api/webhooks/{id}/{token} - // Because anyone with the webhook URL can use your webhook - // you should NOT hard code the URL or ID + token into your application. - using (var client = new DiscordWebhookClient("https://discord.com/api/webhooks/123/abc123")) - { - var embed = new EmbedBuilder - { - Title = "Test Embed", - Description = "Test Description" - }; + Title = "Test Embed", + Description = "Test Description" + }; - // Webhooks are able to send multiple embeds per message - // As such, your embeds must be passed as a collection. - await client.SendMessageAsync(text: "Send a message to this webhook!", embeds: new[] { embed.Build() }); - } - } + // Webhooks are able to send multiple embeds per message + // As such, your embeds must be passed as a collection. + await client.SendMessageAsync(text: "Send a message to this webhook!", embeds: new[] { embed.Build() }); } } diff --git a/samples/WebhookClient/_WebhookClient.csproj b/samples/WebhookClient/_WebhookClient.csproj index 3e0375a705..749dcaaa6f 100644 --- a/samples/WebhookClient/_WebhookClient.csproj +++ b/samples/WebhookClient/_WebhookClient.csproj @@ -7,7 +7,7 @@ - + From b2907d8d8e730835439f2e92d88afa56187c9956 Mon Sep 17 00:00:00 2001 From: Misha133 Date: Thu, 11 Jan 2024 18:18:52 +0300 Subject: [PATCH 2/3] more docs --- .../samples/first-bot/async-context.cs | 4 +-- .../samples/first-bot/client.cs | 4 +-- .../samples/first-bot/complete.cs | 6 ++-- .../samples/first-bot/logging.cs | 2 +- .../samples/first-bot/message.cs | 4 +-- .../samples/first-bot/structure.cs | 30 +++++++------------ .../getting_started/samples/project.xml | 4 +-- 7 files changed, 21 insertions(+), 33 deletions(-) diff --git a/docs/guides/getting_started/samples/first-bot/async-context.cs b/docs/guides/getting_started/samples/first-bot/async-context.cs index 98a3cea15b..266a7320b6 100644 --- a/docs/guides/getting_started/samples/first-bot/async-context.cs +++ b/docs/guides/getting_started/samples/first-bot/async-context.cs @@ -1,8 +1,6 @@ public class Program { - public static Task Main(string[] args) => new Program().MainAsync(); - - public async Task MainAsync() + public static async Task Main() { } } \ No newline at end of file diff --git a/docs/guides/getting_started/samples/first-bot/client.cs b/docs/guides/getting_started/samples/first-bot/client.cs index 25be9f8071..3528fa0120 100644 --- a/docs/guides/getting_started/samples/first-bot/client.cs +++ b/docs/guides/getting_started/samples/first-bot/client.cs @@ -1,6 +1,6 @@ -private DiscordSocketClient _client; +private static DiscordSocketClient _client; -public async Task MainAsync() +public static async Task Main() { _client = new DiscordSocketClient(); diff --git a/docs/guides/getting_started/samples/first-bot/complete.cs b/docs/guides/getting_started/samples/first-bot/complete.cs index 5420564357..50d1411bf7 100644 --- a/docs/guides/getting_started/samples/first-bot/complete.cs +++ b/docs/guides/getting_started/samples/first-bot/complete.cs @@ -1,10 +1,8 @@ public class Program { - private DiscordSocketClient _client; + private static DiscordSocketClient _client; - public static Task Main(string[] args) => new Program().MainAsync(); - - public async Task MainAsync() + public async Task Main() { _client = new DiscordSocketClient(); _client.Log += Log; diff --git a/docs/guides/getting_started/samples/first-bot/logging.cs b/docs/guides/getting_started/samples/first-bot/logging.cs index c6ffc406e5..0df57c8b12 100644 --- a/docs/guides/getting_started/samples/first-bot/logging.cs +++ b/docs/guides/getting_started/samples/first-bot/logging.cs @@ -1,4 +1,4 @@ -private Task Log(LogMessage msg) +private static Task Log(LogMessage msg) { Console.WriteLine(msg.ToString()); return Task.CompletedTask; diff --git a/docs/guides/getting_started/samples/first-bot/message.cs b/docs/guides/getting_started/samples/first-bot/message.cs index f636d6f350..9e2c31ac18 100644 --- a/docs/guides/getting_started/samples/first-bot/message.cs +++ b/docs/guides/getting_started/samples/first-bot/message.cs @@ -1,11 +1,11 @@ -public async Task MainAsync() +public static async Task Main() { // ... _client.MessageReceived += MessageReceived; // ... } -private async Task MessageReceived(SocketMessage message) +private static async Task MessageReceived(SocketMessage message) { if (message.Content == "!ping") { diff --git a/docs/guides/getting_started/samples/first-bot/structure.cs b/docs/guides/getting_started/samples/first-bot/structure.cs index 4e64b17320..1059698702 100644 --- a/docs/guides/getting_started/samples/first-bot/structure.cs +++ b/docs/guides/getting_started/samples/first-bot/structure.cs @@ -10,21 +10,7 @@ class Program { // Program entry point - static Task Main(string[] args) - { - // Call the Program constructor, followed by the - // MainAsync method and wait until it finishes (which should be never). - return new Program().MainAsync(); - } - - private readonly DiscordSocketClient _client; - - // Keep the CommandService and DI container around for use with commands. - // These two types require you install the Discord.Net.Commands package. - private readonly CommandService _commands; - private readonly IServiceProvider _services; - - private Program() + static async Task Main(string[] args) { _client = new DiscordSocketClient(new DiscordSocketConfig { @@ -58,8 +44,14 @@ private Program() // Setup your DI container. _services = ConfigureServices(); - } + + private static DiscordSocketClient _client; + + // Keep the CommandService and DI container around for use with commands. + // These two types require you install the Discord.Net.Commands package. + private static CommandService _commands; + private static IServiceProvider _services; // If any services require the client, or the CommandService, or something else you keep on hand, // pass them as parameters into this method as needed. @@ -110,7 +102,7 @@ private static Task Log(LogMessage message) return Task.CompletedTask; } - private async Task MainAsync() + private static async Task MainAsync() { // Centralize the logic for commands into a separate method. await InitCommands(); @@ -125,7 +117,7 @@ await _client.LoginAsync(TokenType.Bot, await Task.Delay(Timeout.Infinite); } - private async Task InitCommands() + private static async Task InitCommands() { // Either search the program and add all Module classes that can be found. // Module classes MUST be marked 'public' or they will be ignored. @@ -140,7 +132,7 @@ private async Task InitCommands() _client.MessageReceived += HandleCommandAsync; } - private async Task HandleCommandAsync(SocketMessage arg) + private static async Task HandleCommandAsync(SocketMessage arg) { // Bail out if it's a System Message. var msg = arg as SocketUserMessage; diff --git a/docs/guides/getting_started/samples/project.xml b/docs/guides/getting_started/samples/project.xml index 179d3f97b7..b386c0a797 100644 --- a/docs/guides/getting_started/samples/project.xml +++ b/docs/guides/getting_started/samples/project.xml @@ -7,11 +7,11 @@ Exe - netcoreapp2.1 + net6.0 - + From a09847f7f3820425feff040535d065dfb8cc99c6 Mon Sep 17 00:00:00 2001 From: Misha133 Date: Thu, 11 Jan 2024 18:21:55 +0300 Subject: [PATCH 3/3] moar docs --- docs/guides/concepts/samples/events.cs | 8 +++----- .../dependency_injection/samples/program.cs | 16 ++++------------ 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/docs/guides/concepts/samples/events.cs b/docs/guides/concepts/samples/events.cs index 29542ef2f5..eb66d59b35 100644 --- a/docs/guides/concepts/samples/events.cs +++ b/docs/guides/concepts/samples/events.cs @@ -3,10 +3,8 @@ public class Program { - private DiscordSocketClient _client; - static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); - - public async Task MainAsync() + private static DiscordSocketClient _client; + public static async Task MainAsync() { // When working with events that have Cacheable parameters, // you must enable the message cache in your config settings if you plan to @@ -27,7 +25,7 @@ public async Task MainAsync() await Task.Delay(-1); } - private async Task MessageUpdated(Cacheable before, SocketMessage after, ISocketMessageChannel channel) + private static async Task MessageUpdated(Cacheable 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(); diff --git a/docs/guides/dependency_injection/samples/program.cs b/docs/guides/dependency_injection/samples/program.cs index 6d985319a8..14651f0356 100644 --- a/docs/guides/dependency_injection/samples/program.cs +++ b/docs/guides/dependency_injection/samples/program.cs @@ -1,15 +1,7 @@ public class Program { - private readonly IServiceProvider _serviceProvider; - - public Program() - { - _serviceProvider = CreateProvider(); - } - - static void Main(string[] args) - => new Program().RunAsync(args).GetAwaiter().GetResult(); - + private static IServiceProvider _serviceProvider; + static IServiceProvider CreateProvider() { var collection = new ServiceCollection(); @@ -17,8 +9,8 @@ static IServiceProvider CreateProvider() return collection.BuildServiceProvider(); } - async Task RunAsync(string[] args) + static async Task Main(string[] args) { - //... + _serviceProvider = CreateProvider(); } }