# Event Handling & Routing **Package:** `me.bobert.netneutrality.platform.event` **Primary class:** `EventsHandler` `EventsHandler` is the single JDA `EventListener` registered on the `JDA` instance (`jda.addEventListener(new EventsHandler())` in `App.startup()`). It is the funnel through which **every** gateway event passes. Its job is to translate a raw JDA event into a [Context](contexts.md) and route that context to the correct subsystem. ## Listener registry Listeners are held in a static, thread-safe map keyed by the JDA event class: ```java private static final Map, Function> listeners = new ConcurrentHashMap<>(); ``` Each entry is a **factory function**: given an event of that type, it returns a `Context` (or `null`). The map is populated once in a `static {}` block. ### `addListener` vs `addGenericListener` - `addListener(Class, Function)` — registers a factory that produces a `Context` to be routed onward (command or task flow). - `addGenericListener(Class, Consumer)` — registers a factory that runs a side-effecting consumer and returns `null`, so nothing is routed onward. This is the **lambda flow** for simple events. ## Registered events | JDA event | Produces | Flow | |---|---|---| | `MessageReceivedEvent` (prefix match) | `MessageCommandContext` | Command | | `MessageReceivedEvent` (no prefix) | `MessageTaskContext` | Task | | `SlashCommandInteractionEvent` | `SlashCommandContext` | Command | | `UserContextInteractionEvent` | `UserMenuContext` | Command | | `MessageContextInteractionEvent` | `MessageMenuContext` | Command | | `ButtonInteractionEvent` | `ButtonTaskContext` | Task | | `ModalInteractionEvent` | `ModalTaskContext` | Task | | `MessageReactionAddEvent` | `EmojiTaskContext` | Task | | `GuildJoinEvent` | — (consumer) | Lambda | | `GuildLeaveEvent` | — (consumer) | Lambda | ### Message routing detail For `MessageReceivedEvent` the factory: 1. Ignores messages authored by bots and webhook messages (returns `null`). 2. Reads the configured prefix (`NETNEUTRALITY_BOT_PREFIX`, default `$`). 3. If the trimmed message starts with the prefix → **`MessageCommandContext`** (command flow). 4. Otherwise → **`MessageTaskContext`** (task flow). This lets ordinary messages participate in the task system, e.g. as a reply that advances a [step task](tasks.md#33-step-tasks). ### Guild lifecycle (lambda flow) - `GuildJoinEvent` — resolves `GuildService.get(guild)`; if debug logging is enabled for that guild, sends a notice to the debug channel. - `GuildLeaveEvent` — `GuildService.get(guild).deleteGuild()` removes the guild's stored data. ## Dispatch algorithm ```java @Override public void onEvent(@Nonnull GenericEvent event) { handleEvent((Event) event); } private void handleEvent(Event event) { Class eventClass = event.getClass(); while (eventClass != null) { if (listeners.containsKey(eventClass)) { Context context = listeners.get(eventClass).apply(event); if (context instanceof CommandContext c) handleCommand(c); if (context instanceof TaskContext t) handleTask(t); break; } eventClass = eventClass.getSuperclass(); // walk up the class hierarchy } } ``` Key points: - **Superclass walk** — if the exact event class is not registered, the handler walks up the superclass chain until it finds a match. This means a listener registered for a base event type also catches subclasses. - **Routing by context type** — the resulting object is routed purely by its runtime type: a `CommandContext` goes to `CommandProcessor.dispatch()`, a `TaskContext` goes to `TaskManager.getInstance().handle(context)`. - **`null` contexts** (bot messages, consumer-only events) are simply not routed anywhere. ## Extending the event system To handle a new event type: 1. Add an `addListener(SomeEvent.class, SomeContext::new)` (or `addGenericListener(...)` for a consumer-only side effect) to the `static {}` block. 2. If it should run the command or task flow, make the produced context extend [`CommandContext`](contexts.md#commandcontext) or [`TaskContext`](contexts.md#taskcontext) respectively.