twilight_model/gateway/payload/incoming/
thread_members_update.rs

1use crate::{
2    channel::thread::member::{ThreadMember, ThreadMemberIntermediary},
3    id::{
4        marker::{ChannelMarker, GuildMarker, UserMarker},
5        Id,
6    },
7};
8use serde::{
9    de::{value::MapAccessDeserializer, MapAccess, Visitor},
10    Deserialize, Deserializer, Serialize,
11};
12use std::fmt::{Formatter, Result as FmtResult};
13
14#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
15pub struct ThreadMembersUpdate {
16    /// List of thread members.
17    ///
18    /// Includes the [`member`] key.
19    ///
20    /// [`member`]: ThreadMember::member
21    #[serde(default)]
22    pub added_members: Vec<ThreadMember>,
23    pub guild_id: Id<GuildMarker>,
24    pub id: Id<ChannelMarker>,
25    /// Number of members in the thread.
26    ///
27    /// This is an approximation and may not be accurate.
28    pub member_count: i32,
29    #[serde(default)]
30    pub removed_member_ids: Vec<Id<UserMarker>>,
31}
32
33impl<'de> Deserialize<'de> for ThreadMembersUpdate {
34    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
35        deserializer.deserialize_map(ThreadMembersUpdateVisitor)
36    }
37}
38
39#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
40struct ThreadMembersUpdateIntermediary {
41    /// [`ThreadMember`]s without the guild ID.
42    #[serde(default)]
43    pub added_members: Vec<ThreadMemberIntermediary>,
44    pub guild_id: Id<GuildMarker>,
45    pub id: Id<ChannelMarker>,
46    pub member_count: i32,
47    #[serde(default)]
48    pub removed_member_ids: Vec<Id<UserMarker>>,
49}
50
51impl ThreadMembersUpdateIntermediary {
52    fn into_thread_members_update(self) -> ThreadMembersUpdate {
53        let guild_id = self.guild_id;
54        let added_members = self
55            .added_members
56            .into_iter()
57            .map(|tm| tm.into_thread_member(guild_id))
58            .collect();
59
60        ThreadMembersUpdate {
61            added_members,
62            guild_id,
63            id: self.id,
64            member_count: self.member_count,
65            removed_member_ids: self.removed_member_ids,
66        }
67    }
68}
69
70struct ThreadMembersUpdateVisitor;
71
72impl<'de> Visitor<'de> for ThreadMembersUpdateVisitor {
73    type Value = ThreadMembersUpdate;
74
75    fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
76        f.write_str("struct ThreadMembersUpdate")
77    }
78
79    fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
80        let deser = MapAccessDeserializer::new(map);
81        let update = ThreadMembersUpdateIntermediary::deserialize(deser)?;
82
83        Ok(update.into_thread_members_update())
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::ThreadMembersUpdate;
90    use crate::{
91        channel::thread::ThreadMember,
92        gateway::presence::{
93            Activity, ActivityEmoji, ActivityType, ClientStatus, Presence, Status, UserOrId,
94        },
95        guild::{Member, MemberFlags},
96        id::Id,
97        test::image_hash,
98        user::User,
99        util::Timestamp,
100    };
101    use serde_test::Token;
102    use std::str::FromStr;
103
104    #[allow(clippy::too_many_lines)]
105    #[test]
106    fn thread_members_update() {
107        const JOIN_TIMESTAMP: &str = "2015-04-26T06:26:56.936000+00:00";
108        const PREMIUM_SINCE: &str = "2021-03-16T14:29:19.046000+00:00";
109
110        let joined_at = Some(Timestamp::from_str(JOIN_TIMESTAMP).expect("timestamp error"));
111        let premium_since = Timestamp::from_str(PREMIUM_SINCE).expect("timestamp error");
112        let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
113
114        let member = Member {
115            avatar: Some(image_hash::AVATAR),
116            communication_disabled_until: None,
117            deaf: false,
118            flags,
119            joined_at,
120            mute: true,
121            nick: Some("twilight".to_owned()),
122            pending: false,
123            premium_since: Some(premium_since),
124            roles: Vec::new(),
125            user: User {
126                accent_color: None,
127                avatar: None,
128                avatar_decoration: None,
129                avatar_decoration_data: None,
130                banner: None,
131                bot: false,
132                discriminator: 1,
133                email: None,
134                flags: None,
135                global_name: Some("test".to_owned()),
136                id: Id::new(3),
137                locale: None,
138                mfa_enabled: None,
139                name: "twilight".to_owned(),
140                premium_type: None,
141                public_flags: None,
142                system: None,
143                verified: None,
144            },
145        };
146
147        let activity = Activity {
148            application_id: None,
149            assets: None,
150            buttons: Vec::new(),
151            created_at: Some(1_571_048_061_237),
152            details: None,
153            flags: None,
154            id: Some("aaaaaaaaaaaaaaaa".to_owned()),
155            instance: None,
156            kind: ActivityType::Custom,
157            name: "foo".to_owned(),
158            emoji: Some(ActivityEmoji {
159                name: "Test".to_string(),
160                id: None,
161                animated: None,
162            }),
163            party: None,
164            secrets: None,
165            state: None,
166            timestamps: None,
167            url: None,
168        };
169        let presence = Presence {
170            activities: vec![activity],
171            client_status: ClientStatus {
172                desktop: Some(Status::Online),
173                mobile: None,
174                web: None,
175            },
176            guild_id: Id::new(2),
177            status: Status::Online,
178            user: UserOrId::UserId { id: Id::new(3) },
179        };
180
181        let join_timestamp = Timestamp::from_str(JOIN_TIMESTAMP).expect("timestamp error");
182
183        let value = ThreadMembersUpdate {
184            added_members: vec![ThreadMember {
185                flags: 1,
186                id: Some(Id::new(123)),
187                join_timestamp,
188                member: Some(member),
189                presence: Some(presence),
190                user_id: Some(Id::new(3)),
191            }],
192            guild_id: Id::new(2),
193            id: Id::new(4),
194            member_count: 8,
195            removed_member_ids: vec![],
196        };
197
198        serde_test::assert_de_tokens(
199            &value,
200            &[
201                Token::Struct {
202                    name: "ThreadMemberUpdate",
203                    len: 6,
204                },
205                Token::Str("added_members"),
206                Token::Seq { len: Some(1) },
207                Token::Struct {
208                    name: "ThreadMemberIntermediary",
209                    len: 6,
210                },
211                Token::Str("flags"),
212                Token::U64(1),
213                Token::Str("id"),
214                Token::Some,
215                Token::NewtypeStruct { name: "ChannelId" },
216                Token::Str("123"),
217                Token::Str("join_timestamp"),
218                Token::Str(JOIN_TIMESTAMP),
219                Token::Str("member"),
220                Token::Some,
221                Token::Struct {
222                    name: "Member",
223                    len: 12,
224                },
225                Token::Str("avatar"),
226                Token::Some,
227                Token::Str(image_hash::AVATAR_INPUT),
228                Token::Str("communication_disabled_until"),
229                Token::None,
230                Token::Str("deaf"),
231                Token::Bool(false),
232                Token::Str("flags"),
233                Token::U64(flags.bits()),
234                Token::Str("guild_id"),
235                Token::NewtypeStruct { name: "GuildId" },
236                Token::Str("1"),
237                Token::Str("joined_at"),
238                Token::Some,
239                Token::Str(JOIN_TIMESTAMP),
240                Token::Str("mute"),
241                Token::Bool(true),
242                Token::Str("nick"),
243                Token::Some,
244                Token::Str("twilight"),
245                Token::Str("pending"),
246                Token::Bool(false),
247                Token::Str("premium_since"),
248                Token::Some,
249                Token::Str(PREMIUM_SINCE),
250                Token::Str("roles"),
251                Token::Seq { len: Some(0) },
252                Token::SeqEnd,
253                Token::Str("user"),
254                Token::Struct {
255                    name: "User",
256                    len: 9,
257                },
258                Token::Str("accent_color"),
259                Token::None,
260                Token::Str("avatar"),
261                Token::None,
262                Token::Str("avatar_decoration"),
263                Token::None,
264                Token::Str("avatar_decoration_data"),
265                Token::None,
266                Token::Str("banner"),
267                Token::None,
268                Token::Str("bot"),
269                Token::Bool(false),
270                Token::Str("discriminator"),
271                Token::Str("0001"),
272                Token::Str("global_name"),
273                Token::Some,
274                Token::Str("test"),
275                Token::Str("id"),
276                Token::NewtypeStruct { name: "UserId" },
277                Token::Str("3"),
278                Token::Str("username"),
279                Token::Str("twilight"),
280                Token::StructEnd,
281                Token::StructEnd,
282                Token::Str("presence"),
283                Token::Some,
284                Token::Struct {
285                    name: "PresenceIntermediary",
286                    len: 5,
287                },
288                Token::Str("activities"),
289                Token::Seq { len: Some(1) },
290                Token::Struct {
291                    name: "Activity",
292                    len: 5,
293                },
294                Token::Str("created_at"),
295                Token::Some,
296                Token::U64(1_571_048_061_237),
297                Token::Str("emoji"),
298                Token::Some,
299                Token::Struct {
300                    name: "ActivityEmoji",
301                    len: 1,
302                },
303                Token::Str("name"),
304                Token::Str("Test"),
305                Token::StructEnd,
306                Token::Str("id"),
307                Token::Some,
308                Token::Str("aaaaaaaaaaaaaaaa"),
309                Token::Str("type"),
310                Token::U8(4),
311                Token::Str("name"),
312                Token::Str("foo"),
313                Token::StructEnd,
314                Token::SeqEnd,
315                Token::Str("client_status"),
316                Token::Struct {
317                    name: "ClientStatus",
318                    len: 1,
319                },
320                Token::Str("desktop"),
321                Token::Some,
322                Token::Enum { name: "Status" },
323                Token::Str("online"),
324                Token::Unit,
325                Token::StructEnd,
326                Token::Str("guild_id"),
327                Token::Some,
328                Token::NewtypeStruct { name: "GuildId" },
329                Token::Str("2"),
330                Token::Str("status"),
331                Token::Enum { name: "Status" },
332                Token::Str("online"),
333                Token::Unit,
334                Token::Str("user"),
335                Token::Struct {
336                    name: "UserOrId",
337                    len: 1,
338                },
339                Token::Str("id"),
340                Token::NewtypeStruct { name: "UserId" },
341                Token::Str("3"),
342                Token::StructEnd,
343                Token::StructEnd,
344                Token::Str("user_id"),
345                Token::Some,
346                Token::NewtypeStruct { name: "UserId" },
347                Token::Str("3"),
348                Token::StructEnd,
349                Token::SeqEnd,
350                Token::Str("guild_id"),
351                Token::NewtypeStruct { name: "GuildId" },
352                Token::Str("2"),
353                Token::Str("id"),
354                Token::NewtypeStruct { name: "ChannelId" },
355                Token::Str("4"),
356                Token::Str("member_count"),
357                Token::I32(8),
358                Token::Str("removed_member_ids"),
359                Token::Seq { len: Some(0) },
360                Token::SeqEnd,
361                Token::StructEnd,
362            ],
363        );
364    }
365}