///|
/// Transport-neutral services available to interaction handlers.
///
/// Executors construct these values; the public API exposes read-only
/// accessors.
pub struct AppCtx {
priv client_ : @dhttp.Client
priv application_id_ : @model.ApplicationId
priv latency_ms_ : (() -> Int64?)?
}
///|
/// The Discord REST client used by this application.
pub fn AppCtx::http(self : AppCtx) -> @dhttp.Client {
self.client_
}
///|
/// The application id learned during startup.
pub fn AppCtx::application_id(self : AppCtx) -> @model.ApplicationId {
self.application_id_
}
///|
/// The latest gateway heartbeat round-trip time in milliseconds. For a
/// multi-shard bot, this is the arithmetic mean of shards that have measured
/// latency. HTTP interaction executors and gateway connections awaiting their
/// first heartbeat acknowledgement return `None`; use
/// `GatewayCtx::latency_ms()` when the exact value for one shard is required.
pub fn AppCtx::latency_ms(self : AppCtx) -> Int64? {
match self.latency_ms_ {
Some(provider) => provider()
None => None
}
}
///|
/// The current application bound to its Discord REST client.
pub fn AppCtx::application_ref(self : AppCtx) -> @dhttp.ApplicationRef {
self.client_.application_ref(self.application_id_)
}
///|
/// Who may satisfy a component wait.
pub(all) enum WaitFrom {
Invoker
User(@model.UserId)
Anyone
} derive(Debug, Eq)
///|
fn WaitFrom::user_filter(
self : WaitFrom,
invoker : @model.UserId,
) -> @model.UserId? {
match self {
Invoker => Some(invoker)
User(user) => Some(user)
Anyone => None
}
}
///|
/// Executor-provided lookup behind `wait_for_component`: resolves the next
/// component interaction matching a custom id and optional user filter, with
/// an optional timeout in milliseconds.
pub type ComponentWaiter = async (String, @model.UserId?, Int32?) -> @framework.ComponentCtx?
///|
fn require_guild(
scope : @framework.GuildInvocation?,
) -> @framework.GuildInvocation raise HandlerError {
match scope {
Some(guild) => guild
None => raise GuildOnly
}
}
///|
/// Read-only command context for handlers that return their initial response.
pub struct ImmediateCtx {
priv raw_ : @framework.CommandCtx
priv bot_ : AppCtx
}
///|
/// The app-level services: the REST client and application id.
pub fn ImmediateCtx::app(self : ImmediateCtx) -> AppCtx {
self.bot_
}
///|
/// Guild or DM invocation scope, carrying the invoking member or user.
pub fn ImmediateCtx::scope(self : ImmediateCtx) -> @framework.InvocationScope {
self.raw_.scope()
}
///|
/// The validated guild invocation. In a DM this raises
/// `HandlerError::GuildOnly`, which the error policy renders normally.
pub fn ImmediateCtx::guild_scope(
self : ImmediateCtx,
) -> @framework.GuildInvocation raise HandlerError {
require_guild(self.raw_.guild_scope())
}
///|
/// The invoking user.
pub fn ImmediateCtx::user(self : ImmediateCtx) -> @model.User {
self.raw_.user()
}
///|
/// The full interaction payload.
pub fn ImmediateCtx::interaction(self : ImmediateCtx) -> @model.Interaction {
self.raw_.interaction
}
///|
/// The guild the interaction was invoked in, or `None` outside guilds. Use
/// `guild_scope()` for flows that require a guild; this accessor is for
/// maybe-guild flows where DMs are valid.
pub fn ImmediateCtx::guild_id(self : ImmediateCtx) -> @model.GuildId? {
self.raw_.interaction.guild_id
}
///|
/// Milliseconds left to return the initial response, or `None` when the
/// executor set no deadline.
pub fn ImmediateCtx::remaining_ms(self : ImmediateCtx) -> Int32? {
self.raw_.remaining_ms()
}
///|
/// Escape hatch to the underlying context. Its response methods bypass this
/// handler's discipline of returning its initial response: a callback sent
/// through it makes the returned reply fail with
/// `ResponseGateError::AlreadyResponded`, which reaches the error policy.
/// Prefer the read-only accessors on this context. Each use raises an
/// `alert_unchecked` warning; once the bypass is deliberate, silence it with
/// `warnings = "-alert_unchecked"` in that package's `moon.pkg`.
#alert(unchecked, "bypasses the initial-response discipline; a reply sent through it makes the handler's returned reply fail with AlreadyResponded")
pub fn ImmediateCtx::unchecked_raw(
self : ImmediateCtx,
) -> @framework.CommandCtx {
self.raw_
}
///|
/// Command context made available after the initial deferred response.
pub struct DeferredCtx {
priv raw_ : @framework.CommandCtx
priv bot_ : AppCtx
priv waiter_ : ComponentWaiter?
priv warn_ : (AppWarning) -> Unit
}
///|
/// The app-level services: the REST client and application id.
pub fn DeferredCtx::app(self : DeferredCtx) -> AppCtx {
self.bot_
}
///|
/// Guild or DM invocation scope, carrying the invoking member or user.
pub fn DeferredCtx::scope(self : DeferredCtx) -> @framework.InvocationScope {
self.raw_.scope()
}
///|
/// The validated guild invocation. In a DM this raises
/// `HandlerError::GuildOnly`, which the error policy renders normally.
pub fn DeferredCtx::guild_scope(
self : DeferredCtx,
) -> @framework.GuildInvocation raise HandlerError {
require_guild(self.raw_.guild_scope())
}
///|
/// The invoking user.
pub fn DeferredCtx::user(self : DeferredCtx) -> @model.User {
self.raw_.user()
}
///|
/// The full interaction payload.
pub fn DeferredCtx::interaction(self : DeferredCtx) -> @model.Interaction {
self.raw_.interaction
}
///|
/// The guild the interaction was invoked in, or `None` outside guilds. Use
/// `guild_scope()` for flows that require a guild; this accessor is for
/// maybe-guild flows where DMs are valid.
pub fn DeferredCtx::guild_id(self : DeferredCtx) -> @model.GuildId? {
self.raw_.interaction.guild_id
}
///|
/// Escape hatch for advanced inspection. Calling response methods on the
/// raw value opts out of this wrapper's response discipline.
/// Failure handling still follows the gate's real response state.
pub fn DeferredCtx::raw(self : DeferredCtx) -> @framework.CommandCtx {
self.raw_
}
///|
/// Fetch the original response.
pub async fn DeferredCtx::original(self : DeferredCtx) -> @model.Message {
self.raw_.original_response()
}
///|
/// Edit the original response after deferring. New `files` replace
/// the existing attachments unless `keep_attachments` lists the ones to retain;
/// `poll` can be added only here, while editing the deferred response.
pub async fn DeferredCtx::edit_original(
self : DeferredCtx,
content? : String,
clear_content~ : Bool = false,
embeds? : Array[@model.Embed],
components? : Array[@model.Component],
allowed_mentions? : @model.AllowedMentions,
files? : Array[@dhttp.FileUpload],
keep_attachments? : Array[@model.AttachmentRequest],
poll? : @model.PollCreateRequest,
) -> @model.Message {
self.raw_.edit_response(
content?,
clear_content~,
embeds?,
components?,
allowed_mentions?,
files?,
keep_attachments?,
poll?,
)
}
///|
/// Delete the original response.
pub async fn DeferredCtx::delete_original(self : DeferredCtx) -> Unit {
self.raw_.delete_response()
}
///|
/// Send a followup message after the initial deferred response.
pub async fn DeferredCtx::followup(
self : DeferredCtx,
content? : String,
embeds? : Array[@model.Embed],
components? : Array[@model.Component],
files? : Array[@dhttp.FileUpload],
allowed_mentions? : @model.AllowedMentions,
poll? : @model.PollCreateRequest,
ephemeral~ : Bool = false,
) -> @model.Message {
self.raw_.followup(
content?,
embeds?,
components?,
files?,
allowed_mentions?,
poll?,
ephemeral~,
)
}
///|
/// Fetch a followup message sent for this interaction.
pub async fn DeferredCtx::get_followup(
self : DeferredCtx,
message_id : @model.MessageId,
) -> @model.Message {
self.raw_.get_followup(message_id)
}
///|
/// Edit a followup message sent for this interaction. New `files` replace
/// its attachments unless `keep_attachments` lists the ones to retain.
pub async fn DeferredCtx::edit_followup(
self : DeferredCtx,
message_id : @model.MessageId,
content? : String,
clear_content~ : Bool = false,
embeds? : Array[@model.Embed],
components? : Array[@model.Component],
allowed_mentions? : @model.AllowedMentions,
files? : Array[@dhttp.FileUpload],
keep_attachments? : Array[@model.AttachmentRequest],
) -> @model.Message {
self.raw_.edit_followup(
message_id,
content?,
clear_content~,
embeds?,
components?,
allowed_mentions?,
files?,
keep_attachments?,
)
}
///|
/// Delete a followup message sent for this interaction.
pub async fn DeferredCtx::delete_followup(
self : DeferredCtx,
message_id : @model.MessageId,
) -> Unit {
self.raw_.delete_followup(message_id)
}
///|
/// Wait for the next click on the component `route.custom_id(state)`
/// identifies — build the button with the same route and state. By default
/// only the invoking user may satisfy the wait; pass `from=Anyone` for a
/// public flow or `from=User(id)` for someone else. Returns `None` on
/// timeout, or at once without a gateway connection. Raises `CustomIdError`
/// when the state does not encode.
///
/// The returned context has not been answered, and the waited interaction does
/// not pass through admission or the automatic defer: respond to it (for
/// example `update_message` or `defer_update`) within three seconds.
pub async fn[A] DeferredCtx::wait_for_component(
self : DeferredCtx,
route : ComponentRoute[A],
state : A,
from~ : WaitFrom = Invoker,
timeout_ms? : Int32,
) -> @framework.ComponentCtx? {
wait_for_route(
self.waiter_,
self.warn_,
route,
state,
from.user_filter(self.user().id),
timeout_ms,
)
}
///|
/// Wait through the executor's waiter for the exact id `route` builds from
/// `state`, warning when the executor has no gateway to deliver it.
async fn[A] wait_for_route(
waiter : ComponentWaiter?,
warn : (AppWarning) -> Unit,
route : ComponentRoute[A],
state : A,
user : @model.UserId?,
timeout_ms : Int32?,
) -> @framework.ComponentCtx? {
let custom_id = route.custom_id(state)
match waiter {
Some(waiter) => waiter(custom_id, user, timeout_ms)
None => {
warn(WaiterUnavailable)
None
}
}
}