5.1 KiB
HTTP Web API
Package: me.bobert.netneutrality.http (outbound client) and
me.bobert.netneutrality.http.server (server).
Server: embedded Javalin 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 component
(priority 70) registered in App.buildContext().
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(default9000), - creates a
Javalinapp (Javalin 7 requires routes and exception/error handlers to be registered upfront insideJavalin.create), - registers the
OpenApiPlugin(title "Net Neutrality API",X-API-Keysecurity scheme) and theSwaggerPlugin, - installs the
ApiKeyFilteras abeforehandler, - 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,
500with{"status":"error"}. - Unknown paths →
404with{"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); 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). |
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/shutdownis destructive, so the port should not be exposed publicly.