vaidcord 0.2.0

High-performance Discord framework: routers, middleware, resilient gateway and full voice transport
Documentation

VaidCord Rust

Rust SDK for VaidCord. Status: alpha — the core layers described in UNITED.md are implemented: gateway, bot facade, router + middleware + dispatcher, rate-limited REST client, typed models, and voice transport foundations that are wire-compatible with the Python SDK.

Feature overview

Bot facade

Bot wires gateway dispatch -> parsed models -> the dispatcher:

use vaidcord::{Bot, Intents, Message, Router};

#[tokio::main]
async fn main() -> Result<(), vaidcord::Error> {
    let mut router = Router::new();
    router.on_message(|message: &Message| {
        println!("{}: {}", message.author.username, message.content);
        Ok(())
    });

    Bot::builder()
        .token(std::env::var("DISCORD_TOKEN").unwrap())
        .intents(Intents::GUILDS | Intents::GUILD_MESSAGES | Intents::MESSAGE_CONTENT)
        .router(router)
        .run()
        .await
}

Gateway

  • Heartbeat runs on an independent tokio task (interval from HELLO, jittered first beat); a missed heartbeat ACK forces a reconnect.
  • RESUME support (session_id + resume_gateway_url + sequence replay) and automatic reconnect with exponential backoff.
  • Close-code policy per the Discord docs (classify_gateway_close_code): fatal (4004/4010-4014) vs re-identify (4007/4009/1000/1001) vs resume.
  • Typed Intents bitflags, op 3 presence updates and op 8 guild-member requests via the cloneable GatewayHandle.

Router, middleware, dispatcher

Middleware has (event, next) semantics; outer middleware wraps inner middleware and the innermost wraps the handler. Routers nest via Router::include, and the standalone Dispatcher precomposes each route's middleware chain at include-time so the dispatch hot path is O(1) in allocations (UNITED.md §7):

use vaidcord::{Dispatcher, Router, command};

let mut child = Router::named("commands");
child.use_middleware(|event, bag, next| {
    // before handler
    let result = next.run(event, bag);
    // after handler
    result
});
child.on_message_filtered(|message| { println!("pong {}", message.content); Ok(()) },
                          vec![command("ping")]);

let mut root = Router::named("root");
root.include(child); // root middleware would wrap child middleware

let mut dispatcher = Dispatcher::new();
dispatcher.include(&root);

Filters are unchanged: multi-filter AND routing, any = [..] OR composition, command!, ExtractBag extraction, register_on_message! and #[vaidcord::on_message(...)] all keep working.

REST client

  • Per-route rate-limit buckets parsed from X-RateLimit-* headers, with sleep-and-retry on 429 (retry_after, route or global) and exponential backoff on 5xx/transport errors.
  • Endpoints: messages (send/get/edit/delete + reactions), channels (get/modify/delete), guilds (roles/members CRUD basics, bans), interactions (create response, followups, edit original), webhooks (execute), threads (start/join/leave), and application command sync.

Models

Typed User, Channel, Message, Guild, Role, Member, Embed (with a fluent builder), Interaction, Ready. All deserializers ignore unknown fields.

Voice transport foundations

The voice module mirrors vaidcord-py/src/vaidcord/voice/ and is verified wire-compatible (byte-for-byte known-answer tests generated from the Python implementation):

  • Voice gateway v8 websocket client: identify/resume/heartbeat with seq_ack, READY, session description, SSRC->user map from op 5/12/13, and the close-code policy (classify_voice_close_code: resume / rejoin / fatal). The protocol state machine (VoiceGatewayState) is synchronous and fully unit-tested.
  • UDP socket with 74-byte IP discovery packets.
  • RTP packet builder/parser (_rtpsize unencrypted prefix: 12-byte header + CSRCs + 4-byte extension preamble when the X bit is set).
  • Transport encryption in both directions for aead_aes256_gcm_rtpsize, aead_xchacha20_poly1305_rtpsize and xsalsa20_poly1305_lite_rtpsize (RustCrypto). Wire format: prefix || ciphertext || 4-byte BE nonce counter; AEAD nonce = zero-padded counter; AAD = the unencrypted prefix.
  • Drift-corrected 20 ms FramePacer (MissedTickBehavior::Delay), speaking_payload helpers, an AudioSource trait yielding opus packets, and a VoiceReceiver that decrypts inbound RTP into (user_id, opus) frames.
  • DAVE opcode/close-code constants and DaveIdentifyConfig carrier.

Opus encode/decode is optional:

vaidcord = { version = "0.1", features = ["opus"] }  # needs system libopus

Without the opus feature the SDK is opus-passthrough: you feed pre-encoded opus packets in and receive opus packets out.

Examples

Example What it shows
examples/basic.rs REST request parts + formatting helpers
examples/router.rs imperative router registration
examples/decorator_router.rs every #[on_message] filter form
examples/bot_middleware.rs middleware ordering, nesting, dispatcher
examples/voice_transport.rs no-network voice packet seal/open + IP discovery

Run with cargo run --example bot_middleware etc.

Development

cargo test                       # unit + doc tests
cargo test --features opus       # includes Opus encode/decode tests
cargo clippy --all-targets       # lint (warning-free)
cargo doc --open --no-deps       # API reference