Skip to main content

filthy_rich/types/
payloads.rs

1//! Payload types which are used for immutable sends and receives. These do not need to be imported separately as the
2//! values are often already supplied to you via closures, and are clonable / can be debugged.
3//!
4use serde::{Deserialize, Serialize, ser::SerializeStruct};
5
6use crate::{
7    ds,
8    errors::InnerParsingError,
9    types::{ActivitySpec, ActivityType, StatusDisplayType},
10    utils::get_current_timestamp,
11};
12
13/// The assets payload for the activity. Usually contains the large image and the small image, and their respective
14/// subsets of information (i.e. URL and text).
15#[derive(Debug, Deserialize, Clone)]
16pub struct AssetsPayload {
17    pub(crate) large_image: Option<String>,
18    pub(crate) large_url: Option<String>,
19    pub(crate) large_text: Option<String>,
20    pub(crate) small_image: Option<String>,
21    pub(crate) small_text: Option<String>,
22    pub(crate) small_url: Option<String>,
23}
24
25impl AssetsPayload {
26    ds!(large_image, "The key for the large image.");
27    ds!(large_url, "The URL for the large image.");
28    ds!(large_text, "The hover text for the large image.");
29    ds!(small_image, "The key for the small image.");
30    ds!(small_url, "The URL for the small image.");
31    ds!(small_text, "The hover text for the small image.");
32}
33
34// redundant: already filtered during [`crate::types::ActivityBuilder::build`]
35impl Serialize for AssetsPayload {
36    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
37    where
38        S: serde::Serializer,
39    {
40        let mut state = serializer.serialize_struct("AssetsPayload", 6)?;
41
42        if let Some(v) = &self.large_image {
43            state.serialize_field("large_image", v)?;
44
45            if let Some(v) = &self.large_text {
46                state.serialize_field("large_text", v)?;
47            }
48            if let Some(v) = &self.large_url {
49                state.serialize_field("large_url", v)?;
50            }
51        }
52
53        if let Some(v) = &self.small_image {
54            state.serialize_field("small_image", v)?;
55
56            if let Some(v) = &self.small_text {
57                state.serialize_field("small_text", v)?;
58            }
59            if let Some(v) = &self.small_url {
60                state.serialize_field("small_url", v)?;
61            }
62        }
63
64        state.end()
65    }
66}
67
68/// Represents a button of the activity.
69#[derive(Debug, Serialize, Deserialize, Clone)]
70pub struct ButtonPayload {
71    pub(crate) label: String,
72    pub(crate) url: String,
73}
74
75impl ButtonPayload {
76    /// The label for the button.
77    #[must_use]
78    pub fn label(&self) -> &str {
79        self.label.as_ref()
80    }
81    /// The URL the button redirects to when clicked on.
82    #[must_use]
83    pub fn url(&self) -> &str {
84        self.url.as_str()
85    }
86}
87
88/// The timestamps (calculated with `UNIX_EPOCH`) for the activity.
89#[derive(Debug, Serialize, Deserialize, Clone)]
90pub struct TimestampPayload {
91    start: u64,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    end: Option<u64>,
94}
95
96impl TimestampPayload {
97    /// The starting timestamp for the activity.
98    #[must_use]
99    pub fn start(&self) -> u64 {
100        self.start
101    }
102    /// The end timestamp for the activity.
103    #[must_use]
104    pub fn end(&self) -> Option<u64> {
105        self.end
106    }
107}
108
109/// Represents an immutable activity which has been sent.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct ActivityPayload {
112    #[serde(skip_serializing_if = "Option::is_none")]
113    name: Option<String>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    r#type: Option<ActivityType>,
116    created_at: u64,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    instance: Option<bool>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    status_display_type: Option<StatusDisplayType>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    details: Option<String>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    details_url: Option<String>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    state: Option<String>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    state_url: Option<String>,
129    timestamps: TimestampPayload,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    assets: Option<AssetsPayload>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    buttons: Option<Vec<ButtonPayload>>,
134}
135
136impl ActivityPayload {
137    ds!(name, "The name for the activity.");
138
139    /// The type of the activity.
140    #[must_use]
141    pub fn activity_type(&self) -> Option<ActivityType> {
142        self.r#type
143    }
144    /// When the activity was created.
145    #[must_use]
146    pub fn created_at(&self) -> u64 {
147        self.created_at
148    }
149    /// Whether or not the activity is an instance.
150    #[must_use]
151    pub fn instance(&self) -> Option<bool> {
152        self.instance
153    }
154    /// Which element the activity displays as the status (primary text on members banner).
155    #[must_use]
156    pub fn status_display_type(&self) -> Option<StatusDisplayType> {
157        self.status_display_type
158    }
159
160    ds!(details, "The details (top text) for the activity.");
161    ds!(
162        details_url,
163        "The URL which the details field redirects to when clicked on."
164    );
165    ds!(
166        state,
167        "The state (usually the bottom text but could be on top) for the activity."
168    );
169    ds!(
170        state_url,
171        "The URL which the state redirects to when clicked on."
172    );
173
174    /// The timestamp payload object for the activity, containing the start and optionally the end timestamps.
175    #[must_use]
176    pub fn timestamps(&self) -> &TimestampPayload {
177        &self.timestamps
178    }
179    /// The assets payload object for the activity, containing all the detials of the
180    /// assets which were optionally sent with the activity.
181    #[must_use]
182    pub fn assets(&self) -> Option<&AssetsPayload> {
183        self.assets.as_ref()
184    }
185    /// The buttons for the activity. Each button is represented by a [`ButtonPayload`] object instance, and returned as a borrowed
186    /// vector of multiple [`ButtonPayload`] objects through this function.
187    #[must_use]
188    pub fn buttons(&self) -> Option<&Vec<ButtonPayload>> {
189        self.buttons.as_ref()
190    }
191
192    pub(crate) fn create(
193        value: ActivitySpec,
194        session_start: u64,
195    ) -> Result<Self, InnerParsingError> {
196        let current_t = get_current_timestamp()?;
197        let end_timestamp = value.duration.map(|d| current_t + d.as_secs());
198
199        Ok(Self {
200            name: value.name,
201            r#type: value.r#type,
202            created_at: current_t,
203            instance: value.instance,
204            status_display_type: value.status_display_type,
205            details: value.details,
206            details_url: value.details_url,
207            state: value.state,
208            state_url: value.state_url,
209            timestamps: TimestampPayload {
210                start: session_start,
211                end: end_timestamp,
212            },
213            assets: value.assets,
214            buttons: value.buttons,
215        })
216    }
217}