Command Framework
Fluxer4J includes an annotation-based command framework that handles parsing, argument extraction, precondition checks, and execution automatically.
Commands are organized into modules (classes extending
ModuleBase). Each public method annotated with @CommandAttribute becomes a command.Creating a Command Module
Extend ModuleBase and annotate methods with @CommandAttribute:
import fluxer4j.commands.ModuleBase;
import fluxer4j.commands.attributes.*;
import fluxer4j.data.models.Message;
import java.util.concurrent.CompletableFuture;
public class BasicCommands extends ModuleBase {
@CommandAttribute(name = "ping")
@SummaryAttribute("Check if the bot is responsive")
public CompletableFuture<Message> pingCommand() {
return replyAsync("pong ;P");
}
@CommandAttribute(name = "hello")
@AliasAttribute({"hi", "hey"})
public CompletableFuture<Message> helloCommand() {
return replyAsync("Hello, <@" + getContext().getUser().getId() + ">!");
}
}
ModuleBase provides:
| Method | Description |
|---|---|
replyAsync(String) | Send a reply to the current channel |
getContext() | Access the current CommandContext |
getService(Class<T>) | Retrieve an injected service by type |
Annotations Reference
| Annotation | Target | Description |
|---|---|---|
@CommandAttribute(name, runMode) | Method | Defines the command name and optional run mode |
@AliasAttribute({"a", "b"}) | Method | Alternative command names |
@SummaryAttribute("...") | Method | Short description for help text |
@RemainderAttribute | Parameter | Captures the rest of the message as a single string |
@RequireOwnerAttribute | Method/Class | Restrict to bot owner only |
@RequireContextAttribute(ContextType) | Method/Class | Restrict to Guild or DM context |
@RequireUserPermissionAttribute(Permissions) | Method/Class | User must have the specified permission |
@RequireBotPermissionAttribute(Permissions) | Method/Class | Bot must have the specified permission |
Command Parameters
Method parameters are automatically parsed from the user's message. Supported types:
| Type | Example Input | Description |
|---|---|---|
String | !greet Alice | Single word |
int / long | !add 5 10 | Numeric values |
@RemainderAttribute String | !echo hello world | Rest of the message as one string |
@CommandAttribute(name = "echo")
public CompletableFuture<Message> echoCommand(@RemainderAttribute String message) {
return replyAsync(message);
}
@CommandAttribute(name = "add")
public CompletableFuture<Message> addCommand(int a, int b) {
return replyAsync(a + " + " + b + " = " + (a + b));
}
Preconditions
Restrict who can run commands with precondition annotations. Apply on individual methods or on the entire module class.
@RequireContextAttribute(ContextType.Guild)
@RequireUserPermissionAttribute(Permissions.KickMembers)
@RequireBotPermissionAttribute(Permissions.SendMessages)
@CommandAttribute(name = "kick")
public CompletableFuture<Message> kickCommand(long userId) {
return getContext().getClient()
.kickMember(getContext().getGuildId(), userId)
.thenCompose(v -> replyAsync("User kicked!"));
}
When applied to a class, the precondition applies to all commands in that module. Method-level preconditions add to (don't replace) class-level ones.
CommandContext
Every command receives a CommandContext with full access to the current state:
| Method | Returns | Description |
|---|---|---|
getClient() | ApiClient | REST API client for making API calls |
getGateway() | GatewayClient | Gateway client for event handling |
getMessage() | Message | The message that triggered the command |
getUser() | User | The user who sent the command |
getChannelId() | Long | Current channel ID |
getGuildId() | Long | Current guild ID (null in DMs) |
getService(Class<T>) | T | Retrieve injected services |
CommandService
If you're using manual wiring instead of Fluxer4JBot, set up the command service yourself:
CommandService commands = new CommandService('!', logger, null);
commands.addModuleAsync(BasicCommands.class);
commands.addModuleAsync(MusicCommands.class);
// In your message listener:
gateway.addMessageCreateListener(msg -> {
String content = msg.getContent();
if (content != null && content.startsWith("!")) {
CommandContext ctx = new CommandContext(api, gateway, msg, services);
IResult result = commands.executeAsync(ctx, 1).join();
}
});
Full Example Module
package fluxer4j.example;
import fluxer4j.commands.ModuleBase;
import fluxer4j.commands.attributes.*;
import fluxer4j.data.models.*;
import fluxer4j.embed.EmbedBuilder;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class BasicCommands extends ModuleBase {
@CommandAttribute(name = "ping")
@SummaryAttribute("Check if the bot is responsive")
public CompletableFuture<Message> pingCommand() {
return replyAsync("pong ;P");
}
@CommandAttribute(name = "hello")
@AliasAttribute({"hi", "hey"})
@SummaryAttribute("Get a friendly greeting")
public CompletableFuture<Message> helloCommand() {
return replyAsync("Hello, <@" + getContext().getUser().getId() + ">!");
}
@CommandAttribute(name = "embed")
@SummaryAttribute("Show an example rich embed")
public CompletableFuture<Message> embedCommand() {
Embed embed = new EmbedBuilder()
.withTitle("Example Rich Embed")
.withDescription("Demonstrates the EmbedBuilder.")
.withColor(0x5865F2)
.addField("Inline 1", "Value", true)
.addField("Inline 2", "Value", true)
.withFooter("Fluxer4J v1.0.0")
.withTimestamp(Instant.now())
.build();
Message msg = new Message();
msg.setContent("Here's an embed:");
msg.setEmbeds(List.of(embed));
return getContext().getClient().sendMessage(getContext().getChannelId(), msg);
}
@CommandAttribute(name = "echo")
public CompletableFuture<Message> echoCommand(@RemainderAttribute String message) {
return replyAsync(message);
}
@CommandAttribute(name = "add")
public CompletableFuture<Message> addCommand(int a, int b) {
return replyAsync(a + " + " + b + " = " + (a + b));
}
}