diff --git a/DisCatSharp.Docs/articles/application_commands/events.md b/DisCatSharp.Docs/articles/modules/application_commands/events.md similarity index 98% rename from DisCatSharp.Docs/articles/application_commands/events.md rename to DisCatSharp.Docs/articles/modules/application_commands/events.md index 9ce01e13f..d6558ac17 100644 --- a/DisCatSharp.Docs/articles/application_commands/events.md +++ b/DisCatSharp.Docs/articles/modules/application_commands/events.md @@ -1,102 +1,102 @@ --- -uid: application_commands_events +uid: modules_application_commands_events title: Application Commands Events --- # Application Commands events Sometimes we need to add a variety of actions and checks before and after executing a command. We can do this in the commands itself, or we can use special events for this. ## Before execution The simplest example in this case: checking if the command was executed within the guild. Suppose we have a certain class with commands that must be executed ONLY in the guilds: ```cs public class MyGuildCommands : ApplicationCommandsModule { [SlashCommand("mute", "Mute user.")] public static async Task Mute(InteractionContext context) { } [SlashCommand("kick", "Kick user.")] public static async Task Kick(InteractionContext context) { } [SlashCommand("ban", "Ban user.")] public static async Task Ban(InteractionContext context) { } } ``` In this case, the easiest way would be to override the method from [ApplicationCommandsModule](xref:DisCatSharp.ApplicationCommands.ApplicationCommandsModule). ```cs public class MyGuildCommands : ApplicationCommandsModule { public override async Task BeforeSlashExecutionAsync(InteractionContext ctx) { if (ctx.Guild == null) return false; } [SlashCommand("mute", "Mute user.")] public static async Task Mute(InteractionContext context) { } [SlashCommand("kick", "Kick user.")] public static async Task Kick(InteractionContext context) { } [SlashCommand("ban", "Ban user.")] public static async Task Ban(InteractionContext context) { } } ``` Now, before executing any of these commands, the `BeforeSlashExecutionAsync` method will be executed. You can do anything in it, for example, special logging. If you return `true`, then the command method will be executed after that, otherwise the execution will end there. ## After execution If you want to create actions after executing the command, then you need to do the same, but override a different method: ```cs public override async Task AfterSlashExecutionAsync(InteractionContext ctx) { // some actions } ``` ## Context menus You can also add similar actions for the context menus. But this time, you need to override the other methods: ```cs public class MyGuildCommands : ApplicationCommandsModule { public override async Task BeforeContextMenuExecutionAsync(ContextMenuContext ctx) { if (ctx.Guild == null) return false; } public override async Task AfterContextMenuExecutionAsync(ContextMenuContext ctx) { // some actions } } ``` ## Error handling If you want to handle errors in the commands, subscribe to the [SlashCommandErrored](xref:DisCatSharp.ApplicationCommands.SlashCommandErrored) event. It contains a castable field `Exception` with the exception that was thrown during the execution of the command. As example it can be a type of [SlashExecutionChecksFailedException](xref:DisCatSharp.ApplicationCommands.Exceptions.SlashExecutionChecksFailedException) or [ContextMenuExecutionChecksFailedException](xref:DisCatSharp.ApplicationCommands.Exceptions.ContextMenuExecutionChecksFailedException) which contains a list of failed checks. diff --git a/DisCatSharp.Docs/articles/application_commands/intro.md b/DisCatSharp.Docs/articles/modules/application_commands/intro.md similarity index 99% rename from DisCatSharp.Docs/articles/application_commands/intro.md rename to DisCatSharp.Docs/articles/modules/application_commands/intro.md index 286240fe7..6bebf9aec 100644 --- a/DisCatSharp.Docs/articles/application_commands/intro.md +++ b/DisCatSharp.Docs/articles/modules/application_commands/intro.md @@ -1,133 +1,133 @@ --- -uid: application_commands_intro +uid: modules_application_commands_intro title: Application Commands Introduction --- >[!NOTE] > This article assumes you've recently read the article on *[writing your first bot](xref:basics_first_bot)*. # Introduction to App Commands Discord provides built-in commands called: *Application Commands*.
Be sure to install the `DisCatSharp.ApplicationCommands` package from NuGet before continuing. At the moment it is possible to create such commands: - Slash commands - User context menu commands - Message context menu commands ## Writing an Application Commands ### Creation of the first commands >[!NOTE] > In order for the bot to be able to create commands in the guild, it must be added to a guild with `applications.commands` scope. Each command is a method with the attribute [SlashCommand](xref:DisCatSharp.ApplicationCommands.Attributes.SlashCommandAttribute) or [ContextMenu](xref:DisCatSharp.ApplicationCommands.Attributes.ContextMenuAttribute). They must be in classes that inherit from [ApplicationCommandsModule](xref:DisCatSharp.ApplicationCommands.ApplicationCommandsModule). Also, the first argument to the method must be [InteractionContext](xref:DisCatSharp.ApplicationCommands.Context.InteractionContext) or [ContextMenuContext](xref:DisCatSharp.ApplicationCommands.Context.ContextMenuContext). Simple slash command: ```cs public class MyCommand : ApplicationCommandsModule { [SlashCommand("my_command", "This is description of the command.")] public async Task MySlashCommand(InteractionContext context) { } } ``` Simple context menu command: ```cs public class MySecondCommand : ApplicationCommandsModule { [ContextMenu(ApplicationCommandType.User, "My Command")] public async Task MyContextMenuCommand(ContextMenuContext context) { } } ``` Now let's add some actions to the commands, for example, send a reply: ```cs await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() { Content = "Hello :3" }); ``` If the command will be executed for more than 3 seconds, we must response at the beginning of execution and edit it at the end. ```cs await context.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder()); await Task.Delay(5000); // Simulating a long command execution. await ctx.EditResponseAsync(new DiscordWebhookBuilder() { Content = "Hello :3" }); ``` >[!NOTE] > Note that you can make your commands static, but then you cannot use [Dependency Injection](xref:commands_dependency_injection) in them. ### Registration of commands After writing the commands, we must register them. For this we need a [DiscordClient](xref:DisCatSharp.DiscordClient). ```cs var appCommands = client.UseApplicationCommands(); appCommands.RegisterGlobalCommands(); appCommands.RegisterGlobalCommands(); ``` Simple, isn't it? You can register global and guild commands. Global commands will be available on all guilds of which the bot is a member. Guild commands will only appear in a specific guild. >[!NOTE] >Global commands are updated within an hour, so it is recommended to use guild commands for testing and development. To register guild commands, it is enough to specify the Id of the guild as the first argument of the registration method. ```cs var appCommands = client.UseApplicationCommands(); appCommands.RegisterGuildCommands(); appCommands.RegisterGuildCommands(); ``` ## Command Groups Sometimes we may need to combine slash commands into groups. In this case, we need to wrap our class with commands in another class and add the [SlashCommandGroup](xref:DisCatSharp.ApplicationCommands.Attributes.SlashCommandGroupAttribute) attribute. ```cs public class MyCommand : ApplicationCommandsModule { [SlashCommandGroup("my_command", "This is description of the command group.")] public class MyCommandGroup : ApplicationCommandsModule { [SlashCommand("first", "This is description of the command.")] public async Task MySlashCommand(InteractionContext context) { await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() { Content = "This is first subcommand." }); } [SlashCommand("second", "This is description of the command.")] public async Task MySecondCommand(InteractionContext context) { await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() { Content = "This is second subcommand." }); } } } ``` Commands will now be available via `/my_command first` and `/my_command second`. Also, note that both classes must inherit [ApplicationCommandsModule](xref:DisCatSharp.ApplicationCommands.ApplicationCommandsModule). diff --git a/DisCatSharp.Docs/articles/application_commands/modals.md b/DisCatSharp.Docs/articles/modules/application_commands/modals.md similarity index 97% rename from DisCatSharp.Docs/articles/application_commands/modals.md rename to DisCatSharp.Docs/articles/modules/application_commands/modals.md index ba44c618b..fb4aa699e 100644 --- a/DisCatSharp.Docs/articles/application_commands/modals.md +++ b/DisCatSharp.Docs/articles/modules/application_commands/modals.md @@ -1,37 +1,37 @@ --- -uid: application_commands_modals +uid: modules_application_commands_modals title: Modals --- # Modals **The package `DisCatSharp.Interactivity` is required for this to work.** You probably heard about the modal feature in Discord. It's a new feature that allows you to create a popup window that can be used to ask for information from the user. This is a great way to create a more interactive user experience. The code below shows an example application command on how this could look. ```cs using DisCatSharp.Interactivity; using DisCatSharp.Interactivity.Enums; using DisCatSharp.Interactivity.Extensions; ``` ```cs [SlashCommand("modals", "A modal!")] public async Task SendModalAsync(InteractionContext ctx) { DiscordInteractionModalBuilder builder = new DiscordInteractionModalBuilder(); builder.WithCustomId("modal_test"); builder.WithTitle("Modal Test"); builder.AddTextComponent(new DiscordTextComponent(TextComponentStyle.Paragraph, label: "Some input", required: false))); await ctx.CreateModalResponseAsync(builder); var res = await ctx.Client.GetInteractivity().WaitForModalAsync(builder.CustomId, TimeSpan.FromMinutes(1)); if (res.TimedOut) return; await res.Result.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordWebhookBuilder().WithContent(res.Result.Interaction.Data.Components?.First()?.Value ?? "Nothing was submitted.")); } ``` diff --git a/DisCatSharp.Docs/articles/application_commands/options.md b/DisCatSharp.Docs/articles/modules/application_commands/options.md similarity index 99% rename from DisCatSharp.Docs/articles/application_commands/options.md rename to DisCatSharp.Docs/articles/modules/application_commands/options.md index de23d8c9d..65465a7d0 100644 --- a/DisCatSharp.Docs/articles/application_commands/options.md +++ b/DisCatSharp.Docs/articles/modules/application_commands/options.md @@ -1,251 +1,251 @@ --- -uid: application_commands_options +uid: modules_application_commands_options title: Application Commands Options --- # Slash Commands options For slash commands, you can create options. They allow users to submit additional information to commands. Command options can be of the following types: - string - int - long - double - bool - [DiscordUser](xref:DisCatSharp.Entities.DiscordUser) - [DiscordRole](xref:DisCatSharp.Entities.DiscordRole) - [DiscordChannel](xref:DisCatSharp.Entities.DiscordChannel) - [DiscordAttachment](xref:DisCatSharp.Entities.DiscordAttachment) - mentionable (ulong) - Enum ## Basic usage >[!NOTE] >Options can only be added in the slash commands. Context menus do not support this! All of options must contain the [Option](xref:DisCatSharp.ApplicationCommands.Attributes.OptionAttribute) attribute. They should be after [InteractionContext](xref:DisCatSharp.ApplicationCommands.Context.InteractionContext). ```cs public class MyCommand : ApplicationCommandsModule { [SlashCommand("my_command", "This is description of the command.")] public static async Task MySlashCommand(InteractionContext context, [Option("argument", "This is description of the option.")] string firstParam) { await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() { Content = firstParam }); } } ``` ## Choices Sometimes, we need to allow users to choose from several pre-created options. We can of course add a string or long parameter and let users guess the options, but why when we can make things more convenient? We have 3 ways to make choices: - Enums - [Choice Attribute](xref:DisCatSharp.ApplicationCommands.Attributes.ChoiceAttribute) - [Choice Providers](xref:DisCatSharp.ApplicationCommands.Attributes.IChoiceProvider) ### Enums This is the easiest option. We just need to specify the required Enum as a command parameter. ```cs public class MyCommand : ApplicationCommandsModule { [SlashCommand("my_command", "This is description of the command.")] public static async Task MySlashCommand(InteractionContext context, [Option("enum_param", "Description")] MyEnum enumParameter) { } } public enum MyEnum { FirstOption, SecondOption } ``` In this case, the user will be shown this as options: `FirstOption` and `SecondOption`. Therefore, if you want to define different names for options without changing the Enum, you can add a special attribute: ```cs public enum MyEnum { [ChoiceName("First option")] FirstOption, [ChoiceName("Second option")] SecondOption } ``` ### Choice Attribute With this way, you can get rid of unnecessary conversions within the command. To do this, you need to add one or more [Choice Attributes](xref:DisCatSharp.ApplicationCommands.Attributes.ChoiceAttribute) before the [Option](xref:DisCatSharp.ApplicationCommands.Attributes.OptionAttribute) attribute ```cs [SlashCommand("my_command", "This is description of the command.")] public static async Task MySlashCommand(InteractionContext context, [Choice("First option", 1)] [Choice("Second option", 2)] [Option("option", "Description")] long firstParam) { } ``` As the first parameter, [Choice](xref:DisCatSharp.ApplicationCommands.Attributes.ChoiceAttribute) takes a name that will be visible to the user, and the second - a value that will be passed to the command. You can also use strings. ### Choice Provider Perhaps the most difficult way. It consists in writing a method that will generate a list of options when registering commands. This way we don't have to list all of them in the code when there are many of them. To create your own provider, you need to create a class that inherits [IChoiceProvider](xref:DisCatSharp.ApplicationCommands.Attributes.IChoiceProvider) and contains the `Provider()` method. ```cs public class MyChoiceProvider : IChoiceProvider { public Task> Provider() { } } ``` As seen above, the method should return a list of [DiscordApplicationCommandOptionChoice](xref:DisCatSharp.Entities.DiscordApplicationCommandOptionChoice). Now we need to create a list and add items to it: ```cs var options = new List { new DiscordApplicationCommandOptionChoice("First option", 1), new DiscordApplicationCommandOptionChoice("Second option", 2) }; return Task.FromResult(options.AsEnumerable()); ``` Of course you can generate this list as you like. The main thing is that the method should return this list. Now let's add our new provider to the command. ```cs [SlashCommand("my_command", "This is description of the command.")] public static async Task MySlashCommand(InteractionContext context, [ChoiceProvider(typeof(MyChoiceProvider))] [Option("option", "Description")] long option) { } ``` All the code that we got: ```cs public class MyCommand : ApplicationCommandsModule { [SlashCommand("my_command", "This is description of the command.")] public static async Task MySlashCommand(InteractionContext context, [ChoiceProvider(typeof(MyChoiceProvider))] [Option("option", "Description")] long option) { } } public class MyChoiceProvider : IChoiceProvider { public Task> Provider() { var options = new List { new DiscordApplicationCommandOptionChoice("First option", 1), new DiscordApplicationCommandOptionChoice("Second option", 2) }; return Task.FromResult(options.AsEnumerable()); } } ``` That's all, for a better example for [ChoiceProvider](xref:DisCatSharp.ApplicationCommands.Attributes.IChoiceProvider) refer to the examples. ## Autocomplete Autocomplete works in the same way as ChoiceProvider, with one difference: the method that creates the list of choices is triggered not once when the commands are registered, but whenever the user types a command. It is advisable to use this method exactly when you have a list that will be updated while the bot is running. In other cases, when the choices will not change, it is advisable to use the previous methods. Creating an autocomplete is similar to creating a ChoiceProvider with a few changes: ```cs public class MyAutocompleteProvider : IAutocompleteProvider { public async Task> Provider(AutocompleteContext ctx) { var options = new List { new DiscordApplicationCommandAutocompleteChoice("First option", 1), new DiscordApplicationCommandAutocompleteChoice("Second option", 2) }; return Task.FromResult(options.AsEnumerable()); } } ``` The changes are that instead of [IChoiceProvider](xref:DisCatSharp.ApplicationCommands.Attributes.IChoiceProvider), the class inherits [IAutocompleteProvider](xref:DisCatSharp.ApplicationCommands.Attributes.IAutocompleteProvider), and the Provider method should return a list with [DiscordApplicationCommandAutocompleteChoice](xref:DisCatSharp.Entities.DiscordApplicationCommandAutocompleteChoice). Now we add it to the command: ```cs [SlashCommand("my_command", "This is description of the command.")] public static async Task MySlashCommand(InteractionContext context, [Autocomplete(typeof(MyAutocompleteProvider))] [Option("option", "Description", true)] long option) { } ``` Note that we have not only replaced [ChoiceProvider](xref:DisCatSharp.ApplicationCommands.Attributes.ChoiceProviderAttribute) with [Autocomplete](xref:DisCatSharp.ApplicationCommands.Attributes.AutocompleteAttribute), but also added `true` to [Option](xref:DisCatSharp.ApplicationCommands.Attributes.OptionAttribute). ## Channel types Sometimes we may need to give users the ability to select only a certain type of channels, for example, only text, or voice channels. This can be done by adding the [ChannelTypes](xref:DisCatSharp.ApplicationCommands.Attributes.ChannelTypesAttribute) attribute to the option with the [DiscordChannel](xref:DisCatSharp.Entities.DiscordChannel) type. ```cs [SlashCommand("my_command", "This is description of the command.")] public static async Task MySlashCommand(InteractionContext context, [Option("channel", "You can select only text channels."), ChannelTypes(ChannelType.Text)] DiscordChannel channel) { } ``` This will make it possible to select only text channels. ## MinimumValue / MaximumValue Attribute Sometimes we may need to give users the ability to select only a certain range of values. This can be done by adding the [MinimumValue](xref:DisCatSharp.ApplicationCommands.Attributes.MinimumValueAttribute) and [MaximumValue](xref:DisCatSharp.ApplicationCommands.Attributes.MaximumValueAttribute) attribute to the option. It can be used only for the types `double`, `int` and `long` tho. ```cs [SlashCommand("my_command", "This is description of the command.")] public static async Task MySlashCommand(InteractionContext context, [Option("number", "You can select only a certain range."), MinimumValue(50), MaximumValue(100)] int numbers) { } ``` ## MinimumLength / MaximumLength Attribute Sometimes we may need to limit the user to a certain string length. This can be done by adding the [MinimumLength](xref:DisCatSharp.ApplicationCommands.Attributes.MinimumLengthAttribute) and [MaximumLength](xref:DisCatSharp.ApplicationCommands.Attributes.MaximumLengthAttribute) attribute to the option. It can be used only for the type `string`. ```cs [SlashCommand("my_command", "This is description of the command.")] public static async Task MySlashCommand(InteractionContext context, [Option("text", "You can only send text with a length between 10 and 50 characters."), MinimumLength(10), MaximumLength(50)] string text) { } ``` diff --git a/DisCatSharp.Docs/articles/application_commands/paginated_modals.md b/DisCatSharp.Docs/articles/modules/application_commands/paginated_modals.md similarity index 98% rename from DisCatSharp.Docs/articles/application_commands/paginated_modals.md rename to DisCatSharp.Docs/articles/modules/application_commands/paginated_modals.md index 9b81dee22..1246e575c 100644 --- a/DisCatSharp.Docs/articles/application_commands/paginated_modals.md +++ b/DisCatSharp.Docs/articles/modules/application_commands/paginated_modals.md @@ -1,52 +1,52 @@ --- -uid: application_commands_paginated_modals +uid: modules_application_commands_paginated_modals title: Paginated Modals --- # Paginated Modals **The package `DisCatSharp.Interactivity` is required for this to work.** You may need multi-step modals to collect a variety of information from a user. We implemented an easy way of doing this with paginated modals. You simply construct all your modals, call `DiscordInteraction.CreatePaginatedModalResponseAsync` and you're good to go. After the user submitted all modals, you'll get back a `PaginatedModalResponse` which has a `TimedOut` bool, the `DiscordInteraction` that was used to submit the last modal and a `IReadOnlyDictionary` with the component custom ids as key. The code below shows an example application command on how this could look. ```cs using DisCatSharp.Interactivity; using DisCatSharp.Interactivity.Enums; using DisCatSharp.Interactivity.Extensions; ``` ```cs [SlashCommand("paginated-modals", "Paginated modals!")] public async Task PaginatedModals(InteractionContext ctx) { _ = Task.Run(async () => { var responses = await ctx.Interaction.CreatePaginatedModalResponseAsync( new List() { new ModalPage(new DiscordInteractionModalBuilder().WithTitle("First Title") .AddModalComponents(new DiscordTextComponent(TextComponentStyle.Small, "title", "Title", "Name", 0, 250, false))), new ModalPage(new DiscordInteractionModalBuilder().WithTitle("Second Title") .AddModalComponents(new DiscordTextComponent(TextComponentStyle.Small, "title1", "Next Modal", "Some value here")) .AddModalComponents(new DiscordTextComponent(TextComponentStyle.Paragraph, "description1", "Some bigger thing here", required: false))), new ModalPage(new DiscordInteractionModalBuilder().WithTitle("Third Title") .AddModalComponents(new DiscordTextComponent(TextComponentStyle.Small, "title2", "Title2", "Even more here", 0, 250, false)) .AddModalComponents(new DiscordTextComponent(TextComponentStyle.Paragraph, "description2", "and stuff here", required: false))), }); // If the user didn't submit all modals, TimedOut will be true. We return the command as there is nothing to handle. if (responses.TimedOut) return; // We simply throw all response into the Console, you can do whatever with this. foreach (var b in responses.Responses) Console.WriteLine(b.ToString()); // We use EditOriginalResponseAsync here because CreatePaginatedModalResponseAsync responds to the last modal with a thinking state. await responses.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent("Success")); }); } ``` diff --git a/DisCatSharp.Docs/articles/application_commands/translations/reference.md b/DisCatSharp.Docs/articles/modules/application_commands/translations/reference.md similarity index 99% rename from DisCatSharp.Docs/articles/application_commands/translations/reference.md rename to DisCatSharp.Docs/articles/modules/application_commands/translations/reference.md index a3f752660..2fbe085d1 100644 --- a/DisCatSharp.Docs/articles/application_commands/translations/reference.md +++ b/DisCatSharp.Docs/articles/modules/application_commands/translations/reference.md @@ -1,114 +1,114 @@ --- -uid: application_commands_translations_reference +uid: modules_application_commands_translations_reference title: Translation Reference --- # Translation Reference > [!NOTE] > DisCatSharp uses [JSON](https://www.json.org) to inject the translations of [Application Commands](https://discord.com/developers/docs/interactions/application-commands). ## Command Object | Key | Value | Description | | ------------------------ | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | name | string | name of the application command | | description? | string | description of the application command | | type | int | [type](#application-command-type) of application command, used to map command types | | name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command name | | description_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command description, only valid for slash commands | | options | array of [Option Objects](#option-object) | array of option objects containing translations | ### Application Command Type | Type | Value | | ---------------------------- | ----- | | Slash Command | 1 | | User Context Menu Command | 2 | | Message Context Menu Command | 3 | ## Command Group Object | Key | Value | Description | | ------------------------ | --------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | name | string | name of the application command group | | description? | string | description of the application command group | | name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command group name | | description_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command group description | | commands | array of [Command Objects](#command-object) | array of command objects containing translations | | groups | array of [Sub Command Group Objects](#sub-command-group-object) | array of sub command group objects containing translations | ## Sub Command Group Object | Key | Value | Description | | ------------------------ | --------------------------------------------- | -------------------------------------------------------------------------------------- | | name | string | name of the application command sub group | | description? | string | description of the application command group | | name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command sub group name | | description_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command sub group description | | commands | array of [Command Objects](#command-object) | array of command objects containing translations | ## Option Object | Key | Value | Description | | ------------------------ | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | | name | string | name of the application command option | | description? | string | description of the application command group | | name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command option name | | description_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command option description | | choices | array of [Option Choice Objects](#option-choice-object) | array of option choice objects containing translations | ## Option Choice Object | Key | Value | Description | | ------------------------ | --------------------------------------------- | ----------------------------------------------------------------------------------- | | name | string | name of the application command option choice | | name_translations | array of [Translation KVPs](#translation-kvp) | array of translation key-value-pairs for the application command option choice name | ## Translation KVP A translation object is a key-value-pair of `"locale": "value"`. ### Example Translation Array: ```json { "en-US": "Hello", "de": "Hallo" } ``` ## Valid Locales | Locale | Language | | ------ | --------------------- | | da | Danish | | de | German | | en-GB | English, UK | | en-US | English, US | | es-ES | Spanish | | fr | French | | hr | Croatian | | it | Italian | | lt | Lithuanian | | hu | Hungarian | | nl | Dutch | | no | Norwegian | | pl | Polish | | pt-BR | Portuguese, Brazilian | | ro | Romanian, Romania | | fi | Finnish | | sv-SE | Swedish | | vi | Vietnamese | | tr | Turkish | | cs | Czech | | el | Greek | | bg | Bulgarian | | ru | Russian | | uk | Ukrainian | | hi | Hindi | | th | Thai | | zh-CN | Chinese, China | | ja | Japanese | | zh-TW | Chinese, Taiwan | | ko | Korean | diff --git a/DisCatSharp.Docs/articles/application_commands/translations/using.md b/DisCatSharp.Docs/articles/modules/application_commands/translations/using.md similarity index 99% rename from DisCatSharp.Docs/articles/application_commands/translations/using.md rename to DisCatSharp.Docs/articles/modules/application_commands/translations/using.md index a346d7eec..c2a0fbe99 100644 --- a/DisCatSharp.Docs/articles/application_commands/translations/using.md +++ b/DisCatSharp.Docs/articles/modules/application_commands/translations/using.md @@ -1,201 +1,201 @@ --- -uid: application_commands_translations_using +uid: modules_application_commands_translations_using title: Using Translations --- # Using Translations ## Why Do We Outsource Translation In External JSON Files Pretty simple: It's common to have translations external stored. This makes it easier to modify them, while keeping the code itself clean. ## Adding Translations Translations are added the same way like permissions are added to Application Commands: ```cs const string TRANSLATION_PATH = "translations/"; Client.GetApplicationCommands().RegisterGuildCommands(1215484634894646844, translations => { string json = File.ReadAllText(TRANSLATION_PATH + "my_command.json"); translations.AddTranslation(json); }); Client.GetApplicationCommands().RegisterGuildCommands(1215484634894646844, translations => { string json = File.ReadAllText(TRANSLATION_PATH + "my_simple_command.json"); translations.AddTranslation(json); }); ``` > [!WARNING] > If you add a translation to a class, you have to supply translations for every command in this class. Otherwise it will fail. ## Creating The Translation JSON We split the translation in two categories. One for slash command groups and one for normal slash commands and context menu commands. The `name` key in the JSON will be mapped to the command / option / choice names internally. ### Translation For Slash Command Groups Imagine, your class look like the following example: ```cs public class MyCommand : ApplicationCommandsModule { [SlashCommandGroup("my_command", "This is description of the command group.")] public class MyCommandGroup : ApplicationCommandsModule { [SlashCommand("first", "This is description of the command.")] public async Task MySlashCommand(InteractionContext context) { await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() { Content = "This is first subcommand." }); } [SlashCommand("second", "This is description of the command.")] public async Task MySecondCommand(InteractionContext context, [Option("value", "Some string value.")] string value) { await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() { Content = "This is second subcommand. The value was " + value }); } } } ``` The translation json is a object of [Command Group Objects](xref:application_commands_translations_reference#command-group-object) A correct translation json for english and german would look like that: ```json [ { "name": "my_command", "name_translations": { "en-US": "my_command", "de": "mein_befehl" }, "description_translations": { "en-US": "This is description of the command group.", "de": "Das ist die description der Befehl Gruppe." }, "commands": [ { "name": "first", "type": 1, // Type 1 for slash command "name_translations": { "en-US": "first", "de": "erste" }, "description_translations": { "en-US": "This is description of the command.", "de": "Das ist die Beschreibung des Befehls." } }, { "name": "second", "type": 1, // Type 1 for slash command "name_translations": { "en-US": "second", "de": "zweite" }, "description_translations": { "en-US": "This is description of the command.", "de": "Das ist die Beschreibung des Befehls." }, "options": [ { "name": "value", "name_translations": { "en-US": "value", "de": "wert" }, "description_translations": { "en-US": "Some string value.", "de": "Ein string Wert." } } ] } ] } ] ``` ### Translation For Slash Commands & Context Menu Commands Now imagine, that your class look like this example: ```cs public class MySimpleCommands : ApplicationCommandsModule { [SlashCommand("my_command", "This is description of the command.")] public async Task MySlashCommand(InteractionContext context) { } [ContextMenu(ApplicationCommandType.User, "My Command")] public async Task MyContextMenuCommand(ContextMenuContext context) { } } ``` The slash command is a simple [Command Object](xref:application_commands_translations_reference#command-object). Same goes for the context menu command, but note that it can't have a description. Slash Commands has the [type](xref:application_commands_translations_reference#application-command-type) `1` and context menu commands the [type](xref:application_commands_translations_reference#application-command-type) `2` or `3`. We use this to determine, where the translation belongs to. Please note that the description field is optional. We suggest setting it for slash commands if you want to use our translation generator, which we're building right now. Context menu commands can't have a description, so omit it. A correct json for this example would look like that: ```json [ { "name":"my_command", "description": "This is description of the command.", "type": 1, // Type 1 for slash command "name_translations":{ "en-US":"my_command", "de":"mein_befehl" }, "description_translations":{ "en-US":"This is description of the command.", "de":"Das ist die Beschreibung des Befehls." } }, { "name":"My Command", "type": 2, // Type 2 for user context menu command "name_translations":{ "en-US":"My Command", "de":"Mein Befehl" } } ] ``` ## Available Locales Discord has a limited choice of locales, in particular, the ones you can select in the client. To see the available locales, visit [this](xref:application_commands_translations_reference#valid-locales) page. ## Can We Get The User And Guild Locale? Yes, you can! Discord sends the user on all [interaction types](xref:DisCatSharp.InteractionType), except `Ping`. We introduced two new properties `Locale` and `GuildLocale` on [InteractionContext](xref:DisCatSharp.ApplicationCommands.Context.InteractionContext), [ContextMenuContext](xref:DisCatSharp.ApplicationCommands.Context.ContextMenuContext), [AutoCompleteContext](xref:DisCatSharp.ApplicationCommands.Context.AutocompleteContext) and [DiscordInteraction](xref:DisCatSharp.Entities.DiscordInteraction). `Locale` is the locale of the user and always represented. `GuildLocale` is only represented, when the interaction is **not** in a DM. Furthermore we cache known user locales on the [DiscordUser](xref:DisCatSharp.Entities.DiscordUser#DisCatSharp_Entities_DiscordUser_Locale) object. diff --git a/DisCatSharp.Docs/articles/audio/lavalink/configuration.md b/DisCatSharp.Docs/articles/modules/audio/lavalink/configuration.md similarity index 100% rename from DisCatSharp.Docs/articles/audio/lavalink/configuration.md rename to DisCatSharp.Docs/articles/modules/audio/lavalink/configuration.md diff --git a/DisCatSharp.Docs/articles/audio/lavalink/music_commands.md b/DisCatSharp.Docs/articles/modules/audio/lavalink/music_commands.md similarity index 100% rename from DisCatSharp.Docs/articles/audio/lavalink/music_commands.md rename to DisCatSharp.Docs/articles/modules/audio/lavalink/music_commands.md diff --git a/DisCatSharp.Docs/articles/audio/lavalink/setup.md b/DisCatSharp.Docs/articles/modules/audio/lavalink/setup.md similarity index 100% rename from DisCatSharp.Docs/articles/audio/lavalink/setup.md rename to DisCatSharp.Docs/articles/modules/audio/lavalink/setup.md diff --git a/DisCatSharp.Docs/articles/audio/voicenext/prerequisites.md b/DisCatSharp.Docs/articles/modules/audio/voicenext/prerequisites.md similarity index 100% rename from DisCatSharp.Docs/articles/audio/voicenext/prerequisites.md rename to DisCatSharp.Docs/articles/modules/audio/voicenext/prerequisites.md diff --git a/DisCatSharp.Docs/articles/audio/voicenext/receive.md b/DisCatSharp.Docs/articles/modules/audio/voicenext/receive.md similarity index 100% rename from DisCatSharp.Docs/articles/audio/voicenext/receive.md rename to DisCatSharp.Docs/articles/modules/audio/voicenext/receive.md diff --git a/DisCatSharp.Docs/articles/audio/voicenext/transmit.md b/DisCatSharp.Docs/articles/modules/audio/voicenext/transmit.md similarity index 100% rename from DisCatSharp.Docs/articles/audio/voicenext/transmit.md rename to DisCatSharp.Docs/articles/modules/audio/voicenext/transmit.md diff --git a/DisCatSharp.Docs/articles/commands/argument_converters.md b/DisCatSharp.Docs/articles/modules/commandsnext/argument_converters.md similarity index 95% rename from DisCatSharp.Docs/articles/commands/argument_converters.md rename to DisCatSharp.Docs/articles/modules/commandsnext/argument_converters.md index c2e7ed760..e88a91866 100644 --- a/DisCatSharp.Docs/articles/commands/argument_converters.md +++ b/DisCatSharp.Docs/articles/modules/commandsnext/argument_converters.md @@ -1,60 +1,60 @@ --- -uid: commands_argument_converters +uid: modules_commandsnext_argument_converters title: Argument Converter --- ## Custom Argument Converter Writing your own argument converter will enable you to convert custom types and replace the functionality of existing converters. Like many things in DisCatSharp, doing this is straightforward and simple. First, create a new class which implements `IArgumentConverter` and its method `ConvertAsync`. Our example will be a boolean converter, so we'll also pass `bool` as the type parameter for `IArgumentConverter`. ```cs public class CustomArgumentConverter : IArgumentConverter { public Task> ConvertAsync(string value, CommandContext ctx) { if (bool.TryParse(value, out var boolean)) { return Task.FromResult(Optional.FromValue(boolean)); - } + } switch (value.ToLower()) { case "yes": case "y": case "t": return Task.FromResult(Optional.FromValue(true)); case "no": case "n": case "f": return Task.FromResult(Optional.FromValue(false)); default: return Task.FromResult(Optional.FromNoValue()); - } - } + } + } } ``` Then register the argument converter with CommandContext. ```cs var discord = new DiscordClient(); var commands = discord.UseCommandsNext(); commands.RegisterConverter(new CustomArgumentConverter()); ```
Once the argument converter is written and registered, we'll be able to use it: ```cs [Command("boolean")] public async Task BooleanCommand(CommandContext ctx, bool boolean) { await ctx.RespondAsync($"Converted to {boolean}"); } ``` ![true](/images/commands_argument_converters_01.png) diff --git a/DisCatSharp.Docs/articles/commands/command_attributes.md b/DisCatSharp.Docs/articles/modules/commandsnext/command_attributes.md similarity index 98% rename from DisCatSharp.Docs/articles/commands/command_attributes.md rename to DisCatSharp.Docs/articles/modules/commandsnext/command_attributes.md index 9600cdcff..8ffcd27a7 100644 --- a/DisCatSharp.Docs/articles/commands/command_attributes.md +++ b/DisCatSharp.Docs/articles/modules/commandsnext/command_attributes.md @@ -1,103 +1,103 @@ --- -uid: commands_command_attributes +uid: modules_commandsnext_command_attributes title: Command Attributes --- ## Built-In Attributes CommandsNext has a variety of built-in attributes to enhance your commands and provide some access control. The majority of these attributes can be applied to your command methods and command groups. - @DisCatSharp.CommandsNext.Attributes.AliasesAttribute - @DisCatSharp.CommandsNext.Attributes.CooldownAttribute - @DisCatSharp.CommandsNext.Attributes.DescriptionAttribute - @DisCatSharp.CommandsNext.Attributes.DontInjectAttribute - @DisCatSharp.CommandsNext.Attributes.HiddenAttribute - @DisCatSharp.CommandsNext.Attributes.ModuleLifespanAttribute - @DisCatSharp.CommandsNext.Attributes.PriorityAttribute - @DisCatSharp.CommandsNext.Attributes.RemainingTextAttribute - @DisCatSharp.CommandsNext.Attributes.RequireBotPermissionsAttribute - @DisCatSharp.CommandsNext.Attributes.RequireCommunityAttribute - @DisCatSharp.CommandsNext.Attributes.RequireDirectMessageAttribute - @DisCatSharp.CommandsNext.Attributes.RequireDiscordCertifiedModeratorAttribute - @DisCatSharp.CommandsNext.Attributes.RequireDiscordEmployeeAttribute - @DisCatSharp.CommandsNext.Attributes.RequireGuildAttribute - @DisCatSharp.CommandsNext.Attributes.RequireGuildOwnerAttribute - @DisCatSharp.CommandsNext.Attributes.RequireMemberVerificationGateAttribute - @DisCatSharp.CommandsNext.Attributes.RequireNsfwAttribute - @DisCatSharp.CommandsNext.Attributes.RequireOwnerAttribute - @DisCatSharp.CommandsNext.Attributes.RequireOwnerOrIdAttribute - @DisCatSharp.CommandsNext.Attributes.RequirePermissionsAttribute - @DisCatSharp.CommandsNext.Attributes.RequirePrefixesAttribute - @DisCatSharp.CommandsNext.Attributes.RequireRolesAttribute - @DisCatSharp.CommandsNext.Attributes.RequireUserPermissionsAttribute - @DisCatSharp.CommandsNext.Attributes.RequireWelcomeScreenAttribute ## Custom Attributes If the above attributes don't meet your needs, CommandsNext also gives you the option of writing your own! Simply create a new class which inherits from `CheckBaseAttribute` and implement the required method. Our example below will only allow a command to be ran during a specified year. ```cs public class RequireYearAttribute : CheckBaseAttribute { public int AllowedYear { get; private set; } public RequireYearAttribute(int year) { AllowedYear = year; } public override Task ExecuteCheckAsync(CommandContext ctx, bool help) { return Task.FromResult(AllowedYear == DateTime.Now.Year); } } ```
You'll also need to apply the `AttributeUsage` attribute to your attribute.
For our example attribute, we'll set it to only be usable once on methods. ```cs [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class RequireYearAttribute : CheckBaseAttribute { // ... } ``` You can provide feedback to the user using the `CommandsNextExtension#CommandErrored` event. ```cs private async Task MainAsync() { var discord = new DiscordClient(); var commands = discord.UseCommandsNext(); commands.CommandErrored += CmdErroredHandler; } private async Task CmdErroredHandler(CommandsNextExtension _, CommandErrorEventArgs e) { var failedChecks = ((ChecksFailedException)e.Exception).FailedChecks; foreach (var failedCheck in failedChecks) { if (failedCheck is RequireYearAttribute) { var yearAttribute = (RequireYearAttribute)failedCheck; await e.Context.RespondAsync($"Only usable during year {yearAttribute.AllowedYear}."); } } } ```
Once you've got all of that completed, you'll be able to use it on a command! ```cs [Command("generic"), RequireYear(2030)] public async Task GenericCommand(CommandContext ctx, string generic) { await ctx.RespondAsync("Generic response."); } ``` ![Generic Image](/images/commands_command_attributes_01.png) diff --git a/DisCatSharp.Docs/articles/commands/command_handler.md b/DisCatSharp.Docs/articles/modules/commandsnext/command_handler.md similarity index 98% rename from DisCatSharp.Docs/articles/commands/command_handler.md rename to DisCatSharp.Docs/articles/modules/commandsnext/command_handler.md index 0ab01d9f3..84f2dc1d6 100644 --- a/DisCatSharp.Docs/articles/commands/command_handler.md +++ b/DisCatSharp.Docs/articles/modules/commandsnext/command_handler.md @@ -1,92 +1,92 @@ --- -uid: commands_command_handler +uid: modules_commandsnext_command_handler title: Custom Command Handler --- ## Custom Command Handler > [!IMPORTANT] > Writing your own handler logic should only be done if *you know what you're doing*.
> You will be responsible for command execution and preventing deadlocks. - + ### Disable Default Handler To begin, we'll need to disable the default command handler provided by CommandsNext.
This is done by setting the `UseDefaultCommandHandler` configuration property to `false`. ```cs var discord = new DiscordClient(); var commands = discord.UseCommandsNext(new CommandsNextConfiguration() { UseDefaultCommandHandler = false }); ``` ### Create Event Handler We'll then write a new handler for the `MessageCreated` event fired from `DiscordClient`. ```cs discord.MessageCreated += CommandHandler; // ... private Task CommandHandler(DiscordClient client, MessageCreateEventArgs e) { // See below ... } ``` This event handler will be our command handler, and you'll need to write the logic for it. ### Handle Commands Start by parsing the message content for a prefix and command string ```cs var cnext = client.GetCommandsNext(); var msg = e.Message; // Check if message has valid prefix. var cmdStart = msg.GetStringPrefixLength("!"); if (cmdStart == -1) return; // Retrieve prefix. var prefix = msg.Content.Substring(0, cmdStart); // Retrieve full command string. var cmdString = msg.Content.Substring(cmdStart); ``` Then provide the command string to `CommandsNextExtension#FindCommand` ```cs var command = cnext.FindCommand(cmdString, out var args); ``` Create a command context using our message and prefix, along with the command and its arguments ```cs var ctx = cnext.CreateContext(msg, prefix, command, args); ``` And pass the context to `CommandsNextExtension#ExecuteCommandAsync` to execute the command. ```cs _ = Task.Run(async () => await cnext.ExecuteCommandAsync(ctx)); // Wrapped in Task.Run() to prevent deadlocks. ``` ### Finished Product Altogether, your implementation should function similarly to the following: ```cs private Task CommandHandler(DiscordClient client, MessageCreateEventArgs e) { var cnext = client.GetCommandsNext(); var msg = e.Message; var cmdStart = msg.GetStringPrefixLength("!"); if (cmdStart == -1) return Task.CompletedTask; var prefix = msg.Content.Substring(0, cmdStart); var cmdString = msg.Content.Substring(cmdStart); var command = cnext.FindCommand(cmdString, out var args); if (command == null) return Task.CompletedTask; var ctx = cnext.CreateContext(msg, prefix, command, args); Task.Run(async () => await cnext.ExecuteCommandAsync(ctx)); - + return Task.CompletedTask; } -``` \ No newline at end of file +``` diff --git a/DisCatSharp.Docs/articles/commands/dependency_injection.md b/DisCatSharp.Docs/articles/modules/commandsnext/dependency_injection.md similarity index 97% rename from DisCatSharp.Docs/articles/commands/dependency_injection.md rename to DisCatSharp.Docs/articles/modules/commandsnext/dependency_injection.md index 994063654..7a271217f 100644 --- a/DisCatSharp.Docs/articles/commands/dependency_injection.md +++ b/DisCatSharp.Docs/articles/modules/commandsnext/dependency_injection.md @@ -1,98 +1,98 @@ --- -uid: commands_dependency_injection +uid: modules_commandsnext_dependency_injection title: Dependency Injection --- ## Dependency Injection As you begin to write more complex commands, you'll find that you need a way to get data in and out of them. Although you *could* use `static` fields to accomplish this, the preferred solution would be *dependency injection*. This would involve placing all required object instances and types (referred to as *services*) in a container, then providing that container to CommandsNext. Each time a command module is instantiated, CommandsNext will then attempt to populate constructor parameters, `public` properties, and `public` fields exposed by the module with instances of objects from the service container. We'll go through a simple example of this process to help you understand better. ### Create a Service Provider To begin, we'll need to create a service provider; this will act as the container for the services you need for your commands. Create a new variable just before you register CommandsNext with your `DiscordClient` and assign it a new instance of `ServiceCollection`. ```cs -var discord = new DiscordClient(); +var discord = new DiscordClient(); var services = new ServiceCollection(); // Right here! var commands = discord.UseCommandsNext(); ``` We'll use `.AddSingleton` to add type `Random` to the collection, then chain that call with the `.BuildServiceProvider()` extension method. The resulting type will be `ServiceProvider`. ```cs var services = new ServiceCollection() .AddSingleton() .BuildServiceProvider(); ``` Then we'll need to provide CommandsNext with our services. ```cs var commands = discord.UseCommandsNext(new CommandsNextConfiguration() { Services = services }); ``` ### Using Your Services Now that we have our services set up, we're able to use them in commands.
We'll be tweaking our [random number command](xref:commands_intro#argument-converters) to demonstrate. Add a new property to the command module named *Rng*. Make sure it has a `public` setter. ```cs public class MyFirstModule : BaseCommandModule { public Random Rng { private get; set; } // Implied public setter. // ... } ``` Modify the *random* command to use our property. ```cs [Command("random")] public async Task RandomCommand(CommandContext ctx, int min, int max) { await ctx.RespondAsync($"Your number is: {Rng.Next(min, max)}"); } ``` Then we can give it a try! ![Command Execution](/images/commands_dependency_injection_01.png)
CommandsNext has automatically injected our singleton `Random` instance into the `Rng` property when our command module was instantiated. Now, for any command that needs `Random`, we can simply declare one as a property, field, or in the module constructor and CommandsNext will take care of the rest. Ain't that neat? ## Lifespans ### Modules By default, all command modules have a singleton lifespan; this means each command module is instantiated once for the lifetime of the CommandsNext instance. However, if the reuse of a module instance is undesired, you also have the option to change the lifespan of a module to *transient* using the `ModulesLifespan` attribute. ```cs [ModuleLifespan(ModuleLifespan.Transient)] public class MyFirstModule : BaseCommandModule { // ... } ``` Transient command modules are instantiated each time one of its containing commands is executed. ### Services In addition to the `.AddSingleton()` extension method, you're also able to use the `.AddScoped()` and `.AddTransient()` extension methods to add services to the collection. The extension method chosen will affect when and how often the service is instantiated. Scoped and transient services should only be used in transient command modules, as singleton modules will always have their services injected once. Lifespan|Instantiated :---:|:--- Singleton|One time when added to the collection. Scoped|Once for each command module. -Transient|Each time its requested. \ No newline at end of file +Transient|Each time its requested. diff --git a/DisCatSharp.Docs/articles/commands/help_formatter.md b/DisCatSharp.Docs/articles/modules/commandsnext/help_formatter.md similarity index 92% rename from DisCatSharp.Docs/articles/commands/help_formatter.md rename to DisCatSharp.Docs/articles/modules/commandsnext/help_formatter.md index e0b0a4a4a..8853f5a5e 100644 --- a/DisCatSharp.Docs/articles/commands/help_formatter.md +++ b/DisCatSharp.Docs/articles/modules/commandsnext/help_formatter.md @@ -1,83 +1,83 @@ --- -uid: commands_help_formatter +uid: modules_commandsnext_help_formatter title: Help Formatter --- ## Custom Help Formatter The built-in help command provided by CommandsNext is generated with a *help formatter*. This simple mechanism is given a command and its subcommands then returns a formatted help message. If you're not happy with the default help formatter, you're able to write your own and customize the output to your liking.
Simply inherit from `BaseHelpFormatter` and provide an implementation for each of the required methods. ```cs public class CustomHelpFormatter : BaseHelpFormatter { // protected DiscordEmbedBuilder _embed; // protected StringBuilder _strBuilder; public CustomHelpFormatter(CommandContext ctx) : base(ctx) { // _embed = new DiscordEmbedBuilder(); // _strBuilder = new StringBuilder(); - + // Help formatters do support dependency injection. - // Any required services can be specified by declaring constructor parameters. + // Any required services can be specified by declaring constructor parameters. // Other required initialization here ... } public override BaseHelpFormatter WithCommand(Command command) { - // _embed.AddField(command.Name, command.Description); + // _embed.AddField(command.Name, command.Description); // _strBuilder.AppendLine($"{command.Name} - {command.Description}"); return this; } public override BaseHelpFormatter WithSubcommands(IEnumerable cmds) { foreach (var cmd in cmds) { - // _embed.AddField(cmd.Name, cmd.Description); + // _embed.AddField(cmd.Name, cmd.Description); // _strBuilder.AppendLine($"{cmd.Name} - {cmd.Description}"); } return this; } public override CommandHelpMessage Build() { // return new CommandHelpMessage(embed: _embed); // return new CommandHelpMessage(content: _strBuilder.ToString()); } } ```
Alternatively, if you're only wanting to make a few small tweaks to the default help, you can write a simple help formatter which inherits from `DefaultHelpFormatter` and modify the inherited `EmbedBuilder` property. ```cs public class CustomHelpFormatter : DefaultHelpFormatter { public CustomHelpFormatter(CommandContext ctx) : base(ctx) { } public override CommandHelpMessage Build() { EmbedBuilder.Color = DiscordColor.SpringGreen; return base.Build(); } } ```
Your final step is to register your help formatter with CommandsNext. ```cs var discord = new DiscordClient(); var commands = discord.UseCommandsNext(); commands.SetHelpFormatter(); ``` That's all there is to it.
![Fresh New Look](/images/commands_help_formatter_01.png) diff --git a/DisCatSharp.Docs/articles/commands/intro.md b/DisCatSharp.Docs/articles/modules/commandsnext/intro.md similarity index 99% rename from DisCatSharp.Docs/articles/commands/intro.md rename to DisCatSharp.Docs/articles/modules/commandsnext/intro.md index 4d8cf77e7..510d38df2 100644 --- a/DisCatSharp.Docs/articles/commands/intro.md +++ b/DisCatSharp.Docs/articles/modules/commandsnext/intro.md @@ -1,330 +1,330 @@ --- -uid: commands_intro +uid: modules_commandsnext_intro title: CommandsNext Introduction --- >[!NOTE] > This article assumes you've recently read the article on *[writing your first bot](xref:basics_first_bot)*. # Introduction to CommandsNext This article will introduce you to some basic concepts of our native command framework: *CommandsNext*.
Be sure to install the `DisCatSharp.CommandsNext` package from NuGet before continuing. ![CommandsNext NuGet Package](/images/commands_intro_01.png)
## Writing a Basic Command ### Create a Command Module A command module is simply a class which acts as a container for your command methods. Instead of registering individual commands, you'd register a single command module which contains multiple commands. There's no limit to the amount of modules you can have, and no limit to the amount of commands each module can contain. For example: you could have a module for moderation commands and a separate module for image commands. This will help you keep your commands organized and reduce the clutter in your project. Our first demonstration will be simple, consisting of one command module with a simple command.
We'll start by creating a new folder named `Commands` which contains a new class named `MyFirstModule`. ![Solution Explorer](/images/commands_intro_02.png) Give this new class `public` access and have it inherit from `BaseCommandModule`. ```cs public class MyFirstModule : BaseCommandModule { } ``` ### Create a Command Method Within our new module, create a method named `GreetCommand` marked as `async` with a `Task` return type. The first parameter of your method *must* be of type `CommandContext`, as required by CommandsNext. ```cs public async Task GreetCommand(CommandContext ctx) { } ``` In the body of our new method, we'll use `CommandContext#RespondAsync` to send a simple message. ```cs await ctx.RespondAsync("Greetings! Thank you for executing me!"); ``` Finally, mark your command method with the `Command` attribute so CommandsNext will know to treat our method as a command method. This attribute takes a single parameter: the name of the command. We'll name our command *greet* to match the name of the method. ```cs [Command("greet")] public async Task GreetCommand(CommandContext ctx) { await ctx.RespondAsync("Greetings! Thank you for executing me!"); } ```
Your command module should now resemble this: ```cs using System.Threading.Tasks; using DisCatSharp.CommandsNext; using DisCatSharp.CommandsNext.Attributes; public class MyFirstModule : BaseCommandModule { [Command("greet")] public async Task GreetCommand(CommandContext ctx) { await ctx.RespondAsync("Greetings! Thank you for executing me!"); } } ``` ### Cleanup and Configuration Before we can run our new command, we'll need modify our main method.
Start by removing the event handler we created [previously](xref:basics_first_bot#spicing-up-your-bot). ```cs var discord = new DiscordClient(); discord.MessageCreated += async (s, e) => // REMOVE { // ALL if (e.Message.Content.ToLower().StartsWith("ping")) // OF await e.Message.RespondAsync("pong!"); // THESE }; // LINES await discord.ConnectAsync(); ```
Next, call the `UseCommandsNext` extension method on your `DiscordClient` instance and pass it a new `CommandsNextConfiguration` instance. Assign the resulting `CommandsNextExtension` instance to a new variable named *commands*. This important step will enable CommandsNext for your Discord client. ```cs var discord = new DiscordClient(); var commands = discord.UseCommandsNext(new CommandsNextConfiguration()); ``` Create an object initializer for `CommandsNextConfiguration` and assign the `StringPrefixes` property a new `string` array containing your desired prefixes. Our example below will only define a single prefix: `!`. ```cs new CommandsNextConfiguration() { StringPrefixes = new[] { "!" } } ```
Now we'll register our command module. Call the `RegisterCommands` method on our `CommandsNextExtension` instance and provide it with your command module. ```cs var discord = new DiscordClient(); var commands = discord.UseCommandsNext(); commands.RegisterCommands(); await discord.ConnectAsync(); ``` Alternatively, you can pass in your assembly to register commands from all modules in your program. ```cs commands.RegisterCommands(Assembly.GetExecutingAssembly()); ```
Your main method should look similar to the following: ```cs internal static async Task MainAsync() { var discord = new DiscordClient(new DiscordConfiguration()); var commands = discord.UseCommandsNext(new CommandsNextConfiguration() { StringPrefixes = new[] { "!" } }); commands.RegisterCommands(); await discord.ConnectAsync(); await Task.Delay(-1); } ``` ### Running Your Command It's now the moment of truth; all your blood, sweat, and tears have lead to this moment. Hit `F5` on your keyboard to compile and run your bot, then execute your command in any channel that your bot account has access to. ![Congratulations, You've Won!](/images/commands_intro_03.png) [That was easy](https://www.youtube.com/watch?v=GsQXadrmhws).
## Taking User Input ### Command Arguments Now that we have a basic command down, let's spice it up a bit by defining *arguments* to accept user input. Defining an argument is simple; just add additional parameters to your signature of your command method. CommandsNext will automatically parse user input and populate the parameters of your command method with those arguments. To demonstrate, we'll modify our *greet* command to greet a user with a given name. Head back to `MyFirstModule` and add a parameter of type `string` to the `GreetCommand` method. ```cs [Command("greet")] public async Task GreetCommand(CommandContext ctx, string name) ``` CommandsNext will now interpret this as a command named *greet* that takes one argument. Next, replace our original response message with an [interpolated string](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated) which uses our new parameter. ```cs public async Task GreetCommand(CommandContext ctx, string name) { await ctx.RespondAsync($"Greetings, {name}! You're pretty neat!"); } ``` That's all there is to it. Smack `F5` and test it out in a channel your bot account has access to. ![Greet Part 2: Electric Boogaloo](/images/commands_intro_04.png)
Now, you may have noticed that providing more than one word simply does not work.
For example, `!greet Luke Smith` will result in no response from your bot. This fails because a valid [overload](#command-overloads) could not be found for your command. CommandsNext will split arguments by whitespace. This means `Luke Smith` is counted as two separate arguments; `Luke` and `Smith`. In addition to this, CommandsNext will attempt to find and execute an overload of your command that has the *same number* of provided arguments. Together, this means that any additional arguments will prevent CommandsNext from finding a valid overload to execute. The simplest way to get around this would be to wrap your input with double quotes.
CommandsNext will parse this as one argument, allowing your command to be executed. ``` !greet "Luke Smith" ``` If you would prefer not to use quotes, you can use the `RemainingText` attribute on your parameter.
This attribute will instruct CommandsNext to parse all remaining arguments into that parameter. ```cs public async Task GreetCommand(CommandContext ctx, [RemainingText] string name) ``` Alternatively, you can use the `params` keyword to have all remaining arguments parsed into an array. ```cs public async Task GreetCommand(CommandContext ctx, params string[] names) ``` A more obvious solution is to add additional parameters to the method signature of your command method.
```cs public async Task GreetCommand(CommandContext ctx, string firstName, string lastName) ```
Each of these has their own caveats; it'll be up to you to choose the best solution for your commands. ### Argument Converters CommandsNext can convert arguments, which are natively `string`, to the type specified by a command method parameter. This functionality is powered by *argument converters*, and it'll help to eliminate the boilerplate code needed to parse and convert `string` arguments. CommandsNext has built-in argument converters for the following types: Category|Types :---:|:--- Discord|`DiscordGuild`, `DiscordChannel`, `DiscordMember`, `DiscordUser`,
`DiscordRole`, `DiscordMessage`, `DiscordEmoji`, `DiscordColor` Integral|`byte`, `short`, `int`, `long`, `sbyte`, `ushort`, `uint`, `ulong` Floating-Point|`float`, `double`, `decimal` Date|`DateTime`, `DateTimeOffset`, `TimeSpan` Character|`string`, `char` Boolean|`bool` You're also able to create and provide your own [custom argument converters](xref:commands_argument_converters), if desired.
Let's do a quick demonstration of the built-in converters. Create a new command method above our `GreetCommand` method named `RandomCommand` and have it take two integer arguments. As the method name suggests, this command will be named *random*. ```cs [Command("random")] public async Task RandomCommand(CommandContext ctx, int min, int max) { } ``` Make a variable with a new instance of `Random`. ```cs var random = new Random(); ``` Finally, we'll respond with a random number within the range provided by the user. ```cs await ctx.RespondAsync($"Your number is: {random.Next(min, max)}"); ``` Run your bot once more with `F5` and give this a try in a text channel. ![Discord Channel](/images/commands_intro_05.png) CommandsNext converted the two arguments from `string` into `int` and passed them to the parameters of our command, removing the need to manually parse and convert the arguments yourself.
We'll do one more to drive the point home. Head back to our old `GreetCommand` method, remove our `name` parameter, and replace it with a new parameter of type `DiscordMember` named `member`. ```cs public async Task GreetCommand(CommandContext ctx, DiscordMember member) ``` Then modify the response to mention the provided member with the `Mention` property on `DiscordMember`. ```cs public async Task GreetCommand(CommandContext ctx, DiscordMember member) { await ctx.RespondAsync($"Greetings, {member.Mention}! Enjoy the mention!"); } ``` Go ahead and give that a test run. ![According to all known laws of aviation,](/images/commands_intro_06.png) ![there is no way a bee should be able to fly.](/images/commands_intro_07.png) ![Its wings are too small to get its fat little body off the ground.](/images/commands_intro_08.png) The argument converter for `DiscordMember` is able to parse mentions, usernames, nicknames, and user IDs then look for a matching member within the guild the command was executed from. Ain't that neat? ## Command Overloads Command method overloading allows you to create multiple argument configurations for a single command. ```cs [Command("foo")] public Task FooCommand(CommandContext ctx, string bar, int baz) { } [Command("foo")] public Task FooCommand(CommandContext ctx, DiscordUser bar) { } ``` Executing `!foo green 5` will run the first method, and `!foo @SecondUser` will run the second method.
Additionally, all check attributes are shared between overloads.
```cs [Command("foo"), Aliases("bar", "baz")] [RequireGuild, RequireBotPermissions(Permissions.AttachFiles)] public Task FooCommand(CommandContext ctx, int bar, int baz, string qux = "agony") { } [Command("foo")] public Task FooCommand(CommandContext ctx, DiscordChannel bar, TimeSpan baz) { } ``` The additional attributes and checks applied to the first method will also be applied to the second method. ## Further Reading Now that you've gotten an understanding of CommandsNext, it'd be a good idea check out the following: * [Command Attributes](xref:commands_command_attributes) * [Help Formatter](xref:commands_help_formatter) * [Dependency Injection](xref:commands_dependency_injection) diff --git a/DisCatSharp.Docs/articles/hosting.md b/DisCatSharp.Docs/articles/modules/hosting.md similarity index 100% rename from DisCatSharp.Docs/articles/hosting.md rename to DisCatSharp.Docs/articles/modules/hosting.md diff --git a/DisCatSharp.Docs/articles/interactivity.md b/DisCatSharp.Docs/articles/modules/interactivity.md similarity index 100% rename from DisCatSharp.Docs/articles/interactivity.md rename to DisCatSharp.Docs/articles/modules/interactivity.md diff --git a/DisCatSharp.Docs/articles/toc.yml b/DisCatSharp.Docs/articles/toc.yml index 039405123..89cfb4211 100644 --- a/DisCatSharp.Docs/articles/toc.yml +++ b/DisCatSharp.Docs/articles/toc.yml @@ -1,113 +1,119 @@ - name: Preamble href: preamble.md - name: Important Changes items: + - name: Version 10.1.0 + href: important_changes/10_1_0.md - name: Version 10.0.0 href: important_changes/10_0_0.md - name: Version 9.9.0 href: important_changes/9_9_0.md - name: Version 9.8.5 href: important_changes/9_8_5.md - name: Version 9.8.4 href: important_changes/9_8_4.md - name: Version 9.8.3 href: important_changes/9_8_3.md - name: Version 9.8.2 href: important_changes/9_8_2.md - name: The Basics items: - name: Creating a Bot Account href: basics/bot_account.md - name: Writing Your First Bot href: basics/first_bot.md - name: Bot as Hosted Service href: basics/web_app.md - name: Project Templates href: basics/templates.md - name: Beyond Basics items: - name: Events href: beyond_basics/events.md - name: Logging href: beyond_basics/logging/default.md items: - name: The Default Logger href: beyond_basics/logging/default.md - name: Third Party Loggers href: beyond_basics/logging/third_party.md - name: Dependency Injection Loggers href: beyond_basics/logging/di.md - name: Intents href: beyond_basics/intents.md - name: Sharding href: beyond_basics/sharding.md - name: Message Builder href: beyond_basics/messagebuilder.md - name: Components items: - name: Buttons href: beyond_basics/components/buttons.md - name: Select Menu href: beyond_basics/components/select_menus.md - name: Workarounds href: beyond_basics/workarounds.md -- name: Application Commands +- name: Modules items: - - name: Introduction - href: application_commands/intro.md - - name: Options - href: application_commands/options.md - - name: Events - href: application_commands/events.md - - name: Translations - items: - - name: Using Translations - href: application_commands/translations/using.md - - name: Translation Reference - href: application_commands/translations/reference.md - - name: Paginated Modals - href: application_commands/paginated_modals.md -- name: Commands - items: - - name: Introduction - href: commands/intro.md - - name: Command Attributes - href: commands/command_attributes.md - - name: Dependency Injection - href: commands/dependency_injection.md - - name: Customization - items: - - name: Help Formatter - href: commands/help_formatter.md - - name: Argument Converters - href: commands/argument_converters.md - - name: Command Handler - href: commands/command_handler.md -- name: Audio - items: - - name: Lavalink - items: - - name: Setup - href: audio/lavalink/setup.md - - name: Configuration - href: audio/lavalink/configuration.md - - name: Music Commands - href: audio/lavalink/music_commands.md - - name: VoiceNext - items: - - name: Prerequisites - href: audio/voicenext/prerequisites.md - - name: Transmitting - href: audio/voicenext/transmit.md - - name: Receiving - href: audio/voicenext/receive.md -- name: Interactivity - href: interactivity.md -- name: Hosting - href: hosting.md + - name: Application Commands + items: + - name: Introduction + href: modules/application_commands/intro.md + - name: Options + href: modules/application_commands/options.md + - name: Events + href: modules/application_commands/events.md + - name: Translations + items: + - name: Using Translations + href: modules/application_commands/translations/using.md + - name: Translation Reference + href: modules/application_commands/translations/reference.md + - name: Modals + href: modules/application_commands/modals.md + - name: Paginated Modals + href: modules/application_commands/paginated_modals.md + - name: CommandsNext + items: + - name: Introduction + href: modules/commandsnext/intro.md + - name: Command Attributes + href: modules/commandsnext/command_attributes.md + - name: Dependency Injection + href: modules/commandsnext/dependency_injection.md + - name: Customization + items: + - name: Help Formatter + href: modules/commandsnext/help_formatter.md + - name: Argument Converters + href: modules/commandsnext/argument_converters.md + - name: Command Handler + href: modules/commandsnext/command_handler.md + - name: Audio + items: + - name: Lavalink + items: + - name: Setup + href: modules/audio/lavalink/setup.md + - name: Configuration + href: modules/audio/lavalink/configuration.md + - name: Music Commands + href: modules/audio/lavalink/music_commands.md + - name: VoiceNext + items: + - name: Prerequisites + href: modules/audio/voicenext/prerequisites.md + - name: Transmitting + href: modules/audio/voicenext/transmit.md + - name: Receiving + href: modules/audio/voicenext/receive.md + - name: Interactivity + href: modules/interactivity.md + - name: Hosting + href: modules/hosting.md - name: Miscellaneous items: - name: Nightly Builds href: misc/nightly_builds.md - name: Reporting Issues href: misc/reporting_issues.md