| @@ -0,0 +1,62 @@ | |||
| # The Command Service | |||
| [Discord.Commands](xref:Discord.Commands) provides an Attribute-based Command Parser. | |||
| ### Setup | |||
| To use Commands, you must create a [Commands Service](xref:Discord.Commands.CommandService) and a Command Handler. | |||
| Included below is a very bare-bones Command Handler. You can extend your Command Handler as much as you like, however the below is the bare minimum. | |||
| [!code-csharp[Barebones Command Handler](samples/command_handler.cs)] | |||
| ## Modules | |||
| Modules serve as a host for commands you create. | |||
| To create a module, create a class that you will place commands in. Flag this class with the `[Module]` attribute. You may optionally pass in a string to the `Module` attribute to set a prefix for all of the commands inside the module. | |||
| ### Example: | |||
| [!code-csharp[Modules](samples/module.cs)] | |||
| ### Loading Modules Automatically | |||
| The Command Service can automatically discover all classes in an Assembly that are flagged with the `Module` attribute, and load them. | |||
| To have a module opt-out of auto-loading, pass `autoload: false` in the Module attribute. | |||
| Invoke [CommandService.LoadAssembly](Discord.Commands.CommandService#Discord_Commands_CommandService_LoadAssembly) to discover modules and install them. | |||
| ### Loading Modules Manually | |||
| To manually load a module, invoke [CommandService.Load](Discord.Commands.CommandService#Discord_Commands_CommandService_Load), and pass in an instance of your module. | |||
| ### Module Constructors | |||
| When automatically loading modules, you are limited in your constructor. Using a constructor that accepts _no arguments_, or a constructor that accepts a @Discord.Commands.CommandService will always work. | |||
| Alternatively, you can use an @Discord.Commands.IDependencyMap, as shown below. | |||
| ## Dependency Injection | |||
| The Commands Service includes a very basic implementation of Dependency Injection that allows you to have completely custom constructors, within certain limitations. | |||
| ## Setup | |||
| First, you need to create an @Discord.Commands.IDependencyMap . The library includes @Discord.Commands.DependencyMap to help with this, however you may create your own IDependencyMap if you wish. | |||
| Next, add the dependencies your modules will use to the map. | |||
| Finally, pass the map into the `LoadAssembly` method. Your modules will automatically be loaded with this dependency map. | |||
| [!code-csharp[DependencyMap Setup](samples/dependency_map_setup.cs)] | |||
| ## Usage in Modules | |||
| In the constructor of your module, any parameters will be filled in by the @Discord.Commands.IDependencyMap you pass into `LoadAssembly`. | |||
| >[!NOTE] | |||
| >If you accept `CommandService` or `IDependencyMap` as a parameter in your constructor, these parameters will be filled by the CommandService the module was loaded from, and the DependencyMap passed into it, respectively. | |||
| [!code-csharp[DependencyMap in Modules](samples/dependency_module.cs)] | |||
| @@ -0,0 +1,52 @@ | |||
| using System.Threading.Tasks; | |||
| using System.Reflection; | |||
| using Discord; | |||
| using Discord.Commands; | |||
| public class Program | |||
| { | |||
| private CommandService commands; | |||
| private DiscordSocketClient client; | |||
| static void Main(string[] args) => new Program().Start().GetAwaiter().GetResult(); | |||
| public async Task Start() | |||
| { | |||
| client = new DiscordSocketClient(); | |||
| commands = new CommandService(); | |||
| string token = "bot token here"; | |||
| await InstallCommands(); | |||
| await client.LoginAsync(TokenType.Bot, token); | |||
| await client.ConnectAsync(); | |||
| await Task.Delay(-1); | |||
| } | |||
| public async Task InstallCommands() | |||
| { | |||
| // Hook the MessageReceived Event into our Command Handler | |||
| client.MessageReceived += HandleCommand; | |||
| // Discover all of the commands in this assembly and load them. | |||
| await commands.LoadAssembly(Assembly.GetEntryAssembly()); | |||
| } | |||
| public async Task HandleCommand(IMessage msg) | |||
| { | |||
| // Internal integer, marks where the command begins | |||
| int argPos = 0; | |||
| // Get the current user (used for Mention parsing) | |||
| var currentUser = await client.GetCurrentUserAsync(); | |||
| // Determine if the message is a command, based on if it starts with '!' or a mention prefix | |||
| if (msg.HasCharPrefix('!', ref argPos) || msg.HasMentionPrefix(currentUser, ref argPos)) | |||
| { | |||
| // Execute the command. (result does not indicate a return value, | |||
| // rather an object stating if the command executed succesfully) | |||
| var result = await _commands.Execute(msg, argPos); | |||
| if (!result.IsSuccess) | |||
| await msg.Channel.SendMessageAsync(result.ErrorReason); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| using Discord; | |||
| using Discord.Commands; | |||
| public class Commands | |||
| { | |||
| public async Task Install(DiscordSocketClient client) | |||
| { | |||
| var commands = new CommandService(); | |||
| var map = new DependencyMap(); | |||
| map.Add<IDiscordClient>(client); | |||
| var self = await client.GetCurrentUserAsync(); | |||
| map.Add<ISelfUser>(self); | |||
| await commands.LoadAssembly(Assembly.GetCurrentAssembly(), map); | |||
| } | |||
| // ... | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| using Discord; | |||
| using Discord.Commands; | |||
| [Module] | |||
| public class ModuleA | |||
| { | |||
| private DiscordSocketClient client; | |||
| private ISelfUser self; | |||
| public ModuleA(IDiscordClient c, ISelfUser s) | |||
| { | |||
| if (!(c is DiscordSocketClient)) throw new InvalidOperationException("This module requires a DiscordSocketClient"); | |||
| client = c as DiscordSocketClient; | |||
| self = s; | |||
| } | |||
| } | |||
| public class ModuleB | |||
| { | |||
| private IDiscordClient client; | |||
| private CommandService commands; | |||
| public ModuleB(CommandService c, IDependencyMap m) | |||
| { | |||
| commands = c; | |||
| client = m.Get<IDiscordClient>(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| using Discord.Commands; | |||
| // Create a module with no prefix | |||
| [Module] | |||
| public class Info | |||
| { | |||
| // ~say hello -> hello | |||
| [Command("say"), Description("Echos a message.")] | |||
| public async Task Say(IMessage msg, | |||
| [Unparsed, Description("The text to echo")] string echo) | |||
| { | |||
| await msg.Channel.SendMessageAsync(echo); | |||
| } | |||
| } | |||
| // Create a module with the 'sample' prefix | |||
| [Module("sample")] | |||
| public class Sample | |||
| { | |||
| // ~sample square 20 -> | |||
| [Command("square"), Description("Squares a number.")] | |||
| public async Task Square(IMessage msg, | |||
| [Description("The number to square.")] int num) | |||
| { | |||
| await msg.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}"); | |||
| } | |||
| // ~sample userinfo --> foxbot#0282 | |||
| // ~sample userinfo @Khionu --> Khionu#8708 | |||
| // ~sample userinfo Khionu#8708 --> Khionu#8708 | |||
| // ~sample userinfo Khionu --> Khionu#8708 | |||
| // ~sample userinfo 96642168176807936 --> Khionu#8708 | |||
| [Command("userinfo"), Description("Returns info about the current user, or the user parameter, if one passed.")] | |||
| public async Task UserInfo(IMessage msg, | |||
| [Description("The (optional) user to get info for")] IUser user = null) | |||
| { | |||
| var userInfo = user ?? await Program.Client.GetCurrentUserAsync(); | |||
| await msg.Channel.SendMessageAsync($"{userInfo.Username}#{userInfo.Discriminator}"); | |||
| } | |||
| } | |||
| @@ -4,4 +4,6 @@ | |||
| - name: Terminology | |||
| href: terminology.md | |||
| - name: Logging | |||
| href: logging.md | |||
| href: logging.md | |||
| - name: Commands | |||
| href: commands.md | |||