-
Notifications
You must be signed in to change notification settings - Fork 31
Writing a command module
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.
Two things are contractually required from a Module
subclass:
- A call to
super()
in the constructor with aModuleInfo.Builder
- An
invoke()
implementation
Also required, although not contractually, is a publicly accessible no-args constructor.
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, i.e. its name, description, category, permissions, and aliases. 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. Its parameters are set in the typical builder-pattern fashion via the with...
methods.
There are a few things that are done automatically for you, as well:
- The
CommandType
for your module is assigned automatically based on what package it's in. -
build()
is called automatically byModule
's constructor.
After writing your constructor, you can move on to what your command actually does. invoke()
does exactly what you'd think: it gets called when your method is invoked. You can block for a reasonable amount of time, since each command instance receives its own thread; however, you should always try to give the user some feedback as soon as possible, and free the thread back to the executor's pool eventually.
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
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 plain-text message to the channel the command was invoked in. -
CommandContext#replyEmbed()
sends an embed message to the channel the command was invoked in. -
CommandContext#replyDirect()
sends a plain-text message directly to the user who invoked the command. There are blocking and non-blocking variants of each reply method. All of them allow you to access the sent message, either directly through aMono
.
-
Inadvertently extending another
Module
subclass will generate a runtime warning and make your module be ignored by the command registry. Don't do it. You can mark yourModule
classesfinal
to help avoid this across the project. -
Use the
@ReflectiveAccess
annotation where necessary. Reflection is utilized extensively throughout ghost2, both by internal classes and Spring. In most cases, your IDE won't detect that a member is accessed reflectively; for convenience, you can mark it with@ReflectiveAccess
and configure your IDE to suppress unused warnings for members marked with the annotation.