Skip to main content

filthy_rich/
types.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::time::Duration;
4use uuid::Uuid;
5
6#[derive(Debug, Deserialize, Serialize)]
7pub struct IPCActivityCmd {
8    cmd: String,
9    args: IPCActivityCmdArgs,
10    nonce: String,
11}
12impl IPCActivityCmd {
13    pub fn new_with(activity: Option<ActivityPayload>) -> Self {
14        Self {
15            cmd: "SET_ACTIVITY".to_string(),
16            args: IPCActivityCmdArgs {
17                pid: std::process::id(),
18                activity,
19            },
20            nonce: Uuid::new_v4().to_string(),
21        }
22    }
23
24    pub fn to_json(&self) -> Result<String> {
25        serde_json::to_string(self).context("Failed to serialize IPC activity command.")
26    }
27}
28
29#[derive(Debug, Deserialize, Serialize)]
30pub struct IPCActivityCmdArgs {
31    pid: u32,
32    activity: Option<ActivityPayload>,
33}
34
35#[derive(Debug, Deserialize, Serialize)]
36pub struct ActivityPayload {
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub(crate) details: Option<String>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub(crate) state: Option<String>,
41    pub(crate) timestamps: TimestampPayload,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub(crate) assets: Option<AssetsPayload>,
44}
45
46#[derive(Debug, Deserialize, Serialize)]
47pub struct AssetsPayload {
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub(crate) large_image: Option<String>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub(crate) large_text: Option<String>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub(crate) small_image: Option<String>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub(crate) small_text: Option<String>,
56}
57
58#[derive(Debug, Deserialize, Serialize)]
59pub struct TimestampPayload {
60    pub(crate) start: u64,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub(crate) end: Option<u64>,
63}
64
65#[derive(Debug, Deserialize)]
66pub struct RpcFrame {
67    pub cmd: Option<String>,
68    pub evt: Option<String>,
69    pub data: Option<ReadyData>,
70}
71
72#[derive(Debug)]
73pub enum IPCCommand {
74    SetActivity { activity: Activity },
75    ClearActivity,
76    Close,
77}
78
79/// An object which is passed during READY capture from Discord IPC instance.
80#[derive(Debug, Clone, Deserialize)]
81pub struct ReadyData {
82    pub user: DiscordUser,
83}
84
85/// Represents a Discord user.
86#[derive(Debug, Clone, Deserialize)]
87pub struct DiscordUser {
88    pub id: String,
89    pub username: String,
90    pub global_name: Option<String>,
91    pub discriminator: Option<String>,
92    pub avatar: Option<String>,
93    pub avatar_decoration_data: Option<serde_json::Value>,
94    pub bot: bool,
95    pub flags: Option<u64>,
96    pub premium_type: Option<u64>,
97}
98
99/// Represents a Discord Rich Presence activity.
100#[derive(Debug, Clone)]
101pub struct Activity {
102    pub(crate) details: Option<String>,
103    pub(crate) state: Option<String>,
104    pub(crate) duration: Option<Duration>,
105    pub(crate) large_image_key: Option<String>,
106    pub(crate) large_image_text: Option<String>,
107    pub(crate) small_image_key: Option<String>,
108    pub(crate) small_image_text: Option<String>,
109}
110
111pub struct ActivityBuilder;
112
113/// A Rich Presence activity with top text and possibly more attributes.
114/// [`ActivityWithDetails::build`] needs to be called on it in order to
115/// turn it into a proper [`Activity`] instance.
116pub struct ActivityWithDetails {
117    details: String,
118    state: Option<String>,
119    duration: Option<Duration>,
120    large_image_key: Option<String>,
121    large_image_text: Option<String>,
122    small_image_key: Option<String>,
123    small_image_text: Option<String>,
124}
125
126impl Activity {
127    pub fn new() -> ActivityBuilder {
128        ActivityBuilder
129    }
130
131    /// Initializes a Rich Presence activity without any content; useful for small apps.
132    pub fn build_empty() -> Self {
133        Self {
134            details: None,
135            state: None,
136            duration: None,
137            large_image_key: None,
138            large_image_text: None,
139            small_image_key: None,
140            small_image_text: None,
141        }
142    }
143}
144
145impl ActivityBuilder {
146    /// Top text for your activity.
147    pub fn details(self, details: impl Into<String>) -> ActivityWithDetails {
148        ActivityWithDetails {
149            details: details.into(),
150            state: None,
151            duration: None,
152            large_image_key: None,
153            large_image_text: None,
154            small_image_key: None,
155            small_image_text: None,
156        }
157    }
158}
159
160impl ActivityWithDetails {
161    /// Bottom text for your activity.
162    pub fn state(mut self, state: impl Into<String>) -> Self {
163        let state: String = state.into();
164
165        if !state.is_empty() {
166            self.state = Some(state);
167        }
168
169        self
170    }
171
172    /// Countdown duration for your activity.
173    pub fn duration(mut self, duration: Duration) -> Self {
174        self.duration = Some(duration);
175        self
176    }
177
178    /// Large image for your activity (e.g., game icon).
179    pub fn large_image(mut self, key: impl Into<String>, text: Option<impl Into<String>>) -> Self {
180        self.large_image_key = Some(key.into());
181        self.large_image_text = text.map(|t| t.into());
182        self
183    }
184
185    /// Small image for your activity (e.g., status icon).
186    pub fn small_image(mut self, key: impl Into<String>, text: Option<impl Into<String>>) -> Self {
187        self.small_image_key = Some(key.into());
188        self.small_image_text = text.map(|t| t.into());
189        self
190    }
191
192    /// Parses the state of this builder into a usable [`Activity`] for you to pass through either [`DiscordIPC::set_activity`]
193    /// or [`DiscordIPCSync::set_activity`].
194    pub fn build(self) -> Activity {
195        Activity {
196            details: Some(self.details),
197            state: self.state,
198            duration: self.duration,
199            large_image_key: self.large_image_key,
200            large_image_text: self.large_image_text,
201            small_image_key: self.small_image_key,
202            small_image_text: self.small_image_text,
203        }
204    }
205}