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