Files
netneutrality/docs/contexts.md
T

7.5 KiB

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 when Discord triggers an event, or by another context further down a flow (for example, a reply produces an 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, the task manager, 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).

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 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:

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 Interactions (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. It carries the parsed CommandOptions 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 CommandOptions (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. 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).
  • 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:

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.

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