Files
netneutrality/docs/utilities-and-bootstrap.md
2026-10-19 08:21:42 -04:00

12 KiB

Utilities, Bootstrap, Logging & Exceptions

This document covers the cross-cutting glue of the bot: the entry points, configuration, logging / error tracking, the shared utility helpers, and the custom exception hierarchy.

Bootstrap

Package: me.bobert.netneutrality / me.bobert.netneutrality.bootstrap

Class Role
App The application: holds the process instance and the JDA handle, builds the ApplicationContext, runs context.start() / context.stop(), and is the JAR main class (main()). See architecture.md.
bootstrap.ApplicationContext Hand-rolled container: register(T), get(Class<T>), ordered start() / stop() for Lifecycle components.
bootstrap.Lifecycle Optional component contract: start() throws Exception, default stop(), default priority().
bootstrap.BotState Work-in-progress global state helper: exposes a SAFE flag and dump(), which serializes per-guild/music state to state.json. Bound to GuildServiceProvider / MusicService during bootstrap.

The full startup and shutdown sequences are documented in architecture.md.

ApplicationContext + Lifecycle

Domain services are constructed once in App.buildContext(), registered with the context, and resolved later by type:

  • register(component) — indexes the concrete class; if the component implements Lifecycle it participates in ordered start/stop.
  • get(Class<T>) — returns the first registered instance assignable to T (interfaces work).
  • start() — sorts Lifecycle components by ascending priority() and starts them.
  • stop() — stops them in the exact reverse order; individual failures are logged and swallowed so remaining components still tear down.

Constructor injection for reflectively loaded commands/tasks is handled by ReflectionHelper: the richest public constructor whose parameter types all resolve via context.get(type) is preferred; otherwise the no-arg constructor is used.

Interfaces used for injection (implemented by the concrete managers registered above):

Interface Typical implementor
utils.SettingsProvider utils.Settings
db.SessionProvider db.SessionManager
core.thread.ExecutorProvider core.thread.ThreadManager
core.command.CommandRegistry core.command.CommandManager
core.tasks.TaskManager core.tasks.TaskManagerImpl
music.MusicService music.MusicServiceImpl
db.service.GuildServiceProvider db.service.GuildServiceProviderImpl

There are no getInstance() / instance() shims on the managers above. The only intentional static access points left are:

  • Settings.getInstance() — early bootstrap only (main(), Sentry, token validation) before the context exists; the same instance is registered as SettingsProvider for everything else.
  • App.getInstance() — application root used by a couple of shutdown entry points (ShutdownCommand). Prefer constructor injection of the needed collaborators where possible.
  • JpaUtils.bind(SessionProvider) / LogHandler.bind(GuildServiceProvider) / ChannelMessagePurger.bind(ExecutorProvider) — single well-defined static bind points for deep utility graphs that are not constructor-injected (repositories, static log helpers).

Configuration

Settings / SettingsProvider

Class: me.bobert.netneutrality.utils.Settings implements me.bobert.netneutrality.utils.SettingsProvider.

Settings is the single source of truth for configuration. It loads values from the bot.properties file and overlays environment variables on top (environment variables win).

  • Early bootstrap: Settings.getInstance() (process-wide, created once before the context).
  • Runtime injection: depend on SettingsProvider and let ApplicationContext supply the same instance. Prefer this path in commands, tasks, handlers, and services.
  • Settings.getVersion() / Settings.getDiscordInvite() — thin static helpers used by the banner / invite strings; they delegate to the bootstrap instance.

An example configuration file is bot.properties.example. Key configuration values:

Key Purpose
NETNEUTRALITY_BOT_ENV dev / prod; controls debug logging and the Sentry environment.
NETNEUTRALITY_BOT_TOKEN Discord bot token (required).
NETNEUTRALITY_BOT_PREFIX Message-command prefix (default $).
NETNEUTRALITY_BOT_OWNER Bot owner user id (used for owner-only commands / permissions).
NETNEUTRALITY_BOT_INVITE Invite link surfaced by help/info.
NETNEUTRALITY_API_PORT Port for the Web API (default 9000).
MYSQL_* Database connection settings (see persistence.md).
NETNEUTRALITY_SENTRY_DSN Enables Sentry when present.
GITEA_HOST / GITEA_TOKEN / GITEA_OWNER / GITEA_REPO Gitea issue reporting (ReportIssueTask). Token needs write:issue.
NETNEUTRALITY_LAVALINK_URI / _PASSWORD / _NAME Connection settings for the Lavalink node.
NETNEUTRALITY_PO_TOKEN / NETNEUTRALITY_VISITOR_DATA / NETNEUTRALITY_CIPHER_* YouTube auth/cipher settings, consumed by the node via lavalink/application.yml.

Utility helpers

Package: me.bobert.netneutrality.utils (helpers) / me.bobert.netneutrality.bootstrap / me.bobert.netneutrality.exceptions

Class Purpose
Settings Configuration (see above).
ReflectionHelper Classpath scanning / reflective instantiation (see below).
ContextUtils Boilerplate helpers for building/handling contexts.
GuildUtils Guild-related helpers (lookups, permission checks).
JpaUtils Session/transaction helpers for the persistence layer. Bound once to a SessionProvider during bootstrap (JpaUtils.bind).
MusicUtils Voice-channel connectivity and DJ/permission checks for music. Callers pass the guild MusicManager (no static music lookup).
MiscUtils Assorted small helpers.
ChannelMessagePurger Bulk message deletion (used by the purge command). Bound once to an ExecutorProvider during bootstrap.
MessageFunction Standardized message/embed formatting. Help text builders take a CommandRegistry rather than a static command manager.
UserFunction Standardized user/string formatting.

ReflectionHelper

Built on the org.reflections library, ReflectionHelper discovers and instantiates implementation classes in specific packages. It is the mechanism behind the bot's "drop a class in the right package and it's picked up" model, used to auto-load:

When given an ApplicationContext, newInstance(Class, ApplicationContext) selects the public constructor with the most parameters that can all be resolved via context.get(parameterType). If no such constructor exists it falls back to the no-arg constructor. That is how music/admin/react commands receive MusicService, GuildServiceProvider, TaskManager, etc. without static singletons.

Logging & error tracking

LogHandler

Class: me.bobert.netneutrality.core.LogHandler.

LogHandler is the unified logging/error entry point. It routes messages and exceptions to:

  1. Log4j2 (configured by log4j2.properties; SLF4J is bridged via log4j-slf4j2-impl),
  2. Sentry (when NETNEUTRALITY_SENTRY_DSN is configured), and
  3. optionally a per-guild Discord debug channel (e.g. sendMessageToDebugChannel(guild, msg)), gated by GuildService.isDebugEnabled() / OLogging configuration.

The Sentry user context is attached automatically when a AbstractContext is created, so captured errors are attributed to the Discord user that triggered them.

Exception hierarchy

Package: me.bobert.netneutrality.exceptions

RuntimeException
└── ContextException                 ── carries a Context (guild/channel/user)
    ├── CommandExecutionException    ── thrown during command processing
    └── TaskExecutionException       ── thrown during task execution

GracefulContextError                 ── user-facing, non-logged failure → ephemeral reply
ExecutorException                    ── Thread.UncaughtExceptionHandler → routes crashes to LogHandler
  • ContextException — base runtime exception that carries the Context in which it occurred, so downstream handling can respond in the right channel and attribute the error.
  • CommandExecutionException / TaskExecutionException — specializations thrown by the command and task flows respectively.
  • GracefulContextError — signals an expected, user-facing problem (e.g. bad input); when caught it produces an ephemeral message to the user instead of a full error log/Sentry event.
  • ExecutorException — an UncaughtExceptionHandler installed by BaseThreadFactory so exceptions escaping worker threads are still captured by LogHandler.