spectacles_model/
gateway.rs

1//! Structs representing the various elements of the Discord gateway.
2use std::fmt::{Display, Formatter, Result as FmtResult};
3
4use serde_json::{
5    Error as JsonError,
6    value::RawValue,
7};
8use serde_repr::{Deserialize_repr, Serialize_repr};
9
10use crate::{
11    guild::UnavailableGuild,
12    presence::{ClientActivity, ClientPresence},
13    Snowflake,
14    User
15};
16use crate::presence::Status;
17
18/// Denotes structs that can be sent to the gateway.
19pub trait SendablePacket {
20    fn to_json(self) -> Result<String, JsonError>;
21    fn bytes(self) -> Result<Vec<u8>, JsonError>;
22}
23
24/// Returns useful information about the application from the gateway.
25#[derive(Serialize, Deserialize, Debug)]
26pub struct GatewayBot {
27    /// The websocket URL that can be used to begin connecting to this gateway.
28    pub url: String,
29    /// The recommended number of shards to spawn when connecting.
30    pub shards: usize,
31    /// Information regarding the current session start limit.
32    pub session_start_limit: SessionStartLimit
33}
34/// Useful information about a bot's session start limit.
35#[derive(Serialize, Deserialize, Debug)]
36pub struct SessionStartLimit {
37    /// The total number of session starts the current user is allowed.
38    pub total: i32,
39    /// The remaining number of session starts the current user is allowed.
40    pub remaining: i32,
41    /// The time until the limit resets.
42    pub reset_after: i32,
43}
44
45/// A JSON packet that the client would receive over the Discord gateway.
46#[derive(Serialize, Deserialize, Debug, Clone)]
47pub struct ReceivePacket {
48    /// The opcode for this payload.
49    pub op: Opcodes,
50    /// The JSON value for this payload.
51    pub d: Box<RawValue>,
52    pub s: Option<u64>,
53    /// The name of the event that was fired, if applicable.
54    pub t: Option<GatewayEvent>
55}
56
57#[derive(Serialize, Deserialize, Debug)]
58/// A JSON packet that the client would send to the Discord Gateway.
59pub struct SendPacket<T: SendablePacket> {
60    /// The opcode for this packet.
61    pub op: Opcodes,
62    /// The JSON data for this packet.
63    pub d: T
64}
65
66/// A message, sent to a message broker, which contains the packet to be sent to the Discord gateway.
67#[derive(Serialize, Deserialize, Debug)]
68pub struct GatewayBrokerMessage {
69    /// The guild ID of which to calculate the shard ID from.
70    pub guild_id: Snowflake,
71    /// The packet, as a raw JSON value.
72    pub packet: Box<RawValue>,
73}
74
75impl GatewayBrokerMessage {
76    /// Creates a new gateway broker message.
77    pub fn new<T: SendablePacket>(guild_id: Snowflake, packet: T) -> Result<Self, JsonError> {
78        let raw = RawValue::from_string(packet.to_json()?)?;
79
80        Ok(Self {
81            guild_id,
82            packet: raw,
83        })
84    }
85    /// Converts the message to a byte vector, suitable for publishing to most message brokers.
86    pub fn as_bytes(&self) -> Result<Vec<u8>, JsonError> {
87        let json = serde_json::to_string(self)?;
88
89        Ok(json.as_bytes().to_vec())
90    }
91}
92
93/// Used for identifying a shard with the gateway.
94#[derive(Serialize, Deserialize, Debug, Clone)]
95pub struct IdentifyPacket {
96    /// The token this shard will use.
97    pub token: String,
98    /// The properties of the client.
99    pub properties: IdentifyProperties,
100    /// The version of the gateway to use.
101    #[serde(rename = "v")]
102    pub version: u8,
103    /// Whether or not to compress packets.
104    pub compress: bool,
105    /// The total number of members where the gateway will stop sending offline members in the guild member list.
106    pub large_threshold: i32,
107    /// Holds the sharding information for this shard.
108    pub shard: [usize; 2],
109    /// The initial presence of this shard.
110    pub presence: Option<ClientPresence>
111}
112
113
114#[derive(Serialize, Deserialize, Debug, Clone)]
115pub struct IdentifyProperties {
116    /// The client's operating system.
117    #[serde(rename = "$os")]
118    pub os: String,
119    /// The current name of the library.
120    #[serde(rename = "$browser")]
121    pub browser: String,
122    /// The current name of the library.
123    #[serde(rename = "$device")]
124    pub device: String
125}
126
127/// A JSON packet which defines the heartbeat the client should adhere to.
128#[derive(Serialize, Deserialize, Debug)]
129pub struct HelloPacket {
130    /// The heartbeat interval that the shard should follow.
131    pub heartbeat_interval: u64,
132    /// An array of servers that the client is connected to.
133    pub _trace: Vec<String>
134}
135
136/// A packet used to resume a gateway connection.
137#[derive(Serialize, Deserialize, Debug, Clone)]
138pub struct ResumeSessionPacket {
139    /// The client's session ID>
140    pub session_id: String,
141    /// The current sequence.
142    pub seq: u64,
143    /// The token of the client.
144    pub token: String
145}
146/// A JSON packet used to send a heartbeat to the gateway.
147#[derive(Serialize, Deserialize, Debug, Clone)]
148pub struct HeartbeatPacket {
149    /// The shard's last sequence number.
150    pub seq: u64
151}
152
153#[derive(Serialize, Deserialize, Debug, Clone)]
154/// A Request guild members packet.
155pub struct RequestGuildMembers {
156    /// The guild ID to get members for.
157    pub guild_id: Snowflake,
158    /// A string that the username starts with. If omitted, returns all members.
159    pub query: String,
160    /// The maximum number of members to send. If omitted, requests all members.
161    pub limit: i32
162}
163
164
165/// An Update Voice State packet.
166#[derive(Serialize, Deserialize, Debug, Clone)]
167pub struct UpdateVoiceState {
168    /// The guild ID of the guild.
169    pub guild_id: Snowflake,
170    /// The channel ID of the voice channel.
171    pub channel_id: Snowflake,
172    /// Whether or not to mute the current user.
173    pub self_mute: bool,
174    /// Whether or not to deafen the current user.
175    pub self_deaf: bool
176}
177
178/// A packet sent to change the current status of the connected client.
179#[derive(Serialize, Deserialize, Debug, Clone, Default)]
180pub struct UpdateStatus {
181    /// Milliseconds since the client went idle.
182    pub since: Option<i32>,
183    /// The activity object to set.
184    pub game: Option<ClientActivity>,
185    /// The status object to set.
186    pub status: Status,
187    /// Whether or not the client is AFK.
188    pub afk: bool
189}
190
191impl UpdateStatus {
192    /// Sets the activity for this packet.
193    pub fn game(mut self, activity: ClientActivity) -> Self {
194        self.game = Some(activity);
195
196        self
197    }
198
199    /// Sets the status for this packet.
200    pub fn status(mut self, status: Status) -> Self {
201        self.status = status;
202
203        self
204    }
205
206    /// Sets the AFK flag for this packet.
207    pub fn afk(mut self, afk: bool) -> Self {
208        self.afk = afk;
209
210        self
211    }
212}
213
214impl SendablePacket for UpdateStatus {
215    fn to_json(self) -> Result<String, JsonError> {
216        serde_json::to_string(&SendPacket {
217            op: Opcodes::StatusUpdate,
218            d: self
219        })
220    }
221
222    fn bytes(self) -> Result<Vec<u8>, JsonError> {
223        let json = self.to_json()?;
224
225        Ok(json.as_bytes().to_vec())
226    }
227}
228
229
230impl SendablePacket for IdentifyPacket {
231    fn to_json(self) -> Result<String, JsonError> {
232        serde_json::to_string(&SendPacket {
233            op: Opcodes::Identify,
234            d: self
235        })
236    }
237
238    fn bytes(self) -> Result<Vec<u8>, JsonError> {
239        let json = self.to_json()?;
240
241        Ok(json.as_bytes().to_vec())
242    }
243}
244
245
246impl SendablePacket for UpdateVoiceState {
247    fn to_json(self) -> Result<String, JsonError> {
248        serde_json::to_string(&SendPacket {
249            op: Opcodes::VoiceStatusUpdate,
250            d: self,
251        })
252    }
253
254    fn bytes(self) -> Result<Vec<u8>, JsonError> {
255        let json = self.to_json()?;
256
257        Ok(json.as_bytes().to_vec())
258    }
259}
260impl SendablePacket for RequestGuildMembers {
261    fn to_json(self) -> Result<String, JsonError> {
262        serde_json::to_string(&SendPacket {
263            op: Opcodes::RequestGuildMembers,
264            d: self
265        })
266    }
267
268    fn bytes(self) -> Result<Vec<u8>, JsonError> {
269        let json = self.to_json()?;
270
271        Ok(json.as_bytes().to_vec())
272    }
273}
274
275impl SendablePacket for HeartbeatPacket {
276    fn to_json(self) -> Result<String, JsonError> {
277        serde_json::to_string(&SendPacket {
278            op: Opcodes::Heartbeat,
279            d: self
280        })
281    }
282
283    fn bytes(self) -> Result<Vec<u8>, JsonError> {
284        let json = self.to_json()?;
285
286        Ok(json.as_bytes().to_vec())
287    }
288}
289
290impl SendablePacket for ResumeSessionPacket {
291    fn to_json(self) -> Result<String, JsonError> {
292        serde_json::to_string(&SendPacket {
293            op: Opcodes::Resume,
294            d: self
295        })
296    }
297
298    fn bytes(self) -> Result<Vec<u8>, JsonError> {
299        let json = self.to_json()?;
300
301        Ok(json.as_bytes().to_vec())
302    }
303}
304
305/// The packet received when a client completes a handshake with the Discord gateway.
306/// This packet is considered the largest and most complex packet sent.
307#[derive(Deserialize, Serialize, Debug, Clone)]
308pub struct ReadyPacket {
309    /// The current gateway version,
310    pub v: i32,
311    /// Information about the current user.
312    pub user: User,
313    /// An empty array of private channels.
314    pub private_channels: [String; 0],
315    /// The guilds that the user is currently in.
316    /// This will be an array of UnavailableGuild objects.
317    pub guilds: Vec<UnavailableGuild>,
318    /// The session ID that is used to resume a gateway connection.
319    pub session_id: String,
320    /// The guilds that a user is in, used for debugging.
321    pub _trace: Vec<String>,
322    /// Information about the current shard, if applicable.
323    #[serde(default)]
324    pub shard: [u64; 2]
325}
326
327/// This packet is received when the client resumes an existing session.
328#[derive(Serialize, Deserialize, Debug, Clone)]
329pub struct ResumedPacket {
330    /// The servers that the client is connected to.
331    pub _trace: Vec<String>
332
333}
334#[derive(Debug, Deserialize, Serialize, Clone)]
335#[allow(non_camel_case_types)]
336/// An organized list of Discord gateway events.
337pub enum GatewayEvent {
338    HELLO,
339    READY,
340    RESUMED,
341    INVALID_SESSION,
342    CHANNEL_CREATE,
343    CHANNEL_UPDATE,
344    CHANNEL_DELETE,
345    CHANNEL_PINS_UPDATE,
346    GUILD_CREATE,
347    GUILD_UPDATE,
348    GUILD_DELETE,
349    GUILD_BAN_ADD,
350    GUILD_BAN_REMOVE,
351    GUILD_EMOJIS_UPDATE,
352    GUILD_INTEGRATIONS_UPDATE,
353    GUILD_MEMBER_ADD,
354    GUILD_MEMBER_REMOVE,
355    GUILD_MEMBER_UPDATE,
356    GUILD_MEMBERS_CHUNK,
357    GUILD_ROLE_CREATE,
358    GUILD_ROLE_UPDATE,
359    GUILD_ROLE_DELETE,
360    MESSAGE_CREATE,
361    MESSAGE_UPDATE,
362    MESSAGE_DELETE,
363    MESSAGE_DELETE_BULK,
364    MESSAGE_REACTION_ADD,
365    MESSAGE_REACTION_REMOVE,
366    MESSAGE_REACTION_REMOVE_ALL,
367    PRESENCE_UPDATE,
368    PRESENCES_REPLACE,
369    TYPING_START,
370    USER_UPDATE,
371    VOICE_STATE_UPDATE,
372    VOICE_SERVER_UPDATE,
373    WEBHOOKS_UPDATE
374}
375
376impl Display for GatewayEvent {
377    fn fmt(&self, f: &mut Formatter) -> FmtResult {
378        write!(f, "{:?}", self)
379    }
380}
381/// A set of possible Discord gateway opcodes.
382#[derive(Serialize_repr, Deserialize_repr, Debug, Clone)]
383#[repr(u8)]
384pub enum Opcodes {
385    /// Dispatches a gateway event.
386    Dispatch,
387    /// Used for sending ping and heartbeats.
388    Heartbeat,
389    /// Used for obtaining a client handshake.
390    Identify,
391    /// Used to update the shard's status.
392    StatusUpdate,
393    /// Used to join and leave voice channels.
394    VoiceStatusUpdate,
395    /// Used to resume a closed connection.
396    Resume = 6,
397    /// Tells clients to reconnect to the gateway.
398    Reconnect,
399    /// used to request guild members.
400    RequestGuildMembers,
401    /// Used to notify the client of an invlaid session.
402    InvalidSession,
403    /// Sent immediately after connecting, contains heartbeat information.
404    Hello,
405    /// Sent immediately after receiving a heartbeat.
406    HeartbeatAck
407}
408
409/// Codes that denote the cause of the gateway closing.
410#[derive(Debug, Copy, Deserialize_repr, Clone)]
411#[repr(u16)]
412pub enum CloseCodes {
413    /// The cause of the error is unknown.
414    UnknownError = 4000,
415    /// The opcode or the payload for an opcode sent was invalid.
416    UnknownOpcode,
417    /// An invalid payload was sent.
418    DecodeError,
419    /// A payload was sent prior to identifying.
420    NotAuthenticated,
421    /// The token sent with the payload was invalid.
422    AuthenticationFailed,
423    /// More than one identify payload was sent.
424    AlreadyAuthenticated,
425    /// The sequence sent when resuming the session was invalid.
426    InvalidSeq,
427    /// A ratelimit caused by sending payloads too quickly.
428    Ratelimited,
429    /// The session timed out, a reconnect is required.
430    SessionTimeout,
431    /// An invalid shard was sent when identifying.
432    InvalidShard,
433    /// The session would have had too many guilds, which indicated that sharding is required.
434    ShardingRequired,
435}