serenity/gateway/mod.rs
1//! The gateway module contains the pieces - primarily the `Shard` - responsible for maintaining a
2//! WebSocket connection with Discord.
3//!
4//! A shard is an interface for the lower-level receiver and sender. It provides what can otherwise
5//! be thought of as "sugar methods". A shard represents a single connection to Discord. You can
6//! make use of a method named "sharding" to have multiple shards, potentially offloading some
7//! server load to another server(s).
8//!
9//! # Sharding
10//!
11//! Sharding is a method to split portions of bots into separate processes. This is an enforced
12//! strategy by Discord once a bot reaches a certain number of guilds (2500). Once this number is
13//! reached, a bot must be sharded in a way that only 2500 guilds maximum may be allocated per
14//! shard.
15//!
16//! The "recommended" number of guilds per shard is _around_ 1000. Sharding can be useful for
17//! splitting processes across separate servers. Often you may want some or all shards to be in the
18//! same process, allowing for a shared State. This is possible through this library.
19//!
20//! See [Discord's documentation][docs] for more information.
21//!
22//! If you do not require sharding - such as for a small bot - then use [`Client::start`].
23//!
24//! There are a few methods of sharding available:
25//! - [`Client::start_autosharded`]: retrieves the number of shards Discord recommends using from
26//! the API, and then automatically starts that number of shards.
27//! - [`Client::start_shard`]: starts a single shard for use in the instance, handled by the
28//! instance of the Client. Use this if you only want 1 shard handled by this instance.
29//! - [`Client::start_shards`]: starts all shards in this instance. This is best for when you want a
30//! completely shared State.
31//! - [`Client::start_shard_range`]: start a range of shards within this instance. This should be
32//! used when you, for example, want to split 10 shards across 3 instances.
33//!
34//! [`Client`]: crate::Client
35//! [`Client::start`]: crate::Client::start
36//! [`Client::start_autosharded`]: crate::Client::start_autosharded
37//! [`Client::start_shard`]: crate::Client::start_shard
38//! [`Client::start_shard_range`]: crate::Client::start_shard_range
39//! [`Client::start_shards`]: crate::Client::start_shards
40//! [docs]: https://discordapp.com/developers/docs/topics/gateway#sharding
41
42mod bridge;
43mod error;
44mod shard;
45mod ws;
46
47use std::fmt;
48
49#[cfg(feature = "http")]
50use reqwest::IntoUrl;
51use reqwest::Url;
52
53pub use self::bridge::*;
54pub use self::error::Error as GatewayError;
55pub use self::shard::Shard;
56pub use self::ws::WsClient;
57#[cfg(feature = "http")]
58use crate::internal::prelude::*;
59use crate::model::gateway::{Activity, ActivityType};
60use crate::model::id::UserId;
61use crate::model::user::OnlineStatus;
62
63/// Presence data of the current user.
64#[derive(Clone, Debug, Default)]
65pub struct PresenceData {
66 /// The current activity, if present
67 pub activity: Option<ActivityData>,
68 /// The current online status
69 pub status: OnlineStatus,
70}
71
72/// Activity data of the current user.
73#[derive(Clone, Debug, Serialize)]
74pub struct ActivityData {
75 /// The name of the activity
76 pub name: String,
77 /// The type of the activity
78 #[serde(rename = "type")]
79 pub kind: ActivityType,
80 /// The state of the activity, if the type is [`ActivityType::Custom`]
81 pub state: Option<String>,
82 /// The url of the activity, if the type is [`ActivityType::Streaming`]
83 pub url: Option<Url>,
84}
85
86impl ActivityData {
87 /// Creates an activity that appears as `Playing <name>`.
88 #[must_use]
89 pub fn playing(name: impl Into<String>) -> Self {
90 Self {
91 name: name.into(),
92 kind: ActivityType::Playing,
93 state: None,
94 url: None,
95 }
96 }
97
98 /// Creates an activity that appears as `Streaming <name>`.
99 ///
100 /// # Errors
101 ///
102 /// Returns an error if the URL parsing fails.
103 #[cfg(feature = "http")]
104 pub fn streaming(name: impl Into<String>, url: impl IntoUrl) -> Result<Self> {
105 Ok(Self {
106 name: name.into(),
107 kind: ActivityType::Streaming,
108 state: None,
109 url: Some(url.into_url()?),
110 })
111 }
112
113 /// Creates an activity that appears as `Listening to <name>`.
114 #[must_use]
115 pub fn listening(name: impl Into<String>) -> Self {
116 Self {
117 name: name.into(),
118 kind: ActivityType::Listening,
119 state: None,
120 url: None,
121 }
122 }
123
124 /// Creates an activity that appears as `Watching <name>`.
125 #[must_use]
126 pub fn watching(name: impl Into<String>) -> Self {
127 Self {
128 name: name.into(),
129 kind: ActivityType::Watching,
130 state: None,
131 url: None,
132 }
133 }
134
135 /// Creates an activity that appears as `Competing in <name>`.
136 #[must_use]
137 pub fn competing(name: impl Into<String>) -> Self {
138 Self {
139 name: name.into(),
140 kind: ActivityType::Competing,
141 state: None,
142 url: None,
143 }
144 }
145
146 /// Creates an activity that appears as `<state>`.
147 #[must_use]
148 pub fn custom(state: impl Into<String>) -> Self {
149 Self {
150 // discord seems to require a name for custom activities
151 // even though it's not displayed
152 name: "~".to_string(),
153 kind: ActivityType::Custom,
154 state: Some(state.into()),
155 url: None,
156 }
157 }
158}
159
160impl From<Activity> for ActivityData {
161 fn from(activity: Activity) -> Self {
162 Self {
163 name: activity.name,
164 kind: activity.kind,
165 state: activity.state,
166 url: activity.url,
167 }
168 }
169}
170
171/// Indicates the current connection stage of a [`Shard`].
172///
173/// This can be useful for knowing which shards are currently "down"/"up".
174#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
175#[non_exhaustive]
176pub enum ConnectionStage {
177 /// Indicator that the [`Shard`] is normally connected and is not in, e.g., a resume phase.
178 Connected,
179 /// Indicator that the [`Shard`] is connecting and is in, e.g., a resume phase.
180 Connecting,
181 /// Indicator that the [`Shard`] is fully disconnected and is not in a reconnecting phase.
182 Disconnected,
183 /// Indicator that the [`Shard`] is currently initiating a handshake.
184 Handshake,
185 /// Indicator that the [`Shard`] has sent an IDENTIFY packet and is awaiting a READY packet.
186 Identifying,
187 /// Indicator that the [`Shard`] has sent a RESUME packet and is awaiting a RESUMED packet.
188 Resuming,
189}
190
191impl ConnectionStage {
192 /// Whether the stage is a form of connecting.
193 ///
194 /// This will return `true` on:
195 /// - [`Connecting`][`ConnectionStage::Connecting`]
196 /// - [`Handshake`][`ConnectionStage::Handshake`]
197 /// - [`Identifying`][`ConnectionStage::Identifying`]
198 /// - [`Resuming`][`ConnectionStage::Resuming`]
199 ///
200 /// All other variants will return `false`.
201 ///
202 /// # Examples
203 ///
204 /// Assert that [`ConnectionStage::Identifying`] is a connecting stage:
205 ///
206 /// ```rust
207 /// use serenity::gateway::ConnectionStage;
208 ///
209 /// assert!(ConnectionStage::Identifying.is_connecting());
210 /// ```
211 ///
212 /// Assert that [`ConnectionStage::Connected`] is _not_ a connecting stage:
213 ///
214 /// ```rust
215 /// use serenity::gateway::ConnectionStage;
216 ///
217 /// assert!(!ConnectionStage::Connected.is_connecting());
218 /// ```
219 #[must_use]
220 pub fn is_connecting(self) -> bool {
221 use self::ConnectionStage::{Connecting, Handshake, Identifying, Resuming};
222 matches!(self, Connecting | Handshake | Identifying | Resuming)
223 }
224}
225
226impl fmt::Display for ConnectionStage {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 f.write_str(match *self {
229 Self::Connected => "connected",
230 Self::Connecting => "connecting",
231 Self::Disconnected => "disconnected",
232 Self::Handshake => "handshaking",
233 Self::Identifying => "identifying",
234 Self::Resuming => "resuming",
235 })
236 }
237}
238
239#[derive(Debug)]
240#[non_exhaustive]
241pub enum ShardAction {
242 Heartbeat,
243 Identify,
244 Reconnect(ReconnectType),
245}
246
247/// The type of reconnection that should be performed.
248#[derive(Debug)]
249#[non_exhaustive]
250pub enum ReconnectType {
251 /// Indicator that a new connection should be made by sending an IDENTIFY.
252 Reidentify,
253 /// Indicator that a new connection should be made by sending a RESUME.
254 Resume,
255}
256
257/// [Discord docs](https://discord.com/developers/docs/topics/gateway-events#request-guild-members).
258#[derive(Clone, Debug)]
259pub enum ChunkGuildFilter {
260 /// Returns all members of the guilds specified. Requires GUILD_MEMBERS intent.
261 None,
262 /// A common username prefix filter for the members returned.
263 ///
264 /// Will return a maximum of 100 members.
265 Query(String),
266 /// A set of exact user IDs to query for.
267 ///
268 /// Will return a maximum of 100 members.
269 UserIds(Vec<UserId>),
270}