Skip to content

Repository files navigation

discord.rs

Coveralls

discord.rs is a standalone Discord bot framework for Rust with typed models, typed gateway events, Components V2, collectors, cache managers, and an HTTP client.

Brand name: discord.rs. The crates.io package name and Rust import path remain discordrs.

Features

  • Typed Client runtime with Event enum dispatch and compatibility BotClient alias
  • Typed RestClient with shared route/global rate-limit state and compatibility DiscordHttpClient alias
  • prelude::* re-exports for common runtime, builder, helper, and response types
  • Cache-backed manager reads for guilds, channels, members, roles, presences, and messages, with bounded defaults, cheap Arc read APIs for hot member/message/presence paths, and explicit CacheConfig overrides
  • Collectors for messages, interactions, components, and modals behind the collectors feature
  • Gateway WebSocket client with connect, heartbeat, identify, resume, reconnect, terminal close-code handling, and fixed compressed binary frame decoding for explicit zlib-stream connections
  • Shard supervisor and shard messenger control paths for queued shard boot, reconnect, shutdown, presence, and voice state updates
  • Voice manager plus voice runtime support for websocket hello/identify, UDP discovery, select-protocol, speaking updates, raw UDP receive, AES-GCM/XChaCha RTP-size Opus packet decrypt, pure-Rust Opus PCM decode, and Opus-frame RTP send helpers
  • Live-validated dave feature with DAVE opcode state tracking, davey/OpenMLS-backed MLS lifecycle helpers, receive decryptor hooks, and outbound media encryption hooks
  • Optional OAuth2 backend helpers for authorization URLs, authorization-code exchange, and refresh-token exchange
  • Typed Discord coverage for all official REST route shapes audited on 2026-05-02, plus Webhook Events, lobbies, guild incident actions, audit logs, guild count fetches, guild modifications, guild channel creation and reordering, guild ban pagination, single-member ban bodies, guild member profile fields and search/list pagination, current-user guild pagination/counts, guild/member/current-member edits, guild role create/update/reordering bodies, guild widget/welcome/onboarding writes, guild prune count/result including the current JSON-body begin route, guild-member join, role member-count, public widget, Stage Instance writes, sticker pack fetches, typed guild sticker writes, voice-state REST reads/writes, current-application and OAuth2 metadata reads, Create Group DM and Group DM recipient routes, channel invite/target-user and permission routes, voice-channel status updates, guild message search, current and legacy channel-pin routes, forwarded message snapshots, shared client themes, Gateway rate-limit, reaction metadata, and presence metadata events, Activity instances, polls, subscriptions, entitlements, soundboard, threads, forum channel fields, invites, integrations, Auto Moderation, guild preview/vanity, voice regions, OAuth2 user connections, application command permissions, and bulk bans
  • Application framework routing for slash commands, components, and modals behind the interactions feature
  • Components V2 builders (Container, TextDisplay, Section, MediaGallery, Button, SelectMenu with auto-populated defaults, and more)
  • Typed command builders for slash, user, and message commands
  • Modal builders with RadioGroup, CheckboxGroup, Checkbox, and FileUpload
  • V2 modal submission parser with preserved FileUpload, RadioGroup, CheckboxGroup, and other V2 component types
  • Typed interaction decoding with chat-input, context-menu, autocomplete, component, and modal submit variants
  • Interaction routing helpers: parse_interaction, parse_raw_interaction, parse_interaction_context, and try_interactions_endpoint
  • Feature-gated runtime layers: gateway, interactions, collectors, sharding, and voice; the in-memory cache storage is enabled by default and can still be disabled with default-features = false

Install

[dependencies]
discordrs = "2.0.2"
[dependencies]
# Gateway bot client
discordrs = { version = "2.0.2", features = ["gateway"] }

# HTTP Interactions Endpoint
discordrs = { version = "2.0.2", features = ["interactions"] }

# Gateway runtime with default cache storage
discordrs = { version = "2.0.2", features = ["gateway"] }

# Minimal core without cache storage
discordrs = { version = "2.0.2", default-features = false }

# Gateway runtime with collectors
discordrs = { version = "2.0.2", features = ["gateway", "collectors"] }

# Sharding foundations
discordrs = { version = "2.0.2", features = ["gateway", "sharding"] }

# Voice foundations
discordrs = { version = "2.0.2", features = ["voice"] }

# PCM -> Opus voice encode/playback helpers
discordrs = { version = "2.0.2", features = ["voice", "voice-encode"] }

# DAVE receive/outbound media integration
discordrs = { version = "2.0.2", features = ["voice", "dave"] }

# Gateway runtime with zstd-stream transport compression
discordrs = { version = "2.0.2", features = ["gateway", "zstd-stream"] }

# Both runtime modes
discordrs = { version = "2.0.2", features = ["gateway", "interactions"] }

API Cleanup

Recent API cleanup tightened the public surface:

  • RestClient now exposes the typed REST methods as the supported public path. The old raw convenience methods such as send_message, edit_message, create_dm_channel, create_interaction_response, and bulk_overwrite_global_commands are no longer public.
  • Builder implementation submodules are private. Import builders from discordrs::builders::{...} or use the crate root re-exports.
  • ApplicationCommand no longer implements DiscordModel because its ID is optional until Discord assigns one. Use ApplicationCommand::id_opt() and ApplicationCommand::created_at() instead.

If you are upgrading existing code, the common replacements are:

Old path New path
RestClient::send_message(...) send_message(...) helper or RestClient::create_message(...)
RestClient::edit_message(...) RestClient::update_message(...)
RestClient::create_dm_channel(...) RestClient::create_dm_channel_typed(...)
RestClient::create_interaction_response(...) RestClient::create_interaction_response_typed(...) or typed helper functions
RestClient::bulk_overwrite_global_commands(...) RestClient::bulk_overwrite_global_commands_typed(...)
discordrs::builders::modal::* discordrs::builders::{...} or crate root re-exports
generic DiscordModel access for ApplicationCommand ApplicationCommand::id_opt() / ApplicationCommand::created_at()

Documentation Site

A navigation-focused docs website (similar to the discord.js docs browsing style) is available in discordrsdocs/:

Quick Example

use async_trait::async_trait;
use discordrs::{gateway_intents, Client, Context, Event, EventHandler};

struct Handler;

#[async_trait]
impl EventHandler for Handler {
    async fn handle_event(&self, _ctx: Context, event: Event) {
        match event {
            Event::Ready(ready) => {
                println!("Bot ready! User: {}", ready.data.user.username);
            }
            Event::MessageCreate(message) => {
                println!("Message: {}", message.message.content);
            }
            _ => {}
        }
    }
}

#[tokio::main]
async fn main() {
    let token = std::env::var("DISCORD_TOKEN").unwrap();
    Client::builder(&token, gateway_intents::GUILDS | gateway_intents::GUILD_MESSAGES)
        .event_handler(Handler)
        .start()
        .await
        .unwrap();
}

Typed APIs

use discordrs::{
    option_type, CommandOptionBuilder, PermissionsBitField, SlashCommandBuilder,
};

let command = SlashCommandBuilder::new("ticket", "Create a support ticket")
    .option(
        CommandOptionBuilder::new(option_type::STRING, "topic", "Ticket topic")
            .required(true)
            .autocomplete(true),
    )
    .default_member_permissions(PermissionsBitField(0))
    .build();

Components V2 Example

use discordrs::{
    button_style, create_container, send_container_message, ButtonConfig, DiscordHttpClient,
};

async fn send_support_panel(http: &DiscordHttpClient, channel_id: u64) -> Result<(), discordrs::DiscordError> {
    let buttons = vec![
        ButtonConfig::new("ticket_open", "Open Ticket").style(button_style::PRIMARY),
        ButtonConfig::new("ticket_status", "Check Status").style(button_style::SECONDARY),
    ];

    let container = create_container(
        "Support Panel",
        "Use the buttons below to manage support requests.",
        buttons,
        None,
    );

    send_container_message(http, channel_id, container).await?;
    Ok(())
}

Modal Example with RadioGroup

use discordrs::{
    CheckboxBuilder, CheckboxGroupBuilder, FileUploadBuilder, ModalBuilder, RadioGroupBuilder,
    SelectOption,
};

let modal = ModalBuilder::new("preferences_modal", "Preferences")
    .add_radio_group(
        "Theme",
        Some("Pick one"),
        RadioGroupBuilder::new("theme")
            .add_option(SelectOption::new("Light", "light"))
            .add_option(SelectOption::new("Dark", "dark"))
            .required(true),
    )
    .add_checkbox_group(
        "Notifications",
        Some("Choose any"),
        CheckboxGroupBuilder::new("notify_channels")
            .add_option(SelectOption::new("Email", "email"))
            .add_option(SelectOption::new("Push", "push"))
            .min_values(0)
            .max_values(2),
    )
    .add_checkbox(
        "Agree to Terms",
        None,
        CheckboxBuilder::new("agree_terms").required(true),
    )
    .add_file_upload(
        "Screenshot",
        Some("Attach one or more files"),
        FileUploadBuilder::new("attachments").min_values(1),
    );

Interactions Endpoint Example

use async_trait::async_trait;
use axum::Router;
use discordrs::{
    create_container, InteractionContext, InteractionHandler, InteractionResponse, RawInteraction,
    try_interactions_endpoint,
};

#[derive(Clone)]
struct Handler;

#[async_trait]
impl InteractionHandler for Handler {
    async fn handle(
        &self,
        ctx: InteractionContext,
        interaction: RawInteraction,
    ) -> InteractionResponse {
        match interaction {
            RawInteraction::Command { name, .. } if name.as_deref() == Some("hello") => {
                let data = serde_json::json!({
                    "components": [create_container("Hello", "World", vec![], None).build()],
                    "flags": 1 << 15,
                });
                let _ = ctx;
                InteractionResponse::ChannelMessage(data)
            }
            _ => InteractionResponse::Raw(serde_json::json!({ "type": 5 })),
        }
    }
}

fn app(public_key: &str) -> Router {
    try_interactions_endpoint(public_key, Handler)
        .expect("invalid Discord public key")
}

Feature Flags

Feature Description Key deps
(default) Builders, typed models, command builders, parsers, REST client, helpers, and in-memory cache storage reqwest, serde_json, tokio, async-trait
gateway Gateway WebSocket, Client, typed Event, and EventHandler::handle_event(...) dispatch tokio-tungstenite, flate2, async-trait
zstd-stream Gateway zstd-stream transport compression gateway, zstd
interactions HTTP Interactions Endpoint with Ed25519 axum, ed25519-dalek
cache Enables the in-memory cache storage and CacheBackend extension trait used by gateway cache managers; included in default features tokio, async-trait
collectors Async collectors for messages and interactions tokio
sharding Sharding manager and reusable gateway config abstractions tokio
voice Voice connection/player skeletons plus voice gateway/UDP receive, Opus-frame send, transport decrypt, and Opus PCM decode helpers tokio, aes-gcm, chacha20poly1305, opus-decoder
voice-encode PCM source/mixer and opus-rs encoder helpers for 48 kHz stereo 20 ms voice playback through the existing Opus frame path voice, opus-rs
dave DAVE/MLS receive and outbound media hooks backed by davey, with live Discord MLS transition validation coverage voice, davey

Notes

  • discord.rs started as a helper around serenity workflows, and 1.0.0 is the first stabilized standalone framework release with the typed runtime surface.
  • Use try_interactions_endpoint() when you want invalid public keys to fail at startup instead of during requests.
  • Use discordrs::prelude::* when you want the shortest path to the main runtime, command, helper, and response APIs.
  • Use DiscordHttpClient::create_followup_message_with_application_id() when you already have InteractionContext.application_id and the client was not initialized with an application id.
  • Prefer the typed RestClient methods such as create_message, update_message, create_interaction_response_typed, and bulk_overwrite_*_typed.
  • Prefer typed REST wrappers such as bulk_guild_ban, get_guild_role, get_guild_audit_log_typed, get_auto_moderation_rules_typed, get_guild_preview_typed, get_guild_vanity_url, get_voice_regions_typed, get_guild_voice_regions, get_current_application, and get_application_role_connection_metadata_records before falling back to raw serde_json::Value methods.
  • CacheHandle::new() uses bounded cache defaults so normal gateway builds do not retain every gateway entity forever.
  • Use Client::builder(...).cache_config(...) or CacheHandle::with_config(...) to tune cache limits, and use CacheConfig::unbounded() only when unbounded retention is intentional.
  • Use CacheHandle::is_enabled() when code may be compiled with default-features = false; cache storage is enabled in the default feature set.
  • Use member_arc(...), message_arc(...), presence_arc(...), and the manager cached_arc(...) helpers when hot cache reads should avoid deep cloning large cached payloads.
  • Implement CacheBackend when experimenting with an external member/message/presence store; CacheHandle implements the same trait for the default in-memory backend.
  • Invite codes, webhook tokens, interaction tokens, and other token-like REST path segments are validated before authenticated paths are built.
  • Generated REST query strings are percent-encoded, and repeated HTTP 429 responses are retried up to a bounded limit before returning DiscordError::RateLimit.
  • Use OAuth2Client for OAuth2 authorization-code or refresh-token flows; it is intentionally separate from bot-token RestClient authorization.
  • Token-authenticated /interactions/... and /webhooks/... requests intentionally omit bot Authorization headers, and token/path segments are validated before webhook/callback paths are built.
  • Typed slash and autocomplete interaction payloads preserve option value, focused, and nested option input through CommandInteractionOption.
  • Use Client for new gateway code. BotClient remains available as a compatibility alias.
  • Use EventHandler::handle_event(...) when you want one typed entry point for every gateway event. Legacy convenience callbacks such as ready, message_create, and interaction_create still exist, but they now receive typed payloads too.
  • Use Context::new(...) when tests or helper crates need a standalone context outside the live gateway runtime.
  • Use examples/live_dave_capture_bot.rs to capture matching bot VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE values before running the ignored live DAVE validation test.
  • Prefer builder imports from discordrs::builders::{...} or the crate root re-exports. Deeper implementation submodules are private.
  • Use ApplicationCommand::id_opt() until Discord has assigned an ID. Unsaved commands are no longer treated as generic DiscordModels.
  • The parser keeps V2 modal component types, including FileUpload, RadioGroup, and CheckboxGroup, so routing logic can keep full fidelity.

License

Licensed under either of:

at your option.

Developer

About

Typed Discord bot framework for Rust.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages