twilight_model/gateway/presence/
mod.rs

1pub mod activity_button;
2
3mod activity;
4mod activity_assets;
5mod activity_emoji;
6mod activity_flags;
7mod activity_party;
8mod activity_secrets;
9mod activity_timestamps;
10mod activity_type;
11mod client_status;
12mod minimal_activity;
13mod status;
14
15pub use self::{
16    activity::Activity, activity_assets::ActivityAssets, activity_button::ActivityButton,
17    activity_emoji::ActivityEmoji, activity_flags::ActivityFlags, activity_party::ActivityParty,
18    activity_secrets::ActivitySecrets, activity_timestamps::ActivityTimestamps,
19    activity_type::ActivityType, client_status::ClientStatus, minimal_activity::MinimalActivity,
20    status::Status,
21};
22
23use crate::{
24    id::{
25        marker::{GuildMarker, UserMarker},
26        Id,
27    },
28    user::User,
29};
30use serde::{
31    de::{
32        value::MapAccessDeserializer, DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor,
33    },
34    Deserialize, Serialize,
35};
36use std::fmt::{Formatter, Result as FmtResult};
37
38#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
39pub struct Presence {
40    #[serde(default)]
41    pub activities: Vec<Activity>,
42    pub client_status: ClientStatus,
43    pub guild_id: Id<GuildMarker>,
44    pub status: Status,
45    pub user: UserOrId,
46}
47
48#[allow(clippy::large_enum_variant)]
49#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
50#[serde(untagged)]
51pub enum UserOrId {
52    User(User),
53    UserId { id: Id<UserMarker> },
54}
55
56impl UserOrId {
57    /// ID of the inner object.
58    pub const fn id(&self) -> Id<UserMarker> {
59        match self {
60            UserOrId::User(u) => u.id,
61            UserOrId::UserId { id } => *id,
62        }
63    }
64}
65
66#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
67pub(crate) struct PresenceIntermediary {
68    #[serde(default)]
69    pub activities: Vec<Activity>,
70    pub client_status: ClientStatus,
71    pub guild_id: Option<Id<GuildMarker>>,
72    pub nick: Option<String>,
73    pub status: Status,
74    pub user: UserOrId,
75}
76
77impl PresenceIntermediary {
78    /// Inject guild ID into presence if not already present.
79    pub fn into_presence(self, guild_id: Id<GuildMarker>) -> Presence {
80        Presence {
81            activities: self.activities,
82            client_status: self.client_status,
83            guild_id: self.guild_id.unwrap_or(guild_id),
84            status: self.status,
85            user: self.user,
86        }
87    }
88}
89
90struct PresenceVisitor(Id<GuildMarker>);
91
92impl<'de> Visitor<'de> for PresenceVisitor {
93    type Value = Presence;
94
95    fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
96        f.write_str("Presence struct")
97    }
98
99    fn visit_map<M: MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
100        let deser = MapAccessDeserializer::new(map);
101        let presence = PresenceIntermediary::deserialize(deser)?;
102
103        Ok(Presence {
104            activities: presence.activities,
105            client_status: presence.client_status,
106            guild_id: presence.guild_id.unwrap_or(self.0),
107            status: presence.status,
108            user: presence.user,
109        })
110    }
111}
112
113#[derive(Clone, Debug, Eq, PartialEq)]
114pub struct PresenceDeserializer(Id<GuildMarker>);
115
116impl PresenceDeserializer {
117    /// Create a new deserializer for a presence when you know the guild ID but
118    /// the payload probably doesn't contain it.
119    pub const fn new(guild_id: Id<GuildMarker>) -> Self {
120        Self(guild_id)
121    }
122}
123
124impl<'de> DeserializeSeed<'de> for PresenceDeserializer {
125    type Value = Presence;
126
127    fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
128        deserializer.deserialize_map(PresenceVisitor(self.0))
129    }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq)]
133pub struct PresenceListDeserializer(Id<GuildMarker>);
134
135impl PresenceListDeserializer {
136    /// Create a new deserializer for a map of presences when you know the
137    /// Guild ID but the payload probably doesn't contain it.
138    pub const fn new(guild_id: Id<GuildMarker>) -> Self {
139        Self(guild_id)
140    }
141}
142
143struct PresenceListDeserializerVisitor(Id<GuildMarker>);
144
145impl<'de> Visitor<'de> for PresenceListDeserializerVisitor {
146    type Value = Vec<Presence>;
147
148    fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
149        f.write_str("a sequence of presences")
150    }
151
152    fn visit_seq<S: SeqAccess<'de>>(self, mut seq: S) -> Result<Self::Value, S::Error> {
153        let mut list = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity);
154
155        while let Some(presence) = seq.next_element_seed(PresenceDeserializer(self.0))? {
156            list.push(presence);
157        }
158
159        Ok(list)
160    }
161}
162
163impl<'de> DeserializeSeed<'de> for PresenceListDeserializer {
164    type Value = Vec<Presence>;
165
166    fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
167        deserializer.deserialize_any(PresenceListDeserializerVisitor(self.0))
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::{
174        Activity, ActivityEmoji, ActivityType, ClientStatus, Presence, PresenceListDeserializer,
175        Status, UserOrId,
176    };
177    use crate::id::Id;
178    use serde::de::DeserializeSeed;
179    use serde_json::Deserializer;
180    use serde_test::Token;
181
182    #[test]
183    #[allow(clippy::too_many_lines)]
184    fn custom() {
185        let activity = Activity {
186            application_id: None,
187            assets: None,
188            buttons: Vec::new(),
189            created_at: Some(1_571_048_061_237),
190            details: None,
191            flags: None,
192            id: Some("aaaaaaaaaaaaaaaa".to_owned()),
193            instance: None,
194            kind: ActivityType::Custom,
195            name: "foo".to_owned(),
196            emoji: Some(ActivityEmoji {
197                name: "Test".to_string(),
198                id: None,
199                animated: None,
200            }),
201            party: None,
202            secrets: None,
203            state: None,
204            timestamps: None,
205            url: None,
206        };
207        let value = Presence {
208            activities: vec![activity],
209            client_status: ClientStatus {
210                desktop: Some(Status::Online),
211                mobile: None,
212                web: None,
213            },
214            guild_id: Id::new(2),
215            status: Status::Online,
216            user: UserOrId::UserId { id: Id::new(1) },
217        };
218
219        serde_test::assert_de_tokens(
220            &value,
221            &[
222                Token::Struct {
223                    name: "Presence",
224                    len: 4,
225                },
226                Token::Str("user"),
227                Token::Struct {
228                    name: "UserOrId",
229                    len: 1,
230                },
231                Token::Str("id"),
232                Token::Str("1"),
233                Token::StructEnd,
234                Token::Str("guild_id"),
235                Token::NewtypeStruct { name: "Id" },
236                Token::Str("2"),
237                Token::Str("status"),
238                Token::Enum { name: "Status" },
239                Token::Str("online"),
240                Token::Unit,
241                Token::Str("client_status"),
242                Token::Struct {
243                    name: "ClientStatus",
244                    len: 3,
245                },
246                Token::Str("desktop"),
247                Token::Some,
248                Token::Enum { name: "Status" },
249                Token::Str("online"),
250                Token::Unit,
251                Token::Str("mobile"),
252                Token::None,
253                Token::Str("web"),
254                Token::None,
255                Token::StructEnd,
256                Token::Str("activities"),
257                Token::Seq { len: Some(1) },
258                Token::Struct {
259                    name: "Activity",
260                    len: 4,
261                },
262                Token::Str("type"),
263                Token::U8(4),
264                Token::Str("name"),
265                Token::Str("foo"),
266                Token::Str("emoji"),
267                Token::Some,
268                Token::Struct {
269                    name: "ActivityEmoji",
270                    len: 3,
271                },
272                Token::Str("name"),
273                Token::Str("Test"),
274                Token::Str("id"),
275                Token::None,
276                Token::Str("animated"),
277                Token::None,
278                Token::StructEnd,
279                Token::Str("id"),
280                Token::Some,
281                Token::Str("aaaaaaaaaaaaaaaa"),
282                Token::Str("created_at"),
283                Token::Some,
284                Token::U64(1_571_048_061_237),
285                Token::StructEnd,
286                Token::SeqEnd,
287                Token::StructEnd,
288            ],
289        );
290    }
291
292    // Test that presences through the deserializer are given a default guild ID
293    // if they have none.
294    //
295    // Can't test seeded deserializers with serde_test.
296    #[test]
297    fn presence_map_guild_id_default() {
298        let input = r#"[{
299            "user": {
300                "id": "1"
301            },
302            "status": "online",
303            "client_status": {
304                "desktop": "online"
305            },
306            "activities": []
307        }]"#;
308
309        let expected = Vec::from([Presence {
310            activities: vec![],
311            client_status: ClientStatus {
312                desktop: Some(Status::Online),
313                mobile: None,
314                web: None,
315            },
316            guild_id: Id::new(2),
317            status: Status::Online,
318            user: UserOrId::UserId { id: Id::new(1) },
319        }]);
320
321        let mut json_deserializer = Deserializer::from_str(input);
322        let deserializer = PresenceListDeserializer::new(Id::new(2));
323        let actual = deserializer.deserialize(&mut json_deserializer).unwrap();
324
325        assert_eq!(actual, expected);
326    }
327}