155 lines
8.5 KiB
Markdown
155 lines
8.5 KiB
Markdown
# 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` |
|