Fundamentals

Commands

Since version 8 of the websocket, Discord have announced their willingness to migrate traditional commands designed with the MessageCreate event to dedicated components that we call slash commands.

See more about version 8


Basic usage

To simplify the creation of command files, the CLI provides you with an initialisation command.

dart run mineral make:command <MyCommand>

A question/answer game then appears, asking you to select an event from among those available in the database.


The slash commands

The mineral framework provide you with dedicated classes, please consider the examples below.

import 'package:mineral/framework.dart';
import 'package:mineral/core/api.dart';

class HelloCommand extends MineralCommand<GuildCommandInteraction> {
  HelloCommand() : super(
    label: Display("hello"), 
    description: Display("Say Hello !")
  );

  Future<void> handle(CommandInteraction interaction) async {
    await interaction.reply(content: "Hello world !", private: true);
  }
}

A command can also contain options, which are parameters that the user can enter when calling the command. As shown below with the word option, which is a string type.

import 'package:mineral/framework.dart';
import 'package:mineral/core/api.dart';

class HelloCommand extends MineralCommand<GuildCommandInteraction> {
  HelloCommand() : super(
    label: Display("hello"), 
    description: Display("Say Hello !"), 
    options: [
     CommandOption.string(Display("word"), Display("Say what you want !"), required: true)
  ]);

  Future<void> handle(CommandInteraction interaction) async {
    String word = interaction.getValue<String>("word"); // Get the value of the option "word"

    await interaction.reply(content: "Hello $word !", private: true);
  }
}

The sub-commands

In the same update, Discord also announced the ability to create subcommands, which are commands nested within a main command. To create a subcommand, you need to create a class that extends MineralSubCommand<T extends CommandInteraction>. We've simplified this to avoid having all sub commands in the same file. So you can create a file for each sub command.

You cannot use a subcommand and a command, the command will be ignored.

import 'package:mineral/framework.dart';
import 'package:mineral/core/api.dart';

class WorldCommand extends MineralSubcommand<GuildCommandInteraction> {
  WorldCommand(): super(
    label: Display("world"), 
    description: Display("Say hello world")
  );

  Future<void> handle(CommandInteraction interaction) async {
    await interaction.reply(content: "Hello world !", private: true);
  }
}

Register the sub-command

In our previous HelloCommand class, we'll define our subcommand in our constructor:

import 'package:mineral/framework.dart';
import 'package:mineral/core/api.dart';
import 'subcommands/world.dart';

class HelloCommand extends MineralCommand<GuildCommandInteraction> {
  HelloCommand() : super(
    label: Display("hello"), 
    description: Display("Say Hello !"), 
    subcommands: [
       WorldCommand()
    ]);

  // This is useless, that will be ignored because we have a subcommand
  /*Future<void> handle(CommandInteraction interaction) async {
    String word = interaction.getValue<String>("word"); // Get the value of the option "word"

    await interaction.reply(content: "Hello $word !", private: true);
  }*/
}

The sub-command groups

Subcommand groups are subcommands that can contain subcommands, so here's how to register them:

// subcommands/world_subcommand.dart
import 'package:mineral/framework.dart';
import 'package:mineral/core/api.dart';

class WorldSubCommand extends MineralSubcommand<GuildCommandInteraction> {
  WorldSubCommand(): super(
    label: Display("world"), 
    description: Display("Say hello world")
  );

  Future<void> handle(CommandInteraction interaction) async {
    await interaction.reply(content: "Hello world !", private: true);
  }
}

// hello_command.dart

import 'package:mineral/framework.dart';
import 'package:mineral/core/api.dart';
import 'subcommands/world_subcommand.dart';

class HelloCommand extends MineralCommand<GuildCommandInteraction> {
  HelloCommand() : super(
    label: Display("hello"), 
    description: Display("Say Hello !"), 
    groups: [
      MineralCommandGroup(
        label: Display("world"), 
        description: Display("Say hello to world"), 
        subcommands: [WorldSubCommand()]
      )
    ]);
}

Here you go, now you can use groups commands and sub commands !


Accessing the options

The commands allow you to easily define various parameters that the Mineral framework makes available to you through the interaction received as parameters.

import 'package:mineral/framework.dart';
import 'package:mineral/core/api.dart';

class WorldSubCommand extends MineralSubcommand<GuildCommandInteraction> {
  WorldSubCommand(): super(label: Display("world"), description: Display("Say hello world"), options: [
    CommandOption.string(Display("string"), Display("String description"), required: true),
    CommandOption.double(Display("double"), Display("Number description"), required: true),
    CommandOption.bool(Display("boolean"), Display("Boolean description"), required: false), // this can be null
    CommandOption.user(Display("user"), Display("User description"), required: false), // this can be used for guildMember too and can be null
    CommandOption.role(Display("role"), Display("Role description"), required: true),
    CommandOption.channel(Display("channel"), Display("Channel description"), required: true),
    CommandOption.mentionable(Display("mentionable"), Display("Mentionable description"), required: true),
  ]);

  Future<void> handle(CommandInteraction interaction) async {
    final string = interaction.getValue<String>("string");
    final doubleValue = interaction.getValue<double>("double");
    final boolean = interaction.getValue<bool?>("boolean"); // this can be null
    final GuildMember? member = interaction.getMember("user");
    final User? user = interaction.getUser("user"); // this can be used for guildMember too
    final role = interaction.getRole("role");
    final channel = interaction.getChannel("channel");
    final mentionable = interaction.getMentionable("mentionable");

    await interaction.reply(content: "Hello world !", private: true);
  }
}

You'll find that Mineral can't guess whether an option is null or not, so you need to use the ? for options that may be null.

Previous
Events