twilight_gateway/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![warn(
4    clippy::missing_const_for_fn,
5    clippy::missing_docs_in_private_items,
6    clippy::pedantic,
7    missing_docs,
8    unsafe_code
9)]
10#![allow(
11    clippy::module_name_repetitions,
12    clippy::must_use_candidate,
13    clippy::unnecessary_wraps
14)]
15
16pub mod error;
17
18mod channel;
19mod command;
20mod config;
21mod event;
22#[cfg(any(feature = "zlib-stock", feature = "zlib-simd"))]
23mod inflater;
24mod json;
25mod latency;
26mod message;
27mod ratelimiter;
28mod session;
29mod shard;
30mod stream;
31
32#[cfg(any(feature = "zlib-stock", feature = "zlib-simd"))]
33pub use self::inflater::Inflater;
34pub use self::{
35    channel::MessageSender,
36    command::Command,
37    config::{Config, ConfigBuilder},
38    event::EventTypeFlags,
39    json::parse,
40    latency::Latency,
41    message::Message,
42    ratelimiter::CommandRatelimiter,
43    session::Session,
44    shard::{Shard, ShardState},
45    stream::StreamExt,
46};
47pub use twilight_model::gateway::{CloseFrame, Intents, ShardId};
48
49#[doc(no_inline)]
50pub use twilight_gateway_queue as queue;
51#[doc(no_inline)]
52pub use twilight_model::gateway::event::{Event, EventType};
53
54#[cfg(feature = "twilight-http")]
55use self::error::{StartRecommendedError, StartRecommendedErrorType};
56#[cfg(feature = "twilight-http")]
57use twilight_http::Client;
58
59/// Discord Gateway API version used by this crate.
60pub const API_VERSION: u8 = 10;
61
62/// Create a single bucket's worth of shards.
63///
64/// Passing a primary config is required. Further customization of this config
65/// may be performed in the callback.
66///
67/// Internally calls [`create_iterator`] with `(bucket_id..total).step_by(concurrency)`.
68///
69/// # Panics
70///
71/// Panics if `bucket_id >= total`, `bucket_id >= concurrency`, or `concurrency >= total`.
72///
73/// Panics if loading TLS certificates fails.
74#[track_caller]
75pub fn create_bucket<F, Q>(
76    bucket_id: u16,
77    concurrency: u16,
78    total: u32,
79    config: Config<Q>,
80    per_shard_config: F,
81) -> impl ExactSizeIterator<Item = Shard<Q>>
82where
83    F: Fn(ShardId, ConfigBuilder<Q>) -> Config<Q>,
84    Q: Clone,
85{
86    assert!(
87        u32::from(bucket_id) < total,
88        "bucket id must be less than the total"
89    );
90    assert!(
91        bucket_id < concurrency,
92        "bucket id must be less than concurrency"
93    );
94    assert!(
95        (u32::from(concurrency)) < total,
96        "concurrency must be less than the total"
97    );
98
99    create_iterator(
100        (u32::from(bucket_id)..total).step_by(concurrency.into()),
101        total,
102        config,
103        per_shard_config,
104    )
105}
106
107/// Create a iterator of shards.
108///
109/// Passing a primary config is required. Further customization of this config
110/// may be performed in the callback.
111///
112/// # Examples
113///
114/// Start 10 out of 10 shards and count them:
115///
116/// ```no_run
117/// use std::{collections::HashMap, env, sync::Arc};
118/// use twilight_gateway::{Config, Intents};
119///
120/// let token = env::var("DISCORD_TOKEN")?;
121///
122/// let config = Config::new(token.clone(), Intents::GUILDS);
123/// let shards = twilight_gateway::create_iterator(0..10, 10, config, |_, builder| builder.build());
124///
125/// assert_eq!(shards.len(), 10);
126/// # Ok::<(), Box<dyn std::error::Error>>(())
127/// ```
128///
129/// # Panics
130///
131/// Panics if `range` contains values larger than `total`.
132///
133/// Panics if loading TLS certificates fails.
134#[track_caller]
135pub fn create_iterator<F, Q>(
136    numbers: impl ExactSizeIterator<Item = u32>,
137    total: u32,
138    config: Config<Q>,
139    per_shard_config: F,
140) -> impl ExactSizeIterator<Item = Shard<Q>>
141where
142    F: Fn(ShardId, ConfigBuilder<Q>) -> Config<Q>,
143    Q: Clone,
144{
145    numbers.map(move |index| {
146        let id = ShardId::new(index, total);
147        let config = per_shard_config(id, ConfigBuilder::from(config.clone()));
148
149        Shard::with_config(id, config)
150    })
151}
152
153/// Create a range of shards from Discord's recommendation.
154///
155/// Passing a primary config is required. Further customization of this config
156/// may be performed in the callback.
157///
158/// Internally calls [`create_iterator`] with the values from [`GetGatewayAuthed`].
159///
160/// # Errors
161///
162/// Returns a [`StartRecommendedErrorType::Deserializing`] error type if the
163/// response body failed to deserialize.
164///
165/// Returns a [`StartRecommendedErrorType::Request`] error type if the request
166/// failed to complete.
167///
168/// # Panics
169///
170/// Panics if loading TLS certificates fails.
171///
172/// [`GetGatewayAuthed`]: twilight_http::request::GetGatewayAuthed
173#[cfg(feature = "twilight-http")]
174pub async fn create_recommended<F, Q>(
175    client: &Client,
176    config: Config<Q>,
177    per_shard_config: F,
178) -> Result<impl ExactSizeIterator<Item = Shard<Q>>, StartRecommendedError>
179where
180    F: Fn(ShardId, ConfigBuilder<Q>) -> Config<Q>,
181    Q: Clone,
182{
183    let request = client.gateway().authed();
184    let response = request.await.map_err(|source| StartRecommendedError {
185        kind: StartRecommendedErrorType::Request,
186        source: Some(Box::new(source)),
187    })?;
188    let info = response
189        .model()
190        .await
191        .map_err(|source| StartRecommendedError {
192            kind: StartRecommendedErrorType::Deserializing,
193            source: Some(Box::new(source)),
194        })?;
195
196    Ok(create_iterator(
197        0..info.shards,
198        info.shards,
199        config,
200        per_shard_config,
201    ))
202}