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#[derive(Debug, Clone, Deserialize)]
81pub struct ReadyData {
82 pub user: DiscordUser,
83}
84
85#[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#[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
113pub 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 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 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 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 pub fn duration(mut self, duration: Duration) -> Self {
174 self.duration = Some(duration);
175 self
176 }
177
178 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 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 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}