Raxol.Gateway.Adapter implementation for Discord.
Owns only platform I/O and translation: MESSAGE_CREATE dispatches
normalize to the gateway's %{text: binary} event shape (what a text
handler consumes), and rendered replies go out as
plain-text POST /channels/:id/messages REST calls.
Deliberately out of scope here:
- Event feeding: pair with
Raxol.Gateway.Adapter.Discord.GatewaySocket, whose:on_eventpasses raw dispatch frames tonormalize_event/1. - Authorization:
normalize_event/1is a pure translator; checkRaxol.Gateway.Pairing.authorize/2in the feed loop before routing. - Non-text dispatches (embeds, attachments, reactions, threads):
:ignorefor now.
Connection
connect/1 requires :bot_token and accepts :api_base (default
https://discord.com/api/v10), :timeout, and :post_fn (a 2-arity
(url, req_opts) -> {:ok, %{status:, body:}} | {:error, term} override
for tests or alternative HTTP clients, the same shape
Raxol.Telegram.HTTP uses; the default uses the optional Req
dependency). The connection is stateless HTTP; the returned handle is
the validated option list.
Inbound
Frames authored by bots (including this one) are ignored, so an agent
can never be goaded into a bot-to-bot reply loop. Messages with a
guild_id route as chat_type: :guild, the rest as :dm; chat_id
is the channel id either way, which is exactly where the reply goes.
Outbound
Replies are chunked against Discord's 2000-character message limit,
which counts Unicode code points - not UTF-16 units (Telegram) and not
graphemes. Chunking is grapheme-safe. Empty or whitespace-only replies
are a no-op. Failures emit [:raxol_gateway, :discord_adapter, :error]
telemetry with the classified reason; the bot token travels in an
Authorization header and is never part of a URL or log line.