# Contexts **Packages:** `me.bobert.netneutrality.framework.context` (framework), `me.bobert.netneutrality.framework.context` (concrete implementations) A **Context** is a high-level abstraction over a Discord event. It hides the differences between the many JDA event types (slash command, message, button, modal, reaction, …) behind a single, uniform API for reading who/where the event came from and for replying to it. Contexts are created either by the [`EventsHandler`](events.md) when Discord triggers an event, or by another context further down a flow (for example, a reply produces an [`AsyncContext`](#asynccontext) that can be edited later). A context can be handled by one of three flows depending on its type and the event it wraps: the [command processor](commands.md), the [task manager](tasks.md), or a simple lambda. **The id of a context only matters for tasks** — the task manager uses `getId()` to locate the `Task` that should run (see [tasks.md](tasks.md)). ## Type hierarchy ``` Context (interface) ├── InteractionContext (interface) ── reply/defer/ack via IReplyCallback + InteractionHook ├── NoMessageContext (interface) ── defaults for events without a triggering message │ └── MessageContext (interface) ── adds a triggering Message (prefix commands, replies) │ └── AbstractContext (abstract, implements Context) ── stores JDA/Guild/Channel/Member/User ├── CommandContext (abstract) ── command flow; holds CommandOptions + subcommand metadata │ ├── SlashCommandContext │ ├── MessageCommandContext │ ├── MessageMenuContext │ └── UserMenuContext ├── TaskContext (abstract) ── task flow; abstract getId(); contextData state map │ ├── ButtonTaskContext │ ├── EmojiTaskContext │ ├── MessageTaskContext │ └── ModalTaskContext ├── AsyncContext ── wraps the result of a previous reply (edit/follow-up) └── MusicChannelContext ── synthetic context targeting a guild's music channel ``` ## Core framework types ### `Context` (interface) The root contract for every context. Notable methods: - `getJDA()`, `getGuild()`, `getChannel()`, `getGuildUser()` (`Member`), `getDiscordUser()` (`User`) - `reply(...)` — unified send; overloads support plain text, embeds, message components, an `ephemeral` flag, and an async callback that receives an [`AsyncContext`](#asynccontext) for follow-up edits. - `defer(ephemeral, consumer)` — signals "thinking…" and later completes the response. - `ack(ephemeral)` — minimal acknowledgement of the event. - `getInteractionHook()` — the JDA `InteractionHook` when the underlying event is an interaction. ### `AbstractContext` (abstract class) Base implementation that stores the common JDA entities (`JDA`, `Guild`, `MessageChannel`, `Member`, `User`) and exposes them via the `Context` getters. Its constructor also sets the **Sentry user** (id + effective name) so any error captured while handling this context is attributed to the right Discord user: ```java if (user != null) { io.sentry.protocol.User sentryUser = new io.sentry.protocol.User(); sentryUser.setId(user.getId()); sentryUser.setUsername(user.getEffectiveName()); Sentry.setUser(sentryUser); } ``` ### `InteractionContext` (interface) Behavioral mix-in for events that are JDA `Interaction`s (slash commands, buttons, modals, context menus). It implements `reply`/`defer`/`ack` in terms of `IReplyCallback` and `InteractionHook`, so these contexts can reply ephemerally, defer, and edit the original response. ### `NoMessageContext` / `MessageContext` (interfaces) - `NoMessageContext` — provides sensible defaults for events that have **no** triggering message (e.g. reactions, interactions). - `MessageContext extends NoMessageContext` — adds access to a triggering `Message`, used by prefix commands and by replies that drive step tasks. ## Command contexts ### `CommandContext` Abstract base for the [command flow](commands.md). It carries the parsed [`CommandOption`s](commands.md#unified-options) and the active subcommand / subcommand-group metadata, and exposes type-safe option access, e.g. `getOption(name, resolver)`. Concrete subclasses adapt a specific JDA event to this API. | Class | Wraps | Notes | |---|---|---| | `SlashCommandContext` | `SlashCommandInteractionEvent` | Options already parsed by Discord; maps `OptionMapping` → `SlashCommandOption`. Interaction-based replies. | | `MessageCommandContext` | `MessageReceivedEvent` (prefix) | Contains the **manual parser** that turns the raw message string into `CommandOption`s (mentions, integers, booleans, …). Message-based replies. | | `MessageMenuContext` | `MessageContextInteractionEvent` | "Apps → …" action on a message; exposes the target `Message`. | | `UserMenuContext` | `UserContextInteractionEvent` | "Apps → …" action on a user; exposes the target `User`. | ## Task contexts ### `TaskContext` Abstract base for the [task flow](tasks.md). Two things distinguish it: - **`getId()` (abstract)** — the id used by `TaskManager` to find the matching `Task`. How the id is derived depends on the concrete subtype (see the table below and [tasks.md](tasks.md#the-task-id)). - **`contextData` map** — a small state bag used to carry values between steps of a multi-step flow. | Class | Wraps | `getId()` derived from | |---|---|---| | `ButtonTaskContext` | `ButtonInteractionEvent` | `getComponentId()` of the clicked button | | `ModalTaskContext` | `ModalInteractionEvent` | modal `customId` | | `EmojiTaskContext` | `MessageReactionAddEvent` | composite `guildId:messageId` | | `MessageTaskContext` | `MessageReceivedEvent` (no prefix) | the id of the message being replied to (drives step tasks) | ## Reply mechanism & `AsyncContext` ### `AsyncContext` `me.bobert.netneutrality.framework.context.AsyncContext` wraps the **result** of a previous reply — either an `InteractionHook` (for interactions) or a `Message` (for message replies) — so follow-up actions are expressed the same way regardless of the original trigger: ```java context.reply("Initial response", async -> { // 'async' is an AsyncContext for the message just sent async.edit("Updated response"); }); ``` Under the hood: - **Interactions** — edits/follow-ups go through `hook.editOriginal()` / `hook.sendMessage()`. - **Messages** — edits/follow-ups go through `message.editMessage()` / `message.reply()`. This is what makes the command and task code path-agnostic: authors write against `Context` / `AsyncContext` and never branch on "was this a slash command or a text message?". ### `MusicChannelContext` A synthetic `AbstractContext` that targets a guild's configured **music channel** rather than the channel an event came from. The music subsystem uses it to post now-playing / queue messages into the correct channel even when playback state changes without a fresh user interaction. See [music.md](music.md). ## End-to-end summary | Event | Context | Flow | Async result wraps | |---|---|---|---| | `SlashCommandInteractionEvent` | `SlashCommandContext` | Command | `InteractionHook` | | `MessageReceivedEvent` (prefix) | `MessageCommandContext` | Command | `Message` | | `ButtonInteractionEvent` | `ButtonTaskContext` | Task | `InteractionHook` | | `MessageReactionAddEvent` | `EmojiTaskContext` | Task | `Message` | | `ModalInteractionEvent` | `ModalTaskContext` | Task | `InteractionHook` |