Overview

twilight logo

Join us on Discord! :)

twilight is a powerful asynchronous, flexible, and scalable ecosystem of Rust libraries for the Discord API.

Check out the crates on crates.io.

Who Twilight is For

Twilight is meant for people who are very familiar with Rust and at least somewhat familiar with Discord bots. It aims to be the library you use when you want - or, maybe for scaling reasons, need - the freedom to structure things how you want and do things that other libraries may not strongly cater to.

If you're a beginner with Rust, then that's cool and we hope you like it! serenity is a great library for getting started and offers an opinionated, batteries-included approach to making bots. You'll probably have a better experience with it and we recommend you check it out.

The Guide

In this guide you'll learn about the core crates in the twilight ecosystem, useful first-party crates for more advanced use cases, and third-party crates giving you a tailored experience.

Links

The organization for the project is on GitHub.

The crates are available on crates.io.

The API docs are also hosted for the latest version.

There is a community and support server on Discord.

A Quick Example

Below is a quick example of a program printing "Pong!" when a ping command comes in from a channel:

use std::{env, error::Error};
use tokio::stream::StreamExt;
use twilight_cache_inmemory::{EventType, InMemoryCache};
use twilight_gateway::{cluster::{Cluster, ShardScheme}, Event};
use twilight_http::Client as HttpClient;
use twilight_model::gateway::Intents;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
    let token = env::var("DISCORD_TOKEN")?;

    // This is also the default.
    let scheme = ShardScheme::Auto;

    let cluster = Cluster::builder(&token)
        .shard_scheme(scheme)
        // Use intents to only listen to GUILD_MESSAGES events
        .intents(Intents::GUILD_MESSAGES | Intents::DIRECT_MESSAGES)
        .build()
        .await?;

    // Start up the cluster
    let cluster_spawn = cluster.clone();

    tokio::spawn(async move {
        cluster_spawn.up().await;
    });

    // The http client is seperate from the gateway,
    // so startup a new one
    let http = HttpClient::new(&token);

    // Since we only care about messages, make the cache only
    // cache message related events
    let cache = InMemoryCache::builder()
        .event_types(
            EventType::MESSAGE_CREATE
                | EventType::MESSAGE_DELETE
                | EventType::MESSAGE_DELETE_BULK
                | EventType::MESSAGE_UPDATE,
        )
        .build();

    let mut events = cluster.events();
    // Startup an event loop to process each event in the event stream as they
    // come in.
    while let Some((shard_id, event)) = events.next().await {
        // Update the cache.
        cache.update(&event);

        // Spawn a new task to handle the event
        tokio::spawn(handle_event(shard_id, event, http.clone()));
    }

    Ok(())
}

async fn handle_event(
    shard_id: u64,
    event: Event,
    http: HttpClient,
) -> Result<(), Box<dyn Error + Send + Sync>> {
    match event {
        Event::MessageCreate(msg) if msg.content == "!ping" => {
            http.create_message(msg.channel_id).content("Pong!")?.await?;
        }
        Event::ShardConnected(_) => {
            println!("Connected on shard {}", shard_id);
        }
        _ => {}
    }

    Ok(())
}

Support

The guide, and Twilight as a whole, assume familiarity with Rust, Rust's asynchronous features, and bots in general. If you're new to Rust and/or new to bots, consider checking out serenity, which is a beginner-friendly, batteries-included approach to the Discord API.

Support for the library is provided through the GitHub issues section and the Discord server.

If you have a question, then the issues or the server are both good fits for it. If you find a bug, then the issues section is the best place.

The API documentation is also available.

Supported Rust Versions

Twilight currently supports the latest stable Rust version.

Breaking Changes

Although Twilight aims to design APIs right the first time, that obviously won't happen. A lot of effort is spent designing clear and correct interfaces.

While Twilight takes care to avoid the need for breaking changes, it will be fearless when it needs to do so: they won't be avoided for the sake of avoiding a change. Breaking changes won't be piled up over time to make a single big release: major versions will be often and painless.

Crates

Twilight is, at heart, an ecosystem. These components of the ecosystem don't depend on each other in unnecessary ways, allowing you to pick and choose and combine the crates that you need for your use case. The crates for Twilight are categorised into three groups: the core crates, first-party crates, and third-party crates.

Core Crates

Twilight includes a few crates which are the "building blocks" to most peoples' use cases. You might not need them all, but generally speaking you'll need most of them. Most of them wrap Discord's various APIs.

  • model: All of the structs, enums, and bitflags used by the Discord APIs.
  • http: HTTP client supporting all of the documented features of Discord's HTTP API, with support for ratelimiting, proxying, and more.
  • gateway: Clients supporting Discord's gateway API.
  • cache: Definitions for implementating a cache. An in-process memory implementation is included.
  • command-parser: Basic command parser for parsing commands and arguments out of messages.
  • standby: Utility for asynchronously waiting for certain events, like a new message in a channel or a new reaction to a message.

First-Party Crates

There are some first-party crates maintained by the Twilight organization, but not included in the core experience. These might be for more advanced or specific use cases or clients for third-party services. An example of a first-party crate is twilight-lavalink, a Client for interacting with Lavalink.

Third-Party Crates

Third-party crates are crates that aren't officially supported by the Twilight organization, but are recognised by it. An example is rarity-rs/permission-calculator, which has interfaces for things like calculating the permissions for a member in a channel.

Model

twilight-model is a crate of models for use with serde defining the Discord APIs with limited implementations on top of them.

These are in a single crate for ease of use, a single point of definition, and a sort of versioning of the Discord API. Similar to how a database schema progresses in versions, the definition of the API also progresses in versions.

Most other Twilight crates use types from this crate. For example, the Embed Builder crate primarily uses types having to do with channel message embeds, while the Lavalink crate works with a few of the events received from the gateway. These types being in a single versioned definition is beneficial because it removes the need for crates to rely on other large and unnecessary crates.

The types in this crate are reproducible: deserializing a payload into a type, serializing it, and then deserializing it again will result in the same instance.

Defined are a number of modules defining types returned by or owned by resource categories. For example, gateway contains types used to interact with and returned by the gateway API. guild contains types owned by the Guild resource category. These types may be directly returned by, built on top of, or extended by other Twilight crates.

Installation

Add the following to your Cargo.toml:

twilight-model = "0.1"

Links

source: https://github.com/twilight-rs/twilight/tree/master/model

docs: https://twilight-rs.github.io/twilight/twilight_model/index.html

crates.io: https://crates.io/crates/twilight-model

HTTP

twilight-http is an HTTP client wrapping all of the documented Discord HTTP API. It is built on top of Reqwest, and supports taking any generic Reqwest client, allowing you to pick your own TLS backend. By default, it uses RusTLS a Rust TLS implementation, but it can be changed to use NativeTLS which uses the TLS native to the platform, and on Unix uses OpenSSL.

Ratelimiting is included out-of-the-box, along with support for proxies.

Installation

Add the following to your Cargo.toml:

twilight-http = "0.1"

Example

A quick example showing how to send 10 messages, and then print the current user's name:

use futures::future;
use std::{env, error::Error};
use twilight_http::Client;
use twilight_model::id::ChannelId;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
    // Initialize the tracing subscriber.
    tracing_subscriber::fmt::init();

    let client = Client::new(env::var("DISCORD_TOKEN")?);
    let channel_id = ChannelId(381_926_291_785_383_946);

    future::join_all((1u8..=10).map(|x| {
        client
            .create_message(channel_id)
            .content(format!("Ping #{}", x))
            .expect("content not a valid length")
    }))
    .await;

    let me = client.current_user().await?;
    println!("Current user: {}#{}", me.name, me.discriminator);

    Ok(())
}

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/http

docs: https://twilight-rs.github.io/twilight/twilight_http/index.html

crates.io: https://crates.io/crates/twilight-http

Gateway

twilight-gateway is an implementation of a client over Discord's websocket gateway.

The main type is the Shard: it connects to the gateway, receives messages, parses and processes them, and then gives them to you. It will automatically reconnect, resume, and identify, as well as do some additional connectivity checks.

Also provided is the Cluster, which will automatically manage a collection of shards and unify their messages into one stream. It doesn't have a large API, you usually want to spawn a task to bring it up such that you can begin to receive tasks as soon as they arrive.

use futures::StreamExt;
use twilight_gateway::Cluster;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
   let token = "dummy";
let cluster = Cluster::new(token).await?;

let mut events = cluster.events();

let cluster_spawn = cluster.clone();

tokio::spawn(async move {
    cluster_spawn.up().await;
});
let _ = events.next().await;
    Ok(())
}

Installation

Add the following to your Cargo.toml:

twilight-gateway = "0.1"

Features

twilight-gateway includes a few features simd-json for enabling faster json parsing and stock-zlib/simd-zlib for choosing between the zlib to use for decompressing.

Simd JSON

simd-json feature enables [simd-json] support to use simd features of the modern cpus to deserialize json data faster. It is not enabled by default since not every cpu has those features. To use this feature you need to also add these lines to a file in <project root>/.cargo/config

[build]
rustflags = ["-C", "target-cpu=native"]

You can also use the environment variable RUSTFLAGS="-C target-cpu=native".

Zlib

stock-zlib makes flate2 use the stock-zlib which is either upstream or the one included with the operating system.

simd-zlib enables the use of zlib-ng which is a modern fork of zlib that in most cases will be more effective. Though it will add an externel dependency on cmake.

If both are enabled or if the zlib feature of flate2 is enabled anywhere in the dependency tree it will make use of that instead of zlib-ng.

Example

Starting a Shard and printing the contents of new messages as they come in:

use futures::StreamExt;
use std::{env, error::Error};
use twilight_gateway::Shard;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
    // Initialize the tracing subscriber.
    tracing_subscriber::fmt::init();

    let mut shard = Shard::new(env::var("DISCORD_TOKEN")?);
    let mut events = shard.events();

    shard.start().await?;
    println!("Created shard");

    while let Some(event) = events.next().await {
        println!("Event: {:?}", event);
    }

    Ok(())
}

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/gateway

docs: https://twilight-rs.github.io/twilight/twilight_gateway/index.html

crates.io: https://crates.io/crates/twilight-gateway

Cache

Twilight includes an in-process-memory cache. It's responsible for processing events and caching things like guilds, channels, users, and voice states.

Installation

Add the following to your Cargo.toml:

[dependencies]
twilight-cache-inmemory = "0.1"

Examples

Process items that come over a shard into the cache:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use futures::StreamExt;
use std::env;
use twilight_cache_inmemory::InMemoryCache;
use twilight_gateway::Shard;

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

let mut shard = Shard::new(token);
shard.start().await?;

let cache = InMemoryCache::new();

let mut events = shard.events();

while let Some(event) = events.next().await {
    cache.update(&event);
}
    Ok(())
}

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/cache/in-memory

docs: https://docs.rs/twilight-cache-inmemory

crates.io: https://crates.io/crates/twilight-cache-inmemory

Command Parser

The Command Parser is a basic parser for the Twilight ecosystem. We'll get this out of the way first: it's not a framework, and it doesn't try to be.

The parser, for the most part, takes a configuration of prefixes and commands, and attempts to match it to provided strings, returning what command and prefix it matched, if any. The parser will also return a lazy iterator of arguments given to the command.

Included is a mutable configuration that allows you to specify the command names, prefixes, and ignored guilds and users. The parser parses out commands matching an available command and prefix and provides the command arguments to you.

Installation

Add the following to your Cargo.toml:

[dependencies]
twilight-command-parser = "0.1"

Examples

A simple parser for a bot with one prefix ("!") and two commands, "echo" and "ping":

fn main() {
use twilight_command_parser::{Command, CommandParserConfig, Parser};

let mut config = CommandParserConfig::new();

// (Use `Config::add_command` to add a single command)
config.add_command("echo", true);
config.add_command("ping", true);

// Add the prefix `"!"`.
// (Use `Config::add_prefixes` to add multiple prefixes)
config.add_prefix("!");

let parser = Parser::new(config);

// Now pass a command to the parser
match parser.parse("!echo a message") {
    Some(Command { name: "echo", arguments, .. }) => {
        let content = arguments.as_str();

        println!("Got an echo request to send `{}`", content);
    },
    Some(Command { name: "ping", .. }) => {
        println!("Got a ping request");
    },
    // Ignore all other commands.
    Some(_) => {},
    None => println!("Message didn't match a prefix and command"),
}
}

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/command-parser

docs: https://docs.rs/twilight-command-parser

crates.io: https://crates.io/crates/twilight-command-parser

Standby

Standby is a utility to wait for an event to happen based on a predicate check. For example, you may have a command that makes a reaction menu of ✅ and ❌. If you want to handle a reaction to these, using something like an application-level state or event stream may not suit your use case. It may be cleaner to wait for a reaction inline to your function. This is where Standby comes in.

Installation

Add the following to your Cargo.toml:

twilight-standby = "0.1"

Examples

Wait for a message in channel 123 by user 456 with the content "test":

#[allow(unused_variables)]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use twilight_model::{gateway::payload::MessageCreate, id::{ChannelId, UserId}};
use twilight_standby::Standby;

let standby = Standby::new();

// Later on in the application...
let message = standby.wait_for_message(ChannelId(123), |event: &MessageCreate| {
    event.author.id == UserId(456) && event.content == "test"
}).await?;
    Ok(())
}

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/standby

docs: https://twilight-rs.github.io/twilight/twilight_standby/index.html

crates.io: https://crates.io/crates/twilight-standby

First-party

Twilight includes crates maintained by the organization, but not included as part of the core experience. Just like all of the core crates these are entirely opt-in, but are for more advanced or specific use cases, such as integration with other software.

Although not a part of the core experience, these are given the same level of support as the core crates.

Embed Builder

twilight-embed-builder is a utility crate to create validated embeds, useful when creating or updating messages.

With this library, you can create mentions for various types, such as users, emojis, roles, members, or channels.

Installation

Add the following to your Cargo.toml:

twilight-embed-builder = "0.1"

Examples

Build a simple embed:

#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use twilight_embed_builder::{EmbedBuilder, EmbedFieldBuilder};

let embed = EmbedBuilder::new()
    .description("Here's a list of reasons why Twilight is the best pony:")?
    .field(EmbedFieldBuilder::new("Wings", "She has wings.")?.inline())
    .field(EmbedFieldBuilder::new("Horn", "She can do magic, and she's really good at it.")?.inline())
    .build();
    Ok(())
}

Build an embed with an image:

#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use twilight_embed_builder::{EmbedBuilder, ImageSource};

let embed = EmbedBuilder::new()
    .description("Here's a cool image of Twilight Sparkle")?
    .image(ImageSource::attachment("bestpony.png")?)
    .build();
    Ok(())
}

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/utils/embed-builder

docs: https://twilight-rs.github.io/twilight/twilight_embed_builder/index.html

crates.io: https://crates.io/crates/twilight-embed-builder

Mention

twilight-mention is a utility crate to mention model resources.

With this library, you can create mentions for various resources, such as users, emojis, roles, members, or channels.

Installation

Add the following to your Cargo.toml:

twilight-mention = "0.1"

Examples

Create a mention formatter for a user ID, and then format it in a message:

#[allow(unused_variables)]
fn main() {
use twilight_mention::Mention;
use twilight_model::id::UserId;

let user_id = UserId(123);
let message = format!("Hey there, {}!", user_id.mention());
}

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/utils/mention

docs: https://twilight-rs.github.io/twilight/twilight_mention/index.html

crates.io: https://crates.io/crates/twilight-mention

Lavalink

twilight-lavalink is a client for Lavalink for use with model events from the gateway.

It includes support for managing multiple nodes, a player manager for conveniently using players to send events and retrieve information for each guild, and an HTTP module for creating requests using the http crate and providing models to deserialize their responses.

Installation

Add the following to your Cargo.toml:

twilight-lavalink = "0.1"

Examples

Create a client, add a node, and give events to the client to process events:

use futures::StreamExt;
use std::{
    env,
    error::Error,
    net::SocketAddr,
    str::FromStr,
};
use twilight_gateway::Shard;
use twilight_http::Client as HttpClient;
use twilight_lavalink::Lavalink;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
    let token = env::var("DISCORD_TOKEN")?;
    let lavalink_host = SocketAddr::from_str(&env::var("LAVALINK_HOST")?)?;
    let lavalink_auth = env::var("LAVALINK_AUTHORIZATION")?;
    let shard_count = 1_u64;

    let http = HttpClient::new(&token);
    let user_id = http.current_user().await?.id;

    let lavalink = Lavalink::new(user_id, shard_count);
    lavalink.add(lavalink_host, lavalink_auth).await?;

    let mut shard = Shard::new(token);
    let mut events = shard.events();

    shard.start().await?;

    while let Some(event) = events.next().await {
        lavalink.process(&event).await?;
    }

    Ok(())
}

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/lavalink

docs: https://twilight-rs.github.io/twilight/twilight_lavalink/index.html

crates.io: https://crates.io/crates/twilight-lavalink

Util

twilight-util is a utility crate that adds utilities to the twilight ecosystem that do not fit in any other crate. Currently, it contains a trait to make extracting data from Discord identifiers (Snowflakes) easier.

Installation

Add the following to your Cargo.toml:

twilight-util = { features = ["snowflake"], version = "0.1" }

Examples

The snowflake trait can be used like this

#[allow(unused_variables)]
fn main() {
use twilight_util::snowflake::Snowflake;
use twilight_model::id::UserId;

let user = UserId(123456);
let timestamp = user.timestamp();
}

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/util

docs: https://api.twilight.rs/twilight_util/index.html

crates.io: https://crates.io/crates/twilight-util

Gateway queue

twilight-gateway-queue is a trait and some implementations that are used by the gateway to ratelimit identify calls. Developers should prefer to use the re-exports of these crates through the gateway.

Installation

Add the following yo your Cargo.toml:

twilight-gateway-queue = "0.1.0"

Links

source: https://github.com/twilight-rs/twilight/tree/trunk/gateway/queue

docs: https://api.twilight.rs/twilight_gateway_queue/index.html

crates.io: https://crates.io/crates/twilight-gateway-queue

Third-party

Third-party crates are crates that aren't supported by the Twilight organization but are recognised by it. Of course, use these at your own risk. :)

Third-party crates may become first-party crates if they end up becoming useful enough for a large number of users.

List of Crates

Below is a list of crates. If you want yours added, feel free to ask!

rarity-rs/permission-calculator

The permission calculator is a crate for calculating the permissions of a member in a channel, taking into account its roles and permission overwrites.

You can check it out here

Services

Twilight is built with a service-minded approach. This means that it caters to both monolithic and multi-serviced applications equally. If you have a very large bot and have a multi-serviced application and feel like Rust is a good language to use for some of your services, then Twilight is a great choice. If you have a small bot and just want to get it going in a monolithic application, then it's also a good choice. It's easy to split off parts of your application into other services as your application grows.

Gateway clusters

One of the popular design choices when creating a multi-serviced application is to have a service that simply connects shards to the gateway and sends the events to a broker to be processed. As bots grow into hundreds or thousands of shards, multiple instances of the application can be created and clusters - groups of shards - can be managed by each. Twilight is a good choice for this use case: you can receive either events that come in in a loop and send the payloads to the appropriate broker stream, or you can loop over received payloads' bytes to send off.

Gateway session ratelimiting

If you have multiple clusters, then you need to queue and ratelimit your initialized sessions. The Gateway includes a Queue trait which you can implement, and the gateway will submit a request to the queue before starting a session. Twilight comes with a queue that supports sharding and Large Bot sharding, but when you start to have multiple clusters you'll want to implement your own. Our gateway-queue is an example of this.

HTTP proxy ratelimiting

If you have multiple services or lambda functions that can make HTTP requests, then you'll run into ratelimiting issues. Twilight's HTTP client supports proxying, and can be combined with something like our very own http-proxy to proxy requests and ratelimit them.

The sky is the limit

You can do so much more than just this, and that's the beauty of the ecosystem: it's flexible enough to do anything you need, and if you find something it can't then we'll fix it. The goal is to remove all limitations on designs and allow you to do what you need.

Bots using Twilight

Below is a list of bots known to be using the Twilight ecosystem. The use could be as small as only the gateway or HTTP client, or as large as all of the core crates.

Want your bot added? Feel free to send a PR to the repo!

Gearbot

The GearBot team are rewriting their bot to use Twilight, with a need for performance and scalability in mind.

Source: GitHub

Lasagne bot

Lasagne bot is a bot that posts garfield comics.

Source: Sr.ht

Changelogs

0.1.0 - 2020-09-13

Initial release.

Check out the crates on crates.io.