# Architecture Overview Net Neutrality is a Java 25 Discord bot built on top of [JDA](https://github.com/discord-jda/JDA). It is packaged as a single fat JAR (via the Maven Shade plugin) whose entry point is `me.bobert.netneutrality.App`. ## Big picture Discord sends **events** over the gateway. A single JDA `EventListener` (`me.bobert.netneutrality.hooks.EventsHandler`) receives every event and turns it into a **Context** — a high-level abstraction over that event. Each context is then routed to one of three execution paths: ``` ┌──────────────────────────────┐ Discord Gateway ───────▶│ EventsHandler │ (JDA events) │ event class → Context factory│ └───────────────┬──────────────┘ │ builds a Context ┌─────────────────────────┼───────────────────────────┐ ▼ ▼ ▼ CommandContext TaskContext (no Context) │ │ lambda / ▼ ▼ consumer runs CommandProcessor TaskManager.handle() directly .dispatch() (lookup task by id) (e.g. GuildJoin) │ │ ▼ ▼ Command.execute() Task.execute() ``` 1. **Command flow** — the context is a `CommandContext`; it is handed to `CommandProcessor` which resolves and runs a `Command`. See [commands.md](commands.md). 2. **Task flow** — the context is a `TaskContext`; it is handed to `TaskManager` which looks up a `Task` by its **id** and runs it. See [tasks.md](tasks.md). 3. **Lambda flow** — for simple events (e.g. `GuildJoinEvent`, `GuildLeaveEvent`) a consumer runs directly and no context is routed onward. See [events.md](events.md). Around this core are the supporting subsystems: [persistence](persistence.md) (Hibernate + Flyway), [music](music.md) (a remote Lavalink node), the [web API](web-api.md), and the [threading/scheduling](threading.md) infrastructure. ## Technology stack | Concern | Library | Version | |---|---|---| | Discord API | `net.dv8tion:JDA` | 6.4.1 | | Audio engine | Lavalink v4 node (`ghcr.io/lavalink-devs/lavalink`) | 4.2 | | Audio client | `dev.arbjerg:lavalink-client` | 3.4.0 | | ORM | `org.hibernate.orm:hibernate-core` | 7.1.9.Final | | Connection pool | `hibernate-hikaricp` (HikariCP) | 7.1.9.Final | | JDBC driver | `com.mysql:mysql-connector-j` | 9.7.0 | | DB migrations | `org.flywaydb:flyway-core` / `flyway-mysql` | 11.20.3 | | Error tracking | `io.sentry:sentry` | 8.41.0 | | HTTP client | `com.squareup.okhttp3:okhttp-jvm` | 5.3.2 | | JSON | `org.json:json` + `com.google.code.gson:gson` | — | | Classpath scanning | `org.reflections:reflections` | 0.10.2 | | Emoji parsing | `net.fellbaum:jemoji` | 1.7.6 | | Logging | Log4j2 + `log4j-slf4j2-impl` | 2.26.0 | | Testing | JUnit Jupiter 6 + Mockito 5 | — | The HTTP **server** for the internal API uses the JDK built-in `com.sun.net.httpserver.HttpServer` (no external framework); see [web-api.md](web-api.md). ## Startup lifecycle The entry point is `App.main()` followed by the `App()` constructor which calls `startup()`. Bootstrap is driven by a hand-rolled [`ApplicationContext`](utilities-and-bootstrap.md#applicationcontext--lifecycle) that owns wired instances and starts every [`Lifecycle`](utilities-and-bootstrap.md#applicationcontext--lifecycle) component in ascending priority order. 1. **Print banner / read env** — reads `NETNEUTRALITY_BOT_ENV` (default `dev`) via the early-bootstrap `Settings.getInstance()` singleton (the only remaining process-wide settings access point; it is also registered into the context as `SettingsProvider` for constructor injection). 2. **Sentry** — if `NETNEUTRALITY_SENTRY_DSN` is set, initialize the Sentry SDK (environment, release = `VERSION`, traces sample rate 1.0). 3. **Log levels** — in `dev`, raise JDA and Lavalink client logging to `DEBUG`. 4. `startup()`: 1. Validate `NETNEUTRALITY_BOT_TOKEN` is present (throws if missing). 2. `buildContext()` — register domain services and lifecycle adapters (see priorities below). 3. `context.start()` — start every `Lifecycle` component in ascending priority order. 4. Register a JVM shutdown hook calling `shutdown()` → `context.stop()`. ### Lifecycle priorities (startup order) | Priority | Component | What `start()` does | |---|---|---| | 10 | `SessionManager` (`SessionProvider`) | Build Hibernate `SessionFactory` + run Flyway migrations. Binds itself into `JpaUtils`. | | 20 | `ThreadManager` (`ExecutorProvider`) | No-op start (executors are lazy). Bound into `ChannelMessagePurger`. | | 30 | `CommandManager` (`CommandRegistry`) | Reflectively discover/register `Command` implementations with context-aware constructor injection. | | 30 | `TaskManagerImpl` (`TaskManager`) | Reflectively load built-in & scheduled tasks (DI) and reload persistable tasks from the DB. | | 40 | `MusicServiceImpl` (`MusicService`) | Create the shared `LavalinkClient`, register the node and subscribe to its events. | | 55 | `ScheduledTaskManager` | No-op start (tasks are scheduled by `TaskManager` during its own start). | | 60 | JDA adapter | Build `JDA` with gateway intents and await ready. | | 65 | Post-JDA adapter | Register slash commands, attach `EventsHandler` (injected providers), set presence. | | 70 | `WebApi` | Start the internal HTTP server (`NETNEUTRALITY_API_PORT`, default 9000). | Commands and tasks that need shared services declare them as constructor parameters (`MusicService`, `TaskManager`, `GuildServiceProvider`, `SettingsProvider`, `CommandRegistry`, …). `ReflectionHelper` picks the richest public constructor whose parameter types all resolve from the context (falling back to a no-arg constructor when none match). ### Gateway intents `initJDA()` enables: `GUILD_MEMBERS`, `GUILD_MESSAGE_REACTIONS`, `GUILD_VOICE_STATES`, `MESSAGE_CONTENT`. It also installs `new JDAVoiceUpdateListener(music.getClient())` as the voice dispatch interceptor, so voice server/state updates are forwarded to Lavalink instead of JDA's own audio stack. DAVE (end-to-end encrypted voice) is handled by the node, which is why the bot no longer ships native audio encoders. ## Shutdown lifecycle `App.shutdown()` (invoked by the JVM shutdown hook, `ShutdownCommand`, or the HTTP `/actions/shutdown` endpoint) calls `context.stop()`, which stops every registered `Lifecycle` component in the **exact reverse** of start order. Failures in one component are logged and do not block the others. Effectively this means: 1. `WebApi.stop()` — stop the HTTP server and its executor. 2. Post-JDA adapter — no-op. 3. JDA adapter — `jda.shutdown()`. 4. `ScheduledTaskManager.stop()` — cancel all scheduled tasks. 5. `MusicServiceImpl.stop()` — dispose the event subscriptions, destroy the players and close the Lavalink client. 6. `CommandManager` / `TaskManagerImpl` — no-op stop today. 7. `ThreadManager.stop()` — shut down the shared executors. 8. `SessionManager.stop()` — release the Hibernate registry / connection pool. ## Configuration & build - **Config sources** — `Settings` loads `bot.properties` and overlays environment variables (env vars win). See [utilities-and-bootstrap.md](utilities-and-bootstrap.md#settings). An example file lives at `bot.properties.example`. - **Build** — `./mvnw package` produces `target/netneutrality.jar` (Shade fat JAR, main class `me.bobert.netneutrality.App`). Checkstyle (`checkstyle.xml`) runs in the `test` phase and fails the build on violations. - **Run** — the preferred method is Docker Compose (`docker compose build && docker compose up -d`), which also provisions the MariaDB database. See `README.md` and `docker-compose.yml`. ## Package map | Package | Contents | |---------------------------------------------------|---------------------------------------------------------------------------------------------------------| | `me.bobert.netneutrality` | Entry point (`App`) | | `…bootstrap` | `ApplicationContext`, `Lifecycle`, `BotState`, `ReflectionHelper` | | `…core.command` | Command engine (registry, processor, manager, options, impl bases) | | `…core.context` | Context interfaces, abstract bases (`CommandContext`, `TaskContext`, etc.) | | `…core.tasks` | Task engine (manager, subtypes, traits, persistence, generic buttons) | | `…core.thread` | Executors & scheduling (`ThreadManager`, `ScheduledTaskManager`, `BaseThreadFactory`) | | `…core` | `LogHandler` | | `…command.*` | Concrete command implementations (`admin`, `basic`, `bot`, `crypto`, `custom`, `git`, `music`, `react`) | | `…context.command` / `…context.tasks` | Concrete context implementations (slash, message, button, modal, emoji, etc.) | | `…context` | `MusicChannelContext` | | `…tasks.*` | Concrete task implementations (`git`, `help`, `music`, `reaction`) | | `…db` | Session/migration, `Repository`, `JpaUtils`, `GSettings` | | `…db.entities` / `…db.repository` / `…db.service` | Persistence model, repositories, services | | `…hooks` | `EventsHandler` | | `…http` | Internal web API and HTTP client module | | `…music` | Music domain (service, manager, queue, scheduler, track loading) | | `…utils` | `Settings` / `SettingsProvider`, `ChannelMessagePurger`, `MusicUtils` | | `…shared.util` | Cross-cutting helpers | | `…exceptions` | Custom exception hierarchy |