# Commands **Packages:** `me.bobert.netneutrality.core.command` (framework), `me.bobert.netneutrality.command.*` (concrete commands) A **Command** is what a user executes on Discord. It receives a [`CommandContext`](contexts.md#commandcontext) and runs logic based on it. There are two primary kinds: - **Slash commands** — first-class Discord application commands. Their structure (options, subcommands, groups) has rules defined by Discord and must be configured **procedurally** via `CommandOptionsBuilder`. - **Message (prefix) commands** — a plain message beginning with the configured prefix (`NETNEUTRALITY_BOT_PREFIX`, default `$`). A core goal of the framework is that **both kinds are authored the same way**: a command declares its metadata and options once, and the framework adapts it to either interaction style. Most of that adaptation logic lives in `CommandProcessor`. ## The `Command` contract `Command` (interface) defines: - Metadata — `getCommandName()`, `getDescription()`, `getCategory()` ([`CommandCategory`](#commandcategory)). - Type — `getType()` (`SLASH` by default). - Permissions — `needAdmin()`, `canBeExecutedBy(...)`, guild-only / slash-only constraints. - Option declaration — `registerCommandOptions(CommandOptionsBuilder)` (procedural option setup). - Execution — `execute(CommandContext) : CommandExecutionResult`. ### Base implementations (`core.command.impl`) | Class | Adds | |---|---| | `AbstractCommand` | Default `Command` implementation; type defaults to `SLASH`; basic permission logic. | | `AdminCommand` | Sets `needAdmin() = true` and category `ADMINISTRATION`. | | `MusicCommand` | Guild-only; verifies the guild's music role and music channel before execution. | | `MessageMenuCommand` | Discord **message** context-menu command; skips option registration; wraps the target `Message`. | | `UserMenuCommand` | Discord **user** context-menu command; skips option registration; wraps the target `User`. | ## Unified options To let one command definition serve both slash and message input, values are read through a wrapper interface: - `CommandOption` (interface) — uniform accessors: `getAsString()`, `getAsInt()`, `getAsMember()`, `getAsBoolean()`, … . - `SlashCommandOption` — wraps JDA's `OptionMapping` (Discord already parsed the value). - `MessageCommandOption` — wraps a value parsed manually from the message text. ### Procedural option configuration — `CommandOptionsBuilder` `CommandOptionsBuilder` implements JDA's `SlashCommandData` and acts as a proxy over JDA's internal `CommandDataImpl`. A command builds its argument surface in `registerCommandOptions(builder)` by adding options, subcommands, and subcommand groups. The same declaration is used both to: 1. register the slash command with Discord (see [registration](#registration-lifecycle)), and 2. drive the message-command parser (which tries to match tokens in the message against the declared `OptionData`). ## Command management ### `CommandManager` Responsible for **discovery** and **registration**. - **Discovery** — at `init()`, uses [`ReflectionHelper`](utilities-and-bootstrap.md#reflectionhelper) to scan for `Command` implementations and instantiate them. - **Storage** — commands are kept in a `ConcurrentHashMap` keyed by command name. - **Slash registration** — `registerSlashCommands(JDA)` converts each command's declaration into JDA `SlashCommandData` and pushes it to Discord's API. ### `CommandContainer` Wraps a `Command` instance together with its JDA metadata (`CommandDataImpl`). It also builds the "usage" string shown by the help command. ### `CommandCategory` Enum grouping commands for help/display purposes (e.g. `ADMINISTRATION`, `UTILITY`, `MUSIC`, `CRYPTO`, and an unlisted/hidden group for owner/debug commands). ### `CommandExecutionResult` The value returned by `execute(...)`. It encodes the outcome and any response payload: - `success(...)` — normal success (optionally with a message/embed to send). - `error(...)` / `warning(...)` — failure/advisory outcomes with a message. - `none()` — nothing to send synchronously (the command handles its own async response, e.g. music). - May also carry a follow-up **`Task`** so a command can install an interactive [task](tasks.md) (e.g. buttons) as part of its result. ## The dispatch flow — `CommandProcessor` `CommandProcessor` is constructed with the `CommandContext` and driven by `dispatch()`: 1. **Resolution** — match the invoked name against a `CommandContainer` in `CommandManager`. 2. **Validation** - command enabled? - guild-only / slash-only constraints satisfied? - **permissions** — `canBeExecutedBy(...)` and, for admin commands, `needAdmin()` verified against the bot owner (`NETNEUTRALITY_BOT_OWNER`) and the member's guild-admin status. 3. **Usage parsing (`checkUsage`)** — detect whether a subcommand or subcommand group is being invoked and record the active subcommand on the context. 4. **Option parsing** - **Slash** — Discord already parsed the arguments; map each `OptionMapping` to a `SlashCommandOption`. - **Message** — `MessageCommandContext` tokenizes the message and matches tokens against the declared `OptionData` (resolving mentions to members/roles, parsing integers/booleans, etc.). 5. **Execution** — call `command.execute(context)`. 6. **Result handling** — act on the returned `CommandExecutionResult`: send success/error/warning responses, or, if a `Task` was returned, register it with the [`TaskManager`](tasks.md). Errors are surfaced through the exception hierarchy ([`CommandExecutionException`](utilities-and-bootstrap.md#exceptions), and `GracefulContextError` for user-facing, non-logged failures). ## Registration lifecycle ``` App.startup() ├─ CommandManager.init() // reflective discovery → ConcurrentHashMap └─ CommandManager.registerSlashCommands(jda) // push SlashCommandData to Discord ``` At runtime, an incoming command event becomes a `CommandContext` in the [`EventsHandler`](events.md), which calls `new CommandProcessor(context).dispatch()`. ## Command catalog Concrete commands live under `me.bobert.netneutrality.command.*`, grouped by feature package: | Command | Package | Type | Description | |---|---|---|---| | `BlacklistCommand` | `admin` | Slash | Manage the server's word blacklist. | | `ReactionCommand` | `admin` | Slash | Manage emoji-reaction roles on the server. | | `HelpCommand` | `basic` | Slash | List every command with its usage. | | `PingCommand` | `basic` | Slash | Reply with the gateway ping. | | `ConfigureCommand` | `bot` | Slash | Configure the bot for the server (subcommands). | | `CryptoPriceCommand` | `crypto` | Slash | Fetch cryptocurrency prices. | | `ReportIssueCommand` | `git` | Slash | Report a bug (opens the Gitea report flow). | | `PlayCommand` | `music` | Slash | Search/queue and start playback. | | `QueueCommand` / `ViewQueueCommand` | `music` | Slash | View/manage the queue. | | `SkipCommand` / `VoteSkipCommand` | `music` | Slash | Skip (or vote-skip) the current track. | | `ClearQueueCommand` / `ShuffleCommand` | `music` | Slash | Clear / shuffle the queue. | | `JoinCommand` / `LeaveCommand` | `music` | Slash | Join / leave the voice channel. | | `CurrentlyPlayingCommand` | `music` | Slash | Show the currently playing track. | | `ResumeCommand` / `StopCommand` / `VolumeCommand` | `music` | Slash | Playback controls. | | `AutoplayCommand` | `music` | Slash | Toggle autoplay: queues the YouTube radio mix of the last track so music keeps playing. | | `BitconnectCommand` | `music` | Slash | Novelty music command. | | `CreateEmojiReaction` / `ModifyEmojiReaction` | `react` | Message menu | Create/modify emoji-role mappings from a message. | | `DatabaseCommand`, `PurgeCommand`, `ShutdownCommand`, `JoinClanCommand`, `TestCommand`, `SlashCommandTest`, `TrackStuckTest` | `custom` | Mixed | Owner/debug and personal utilities (the `custom` package is gitignored per-deployment). | ### Representative commands **`PingCommand` (simplest slash command)** — extends `AbstractCommand`, overrides `execute` to return `CommandExecutionResult.success(...)` containing the JDA gateway ping. It illustrates the minimal execution path (no options, synchronous response). **`ConfigureCommand` (complex slash command)** — uses `registerCommandOptions` to declare several `SubcommandData` (e.g. music-volume, error-logging). In `execute`, it switches on `context.getSubcommand()` and reads typed values via `context.getOption(name, resolver)`, then persists them through the [guild settings service](persistence.md#service-layer). **`PlayCommand` (music)** — extends `MusicCommand` (so it enforces guild-only + music role/channel). It declares a single required `STRING` query option, delegates loading to [`MusicManager`](music.md), and returns `CommandExecutionResult.none()` because the audio subsystem produces the user-facing response asynchronously (often installing a [`LoadPlaylistTask`](tasks.md#concrete-task-catalog) with choice buttons).