1use crate::{
2 channel::thread::member::{ThreadMember, ThreadMemberIntermediary},
3 id::{
4 Id,
5 marker::{ChannelMarker, GuildMarker, UserMarker},
6 },
7};
8use serde::{
9 Deserialize, Deserializer, Serialize,
10 de::{MapAccess, Visitor, value::MapAccessDeserializer},
11};
12use std::fmt::{Formatter, Result as FmtResult};
13
14#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
15pub struct ThreadMembersUpdate {
16 #[serde(default)]
22 pub added_members: Vec<ThreadMember>,
23 pub guild_id: Id<GuildMarker>,
24 pub id: Id<ChannelMarker>,
25 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 #[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 avatar_decoration_data: None,
117 banner: None,
118 communication_disabled_until: None,
119 deaf: false,
120 flags,
121 joined_at,
122 mute: true,
123 nick: Some("twilight".to_owned()),
124 pending: false,
125 premium_since: Some(premium_since),
126 roles: Vec::new(),
127 user: User {
128 accent_color: None,
129 avatar: None,
130 avatar_decoration: None,
131 avatar_decoration_data: None,
132 banner: None,
133 bot: false,
134 discriminator: 1,
135 email: None,
136 flags: None,
137 global_name: Some("test".to_owned()),
138 id: Id::new(3),
139 locale: None,
140 mfa_enabled: None,
141 name: "twilight".to_owned(),
142 premium_type: None,
143 primary_guild: None,
144 public_flags: None,
145 system: None,
146 verified: None,
147 },
148 };
149
150 let activity = Activity {
151 application_id: None,
152 assets: None,
153 buttons: Vec::new(),
154 created_at: Some(1_571_048_061_237),
155 details: None,
156 flags: None,
157 id: Some("aaaaaaaaaaaaaaaa".to_owned()),
158 instance: None,
159 kind: ActivityType::Custom,
160 name: "foo".to_owned(),
161 emoji: Some(ActivityEmoji {
162 name: "Test".to_string(),
163 id: None,
164 animated: None,
165 }),
166 party: None,
167 secrets: None,
168 state: None,
169 timestamps: None,
170 url: None,
171 details_url: None,
172 state_url: None,
173 status_display_type: None,
174 };
175 let presence = Presence {
176 activities: vec![activity],
177 client_status: ClientStatus {
178 desktop: Some(Status::Online),
179 mobile: None,
180 web: None,
181 vr: None,
182 },
183 guild_id: Id::new(2),
184 status: Status::Online,
185 user: UserOrId::UserId { id: Id::new(3) },
186 };
187
188 let join_timestamp = Timestamp::from_str(JOIN_TIMESTAMP).expect("timestamp error");
189
190 let value = ThreadMembersUpdate {
191 added_members: vec![ThreadMember {
192 flags: 1,
193 id: Some(Id::new(123)),
194 join_timestamp,
195 member: Some(member),
196 presence: Some(presence),
197 user_id: Some(Id::new(3)),
198 }],
199 guild_id: Id::new(2),
200 id: Id::new(4),
201 member_count: 8,
202 removed_member_ids: vec![],
203 };
204
205 serde_test::assert_de_tokens(
206 &value,
207 &[
208 Token::Struct {
209 name: "ThreadMemberUpdate",
210 len: 6,
211 },
212 Token::Str("added_members"),
213 Token::Seq { len: Some(1) },
214 Token::Struct {
215 name: "ThreadMemberIntermediary",
216 len: 6,
217 },
218 Token::Str("flags"),
219 Token::U64(1),
220 Token::Str("id"),
221 Token::Some,
222 Token::NewtypeStruct { name: "ChannelId" },
223 Token::Str("123"),
224 Token::Str("join_timestamp"),
225 Token::Str(JOIN_TIMESTAMP),
226 Token::Str("member"),
227 Token::Some,
228 Token::Struct {
229 name: "Member",
230 len: 12,
231 },
232 Token::Str("avatar"),
233 Token::Some,
234 Token::Str(image_hash::AVATAR_INPUT),
235 Token::Str("communication_disabled_until"),
236 Token::None,
237 Token::Str("deaf"),
238 Token::Bool(false),
239 Token::Str("flags"),
240 Token::U64(flags.bits()),
241 Token::Str("guild_id"),
242 Token::NewtypeStruct { name: "GuildId" },
243 Token::Str("1"),
244 Token::Str("joined_at"),
245 Token::Some,
246 Token::Str(JOIN_TIMESTAMP),
247 Token::Str("mute"),
248 Token::Bool(true),
249 Token::Str("nick"),
250 Token::Some,
251 Token::Str("twilight"),
252 Token::Str("pending"),
253 Token::Bool(false),
254 Token::Str("premium_since"),
255 Token::Some,
256 Token::Str(PREMIUM_SINCE),
257 Token::Str("roles"),
258 Token::Seq { len: Some(0) },
259 Token::SeqEnd,
260 Token::Str("user"),
261 Token::Struct {
262 name: "User",
263 len: 9,
264 },
265 Token::Str("accent_color"),
266 Token::None,
267 Token::Str("avatar"),
268 Token::None,
269 Token::Str("avatar_decoration"),
270 Token::None,
271 Token::Str("avatar_decoration_data"),
272 Token::None,
273 Token::Str("banner"),
274 Token::None,
275 Token::Str("bot"),
276 Token::Bool(false),
277 Token::Str("discriminator"),
278 Token::Str("0001"),
279 Token::Str("global_name"),
280 Token::Some,
281 Token::Str("test"),
282 Token::Str("id"),
283 Token::NewtypeStruct { name: "UserId" },
284 Token::Str("3"),
285 Token::Str("username"),
286 Token::Str("twilight"),
287 Token::StructEnd,
288 Token::StructEnd,
289 Token::Str("presence"),
290 Token::Some,
291 Token::Struct {
292 name: "PresenceIntermediary",
293 len: 5,
294 },
295 Token::Str("activities"),
296 Token::Seq { len: Some(1) },
297 Token::Struct {
298 name: "Activity",
299 len: 5,
300 },
301 Token::Str("created_at"),
302 Token::Some,
303 Token::U64(1_571_048_061_237),
304 Token::Str("emoji"),
305 Token::Some,
306 Token::Struct {
307 name: "ActivityEmoji",
308 len: 1,
309 },
310 Token::Str("name"),
311 Token::Str("Test"),
312 Token::StructEnd,
313 Token::Str("id"),
314 Token::Some,
315 Token::Str("aaaaaaaaaaaaaaaa"),
316 Token::Str("type"),
317 Token::U8(4),
318 Token::Str("name"),
319 Token::Str("foo"),
320 Token::StructEnd,
321 Token::SeqEnd,
322 Token::Str("client_status"),
323 Token::Struct {
324 name: "ClientStatus",
325 len: 1,
326 },
327 Token::Str("desktop"),
328 Token::Some,
329 Token::Enum { name: "Status" },
330 Token::Str("online"),
331 Token::Unit,
332 Token::StructEnd,
333 Token::Str("guild_id"),
334 Token::Some,
335 Token::NewtypeStruct { name: "GuildId" },
336 Token::Str("2"),
337 Token::Str("status"),
338 Token::Enum { name: "Status" },
339 Token::Str("online"),
340 Token::Unit,
341 Token::Str("user"),
342 Token::Struct {
343 name: "UserOrId",
344 len: 1,
345 },
346 Token::Str("id"),
347 Token::NewtypeStruct { name: "UserId" },
348 Token::Str("3"),
349 Token::StructEnd,
350 Token::StructEnd,
351 Token::Str("user_id"),
352 Token::Some,
353 Token::NewtypeStruct { name: "UserId" },
354 Token::Str("3"),
355 Token::StructEnd,
356 Token::SeqEnd,
357 Token::Str("guild_id"),
358 Token::NewtypeStruct { name: "GuildId" },
359 Token::Str("2"),
360 Token::Str("id"),
361 Token::NewtypeStruct { name: "ChannelId" },
362 Token::Str("4"),
363 Token::Str("member_count"),
364 Token::I32(8),
365 Token::Str("removed_member_ids"),
366 Token::Seq { len: Some(0) },
367 Token::SeqEnd,
368 Token::StructEnd,
369 ],
370 );
371 }
372}