@@ -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 | - name: Terminology | ||||
href: terminology.md | href: terminology.md | ||||
- name: Logging | - name: Logging | ||||
href: logging.md | |||||
href: logging.md | |||||
- name: Commands | |||||
href: commands.md |