1use serde::{Deserialize, Serialize, ser::SerializeStruct};
4use serde_json::Value;
5use std::{collections::HashMap, time::Duration};
6use uuid::Uuid;
7
8use crate::{errors::InnerParsingError, utils::filter_none_string};
9
10#[derive(Debug, Serialize)]
11pub(crate) struct ActivityCommand {
12 cmd: String,
13 args: ActivityCommandArgs,
14 nonce: String,
15}
16
17impl ActivityCommand {
18 pub fn new_with(activity: Option<ActivityPayload>) -> Self {
19 Self {
20 cmd: "SET_ACTIVITY".to_string(),
21 args: ActivityCommandArgs {
22 pid: std::process::id(),
23 activity,
24 },
25 nonce: Uuid::new_v4().to_string(),
26 }
27 }
28
29 pub fn to_json(&self) -> Result<String, InnerParsingError> {
30 serde_json::to_string(self).map_err(InnerParsingError::SerializeFailed)
31 }
32}
33
34#[derive(Debug, Serialize)]
35struct ActivityCommandArgs {
36 pid: u32,
37 activity: Option<ActivityPayload>,
38}
39
40#[derive(Debug, Serialize)]
44pub(crate) struct ActivityPayload {
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub name: Option<String>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub r#type: Option<u8>,
49 pub created_at: u64,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub instance: Option<bool>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub status_display_type: Option<u8>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub details: Option<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub details_url: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub state: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub state_url: Option<String>,
62 pub timestamps: TimestampPayload,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub assets: Option<AssetsPayload>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub buttons: Option<Vec<ButtonPayload>>,
67}
68
69#[derive(Debug)]
70pub(crate) struct AssetsPayload {
71 pub large_image: Option<String>,
72 pub large_url: Option<String>,
73 pub large_text: Option<String>,
74 pub small_image: Option<String>,
75 pub small_text: Option<String>,
76 pub small_url: Option<String>,
77}
78
79impl Serialize for AssetsPayload {
82 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
83 where
84 S: serde::Serializer,
85 {
86 let mut state = serializer.serialize_struct("AssetsPayload", 6)?;
87
88 if let Some(v) = &self.large_image {
89 state.serialize_field("large_image", v)?;
90
91 if let Some(v) = &self.large_text {
92 state.serialize_field("large_text", v)?;
93 }
94 if let Some(v) = &self.large_url {
95 state.serialize_field("large_url", v)?;
96 }
97 }
98
99 if let Some(v) = &self.small_image {
100 state.serialize_field("small_image", v)?;
101
102 if let Some(v) = &self.small_text {
103 state.serialize_field("small_text", v)?;
104 }
105 if let Some(v) = &self.small_url {
106 state.serialize_field("small_url", v)?;
107 }
108 }
109
110 state.end()
111 }
112}
113
114#[derive(Debug, Serialize)]
115pub(crate) struct ButtonPayload {
116 pub label: String,
117 pub url: String,
118}
119
120#[derive(Debug, Serialize)]
121pub(crate) struct TimestampPayload {
122 pub start: u64,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub end: Option<u64>,
125}
126
127#[derive(Debug, Deserialize)]
129pub(crate) struct ReadyRPCFrame {
130 pub cmd: Option<String>,
131 pub evt: Option<String>,
132 pub data: Option<ReadyData>,
133}
134
135#[derive(Debug, Deserialize)]
137pub(crate) struct DynamicRPCFrame {
138 #[allow(unused)]
139 pub cmd: Option<String>,
140 pub evt: Option<String>,
141 #[allow(unused)]
142 pub nonce: Option<String>,
143 pub data: Option<Value>,
144}
145
146#[derive(Debug)]
147pub(crate) enum IPCCommand {
148 SetActivity {
149 activity: Box<Activity>,
150 },
151 ClearActivity,
152 Close {
153 done: tokio::sync::oneshot::Sender<()>,
154 },
155}
156
157#[derive(Debug, Clone, Deserialize)]
162pub struct ActivityResponseData {
163 pub application_id: String,
164 pub platform: String,
165 pub name: String,
166 pub metadata: Value,
167}
168
169#[derive(Debug, Clone, Deserialize)]
171pub struct ReadyData {
172 pub user: DiscordUser,
173}
174
175#[derive(Debug, Clone, Deserialize)]
177pub struct DiscordUser {
178 pub id: String,
179 pub username: String,
180 pub global_name: Option<String>,
181 pub discriminator: Option<String>,
182 pub avatar: Option<String>,
183 pub avatar_decoration_data: Option<Value>,
184 pub bot: bool,
185 pub flags: Option<u64>,
186 pub premium_type: Option<u64>,
187}
188
189#[repr(u8)]
191#[derive(Clone, Debug, Eq, PartialEq, Copy)]
192pub enum ActivityType {
193 Playing = 0,
194 Listening = 2,
195 Watching = 3,
196 Competing = 5,
197}
198
199impl From<ActivityType> for u8 {
200 fn from(value: ActivityType) -> Self {
201 value as u8
202 }
203}
204
205#[repr(u8)]
207#[derive(Clone, Debug, Eq, PartialEq, Copy)]
208pub enum StatusDisplayType {
209 Name = 0,
210 Details = 2,
211 State = 1,
212}
213
214impl From<StatusDisplayType> for u8 {
215 fn from(value: StatusDisplayType) -> Self {
216 value as u8
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct Activity {
223 pub(crate) name: Option<String>,
224 pub(crate) activity_type: Option<ActivityType>,
225 pub(crate) status_display_type: Option<StatusDisplayType>,
226 pub(crate) details: Option<String>,
227 pub(crate) details_url: Option<String>,
228 pub(crate) state: Option<String>,
229 pub(crate) state_url: Option<String>,
230 pub(crate) instance: Option<bool>,
231 pub(crate) duration: Option<Duration>,
232 pub(crate) large_image: Option<String>,
233 pub(crate) large_text: Option<String>,
234 pub(crate) large_url: Option<String>,
235 pub(crate) small_image: Option<String>,
236 pub(crate) small_text: Option<String>,
237 pub(crate) small_url: Option<String>,
238 pub(crate) buttons: Option<HashMap<String, String>>,
239}
240
241impl Activity {
242 #[must_use]
244 #[allow(clippy::new_ret_no_self)]
245 pub fn new() -> ActivityBuilder {
246 ActivityBuilder::default()
247 }
248}
249
250impl Default for Activity {
251 fn default() -> Self {
257 Self {
258 name: None,
259 activity_type: None,
260 status_display_type: None,
261 details: None,
262 details_url: None,
263 state: None,
264 state_url: None,
265 instance: None,
266 duration: None,
267 large_image: None,
268 large_text: None,
269 large_url: None,
270 small_image: None,
271 small_text: None,
272 small_url: None,
273 buttons: None,
274 }
275 }
276}
277
278#[derive(Default)]
281pub struct ActivityBuilder {
282 name: Option<String>,
283 activity_type: Option<ActivityType>,
284 status_display_type: Option<StatusDisplayType>,
285 instance: Option<bool>,
286 details: Option<String>,
287 details_url: Option<String>,
288 state: Option<String>,
289 state_url: Option<String>,
290 duration: Option<Duration>,
291 large_image: Option<String>,
292 large_text: Option<String>,
293 large_url: Option<String>,
294 small_image: Option<String>,
295 small_text: Option<String>,
296 small_url: Option<String>,
297 buttons: Option<HashMap<String, String>>,
298}
299
300impl ActivityBuilder {
301 pub fn name(mut self, text: impl Into<String>) -> Self {
303 self.name = filter_none_string(text);
304 self
305 }
306
307 #[must_use]
309 pub fn activity_type(mut self, r#type: ActivityType) -> Self {
310 self.activity_type = Some(r#type);
311 self
312 }
313
314 pub fn details(mut self, text: impl Into<String>) -> Self {
316 self.details = filter_none_string(text);
317 self
318 }
319
320 pub fn details_url(mut self, url: impl Into<String>) -> Self {
322 self.details_url = filter_none_string(url);
323 self
324 }
325
326 pub fn state(mut self, text: impl Into<String>) -> Self {
328 self.state = filter_none_string(text);
329 self
330 }
331
332 pub fn state_url(mut self, url: impl Into<String>) -> Self {
334 self.state_url = filter_none_string(url);
335 self
336 }
337
338 #[must_use]
340 pub fn set_as_instance(mut self) -> Self {
341 self.instance = Some(true);
342 self
343 }
344
345 #[must_use]
347 pub fn status_display_type(mut self, r#type: StatusDisplayType) -> Self {
348 self.status_display_type = Some(r#type);
349 self
350 }
351
352 #[must_use]
354 pub fn duration(mut self, duration: Duration) -> Self {
355 self.duration = Some(duration);
356 self
357 }
358
359 pub fn add_button(mut self, label: impl Into<String>, url: impl Into<String>) -> Self {
364 if let Some(btns) = &mut self.buttons {
365 btns.insert(label.into(), url.into());
366 } else {
367 let mut btns: HashMap<String, String> = HashMap::new();
368 btns.insert(label.into(), url.into());
369 self.buttons = Some(btns);
370 };
371
372 self
373 }
374
375 pub fn large_image(mut self, key: impl Into<String>) -> Self {
377 self.large_image = Some(key.into());
378 self
379 }
380
381 pub fn large_text(mut self, text: impl Into<String>) -> Self {
383 self.large_text = Some(text.into());
384 self
385 }
386
387 pub fn large_url(mut self, url: impl Into<String>) -> Self {
389 self.large_url = Some(url.into());
390 self
391 }
392
393 pub fn small_image(mut self, key: impl Into<String>) -> Self {
395 self.small_image = Some(key.into());
396 self
397 }
398
399 pub fn small_text(mut self, text: impl Into<String>) -> Self {
401 self.small_text = Some(text.into());
402 self
403 }
404
405 pub fn small_url(mut self, url: impl Into<String>) -> Self {
407 self.small_url = Some(url.into());
408 self
409 }
410
411 #[must_use]
413 pub fn build(self) -> Activity {
414 Activity {
415 name: self.name,
416 activity_type: self.activity_type,
417 status_display_type: self.status_display_type,
418 details: self.details,
419 details_url: self.details_url,
420 state: self.state,
421 state_url: self.state_url,
422 instance: self.instance,
423 duration: self.duration,
424 large_image: self.large_image,
425 large_text: self.large_text,
426 large_url: self.large_url,
427 small_image: self.small_image,
428 small_text: self.small_text,
429 small_url: self.small_url,
430 buttons: self.buttons,
431 }
432 }
433}