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:

MethodDescription
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

AnnotationTargetDescription
@CommandAttribute(name, runMode)MethodDefines the command name and optional run mode
@AliasAttribute({"a", "b"})MethodAlternative command names
@SummaryAttribute("...")MethodShort description for help text
@RemainderAttributeParameterCaptures the rest of the message as a single string
@RequireOwnerAttributeMethod/ClassRestrict to bot owner only
@RequireContextAttribute(ContextType)Method/ClassRestrict to Guild or DM context
@RequireUserPermissionAttribute(Permissions)Method/ClassUser must have the specified permission
@RequireBotPermissionAttribute(Permissions)Method/ClassBot must have the specified permission

Command Parameters

Method parameters are automatically parsed from the user's message. Supported types:

TypeExample InputDescription
String!greet AliceSingle word
int / long!add 5 10Numeric values
@RemainderAttribute String!echo hello worldRest 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:

MethodReturnsDescription
getClient()ApiClientREST API client for making API calls
getGateway()GatewayClientGateway client for event handling
getMessage()MessageThe message that triggered the command
getUser()UserThe user who sent the command
getChannelId()LongCurrent channel ID
getGuildId()LongCurrent guild ID (null in DMs)
getService(Class<T>)TRetrieve 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));
    }
}