71 releases

Uses new Rust 2024

0.17.1 Dec 13, 2025
0.17.0 Nov 8, 2025
0.16.0 Jan 12, 2025
0.16.0-rc.1 Feb 21, 2024
0.2.5 Nov 29, 2020

#2 in #discord-api

Download history 2380/week @ 2026-01-17 2600/week @ 2026-01-24 3502/week @ 2026-01-31 2946/week @ 2026-02-07 3224/week @ 2026-02-14 3530/week @ 2026-02-21 4573/week @ 2026-02-28 5030/week @ 2026-03-07 3594/week @ 2026-03-14 2831/week @ 2026-03-21 3724/week @ 2026-03-28 3837/week @ 2026-04-04 4220/week @ 2026-04-11 3183/week @ 2026-04-18 3569/week @ 2026-04-25 4146/week @ 2026-05-02

15,846 downloads per month
Used in 17 crates (15 directly)

ISC license

2.5MB
60K SLoC

twilight-gateway

codecov badge discord badge github badge license badge rust badge

twilight-gateway is an implementation of Discord's sharding gateway sessions. This is responsible for receiving stateful events in real-time from Discord and sending some stateful information.

The primary type is the Shard, a stateful interface to maintain a Websocket connection to Discord's gateway. Much of its functionality can be configured, and it's used to receive gateway events or raw Websocket messages, useful for load balancing and microservices.

Multiple shards may easily be created at once, with a per shard config created from a Fn(ShardId, ConfigBuilder) -> Config closure, with the help of the create_ set of functions. These functions will reuse shards' TLS context and [session queue][queue], something otherwise achieved by cloning an existing Config.

Features

  • simd-json: use simd-json instead of serde_json for deserializing events
  • TLS (mutually exclusive)
    • native-tls: platform's native TLS implementation via native-tls
    • rustls-native-roots: rustls using native root certificates
    • rustls-platform-verifier (default): rustls using operating system's certificate facilities via rustls-platform-verifier
    • rustls-webpki-roots: rustls using webpki-roots for root certificates, useful for scratch containers
  • twilight-http (default): enable the stream::create_recommended function
  • Transport compression (mutually exclusive)
    • zlib: Zlib transport compression using [zlib-rs][^1]
    • zstd (default): Zstandard transport compression using zstd-sys

Example

Create the recommended number of shards and loop over their guild messages:

use std::{env, sync::Arc};
use tokio::{signal, sync::watch};
use twilight_gateway::{
    CloseFrame, Config, Event, EventTypeFlags, Intents, MessageSender, Shard, StreamExt as _,
};
use twilight_http::Client;
use twilight_model::gateway::payload::{incoming::MessageCreate, outgoing::UpdateVoiceState};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt::init();

    // Select rustls backend
    rustls::crypto::ring::default_provider().install_default().unwrap();

    let token = env::var("DISCORD_TOKEN")?;

    let (shutdown_tx, shutdown_rx) = watch::channel(false);
    let client = Arc::new(Client::new(token.clone()));
    let config = Config::new(token, Intents::GUILD_MESSAGES | Intents::MESSAGE_CONTENT);

    let tasks = twilight_gateway::create_recommended(&client, config, |_, builder| builder.build())
        .await?
        .map(|shard| tokio::spawn(dispatcher(Arc::clone(&client), shard, shutdown_rx.clone())))
        .collect::<Vec<_>>();

    signal::ctrl_c().await?;
    _ = shutdown_tx.send(true);

    for task in tasks {
        _ = task.await;
    }

    Ok(())
}

#[tracing::instrument(fields(shard = %shard.id()), skip_all)]
async fn dispatcher(client: Arc<Client>, mut shard: Shard, mut shutdown: watch::Receiver<bool>) {
    loop {
        tokio::select! {
            _ = shutdown.changed() => shard.close(CloseFrame::NORMAL),
            Some(item) = shard.next_event(EventTypeFlags::all()) => {
                let event = match item {
                    Ok(event) => event,
                    Err(source) => {
                        tracing::warn!(?source, "error receiving event");
                        continue;
                    }
                };

                match event {
                    Event::GatewayClose(_) if *shutdown.borrow() => break,
                    Event::MessageCreate(e) => {
                        tokio::spawn(msg_handler(Arc::clone(&client), e, shard.sender()));
                    }
                    _ => {}
                }
            }
        }
    }
}

#[tracing::instrument(fields(id = %event.id), skip_all)]
async fn msg_handler(client: Arc<Client>, event: Box<MessageCreate>, sender: MessageSender) {
    match event.content.as_ref() {
        "!join" if event.guild_id.is_some() => {
            let _result = sender.command(&UpdateVoiceState::new(
                event.guild_id.unwrap(),
                Some(event.channel_id),
                false,
                false,
            ));
        }
        "!ping" => {
            let _result = client
                .create_message(event.channel_id)
                .content("pong!")
                .await;
        }
        _ => {}
    }
}

There are a few additional examples located in the repository.

[^1]: Except for the s390x arch, where zlib-ng-sys is used instead.

Dependencies

~13–33MB
~428K SLoC