Overview
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 futures::stream::StreamExt; use std::{env, error::Error, sync::Arc}; use twilight_cache_inmemory::{InMemoryCache, ResourceType}; use twilight_gateway::{cluster::{Cluster, ShardScheme}, Event, Intents}; use twilight_http::Client as HttpClient; #[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; // Specify intents requesting events about things like new and updated // messages in a guild and direct messages. let intents = Intents::GUILD_MESSAGES | Intents::DIRECT_MESSAGES; let (cluster, mut events) = Cluster::builder(&token, intents) .shard_scheme(scheme) .build() .await?; let cluster = Arc::new(cluster); // 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, also use Arc such that it can be cloned to other threads. let http = Arc::new(HttpClient::new(token)); // Since we only care about messages, make the cache only process messages. let cache = InMemoryCache::builder() .resource_types(ResourceType::MESSAGE) .build(); // 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, Arc::clone(&http))); } Ok(()) } async fn handle_event( shard_id: u64, event: Event, http: Arc<HttpClient>, ) -> Result<(), Box<dyn Error + Send + Sync>> { match event { Event::MessageCreate(msg) if msg.content == "!ping" => { http.create_message(msg.channel_id).content("Pong!")?.exec().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 an MSRV of Rust 1.53+.
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.
Links
source: https://github.com/twilight-rs/twilight/tree/main/model
docs: https://docs.rs/twilight-model
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.
Features
Deserialization
twilight-gateway
supports serde_json
and simd-json
for deserializing
and serializing events.
SIMD
The simd-json
feature enables usage of simd-json
, which uses modern CPU
features to more efficiently deserialize JSON data. It is not enabled by
default.
In addition to enabling the feature, you will need to add the following to your
<project_root>/.cargo/config
:
[build]
rustflags = ["-C", "target-cpu=native"]
TLS
twilight-http
has features to enable certain HTTPS TLS connectors.
These features are mutually exclusive. rustls
is enabled by default.
Native
The native
feature causes the client to use hyper-tls
. This will use the
native TLS backend, such as OpenSSL on Linux.
RusTLS
The rustls
feature causes the client to use hyper-rustls
. This enables
usage of the RusTLS crate as the TLS backend.
This is enabled by default.
Example
A quick example showing how to get the current user's name:
use std::{env, error::Error}; use twilight_http::Client; #[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 me = client.current_user().exec().await?.model().await?; println!("Current user: {}#{}", me.name, me.discriminator); Ok(()) }
Links
source: https://github.com/twilight-rs/twilight/tree/main/http
docs: https://docs.rs/twilight-http
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 std::sync::Arc; use futures::StreamExt; use twilight_gateway::{Cluster, Intents}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let token = "dummy"; let intents = Intents::GUILD_MESSAGES | Intents::GUILDS; let (cluster, mut events) = Cluster::new(token, intents).await?; let cluster = Arc::new(cluster); let cluster_spawn = cluster.clone(); tokio::spawn(async move { cluster_spawn.up().await; }); let _ = events.next().await; Ok(()) }
Features
twilight-gateway
includes a number of features for things ranging from
payload deserialization to TLS features.
Deserialization
twilight-gateway
supports serde_json
and simd-json
for deserializing
and serializing events.
SIMD
The simd-json
feature enables usage of simd-json
, which uses modern CPU
features to more efficiently deserialize JSON data. It is not enabled by
default.
In addition to enabling the feature, you will need to add the following to your
<project_root>/.cargo/config
:
[build]
rustflags = ["-C", "target-cpu=native"]
Metrics
The metrics
feature provides metrics information via the metrics
crate.
Some of the metrics logged are counters about received event counts and their
types and gauges about the capacity and efficiency of the inflater of each
shard.
This is disabled by default.
TLS
twilight-gateway
has features to enable async-tungstenite
and
twilight-http
's TLS features. These features are mutually exclusive. rustls
is enabled by default.
Native
The native
feature enables async-tungstenite
's tokio-native-tls
feature
as well as twilight-http
's native
feature which uses hyper-tls
.
RusTLS
The rustls
feature enables async-tungstenite
's tokio-rustls
feature and
twilight-http
's rustls
feature, which use RusTLS as the TLS backend.
This is enabled by default.
Zlib
Stock
The zlib-stock
feature makes flate2 use of the stock Zlib which is either
upstream or the one included with the operating system.
SIMD
zlib-simd
enables the use of zlib-ng which is a modern fork of zlib that in
most cases will be more effective. However, this 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::{Intents, Shard}; #[tokio::main] async fn main() -> Result<(), Box<dyn Error + Send + Sync>> { // Initialize the tracing subscriber. tracing_subscriber::fmt::init(); let token = env::var("DISCORD_TOKEN")?; let (shard, mut events) = Shard::new(token, Intents::GUILD_MESSAGES); 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/main/gateway
docs: https://docs.rs/twilight-gateway
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.
Examples
Process new messages 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::{Intents, Shard}; let token = env::var("DISCORD_TOKEN")?; let (shard, mut events) = Shard::new(token, Intents::GUILD_MESSAGES); shard.start().await?; let cache = InMemoryCache::new(); while let Some(event) = events.next().await { cache.update(&event); } Ok(()) }
Links
source: https://github.com/twilight-rs/twilight/tree/main/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.
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/main/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.
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::incoming::MessageCreate, id::{ChannelId, UserId}, }; use twilight_standby::Standby; let standby = Standby::new(); // Later on in the application... let message = standby .wait_for_message( ChannelId::new(123).expect("zero id"), |event: &MessageCreate| { event.author.id == UserId::new(456).expect("zero id") && event.content == "test" }, ) .await?; Ok(()) }
Links
source: https://github.com/twilight-rs/twilight/tree/main/standby
docs: https://docs.rs/twilight-standby
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.
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/main/utils/embed-builder
docs: https://docs.rs/twilight-embed-builder
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.
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::new(123).expect("zero id"); let message = format!("Hey there, {}!", user_id.mention()); }
Links
source: https://github.com/twilight-rs/twilight/tree/main/utils/mention
docs: https://docs.rs/twilight-mention
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.
Features
HTTP Support
The http-support
feature adds types for creating requests and deserializing
response bodies of Lavalink's HTTP routes via the http
crate.
This is enabled by default.
TLS
twilight-lavalink
has features to enable async-tungstenite
's TLS features.
These features are mutually exclusive.
rustls
is enabled by default.
Native
The native
feature enables async-tungstenite
's tokio-native-tls
feature.
This will use native TLS support, for example OpenSSL on Linux.
RusTLS
The rustls
feature enables async-tungstenite
's tokio-rustls
which uses
the RusTLS crate as the TLS backend.
This is enabled by default.
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::{Intents, 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.clone()); let user_id = http.current_user().exec().await?.model().await?.id; let lavalink = Lavalink::new(user_id, shard_count); lavalink.add(lavalink_host, lavalink_auth).await?; let intents = Intents::GUILD_MESSAGES | Intents::GUILD_VOICE_STATES; let (shard, mut events) = Shard::new(token, intents); shard.start().await?; while let Some(event) = events.next().await { lavalink.process(&event).await?; } Ok(()) }
Links
source: https://github.com/twilight-rs/twilight/tree/main/lavalink
docs: https://docs.rs/twilight-lavalink
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. One example feature of the crate
is a trait to make extracting data from Discord identifiers (snowflakes) easier.
Features
twilight-util
by default exports nothing. Features must be individually
enabled via feature flags.
Builder
The builder
feature enables builders for large structs, at the
moment only a builder for commands is availible.
Examples
Create a command that can be used to send a animal picture in a certain category:
fn main() { use twilight_model::application::command::CommandType; use twilight_util::builder::command::{BooleanBuilder, CommandBuilder, StringBuilder}; CommandBuilder::new( "blep".into(), "Send a random adorable animal photo".into(), CommandType::ChatInput, ) .option( StringBuilder::new("animal".into(), "The type of animal".into()) .required(true) .choices([ ("Dog".into(), "animal_dog".into()), ("Cat".into(), "animal_cat".into()), ("Penguin".into(), "animal_penguin".into()), ]), ) .option(BooleanBuilder::new( "only_smol".into(), "Whether to show only baby animals".into(), )); }
Link
The link
feature enables the parsing and formatting of URLs to resources, such
as parsing and formatting webhook links or links to a user's avatar.
Examples
Parse a webhook URL with a token:
#[allow(unused_variables)] fn main() -> Result<(), Box<dyn std::error::Error>> { use twilight_model::id::WebhookId; use twilight_util::link::webhook; let url = "https://discord.com/api/webhooks/794590023369752587/tjxHaPHLKp9aEdSwJuLeHhHHGEqIxt1aay4I67FOP9uzsYEWmj0eJmDn-2ZvCYLyOb_K"; let (id, token) = webhook::parse(url)?; assert_eq!(WebhookId::new(794590023369752587).expect("zero id"), id); assert_eq!( Some("tjxHaPHLKp9aEdSwJuLeHhHHGEqIxt1aay4I67FOP9uzsYEWmj0eJmDn-2ZvCYLyOb_K"), token, ); Ok(()) }
Permission Calculator
The permission-calculator
feature is used for calculating the permissions
of a member in a channel, taking into account its roles and permission
overwrites.
Snowflake
The snowflake
feature calculates information out of snowflakes, such as the
timestamp or the ID of the worker that created it.
Examples
Retrieve the timestamp of a snowflake in milliseconds from the Unix epoch as a 64-bit integer:
#[allow(unused_variables)] fn main() { use twilight_util::snowflake::Snowflake; use twilight_model::id::UserId; let user = UserId::new(123456).expect("zero id"); let timestamp = user.timestamp(); }
Links
source: https://github.com/twilight-rs/twilight/tree/main/util
docs: https://docs.rs/twilight-util
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.
Links
source: https://github.com/twilight-rs/twilight/tree/main/gateway/queue
docs: https://docs.rs/twilight-gateway-queue
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!
baptiste0928/twilight-interactions
twilight-interactions
provides macros and utilities to make interactions easier to use.
Its features include slash command parsing and creation from structs with derive macros.
GitHub repository - Documentation
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!
Open-Source
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
HarTex
HarTex is a Discord bot built and optimized for server administration and moderation needs in mind.
Source: GitHub
interchannel message mover
a discord bot to move messages between channels easily
Source: GitHub
Timezoner
Timezoner is a bot that lets people send a date/time that appears in everyone's own timezone.
Source: GitHub
Changelogs
0.1.0 - 2020-09-13
Initial release.
Check out the crates on crates.io.