1use crate::opcode::OpCode;
7use serde::{Deserialize, Serialize};
8use titanium_model::{Application, Intents, UnavailableGuild, User};
9
10#[derive(Debug, Deserialize)]
14pub struct RawGatewayPayload<'a> {
15 pub op: OpCode,
17
18 #[cfg(not(feature = "simd"))]
20 #[serde(borrow)]
21 pub d: Option<&'a serde_json::value::RawValue>,
22
23 #[cfg(feature = "simd")]
25 pub d: Option<titanium_model::json::Value>,
26
27 #[allow(dead_code)]
29 pub s: Option<u64>,
30
31 #[allow(dead_code)]
33 pub t: Option<String>,
34
35 #[cfg(feature = "simd")]
36 #[serde(skip)]
37 pub _marker: std::marker::PhantomData<&'a ()>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct GatewayPayload<D> {
43 pub op: OpCode,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub d: Option<D>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub s: Option<u64>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub t: Option<String>,
57}
58
59impl<D: Serialize> GatewayPayload<D> {
60 pub fn new(op: OpCode, data: D) -> Self {
62 Self {
63 op,
64 d: Some(data),
65 s: None,
66 t: None,
67 }
68 }
69
70 pub fn opcode_only(op: OpCode) -> GatewayPayload<()> {
72 GatewayPayload {
73 op,
74 d: None,
75 s: None,
76 t: None,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Deserialize)]
89pub struct HelloPayload {
90 pub heartbeat_interval: u64,
92}
93
94#[derive(Debug, Clone, Serialize)]
102pub struct IdentifyPayload<'a> {
103 pub token: std::borrow::Cow<'a, str>,
105
106 pub intents: Intents,
108
109 pub properties: ConnectionProperties<'a>,
111
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub compress: Option<bool>,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub large_threshold: Option<u8>,
119
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub shard: Option<[u16; 2]>,
123
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub presence: Option<PresenceUpdate>,
127}
128
129impl<'a> IdentifyPayload<'a> {
130 pub fn new(token: impl Into<std::borrow::Cow<'a, str>>, intents: Intents) -> Self {
132 Self {
133 token: token.into(),
134 intents,
135 properties: ConnectionProperties::default(),
136 compress: None,
137 large_threshold: Some(250),
138 shard: None,
139 presence: None,
140 }
141 }
142
143 pub fn with_shard(mut self, shard_id: u16, total_shards: u16) -> Self {
145 self.shard = Some([shard_id, total_shards]);
146 self
147 }
148}
149
150#[derive(Debug, Clone, Serialize)]
152pub struct ConnectionProperties<'a> {
153 pub os: std::borrow::Cow<'a, str>,
155
156 pub browser: std::borrow::Cow<'a, str>,
158
159 pub device: std::borrow::Cow<'a, str>,
161}
162
163impl<'a> Default for ConnectionProperties<'a> {
164 fn default() -> Self {
165 Self {
166 os: std::borrow::Cow::Owned(std::env::consts::OS.to_string()),
167 browser: std::borrow::Cow::Borrowed("titanium-rs"),
168 device: std::borrow::Cow::Borrowed("titanium-rs"),
169 }
170 }
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct PresenceUpdate {
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub since: Option<u64>,
179
180 pub activities: Vec<Activity>,
182
183 pub status: Status,
185
186 pub afk: bool,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct Activity {
193 pub name: String,
195
196 #[serde(rename = "type")]
198 pub activity_type: ActivityType,
199
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub url: Option<String>,
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
207#[serde(from = "u8", into = "u8")]
208pub enum ActivityType {
209 Playing,
211 Streaming,
213 Listening,
215 Watching,
217 Custom,
219 Competing,
221}
222
223impl From<u8> for ActivityType {
224 fn from(value: u8) -> Self {
225 match value {
226 0 => ActivityType::Playing,
227 1 => ActivityType::Streaming,
228 2 => ActivityType::Listening,
229 3 => ActivityType::Watching,
230 4 => ActivityType::Custom,
231 5 => ActivityType::Competing,
232 _ => ActivityType::Playing,
233 }
234 }
235}
236
237impl From<ActivityType> for u8 {
238 fn from(value: ActivityType) -> Self {
239 match value {
240 ActivityType::Playing => 0,
241 ActivityType::Streaming => 1,
242 ActivityType::Listening => 2,
243 ActivityType::Watching => 3,
244 ActivityType::Custom => 4,
245 ActivityType::Competing => 5,
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
252#[serde(rename_all = "lowercase")]
253pub enum Status {
254 #[default]
256 Online,
257 Dnd,
259 Idle,
261 Invisible,
263 Offline,
265}
266
267#[derive(Debug, Clone, Serialize)]
273pub struct ResumePayload<'a> {
274 pub token: std::borrow::Cow<'a, str>,
276
277 pub session_id: std::borrow::Cow<'a, str>,
279
280 pub seq: u64,
282}
283
284#[derive(Debug, Clone, Deserialize)]
290pub struct ReadyEvent<'a> {
291 pub v: u8,
293
294 #[serde(borrow)]
296 pub user: User<'a>,
297
298 pub guilds: Vec<UnavailableGuild>,
300
301 pub session_id: String,
303
304 pub resume_gateway_url: String,
306
307 #[serde(default)]
309 pub shard: Option<[u16; 2]>,
310
311 pub application: Application,
313}
314
315pub fn create_heartbeat_payload(sequence: Option<u64>) -> String {
323 match sequence {
324 Some(seq) => format!(r#"{{"op":1,"d":{}}}"#, seq),
325 None => r#"{"op":1,"d":null}"#.to_string(),
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_hello_payload() {
335 let json = r#"{"heartbeat_interval": 41250}"#;
336 let payload: HelloPayload = serde_json::from_str(json).unwrap();
337 assert_eq!(payload.heartbeat_interval, 41250);
338 }
339
340 #[test]
341 fn test_identify_serialization() {
342 let identify =
343 IdentifyPayload::new("test_token", Intents::GUILDS | Intents::GUILD_MESSAGES)
344 .with_shard(0, 1);
345
346 let json = serde_json::to_string(&identify).unwrap();
347 assert!(json.contains("test_token"));
348 assert!(json.contains("shard"));
349 }
350
351 #[test]
352 fn test_heartbeat_payload() {
353 let payload = create_heartbeat_payload(Some(42));
354 assert_eq!(payload, r#"{"op":1,"d":42}"#);
355
356 let payload_null = create_heartbeat_payload(None);
357 assert_eq!(payload_null, r#"{"op":1,"d":null}"#);
358 }
359}