Remove deprecated classes and static singleton-based architecture
This commit is contained in:
1 parent
54a27883d1
commit
b8786c8dc9
133 files changed
+4984
-1038
No files matched your search
@@ -0,0 +1,169 @@
|
||||
# Commands
|
||||
|
||||
**Packages:** `me.bobert.netneutrality.framework.command` (framework),
|
||||
`me.bobert.netneutrality.feature.*.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<String, CommandContainer>` 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.feature.*.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 GitLab 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. |
|
||||
| `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).
|
||||
Reference in new issue
Block a user