Migrated issue reporting functionality from GitLab to Gitea. Updated environment variables, dependencies, and configuration references to support Gitea. Introduced `GiteaClient` for communication with the Gitea API and refactored related code and tests to align with the new integration.
12 KiB
Architecture Overview
Net Neutrality is a Java 25 Discord bot built on top of 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()
- Command flow — the context is a
CommandContext; it is handed toCommandProcessorwhich resolves and runs aCommand. See commands.md. - Task flow — the context is a
TaskContext; it is handed toTaskManagerwhich looks up aTaskby its id and runs it. See tasks.md. - Lambda flow — for simple events (e.g.
GuildJoinEvent,GuildLeaveEvent) a consumer runs directly and no context is routed onward. See events.md.
Around this core are the supporting subsystems: persistence (Hibernate + Flyway), music (a remote Lavalink node), the web API, and the threading/scheduling 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.
Startup lifecycle
The entry point is App.main() followed by the App() constructor which calls
startup(). Bootstrap is driven by a hand-rolled
ApplicationContext that owns wired
instances and starts every Lifecycle
component in ascending priority order.
- Print banner / read env — reads
NETNEUTRALITY_BOT_ENV(defaultdev) via the early-bootstrapSettings.getInstance()singleton (the only remaining process-wide settings access point; it is also registered into the context asSettingsProviderfor constructor injection). - Sentry — if
NETNEUTRALITY_SENTRY_DSNis set, initialize the Sentry SDK (environment, release =VERSION, traces sample rate 1.0). - Log levels — in
dev, raise JDA and Lavalink client logging toDEBUG. startup():- Validate
NETNEUTRALITY_BOT_TOKENis present (throws if missing). buildContext()— register domain services and lifecycle adapters (see priorities below).context.start()— start everyLifecyclecomponent in ascending priority order.- Register a JVM shutdown hook calling
shutdown()→context.stop().
- Validate
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:
WebApi.stop()— stop the HTTP server and its executor.- Post-JDA adapter — no-op.
- JDA adapter —
jda.shutdown(). ScheduledTaskManager.stop()— cancel all scheduled tasks.MusicServiceImpl.stop()— dispose the event subscriptions, destroy the players and close the Lavalink client.CommandManager/TaskManagerImpl— no-op stop today.ThreadManager.stop()— shut down the shared executors.SessionManager.stop()— release the Hibernate registry / connection pool.
Configuration & build
- Config sources —
Settingsloadsbot.propertiesand overlays environment variables (env vars win). See utilities-and-bootstrap.md. An example file lives atbot.properties.example. - Build —
./mvnw packageproducestarget/netneutrality.jar(Shade fat JAR, main classme.bobert.netneutrality.App). Checkstyle (checkstyle.xml) runs in thetestphase 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. SeeREADME.mdanddocker-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 |