discord

An experimental Discord library for MoonBit: typed APIs, a native gateway with voice (DAVE E2EE), and JS/serverless HTTP interactions.

discord
bot
gateway
api
voice
interactions
Download zip
Author
Version
0.1.0
License
Apache-2.0
Last updated
16 hours ago
Downloads
2

Dependencies

#discord.mbt

CI Ask DeepWiki

A Discord application library for MoonBit: typed interaction declarations, API models, a rate-limited REST client, JS/serverless HTTP interactions, and a native WebSocket gateway shard.

The design follows twilight: loosely coupled packages that model the Discord API, plus an App layer for typed interaction declarations.

Status: experimental, pre-1.0. APIs may change between releases.

Long-form guides live in src/guide/; their code blocks compile and run as part of the test suite, so the examples cannot drift from the library. Task-focused recipes are docstring examples on the relevant symbols — look anything up with moon ide doc (see Development).

#Prerequisites

On a clean machine, install git and run moon update before resolving the module dependencies. Moon uses git to clone the package registry.

Every native build compiles the Gateway package's zlib_stream.c, which always includes <zlib.h> even when compress=false. The zlib development headers are therefore required for all native builds: install zlib1g-dev on Debian/Ubuntu or zlib-devel on Fedora/openSUSE. The zlib shared library is additionally required at runtime when Gateway zlib-stream compression is used.

#Install

moon update moon add gaato/discord

The interaction App, REST client, and data packages support native and JavaScript. The gateway Bot executor is native-only. Both are built on moonbitlang/async.

#Quickstart

Build the interaction App, then pass it to the gateway Bot executor. This example registers and answers a /echo command. See src/examples/slash_echo for the runnable version.

///|
struct QuickstartEchoArgs {
text : String
times : Int
}

///|
async fn run_echo_bot(token : String) -> Unit {
let args : @discord.Args[QuickstartEchoArgs] = @discord.Args::map2(
@discord.arg_string(name="text", description="What to echo"),
@discord.arg_int(
name="times",
description="How many times (1-5)",
min=1,
max=5,
).with_default(1L),
(text, times) => { text, times: times.to_int(), },
)
let echo = @discord.slash(
name="echo",
description="Echo your text back",
args~,
handler=Immediate((_ctx, value) => {
@discord.CommandReply::message(
content=Array::make(value.times, value.text).join("\n"),
)
}),
)
let app = @discord.App()
app.command(echo)
let bot = @discord.Bot(app, token~)
bot.on(@discord.Events::ready(), (_ctx, ready) => {
println("ready as \{ready.user.username}")
})
bot.run()
}

#Packages

PackageWhat it isNativeJS
gaato/discordFacade: aliases for the types a typical application names directlyYesYes*
gaato/discord/modelPure data: ~24 entity domains, gateway payloads, zero IOYesYes
gaato/discord/telemetryStructured REST, gateway, and dispatch observability valuesYesYes
gaato/discord/httpREST Client, routes, rate limiting, multipart uploadsYesYes
gaato/discord/gatewayShard: connection state machine, heartbeat, resumeYesNo
gaato/discord/voiceExperimental native voice gateway v8, DAVE, RTP, and Opus send/receiveYesNo
gaato/discord/interactionCommand/component builders, typed args and autocomplete dataYesYes
gaato/discord/frameworkInteraction routing, response gates, low-level response contextsYesYes
gaato/discord/appGateway-free typed commands/components/modals, HTTP endpoint, sync and policyYesYes
gaato/discord/botNative gateway executor and typed gateway event descriptorsYesNo
gaato/discord/endpoint_httpNative signed-interactions HTTP server (serve_interactions)YesNo
gaato/discord/cacheOpt-in, gateway-driven in-memory cacheYesYes
gaato/discord/utilPure helpers: permissions, mentions, timestamps, CDN URLsYesYes
gaato/discord/verifyPure MoonBit Ed25519 request verificationYesYes
gaato/discord/ratelimitRate limiter trait + in-memory implementationYesYes
gaato/discord/queueIdentify queue trait + in-memory implementationYesYes
gaato/discord/coordinatorNative TCP coordinator for multi-process Identify and REST limitsYesNo

* The facade's gateway and HTTP-server exports exist only on native. WebAssembly is not currently enabled or validated as an application target in this repository.

Packages remain usable on their own. A REST-only tool needs http and model. The dependency graph, generated from the moon.pkg declarations by tools/package_graph and transitively reduced:

Package dependency graph

Amber nodes are native-only; every other package also runs on JS.

#Examples

Runnable programs live under src/examples/:

  • slash_echo: minimal typed Gateway bot.
  • kitchen_sink: subcommands, autocomplete, context menus, components, and modals.
  • ping_gateway: low-level Gateway and REST use.
  • low_level: manual Framework and Shard wiring.
  • workers_echo: Cloudflare Workers adapter.
  • interactions_http: native signed-interactions HTTP server.
  • plugin_demo: a stateful feedback feature installed as a separate package.
  • voice_player: join a voice channel and play an Ogg/Opus file.
  • voice_recorder: record a user's voice to an Ogg/Opus file.

#App core and executors

App owns the interaction declaration: commands, components, modals, autocomplete routes, command synchronization, and the error policy. It has no gateway dependency. After building an App, choose an executor:

  • Bot(app, token~) connects to the gateway and routes InteractionCreate events through the App.
  • app.serve(group, token~) creates an InteractionEndpoint for an HTTP adapter.

Both executors use the same handlers, so an application can move between a persistent gateway process and an HTTP or serverless deployment without rewriting its interaction declarations.

Command[A] pairs one handler with Args[A]; the argument value drives both Discord's registration payload and interaction decoding (build records with Args::map1 through Args::map8). Choose a handler mode against Discord's three-second initial-response deadline: Immediate computes and returns a CommandReply before it, Deferred acknowledges first and continues on a DeferredCtx, and Raw receives the underlying CommandCtx for imperative flows. Subcommands carry their own typed Args, autocomplete attaches directly to the focused argument, and context-menu commands, components, and typed modals route through the same App — see the commands and components and modals guides.

Client, App, and Bot each accept onion-style middleware — around one logical REST call, around routed interaction dispatch, and around gateway event fan-out — plus structured telemetry callbacks. See the middleware guide.

#Gateway executor

Bot adds typed gateway event subscriptions and services:

bot.on(@discord.Events::message_create(), (ctx, event) => {
let message = event.message
println("\{message.author.username}: \{message.content}")
})

If intents is omitted, Bot derives non-privileged intents from typed subscriptions; pass privileged intents (members, presences, message content) explicitly, and pass the full set when raw event handlers are used. ctx.wait_for(...) makes one-shot typed event observations inside a handler. The events and intents guide covers descriptors, intent derivation, decode-error observers, opt-in zlib-stream compression, and the gateway-driven in-memory cache.

Several shards can run in one process behind the same dispatch path, so handlers, the cache, collectors, and telemetry observe every shard:

///|
let bot = @discord.Bot(app, token~, shards=Auto)

Identify calls honor the max_concurrency bucket rules from GET /gateway/bot, and multi-process deployments can share Identify and REST limits through the bundled TCP coordinator. See the scaling guide.

#Voice (experimental)

Native builds can join voice gateway v8 calls, play 20 ms Opus frames, and receive per-user Opus streams. Discord requires DAVE encryption, so voice applications use the native-only gaato/dave binding to Discord's official libdave for MLS and media encryption. The separate Rust library in voice-shim/ handles RTP transport AEAD only. gaato/dave currently pins upstream v1.2.0/cpp; gaato/discord pins transport component release voice-shim-v0.1.0. A native voice build can bootstrap and verify both prebuilt runtimes without installing Cargo. See the voice guide for installation and runtime limits, the dave_probe, voice_player, and voice_recorder examples, and the accepted design in src/voice/DESIGN.md.

#Synchronization and failures

CommandSync belongs to App and defaults to Global. The gateway executor synchronizes after its first READY; App::serve synchronizes during startup only when you pass sync=true. Guild and multi-guild targets are available.

Synchronization uses Discord's bulk overwrite endpoints. A required PUT deletes commands registered outside this App from the selected scope. Use Disabled when another process owns registration.

Install app.error_policy(...) to map failures to logs or interaction responses; handlers raise HandlerError variants such as UserMessage or MissingPermission for expected failures, and commands accept ordered pre-execution checks and fixed-window cooldowns. See the structuring bots guide.

#HTTP interactions (experimental)

App::serve(group, token~) starts the HTTP interaction executor and returns an InteractionEndpoint that uses the App's declarations and error policy without opening a gateway. handle accepts decoded interaction JSON and returns callback JSON for Reply; the caller owns the task group, so handlers that defer keep running on it after handle returns:

///|
async fn handle_http_interaction(
app : @discord.App,
group : @async.TaskGroup[Unit],
token : String,
body : Json,
) -> Json? {
app.serve(group, token~).handle(body)
}

Serverless targets such as Cloudflare Workers are first class: adapters verify the raw request bytes with the reusable pure MoonBit @discord.InteractionVerifier before parsing. The workers_echo example is tested inside Cloudflare's local workerd runtime and supports streamed multipart callbacks for in-memory FileUpload values. On native, @discord.serve_interactions(group, app, addr~, public_key~, token~) is a complete signed-interactions HTTP server. See the HTTP interactions guide and the interactions_http and workers_echo examples.

The JavaScript REST client supports null-body 204 responses through moonbitlang/async@0.21.2; the workers_echo workerd suite covers a deferred interaction-response deletion end to end. Gateway and Voice transports remain native-only.

#Typed models

Every entity decodes from real API payloads, and unknown enum values and unknown JSON keys round-trip through Unknown(...) variants, so a new Discord feature does not break your application. IDs are phantom-typed snowflakes — a UserId cannot be passed where a ChannelId is expected — that keep full precision above 2^53:

let id : @model.MessageId = @model.Id::parse("175928847299117063")
println(id.timestamp_ms()) // 1462015105796

Wire models use T? for optional fields and Nullable[T] for nullable fields; PATCH methods expose plain optional arguments with clear_<field>=true flags for clearing. See the models and utilities guide.

#REST without the gateway

http.Client stands alone: typed wrappers over the routes, per-bucket rate limiting with a global window, automatic 429 retry, multipart file uploads, and a raw client.request(route) escape hatch for anything not wrapped yet. Ordinary channel messages suppress @everyone and @here by default while still parsing user and role mentions.

let client = @dhttp.Client(token)
let message = client.create_message(channel_id, content="hello")
client.create_message(channel_id, content="with reply", reply_to=message.id)
|> ignore

Every endpoint with a resource anchor is also available through client-bound refs, which chain from parent to child resource before applying a verb:

client.guild_ref(guild_id).emoji_ref(emoji_id).edit(name="renamed") |> ignore

Validation failures raise before any I/O, request failures use DiscordHttpError, and list endpoints expose stateful Paginator[T] values. The REST guide covers the full ref surface, allowed mentions, file uploads, pagination, and custom routes.

Also in the box, each with its guide chapter:

#Development

moon check --target native --deny-warn moon check --target js --deny-warn moon test --target native --release # debug native builds need a working tcc setup moon test --target js --release moon fmt moon -C template fmt --check moon -C tools/package_graph fmt --check moon info --target native # regenerate pkg.generated.mbti (API review signal)

Look up any package, type, or symbol — including its docstring examples — from the terminal:

moon ide doc "@discord" moon ide doc "@http.Client::create_message" moon ide doc "@model.Message" moon ide doc "@util.channel_permissions"

Each package's pkg.generated.mbti is a concise public-interface snapshot; review its diff when checking API drift.

Every MoonBit code block in this README has a compiled twin in src/readme_native_test.mbt, and code blocks marked mbt check in the guides and in docstrings compile and run as part of the test suite.

#License

Apache-2.0

#discord.mbt

CI Ask DeepWiki

A Discord application library for MoonBit: typed interaction declarations, API models, a rate-limited REST client, JS/serverless HTTP interactions, and a native WebSocket gateway shard.

The design follows twilight: loosely coupled packages that model the Discord API, plus an App layer for typed interaction declarations.

Status: experimental, pre-1.0. APIs may change between releases.

Long-form guides live in src/guide/; their code blocks compile and run as part of the test suite, so the examples cannot drift from the library. Task-focused recipes are docstring examples on the relevant symbols — look anything up with moon ide doc (see Development).

#Prerequisites

On a clean machine, install git and run moon update before resolving the module dependencies. Moon uses git to clone the package registry.

Every native build compiles the Gateway package's zlib_stream.c, which always includes <zlib.h> even when compress=false. The zlib development headers are therefore required for all native builds: install zlib1g-dev on Debian/Ubuntu or zlib-devel on Fedora/openSUSE. The zlib shared library is additionally required at runtime when Gateway zlib-stream compression is used.

#Install

moon update moon add gaato/discord

The interaction App, REST client, and data packages support native and JavaScript. The gateway Bot executor is native-only. Both are built on moonbitlang/async.

#Quickstart

Build the interaction App, then pass it to the gateway Bot executor. This example registers and answers a /echo command. See src/examples/slash_echo for the runnable version.

///|
struct QuickstartEchoArgs {
text : String
times : Int
}

///|
async fn run_echo_bot(token : String) -> Unit {
let args : @discord.Args[QuickstartEchoArgs] = @discord.Args::map2(
@discord.arg_string(name="text", description="What to echo"),
@discord.arg_int(
name="times",
description="How many times (1-5)",
min=1,
max=5,
).with_default(1L),
(text, times) => { text, times: times.to_int(), },
)
let echo = @discord.slash(
name="echo",
description="Echo your text back",
args~,
handler=Immediate((_ctx, value) => {
@discord.CommandReply::message(
content=Array::make(value.times, value.text).join("\n"),
)
}),
)
let app = @discord.App()
app.command(echo)
let bot = @discord.Bot(app, token~)
bot.on(@discord.Events::ready(), (_ctx, ready) => {
println("ready as \{ready.user.username}")
})
bot.run()
}

#Packages

PackageWhat it isNativeJS
gaato/discordFacade: aliases for the types a typical application names directlyYesYes*
gaato/discord/modelPure data: ~24 entity domains, gateway payloads, zero IOYesYes
gaato/discord/telemetryStructured REST, gateway, and dispatch observability valuesYesYes
gaato/discord/httpREST Client, routes, rate limiting, multipart uploadsYesYes
gaato/discord/gatewayShard: connection state machine, heartbeat, resumeYesNo
gaato/discord/voiceExperimental native voice gateway v8, DAVE, RTP, and Opus send/receiveYesNo
gaato/discord/interactionCommand/component builders, typed args and autocomplete dataYesYes
gaato/discord/frameworkInteraction routing, response gates, low-level response contextsYesYes
gaato/discord/appGateway-free typed commands/components/modals, HTTP endpoint, sync and policyYesYes
gaato/discord/botNative gateway executor and typed gateway event descriptorsYesNo
gaato/discord/endpoint_httpNative signed-interactions HTTP server (serve_interactions)YesNo
gaato/discord/cacheOpt-in, gateway-driven in-memory cacheYesYes
gaato/discord/utilPure helpers: permissions, mentions, timestamps, CDN URLsYesYes
gaato/discord/verifyPure MoonBit Ed25519 request verificationYesYes
gaato/discord/ratelimitRate limiter trait + in-memory implementationYesYes
gaato/discord/queueIdentify queue trait + in-memory implementationYesYes
gaato/discord/coordinatorNative TCP coordinator for multi-process Identify and REST limitsYesNo

* The facade's gateway and HTTP-server exports exist only on native. WebAssembly is not currently enabled or validated as an application target in this repository.

Packages remain usable on their own. A REST-only tool needs http and model. The dependency graph, generated from the moon.pkg declarations by tools/package_graph and transitively reduced:

Package dependency graph

Amber nodes are native-only; every other package also runs on JS.

#Examples

Runnable programs live under src/examples/:

  • slash_echo: minimal typed Gateway bot.
  • kitchen_sink: subcommands, autocomplete, context menus, components, and modals.
  • ping_gateway: low-level Gateway and REST use.
  • low_level: manual Framework and Shard wiring.
  • workers_echo: Cloudflare Workers adapter.
  • interactions_http: native signed-interactions HTTP server.
  • plugin_demo: a stateful feedback feature installed as a separate package.
  • voice_player: join a voice channel and play an Ogg/Opus file.
  • voice_recorder: record a user's voice to an Ogg/Opus file.

#App core and executors

App owns the interaction declaration: commands, components, modals, autocomplete routes, command synchronization, and the error policy. It has no gateway dependency. After building an App, choose an executor:

  • Bot(app, token~) connects to the gateway and routes InteractionCreate events through the App.
  • app.serve(group, token~) creates an InteractionEndpoint for an HTTP adapter.

Both executors use the same handlers, so an application can move between a persistent gateway process and an HTTP or serverless deployment without rewriting its interaction declarations.

Command[A] pairs one handler with Args[A]; the argument value drives both Discord's registration payload and interaction decoding (build records with Args::map1 through Args::map8). Choose a handler mode against Discord's three-second initial-response deadline: Immediate computes and returns a CommandReply before it, Deferred acknowledges first and continues on a DeferredCtx, and Raw receives the underlying CommandCtx for imperative flows. Subcommands carry their own typed Args, autocomplete attaches directly to the focused argument, and context-menu commands, components, and typed modals route through the same App — see the commands and components and modals guides.

Client, App, and Bot each accept onion-style middleware — around one logical REST call, around routed interaction dispatch, and around gateway event fan-out — plus structured telemetry callbacks. See the middleware guide.

#Gateway executor

Bot adds typed gateway event subscriptions and services:

bot.on(@discord.Events::message_create(), (ctx, event) => {
let message = event.message
println("\{message.author.username}: \{message.content}")
})

If intents is omitted, Bot derives non-privileged intents from typed subscriptions; pass privileged intents (members, presences, message content) explicitly, and pass the full set when raw event handlers are used. ctx.wait_for(...) makes one-shot typed event observations inside a handler. The events and intents guide covers descriptors, intent derivation, decode-error observers, opt-in zlib-stream compression, and the gateway-driven in-memory cache.

Several shards can run in one process behind the same dispatch path, so handlers, the cache, collectors, and telemetry observe every shard:

///|
let bot = @discord.Bot(app, token~, shards=Auto)

Identify calls honor the max_concurrency bucket rules from GET /gateway/bot, and multi-process deployments can share Identify and REST limits through the bundled TCP coordinator. See the scaling guide.

#Voice (experimental)

Native builds can join voice gateway v8 calls, play 20 ms Opus frames, and receive per-user Opus streams. Discord requires DAVE encryption, so voice applications use the native-only gaato/dave binding to Discord's official libdave for MLS and media encryption. The separate Rust library in voice-shim/ handles RTP transport AEAD only. gaato/dave currently pins upstream v1.2.0/cpp; gaato/discord pins transport component release voice-shim-v0.1.0. A native voice build can bootstrap and verify both prebuilt runtimes without installing Cargo. See the voice guide for installation and runtime limits, the dave_probe, voice_player, and voice_recorder examples, and the accepted design in src/voice/DESIGN.md.

#Synchronization and failures

CommandSync belongs to App and defaults to Global. The gateway executor synchronizes after its first READY; App::serve synchronizes during startup only when you pass sync=true. Guild and multi-guild targets are available.

Synchronization uses Discord's bulk overwrite endpoints. A required PUT deletes commands registered outside this App from the selected scope. Use Disabled when another process owns registration.

Install app.error_policy(...) to map failures to logs or interaction responses; handlers raise HandlerError variants such as UserMessage or MissingPermission for expected failures, and commands accept ordered pre-execution checks and fixed-window cooldowns. See the structuring bots guide.

#HTTP interactions (experimental)

App::serve(group, token~) starts the HTTP interaction executor and returns an InteractionEndpoint that uses the App's declarations and error policy without opening a gateway. handle accepts decoded interaction JSON and returns callback JSON for Reply; the caller owns the task group, so handlers that defer keep running on it after handle returns:

///|
async fn handle_http_interaction(
app : @discord.App,
group : @async.TaskGroup[Unit],
token : String,
body : Json,
) -> Json? {
app.serve(group, token~).handle(body)
}

Serverless targets such as Cloudflare Workers are first class: adapters verify the raw request bytes with the reusable pure MoonBit @discord.InteractionVerifier before parsing. The workers_echo example is tested inside Cloudflare's local workerd runtime and supports streamed multipart callbacks for in-memory FileUpload values. On native, @discord.serve_interactions(group, app, addr~, public_key~, token~) is a complete signed-interactions HTTP server. See the HTTP interactions guide and the interactions_http and workers_echo examples.

The JavaScript REST client supports null-body 204 responses through moonbitlang/async@0.21.2; the workers_echo workerd suite covers a deferred interaction-response deletion end to end. Gateway and Voice transports remain native-only.

#Typed models

Every entity decodes from real API payloads, and unknown enum values and unknown JSON keys round-trip through Unknown(...) variants, so a new Discord feature does not break your application. IDs are phantom-typed snowflakes — a UserId cannot be passed where a ChannelId is expected — that keep full precision above 2^53:

let id : @model.MessageId = @model.Id::parse("175928847299117063")
println(id.timestamp_ms()) // 1462015105796

Wire models use T? for optional fields and Nullable[T] for nullable fields; PATCH methods expose plain optional arguments with clear_<field>=true flags for clearing. See the models and utilities guide.

#REST without the gateway

http.Client stands alone: typed wrappers over the routes, per-bucket rate limiting with a global window, automatic 429 retry, multipart file uploads, and a raw client.request(route) escape hatch for anything not wrapped yet. Ordinary channel messages suppress @everyone and @here by default while still parsing user and role mentions.

let client = @dhttp.Client(token)
let message = client.create_message(channel_id, content="hello")
client.create_message(channel_id, content="with reply", reply_to=message.id)
|> ignore

Every endpoint with a resource anchor is also available through client-bound refs, which chain from parent to child resource before applying a verb:

client.guild_ref(guild_id).emoji_ref(emoji_id).edit(name="renamed") |> ignore

Validation failures raise before any I/O, request failures use DiscordHttpError, and list endpoints expose stateful Paginator[T] values. The REST guide covers the full ref surface, allowed mentions, file uploads, pagination, and custom routes.

Also in the box, each with its guide chapter:

#Development

moon check --target native --deny-warn moon check --target js --deny-warn moon test --target native --release # debug native builds need a working tcc setup moon test --target js --release moon fmt moon -C template fmt --check moon -C tools/package_graph fmt --check moon info --target native # regenerate pkg.generated.mbti (API review signal)

Look up any package, type, or symbol — including its docstring examples — from the terminal:

moon ide doc "@discord" moon ide doc "@http.Client::create_message" moon ide doc "@model.Message" moon ide doc "@util.channel_permissions"

Each package's pkg.generated.mbti is a concise public-interface snapshot; review its diff when checking API drift.

Every MoonBit code block in this README has a compiled twin in src/readme_native_test.mbt, and code blocks marked mbt check in the guides and in docstrings compile and run as part of the test suite.

#License

Apache-2.0

AllowedMentionType

A mention category Discord may derive directly from message content.

AllowedMentions

Controls which mentions Discord resolves and notifies for an outgoing message.

Use AllowedMentions(...) so parse categories and explicit ID lists cannot conflict. The stored arrays are copied during construction to preserve that invariant if callers later mutate their input arrays.

AllowedMentionsError

An invalid allowed-mentions configuration rejected during construction.

App

using @gaato/discord/app { type App }

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

AppConfigError

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

AppCtx

using @gaato/discord/app { type AppCtx }

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ApplicationEmojiRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

ApplicationRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

Arg

One typed command option, combining its registration definition and reader.

Args

A typed collection of command options used for registration and decoding.

ArgsDecodeError

Errors introduced by typed argument combinators.

AudioSource

Experimental native voice connection, playback, and receive APIs.

AutoModerationRuleRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

AutocompleteCtx

Context handed to an autocomplete handler.

Bot

using @gaato/discord/bot { type Bot }

Native gateway executor and typed event descriptors.

BotError

Native gateway executor and typed event descriptors.

ChannelRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

CheckCtx

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

Client

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

Command

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

CommandCheck

A command guard. Returning false produces HandlerError::CheckFailed; expected denials can instead raise a more specific HandlerError.

CommandCtx

Context handed to a slash / context-menu command handler.

CommandModel

A typed view of one command invocation, decoded from its submitted options.

Implement this by hand per command (MoonBit has no derive macro for it):

struct Echo {
text : String
times : Int64?
}

impl @interaction.CommandModel for Echo with fn from_options(options) {
{ text: options.string("text"), times: options.int_opt("times"), }
}

Commands with subcommands are naturally modeled as an enum whose from_options matches on options.path().

CommandOptions

Typed reader over the options submitted with an application command.

Subcommand and subcommand-group levels are flattened away on construction: path() reports the traversed names and the value accessors see only the leaf options. Required accessors raise OptionError; _opt variants return None when the option is absent but still raise if a present option has an unexpected shape.

CommandReply

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

CommandSpec

A declarative description of one application command, used both to register the command (its ToJson is the registration payload) and to route incoming interactions by command type and name.

CommandSync

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ComponentCtx

Context handed to a message-component (button / select) handler.

ComponentDeferredCtx

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ComponentHandler

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ComponentImmediateCtx

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ComponentReply

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ComponentWaiter

type ComponentWaiter = async (String, Int?) ->
ComponentCtx
?

Executor-provided lookup behind wait_for_component: resolves the next component interaction matching a custom id, with an optional timeout in milliseconds.

CooldownBucket

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

Coordinator

Native cross-process Identify and REST rate-limit coordination.

CoordinatorError

Native cross-process Identify and REST rate-limit coordination.

DeferredCtx

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

EmojiRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

EntitlementRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

ErrorPolicy

type ErrorPolicy = async (
FailureCtx
, Error) -> Unit

Policy invoked for interaction, event, and service failures.

Event

A gateway dispatch event decoded by its wire event name.

Unknown event names retain both the name and payload for forward compatibility.

EventType

Native gateway executor and typed event descriptors.

Events

using @gaato/discord/bot { type Events }

Native gateway executor and typed event descriptors.

FailureCtx

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

FailureOrigin

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

FailureRaw

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

FileUpload

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

Framework

Routes incoming interactions to declared command / component / modal handlers, and can register the declared commands with Discord.

Registration methods return self for chaining. Feed every InteractionCreate gateway event to process.

GatewayCtx

Native gateway executor and typed event descriptors.

GlobalCommandRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

GuildCommandRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

GuildInvocation

Proof that an interaction was invoked in a guild: the guild id and the invoking member, both validated when the context was built.

GuildRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

HandlerError

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ImmediateCtx

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

InitialResponse

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

Intents

Gateway intents: the server-side event subscription bitfield sent in Identify. Unknown bits are preserved. Wire format: JSON number.

test "compose the gateway event subscriptions a bot needs" {
let intents = @model.Intents::guilds() |
@model.Intents::guild_messages() |
@model.Intents::message_content()
inspect(
intents.bits(),
content=(
#|33281
),
)
assert_true(intents.contains(@model.Intents::message_content()))
}

InteractionContextError

A malformed interaction that cannot satisfy the guarantees exposed by a typed handler context.

InteractionEndpoint

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

InteractionHandler

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

InteractionOutcome

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

InteractionVerifier

Pure MoonBit verification for Discord HTTP interaction signatures.

InteractionVerifierError

Pure MoonBit verification for Discord HTTP interaction signatures.

InteractionsServer

Native Discord HTTP-interactions server.

InviteRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

InvocationScope

The context in which an interaction was invoked.

MemberRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

MessageRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.
using @gaato/discord/app { type Modal }

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ModalCtx

Context handed to a modal-submit handler.

ModalDeferredCtx

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ModalField

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ModalFields

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ModalHandle

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ModalImmediateCtx

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

ModalOrigin

The interaction that opened a submitted modal.

ModalSubmitHandler

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

OggOpusError

Experimental native voice connection, playback, and receive APIs.

OggOpusSource

Experimental native voice connection, playback, and receive APIs.

OggOpusWriter

Experimental native voice connection, playback, and receive APIs.

Paginator

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

RegisteredCommand

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

RemoteIdentifyQueue

Native cross-process Identify and REST rate-limit coordination.

RemoteRateLimiter

Native cross-process Identify and REST rate-limit coordination.

ResponsePhase

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

RoleRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

ScheduledEventRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

Shard

Native Discord Gateway transport primitives.

ShardConfig

Native gateway executor and typed event descriptors.

ShardEvent

Native Discord Gateway transport primitives.

SkuRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

SlashChild

Gateway-free application declarations, typed interaction handlers, command synchronization, and error policy contexts.

SoundboardSoundRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

Spawner

type Spawner = async (async () -> Unit) -> Unit

Executor-provided function that runs a handler task concurrently on the surrounding async runtime.

StickerRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

SuggestCtx

Data available while producing autocomplete choices. This context is transport-neutral and intentionally does not own an HTTP client.

TargetUser

The resolved target of a USER context-menu command.

TelemetryEvent

Structured observability events shared by REST and gateway runtimes.

TemplateRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

UserRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

VoiceConnection

Experimental native voice connection, playback, and receive APIs.

VoiceConnectionState

Experimental native voice connection, playback, and receive APIs.

VoiceError

Experimental native voice connection, playback, and receive APIs.

VoiceEvent

Experimental native voice connection, playback, and receive APIs.

VoiceReceiveStream

Experimental native voice connection, playback, and receive APIs.

VoiceTelemetry

Experimental native voice connection, playback, and receive APIs.

WebhookMessageRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

WebhookRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

WebhookTokenRef

Convenience aliases for the types a typical bot names directly, so the core loop can be written against @discord alone. Everything else — entity models, REST wrappers, gateway internals, option builders — lives in the focused packages: @discord/model, @discord/http, @discord/gateway, @discord/interaction, @discord/framework.

API_VERSION

let API_VERSION : Int

Discord API version this library targets.

SILENCE_FRAME

let SILENCE_FRAME : Bytes

Discord's canonical Opus silence frame.

action_row

Build a message component row.

arg_attachment

fn arg_attachment(name~ : String, description~ : String, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Arg
[
Attachment
]

Define a required attachment option resolved to its attachment object.

arg_bool

fn arg_bool(name~ : String, description~ : String, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Arg
[Bool]

Define a required boolean option.

arg_channel

fn arg_channel(name~ : String, description~ : String, channel_types? : Array[
ChannelType
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Arg
[
Id
[
ChannelMarker
]]

Define a required channel option.

arg_int

fn arg_int(name~ : String, description~ : String, min? : Int64, max? : Int64, choices? : Array[
CommandOptionChoice
], suggest? : async (
SuggestCtx
, Int64) -> Array[
CommandOptionChoice
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Arg
[Int64]

Define a required integer option.

arg_mentionable

fn arg_mentionable(name~ : String, description~ : String, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Arg
[
Id
[
GenericMarker
]]

Define a required mentionable option.

arg_number

fn arg_number(name~ : String, description~ : String, min? : Double, max? : Double, choices? : Array[
CommandOptionChoice
], suggest? : async (
SuggestCtx
, Double) -> Array[
CommandOptionChoice
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Arg
[Double]

Define a required floating-point number option.

arg_role

fn arg_role(name~ : String, description~ : String, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Arg
[
Id
[
RoleMarker
]]

Define a required role option.

arg_string

fn arg_string(name~ : String, description~ : String, choices? : Array[
CommandOptionChoice
], min_length? : Int, max_length? : Int, suggest? : async (
SuggestCtx
, String) -> Array[
CommandOptionChoice
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Arg
[String]

Define a required string option.

arg_user

fn arg_user(name~ : String, description~ : String, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Arg
[
Id
[
UserMarker
]]

Define a required user option.

attachment_option

fn attachment_option(name : String, description : String, required? : Bool, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe an ATTACHMENT option.

boolean_option

fn boolean_option(name : String, description : String, required? : Bool, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe a BOOLEAN option.

button

fn button(custom_id~ : String, label? : String, style? :
ButtonStyle
, emoji? :
Emoji
, disabled? : Bool) ->
Component

Build an interactive button.

channel_option

fn channel_option(name : String, description : String, required? : Bool, channel_types? : Array[
ChannelType
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe a CHANNEL option.

channel_select

fn channel_select(custom_id~ : String, placeholder? : String, min_values? : Int, max_values? : Int, disabled? : Bool, channel_types? : Array[
ChannelType
]) ->
Component

Build a channel select menu, optionally restricted to channel_types; the chosen channels arrive via the component context's selected_channels.

container

fn container(components~ : Array[
Component
], accent_color? : Int, spoiler? : Bool) ->
Component

Build a container (components v2): groups child components in a rounded box with an optional accent_color (0xRRGGBB) stripe, similar to an embed. Reply and send layers set the required IS_COMPONENTS_V2 message flag automatically.

dm_only

Require a direct-message invocation.

guild_only

Require a guild invocation.

int_choice

fn int_choice(name : String, value : Int64, name_localizations? : Map[String, String]) ->
CommandOptionChoice

Describe an integer-valued option choice.

integer_option

fn integer_option(name : String, description : String, required? : Bool, choices? : Array[
CommandOptionChoice
], min_value? : Int64, max_value? : Int64, autocomplete? : Bool, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe an INTEGER option.

label

fn label(label~ : String, component~ :
Component
, description? : String) ->
Component

Wrap a modal component with a label and optional description. Modals require every input to be wrapped in a label; the typed ModalField builders do this automatically.
fn link_button(url~ : String, label? : String, emoji? :
Emoji
, disabled? : Bool) ->
Component

Build a link button.

Build a media gallery (components v2): a grid of 1–10 media items. Reply and send layers set the required IS_COMPONENTS_V2 message flag automatically.

media_item

fn media_item(url~ : String, description? : String, spoiler? : Bool) ->
MediaGalleryItem

Build one media gallery entry from an image or video url (https://... or attachment://<filename>), for use with media_gallery.

mentionable_option

fn mentionable_option(name : String, description : String, required? : Bool, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe a MENTIONABLE option.

mentionable_select

fn mentionable_select(custom_id~ : String, placeholder? : String, min_values? : Int, max_values? : Int, disabled? : Bool) ->
Component

Build a mentionable select menu accepting both users and roles.
fn[A] modal(custom_id~ : String, title~ : String, fields~ :
ModalFields
[A]) ->
Modal
[A]

Define a typed modal.

test "construct and validate a typed modal" {
let feedback = @app.modal(
custom_id="feedback",
title="Feedback",
fields=@app.ModalFields::of(
@app.text_field(custom_id="details", label="Details").optional(),
),
)
let app = @app.App(sync=Disabled)
app.on_modal(feedback, Raw(_ => ()))
app.validate()
let _ : @app.ModalHandle = feedback.show(state="draft")
}

number_choice

fn number_choice(name : String, value : Double, name_localizations? : Map[String, String]) ->
CommandOptionChoice

Describe a number-valued option choice.

number_option

fn number_option(name : String, description : String, required? : Bool, choices? : Array[
CommandOptionChoice
], min_value? : Double, max_value? : Double, autocomplete? : Bool, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe a NUMBER option.

required_permissions

Require effective guild permissions supplied on the interaction member. Discord does not provide member permissions for DM invocations.

role_option

fn role_option(name : String, description : String, required? : Bool, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe a ROLE option.

role_select

fn role_select(custom_id~ : String, placeholder? : String, min_values? : Int, max_values? : Int, disabled? : Bool) ->
Component

Build a role select menu; the chosen roles arrive via the component context's selected_roles.

section

Build a section (components v2): up to three text displays laid out next to an accessory (a thumbnail or button). Reply and send layers set the required IS_COMPONENTS_V2 message flag automatically.

select_field

fn select_field(custom_id~ : String, label~ : String, options~ : Array[
SelectOption
], description? : String, placeholder? : String, min_values? : Int, max_values? : Int) ->
ModalField
[Array[String]]

Define a modern label-wrapped string select.

select_option

fn select_option(label~ : String, value~ : String, description? : String, emoji? :
Emoji
, default? : Bool) ->
SelectOption

Build an option for a string select menu.

separator

fn separator(divider? : Bool, spacing? : Int) ->
Component

Build a separator (components v2): vertical padding between components, with an optional visible divider line and spacing size (1 = small, 2 = large). Reply and send layers set the required IS_COMPONENTS_V2 message flag automatically.

serve_interactions

async fn[X] serve_interactions(group :
TaskGroup
[X], app :
App
, addr~ : String, public_key~ : String, client? :
Client
, token? : String, application_id? :
Id
[
ApplicationMarker
], sync? : Bool, path? : String, deadline_ms? : Int) ->
InteractionsServer

Start a native Discord HTTP-interactions server in group.

Use port zero in addr to let the OS choose a free port, then read the resolved address from InteractionsServer::addr.

slash

fn[A] slash(name~ : String, description~ : String, args~ :
Args
[A], handler~ :
InteractionHandler
[A], default_member_permissions? :
Permissions
, nsfw? : Bool, integration_types? : Array[
ApplicationIntegrationType
], contexts? : Array[
InteractionContextType
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Command
[A]

Define a typed slash command.

test "inspect a slash-command registration spec" {
let command = @app.slash(
name="echo",
description="Echo text",
args=@interaction.Args::of(
@interaction.arg_string(name="text", description="Text to echo"),
),
handler=Raw(_ => ()),
)
json_inspect(command.spec().to_json(), content={
"name": "echo",
"description": "Echo text",
"options": [
{
"type": 3,
"name": "text",
"description": "Text to echo",
"required": true,
},
],
})
}

slash_group

fn slash_group(name~ : String, description~ : String, children~ : Array[
SlashChild
], default_member_permissions? :
Permissions
, nsfw? : Bool, integration_types? : Array[
ApplicationIntegrationType
], contexts? : Array[
InteractionContextType
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
Command
[Unit]

Define a slash command whose children dispatch by submitted option path.

string_choice

fn string_choice(name : String, value : String, name_localizations? : Map[String, String]) ->
CommandOptionChoice

Describe a string-valued option choice.

string_option

fn string_option(name : String, description : String, required? : Bool, choices? : Array[
CommandOptionChoice
], min_length? : Int, max_length? : Int, autocomplete? : Bool, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe a STRING option.

string_select

fn string_select(custom_id~ : String, options~ : Array[
SelectOption
], placeholder? : String, min_values? : Int, max_values? : Int, disabled? : Bool) ->
Component

Build a string select menu.

sub_command

fn sub_command(name : String, description : String, options? : Array[
CommandOption
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe a SUB_COMMAND option.

sub_command_group

fn sub_command_group(name : String, description : String, sub_commands : Array[
CommandOption
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe a SUB_COMMAND_GROUP option.

subcommand

fn[A] subcommand(name~ : String, description~ : String, args~ :
Args
[A], handler~ :
InteractionHandler
[A], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
SlashChild

Define a typed slash subcommand.

subcommand_group

fn subcommand_group(name~ : String, description~ : String, children~ : Array[
SlashChild
], name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
SlashChild

Define a slash subcommand group. Nested groups are retained for validation and rejected by App::validate, never by panic or abort here.

text_display

fn text_display(content : String) ->
Component

Build a text display block (components v2): markdown content rendered in the message body. Reply and send layers set the required IS_COMPONENTS_V2 message flag automatically.

text_field

fn text_field(custom_id~ : String, label~ : String, style? :
TextInputStyle
, description? : String, placeholder? : String, min_length? : Int, max_length? : Int, value? : String) ->
ModalField
[String]

Define a modern label-wrapped text input. value is its static prefill; Modal::show(values=...) can override it for one response.

text_input

fn text_input(custom_id~ : String, style? :
TextInputStyle
, placeholder? : String, min_length? : Int, max_length? : Int, required? : Bool, value? : String) ->
Component

Build a modal text input. style picks single-line Short (default) or multi-line Paragraph; value prefills the field. Wrap it with label before placing it in a modal (the typed ModalField builders handle this).

thumbnail

fn thumbnail(url~ : String, description? : String, spoiler? : Bool) ->
Component

Build a thumbnail accessory (components v2) from an image url (https://... or attachment://<filename>).

user_command

Define a USER context-menu command.

test "inspect context-menu command kinds" {
let user = @app.user_command(name="Inspect user", handler=Raw(_ => ()))
let message = @app.message_command(
name="Inspect message",
handler=Raw(_ => ()),
)
json_inspect(user.spec().to_json(), content={
"name": "Inspect user",
"description": "",
"type": 2,
})
json_inspect(message.spec().to_json(), content={
"name": "Inspect message",
"description": "",
"type": 3,
})
}

user_option

fn user_option(name : String, description : String, required? : Bool, name_localizations? : Map[String, String], description_localizations? : Map[String, String]) ->
CommandOption

Describe a USER option.

user_select

fn user_select(custom_id~ : String, placeholder? : String, min_values? : Int, max_values? : Int, disabled? : Bool) ->
Component

Build a user select menu; the chosen users arrive in resolved and via the component context's selected_users.

verify_signature

fn verify_signature(public_key~ : String, signature~ : String, timestamp~ : String, body~ : BytesView) -> Bool

Verify a Discord HTTP interaction signature without retaining the key.

This convenience function validates and expands public_key on every call. Long-lived adapters should construct and reuse an InteractionVerifier instead. Malformed keys and signatures return false.