Introduced autoplay functionality to keep music playing when the queue ends. Added new `/autoplay` API endpoints, command handling, and service implementations. Enhanced `TrackScheduler`, queue operations, and testing to support autoplay behavior. Integrated YouTube radio mix as the initial provider.
93 lines
5.3 KiB
Markdown
93 lines
5.3 KiB
Markdown
# HTTP Web API
|
||
|
||
**Package:** `me.bobert.netneutrality.http` (outbound client) and
|
||
`me.bobert.netneutrality.http.server` (server).
|
||
**Server:** embedded [Javalin](https://javalin.io) 7 with the OpenAPI and Swagger UI plugins.
|
||
|
||
The bot exposes an internal HTTP API for health monitoring, control, guild introspection, and music
|
||
control — intended for an external dashboard/companion, not for end users in Discord.
|
||
|
||
## Server startup
|
||
|
||
`WebApi` is a [`Lifecycle`](utilities-and-bootstrap.md#applicationcontext--lifecycle) component
|
||
(priority 70) registered in [`App.buildContext()`](architecture.md#startup-lifecycle).
|
||
It receives `SettingsProvider`, `GuildServiceProvider`, `MusicService`, and suppliers for the live
|
||
`JDA` / shutdown callback via its constructor. On `start()` it:
|
||
|
||
- reads the port from `NETNEUTRALITY_API_PORT` (default `9000`),
|
||
- creates a `Javalin` app (Javalin 7 requires routes and exception/error handlers to be registered
|
||
upfront inside `Javalin.create`),
|
||
- registers the `OpenApiPlugin` (title "Net Neutrality API", `X-API-Key` security scheme) and the
|
||
`SwaggerPlugin`,
|
||
- installs the `ApiKeyFilter` as a `before` handler,
|
||
- registers the route groups (below), the exception handlers and the 404 handler, and
|
||
- starts the server on the port.
|
||
|
||
On `stop()` the Javalin app is stopped.
|
||
|
||
## Authentication
|
||
|
||
`ApiKeyFilter` compares the `X-API-Key` request header with the `NETNEUTRALITY_API_KEY` setting.
|
||
A missing/invalid key — or no configured key at all — yields `401` ("Missing or invalid API key").
|
||
Public paths that skip the check: `/`, and anything starting with `/openapi`, `/swagger` or
|
||
`/webjars` (Swagger UI and its assets).
|
||
|
||
## Error handling
|
||
|
||
- `HttpResponseException` (e.g. `BadRequestResponse`, `NotFoundResponse`) →
|
||
`{"status":"error","message":"..."}` with the exception's status.
|
||
- Any other exception → logged, `500` with `{"status":"error"}`.
|
||
- Unknown paths → `404` with `{"status":"error","message":"Not found"}`.
|
||
|
||
## Route groups
|
||
|
||
Each group is a `*Routes` class with a static `register(RoutesConfig, ...)` method; handlers are
|
||
annotated with `@OpenApi` so they appear in the generated OpenAPI document. Request/response shapes
|
||
live in the `http.server.dto` package.
|
||
|
||
| Class | Endpoints |
|
||
|---|---|
|
||
| `WebApi` | `GET /` — HTML liveness message. |
|
||
| `GuildRoutes` | `GET /guilds` (optional `user_id` query restricts to mutual guilds); `GET /guilds/owner`; `GET /guilds/{guildId}/owner`; `/channels`; `/roles`; `/members/{userId}`, `/members/{userId}/access`, `/members/{userId}/roles`, `/members/{userId}/admin`; `/voice-channels`, `/voice-channels/{channelId}`; `GET /guilds/{guildId}/config`; `PUT /guilds/{guildId}/debug`, `/logging`, `/log-channel`. |
|
||
| `ActionRoutes` | `GET /actions/status` — JDA health + gateway ping; `POST /actions/shutdown` — graceful shutdown. |
|
||
| `SettingsRoutes` | `GET /settings/catalog` — every known setting with default and description; `GET /settings/current/{guildId}` — current guild settings; `PUT /settings/current/{guildId}/{name}` — update a setting; `PUT /settings/reload/{guildId}` — invalidate & reload the settings cache. |
|
||
| `StatsRoutes` | `GET /stats/bot`; `GET /stats/guilds/{guildId}`. |
|
||
| `MusicRoutes` | See below. |
|
||
|
||
### Music endpoints
|
||
|
||
Mirror the slash commands (see [music.md](music.md)); announcements are also posted to the guild's
|
||
music channel when a context is attached.
|
||
|
||
| Method & path | Purpose |
|
||
|---|---|
|
||
| `GET /music/sources` | Supported music sources. |
|
||
| `GET /music/{guildId}/status` | Connection, playback, current track, position, queue size. |
|
||
| `GET /music/{guildId}/queue` | Queued tracks. |
|
||
| `POST /music/{guildId}/search` | Resolve a query (optional `searchEngine`) into candidates; returns a `search_id`. Nothing is queued. |
|
||
| `POST /music/{guildId}/queue` | Queue a candidate by `searchId` + `index` (or all with `queueAll=true`) for `userId`; the bot must already be in a voice channel. |
|
||
| `DELETE /music/{guildId}/queue` | Clear the queue, or remove one track with `?position=` (1-based). |
|
||
| `PATCH /music/{guildId}/queue/reorder` | Move a track (`fromPosition` → `toPosition`). |
|
||
| `POST /music/{guildId}/skip`, `/stop`, `/pause`, `/resume`, `/shuffle`, `/leave` | Playback controls. |
|
||
| `GET` / `PUT /music/{guildId}/volume` | Read / set volume (0–100). |
|
||
| `GET` / `PUT /music/{guildId}/autoplay` | Read / set autoplay (`{"enabled": true}`). In-memory, resets when the player is destroyed. |
|
||
| `PUT /music/{guildId}/seek` | Seek to `positionMs`. |
|
||
| `GET /music/{guildId}/vote-skip` | Vote-skip status. |
|
||
| `POST /music/{guildId}/vote-skip/{userId}` | Cast a vote on behalf of a member. |
|
||
| `GET /music/{guildId}/dj/{userId}` | Whether the member is currently allowed to act as DJ. |
|
||
|
||
Search results are held in `PendingSearchResults` and expire after a TTL (returned as
|
||
`search_id_ttl_seconds`).
|
||
|
||
## HTTP client — `WebModule`
|
||
|
||
`WebModule` (package `me.bobert.netneutrality.http`) wraps an `OkHttpClient` for the bot's own
|
||
outbound HTTP calls (e.g. checking for updates via GitLab). Responses are cached in
|
||
`ResponseCache`, a `LinkedHashMap`-based LRU limited to 50 entries.
|
||
|
||
## Notes
|
||
|
||
- All routes except the public ones require the API key. Even so, the API is expected to be bound to
|
||
a trusted/internal network (e.g. within the Docker network); `/actions/shutdown` is destructive,
|
||
so the port should not be exposed publicly.
|