Added new `MusicEvent` types, including `TRACK_ERROR`, `TRACK_STUCK`, and `PLAYER_POSITION`, providing enhanced diagnostics and feedback. Improved queue mutation notifications by introducing granular events like `added`, `removed`, `moved`, `cleared`, and `shuffled`. Updated tests and documentation to reflect the changes, ensuring consistency and clarity across the event system.
Net Neutrality Discord Bot
A custom Discord bot written in Java. It uses JDA for Discord, a Lavalink node for music playback, MariaDB/MySQL for persistence, and exposes an HTTP API built with Javalin.
This is a personal project. No support is offered for building or running it.
Table of Contents
Requirements
- Docker and Docker Compose (recommended), or
- JDK 25 and Maven (the included
mvnwwrapper works) for local runs - A MariaDB/MySQL database
- A Lavalink node, version 4.2.0 or newer
- A Discord bot token
Configuration
All settings are provided as environment variables. A template with every supported variable is available in bot.properties.example.
Key variables:
| Variable | Description |
|---|---|
NETNEUTRALITY_BOT_TOKEN |
Discord bot token |
NETNEUTRALITY_BOT_PREFIX |
Command prefix |
NETNEUTRALITY_BOT_OWNER |
Discord user ID of the bot owner |
NETNEUTRALITY_API_PORT |
Port for the HTTP API (default 9000) |
MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE |
Database connection settings |
NETNEUTRALITY_LAVALINK_URI, NETNEUTRALITY_LAVALINK_PASSWORD |
Lavalink connection settings |
NETNEUTRALITY_SENTRY_DSN |
Optional Sentry DSN for error reporting |
GITEA_HOST, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO |
Optional Gitea settings for issue reporting |
NETNEUTRALITY_WEBHOOK_URL, NETNEUTRALITY_WEBHOOK_SECRET |
Optional frontend webhook URL and HMAC secret (see below) |
Webhook events
When NETNEUTRALITY_WEBHOOK_URL is set, the bot POSTs JSON music-state events (TRACK_START, TRACK_END, QUEUE_UPDATED, PLAYER_PAUSED, PLAYER_RESUMED, VOLUME_CHANGED, VOICE_CONNECTED, VOICE_DISCONNECTED) to it asynchronously. Each event has type, guildId, timestamp, a per-guild seq and a payload. Requests carry an X-Signature: sha256=<hex HMAC-SHA256 of the body> header computed with NETNEUTRALITY_WEBHOOK_SECRET. Failed deliveries are retried 3 times with exponential backoff and then dropped, so the frontend should still do a full resync on load and periodically. The polling endpoints remain available.
PLAYER_SEEKED is sent after a seek with payload { position } (milliseconds). VOTE_SKIP_UPDATED is sent after a vote that doesn't skip the track yet, with payload { userId, votes, threshold } (threshold is the percentage of listeners needed). A vote that does skip produces the usual TRACK_END / TRACK_START / QUEUE_UPDATED events instead.
TRACK_START / TRACK_END payloads are { identifier, title, author, length, uri, artworkUrl } (plus reason on TRACK_END). uri and artworkUrl may be null when the source doesn't provide them.
VOICE_CONNECTED / VOICE_DISCONNECTED are sent for the bot itself and for any member joining or leaving the voice channel the bot is currently in. Their payload is { channelId, userId, username, nickname, bot, self }, where nickname is the member's effective display name in the guild, bot marks bot accounts and self marks this bot's own voice changes.
Running with Docker Compose
Docker Compose is the preferred way to run the bot. It starts the bot, the database, Lavalink, and the supporting YouTube cipher and PO token services.
- Copy
bot.properties.exampleand fill in the values (see Configuration). - Build and start the stack:
docker compose build
docker compose up -d
- View logs:
docker compose logs -f netneutrality-bot
Logs are also written to the logs directory.
Running Locally
- Copy
bot.properties.exampletobot.propertiesand fill in the values. - Make sure a database and a Lavalink node are reachable at the configured addresses.
- Build the project:
./mvnw package
- Run the bot:
java -jar target/netneutrality.jar
Dependencies are copied to target/lib during packaging and referenced by the jar manifest.
Testing
./mvnw test
Database tests use Testcontainers, so Docker must be running. Checkstyle runs during the test phase.
License
See the LICENSE file.