Table of Contents

The Text Command Service

Discord.Commands provides an attribute-based command parser.

Important

The 'Message Content' intent, required for text commands, is now a privileged intent. Please use Slash commands instead for making commands. For more information about this change please check this announcement made by discord

Get Started

To use commands, you must create a Command Service and a command handler.

Included below is a barebone command handler. You can extend your command handler as much as you like; however, the below is the bare minimum.

Note

The CommandService will optionally accept a CommandServiceConfig, which does set a few default values for you. It is recommended to look over the properties in CommandServiceConfig and their default values.

public class CommandHandler
{
    private readonly DiscordSocketClient _client;
    private readonly CommandService _commands;

    // Retrieve client and CommandService instance via ctor
    public CommandHandler(DiscordSocketClient client, CommandService commands)
    {
        _commands = commands;
        _client = client;
    }
    
    public async Task InstallCommandsAsync()
    {
        // Hook the MessageReceived event into our command handler
        _client.MessageReceived += HandleCommandAsync;

        // Here we discover all of the command modules in the entry 
        // assembly and load them. Starting from Discord.NET 2.0, a
        // service provider is required to be passed into the
        // module registration method to inject the 
        // required dependencies.
        //
        // If you do not use Dependency Injection, pass null.
        // See Dependency Injection guide for more information.
        await _commands.AddModulesAsync(assembly: Assembly.GetEntryAssembly(), 
                                        services: null);
    }

    private async Task HandleCommandAsync(SocketMessage messageParam)
    {
        // Don't process the command if it was a system message
        var message = messageParam as SocketUserMessage;
        if (message == null) return;

        // Create a number to track where the prefix ends and the command begins
        int argPos = 0;

        // Determine if the message is a command based on the prefix and make sure no bots trigger commands
        if (!(message.HasCharPrefix('!', ref argPos) || 
            message.HasMentionPrefix(_client.CurrentUser, ref argPos)) ||
            message.Author.IsBot)
            return;

        // Create a WebSocket-based command context based on the message
        var context = new SocketCommandContext(_client, message);

        // Execute the command with the command context we just
        // created, along with the service provider for precondition checks.
        await _commands.ExecuteAsync(
            context: context, 
            argPos: argPos,
            services: null);
    }
}

With Attributes

Starting from 1.0, commands can be defined ahead of time with attributes, or at runtime with builders.

For most bots, ahead-of-time commands should be all you need, and this is the recommended method of defining commands.

Modules

The first step to creating commands is to create a module.

A module is an organizational pattern that allows you to write your commands in different classes and have them automatically loaded.

Discord.Net's implementation of "modules" is influenced heavily by the ASP.NET Core's Controller pattern. This means that the lifetime of a module instance is only as long as the command is being invoked.

Before we create a module, it is crucial for you to remember that in order to create a module and have it automatically discovered, your module must:

By now, your module should look like this:

using Discord.Commands;

// Keep in mind your module **must** be public and inherit ModuleBase.
// If it isn't, it will not be discovered by AddModulesAsync!
public class InfoModule : ModuleBase<SocketCommandContext>
{
    
}
Note

ModuleBase is an abstract class, meaning that you may extend it or override it as you see fit. Your module may inherit from any extension of ModuleBase.

Adding/Creating Commands

Warning

Avoid using long-running code in your modules wherever possible. Long-running code, by default, within a command module can cause gateway thread to be blocked; therefore, interrupting the bot's connection to Discord.

You may read more about it in @FAQ.Commands.General .

The next step to creating commands is actually creating the commands.

For a command to be valid, it must have a return type of Task or Task<RuntimeResult>. Typically, you might want to mark this method as async, although it is not required.

Then, flag your command with the CommandAttribute. Note that you must specify a name for this command, except for when it is part of a Module Group.

Command Parameters

Adding parameters to a command is done by adding parameters to the parent Task.

For example:

  • To take an integer as an argument from the user, add int num.
  • To take a user as an argument from the user, add IUser user.
  • ...etc.

Starting from 1.0, a command can accept nearly any type of argument; a full list of types that are parsed by default can be found in @Guides.Commands.TypeReaders.

Optional Parameters

Parameters, by default, are always required. To make a parameter optional, give it a default value (i.e., int num = 0).

Parameters with Spaces

To accept a space-separated list, set the parameter to params Type[].

Should a parameter include spaces, the parameter must be wrapped in quotes. For example, for a command with a parameter string food, you would execute it with !favoritefood "Key Lime Pie".

If you would like a parameter to parse until the end of a command, flag the parameter with the RemainderAttribute. This will allow a user to invoke a command without wrapping a parameter in quotes.

Command Overloads

You may add overloads to your commands, and the command parser will automatically pick up on it.

If, for whatever reason, you have two commands which are ambiguous to each other, you may use the PriorityAttribute to specify which should be tested before the other.

The Priority attributes are sorted in descending order; the higher priority will be called first.

Command Context

Every command can access the execution context through the Context property on ModuleBase. ICommandContext allows you to access the message, channel, guild, user, and the underlying Discord client that the command was invoked from.

Different types of Context may be specified using the generic variant of ModuleBase. When using a SocketCommandContext, for example, the properties on this context will already be Socket entities, so you will not need to cast them.

To reply to messages, you may also invoke ReplyAsync, instead of accessing the channel through the Context and sending a message.

Warning

Contexts should NOT be mixed! You cannot have one module that uses CommandContext and another that uses SocketCommandContext.

Tip

At this point, your module should look comparable to this example:

// Create a module with no prefix
public class InfoModule : ModuleBase<SocketCommandContext>
{
    // ~say hello world -> hello world
    [Command("say")]
    [Summary("Echoes a message.")]
    public Task SayAsync([Remainder] [Summary("The text to echo")] string echo)
        => ReplyAsync(echo);
        
    // ReplyAsync is a method on ModuleBase 
}

// Create a module with the 'sample' prefix
[Group("sample")]
public class SampleModule : ModuleBase<SocketCommandContext>
{
    // ~sample square 20 -> 400
    [Command("square")]
    [Summary("Squares a number.")]
    public async Task SquareAsync(
        [Summary("The number to square.")] 
        int num)
    {
        // We can also access the channel from the Command Context.
        await Context.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
    // ~sample whois 96642168176807936 --> Khionu#8708
    [Command("userinfo")]
    [Summary
    ("Returns info about the current user, or the user parameter, if one passed.")]
    [Alias("user", "whois")]
    public async Task UserInfoAsync(
        [Summary("The (optional) user to get info from")]
        SocketUser user = null)
    {
        var userInfo = user ?? Context.Client.CurrentUser;
        await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}");
    }
}

Loading Modules Automatically

The Command Service can automatically discover all classes in an Assembly that inherit ModuleBase and load them. Invoke CommandService.AddModulesAsync to discover modules and install them.

To opt a module out of auto-loading, flag it with DontAutoLoadAttribute.

Loading Modules Manually

To manually load a module, invoke CommandService.AddModuleAsync by passing in the generic type of your module and optionally, a service provider.

Module Constructors

Modules are constructed using Dependency Injection. Any parameters that are placed in the Module's constructor must be injected into an IServiceProvider first.

Tip

Alternatively, you may accept an IServiceProvider as an argument and extract services yourself, although this is discouraged.

Module Properties

Modules with public settable properties will have the dependencies injected after the construction of the module. See @Guides.Commands.DI to learn more.

Module Groups

Module Groups allow you to create a module where commands are prefixed. To create a group, flag a module with the GroupAttribute.

Module Groups also allow you to create nameless Commands, where the CommandAttribute is configured with no name. In this case, the command will inherit the name of the group it belongs to.

Submodules

Submodules are "modules" that reside within another one. Typically, submodules are used to create nested groups (although not required to create nested groups).

[Group("admin")]
public class AdminModule : ModuleBase<SocketCommandContext>
{
    [Group("clean")]
    public class CleanModule : ModuleBase<SocketCommandContext>
    {
        // ~admin clean
        [Command]
        public async Task DefaultCleanAsync()
        {
            // ...
        }

        // ~admin clean messages 15
        [Command("messages")]
        public async Task CleanAsync(int count)
        {
            // ...
        }
    }
    // ~admin ban foxbot#0282
    [Command("ban")]
    public Task BanAsync(IGuildUser user) => 
        Context.Guild.AddBanAsync(user);
}