Skip to content
This repository was archived by the owner on Nov 25, 2022. It is now read-only.

Writing a command module

Cole Bryant edited this page Jul 16, 2019 · 7 revisions
**Basic module example**
public class ModulePing extends Module {
    public ModulePing() {
        super(new ModuleInfo.Builder(ModulePing.class)
                .withName("ping")
                .withDescription("Check for bot responsiveness"));
    }

    @Override
    public void invoke(@NotNull final CommandContext ctx) {
        ctx.reply("Pong!");
    }
}

Creating a command module is relatively easy, which makes doing so a good beginner task that doesn't require deep knowledge of the bot. Every command in ghost2 extends Module, and so will any modules you write. Make sure you precisely adhere to the rules on writing a Module class or you're guaranteed to get an InvalidModuleException somewhere down the line.

Requirements

Two things are contractually required from a Module subclass:

  1. A call to super() in the constructor with a ModuleInfo.Builder
  2. An invoke() implementation

Also required, although not contractually, is a publicly accessible no-args constructor.

ModuleInfo

Finding your way around ModuleInfo is the part of writing a Module that's not obvious at first glance. ModuleInfo contains all the metadata for a module, including its name, description, type (see CommandType), and other data. Name, description, and type are the three fields you should be most concerned with, as they're required for every ModuleInfo instance. Name and description, specifically, should be not-blank, meaning they're both not null and contain at least one non-whitespace character.

To construct a ModuleInfo object, you need to use the built-in ModuleInfo.Builder class. When you look at the arguments for the builder's constructor, you'll notice that it takes a Class<? extends Module>. Pass in the actual class of the Module you're creating here. After that, you can do what you want with the builder; parameters are set in the typical builder-pattern fashion via the with... methods. You can't and don't need to call build() after setting your fields, as it's called automatically by the Module superclass.

Note that you also can't and don't need to set a CommandType parameter. That's what the Class argument in the constructor is for. ModuleInfo.Builder uses this parameter to pull the @CommandPackage annotation from your module's package and uses the annotation's value as its type. This means that setting a CommandType is a matter of placing your module class in the right package.

invoke

After writing your constructor, you can move on to what your command actually does. invoke() is the method that gets called when your command is... well, invoked. After control is passed to your module, everything is up to you.

CommandContext

CommandContext offers basically everything you could possibly need when your command is invoked. Upon invocation, CommandDispatcher will construct a CommandContext instance for you and pass it in to invoke(). From there, you can use all the getter methods that CommandContext offers to create rich commands that respond fluently to most situations. CommandContext fields are null-checked on construction, so you shouldn't have to worry about null pointers at all. Refer to the JavaDocs to view the exhaustive list of fields that are available.

CommandContext also gives you some quickfire reply methods that just take a message string if you want to send a plaintext message:

  • CommandContext#reply() sends a message to the channel the command was invoked in.
  • CommandContext#replyDirect() sends a message directly to the used who invoked the command.

Notes and best practices

  • Mark your Module classes as final. Inadvertently extending an implemented Module effectively creates a duplicate command entry with separate code. CommandRegistry will handle duplicate command entries and duplicate names by the time 1.0 is finished, but marking your classes final is still a good practice.

  • Use the @ReflectiveAccess annotation where necessary. Reflection is utilized extensively throughout ghost2, both by internal classes and Spring. In many cases, your IDE won't detect that a member is accessed reflectively. In these cases, you can mark it with @ReflectiveAccess and configure your IDE to suppress "Unused declaration" and other related warnings for members marked with the annotation.

Clone this wiki locally