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 };
172 let presence = Presence {
173 activities: vec![activity],
174 client_status: ClientStatus {
175 desktop: Some(Status::Online),
176 mobile: None,
177 web: None,
178 },
179 guild_id: Id::new(2),
180 status: Status::Online,
181 user: UserOrId::UserId { id: Id::new(3) },
182 };
183
184 let join_timestamp = Timestamp::from_str(JOIN_TIMESTAMP).expect("timestamp error");
185
186 let value = ThreadMembersUpdate {
187 added_members: vec![ThreadMember {
188 flags: 1,
189 id: Some(Id::new(123)),
190 join_timestamp,
191 member: Some(member),
192 presence: Some(presence),
193 user_id: Some(Id::new(3)),
194 }],
195 guild_id: Id::new(2),
196 id: Id::new(4),
197 member_count: 8,
198 removed_member_ids: vec![],
199 };
200
201 serde_test::assert_de_tokens(
202 &value,
203 &[
204 Token::Struct {
205 name: "ThreadMemberUpdate",
206 len: 6,
207 },
208 Token::Str("added_members"),
209 Token::Seq { len: Some(1) },
210 Token::Struct {
211 name: "ThreadMemberIntermediary",
212 len: 6,
213 },
214 Token::Str("flags"),
215 Token::U64(1),
216 Token::Str("id"),
217 Token::Some,
218 Token::NewtypeStruct { name: "ChannelId" },
219 Token::Str("123"),
220 Token::Str("join_timestamp"),
221 Token::Str(JOIN_TIMESTAMP),
222 Token::Str("member"),
223 Token::Some,
224 Token::Struct {
225 name: "Member",
226 len: 12,
227 },
228 Token::Str("avatar"),
229 Token::Some,
230 Token::Str(image_hash::AVATAR_INPUT),
231 Token::Str("communication_disabled_until"),
232 Token::None,
233 Token::Str("deaf"),
234 Token::Bool(false),
235 Token::Str("flags"),
236 Token::U64(flags.bits()),
237 Token::Str("guild_id"),
238 Token::NewtypeStruct { name: "GuildId" },
239 Token::Str("1"),
240 Token::Str("joined_at"),
241 Token::Some,
242 Token::Str(JOIN_TIMESTAMP),
243 Token::Str("mute"),
244 Token::Bool(true),
245 Token::Str("nick"),
246 Token::Some,
247 Token::Str("twilight"),
248 Token::Str("pending"),
249 Token::Bool(false),
250 Token::Str("premium_since"),
251 Token::Some,
252 Token::Str(PREMIUM_SINCE),
253 Token::Str("roles"),
254 Token::Seq { len: Some(0) },
255 Token::SeqEnd,
256 Token::Str("user"),
257 Token::Struct {
258 name: "User",
259 len: 9,
260 },
261 Token::Str("accent_color"),
262 Token::None,
263 Token::Str("avatar"),
264 Token::None,
265 Token::Str("avatar_decoration"),
266 Token::None,
267 Token::Str("avatar_decoration_data"),
268 Token::None,
269 Token::Str("banner"),
270 Token::None,
271 Token::Str("bot"),
272 Token::Bool(false),
273 Token::Str("discriminator"),
274 Token::Str("0001"),
275 Token::Str("global_name"),
276 Token::Some,
277 Token::Str("test"),
278 Token::Str("id"),
279 Token::NewtypeStruct { name: "UserId" },
280 Token::Str("3"),
281 Token::Str("username"),
282 Token::Str("twilight"),
283 Token::StructEnd,
284 Token::StructEnd,
285 Token::Str("presence"),
286 Token::Some,
287 Token::Struct {
288 name: "PresenceIntermediary",
289 len: 5,
290 },
291 Token::Str("activities"),
292 Token::Seq { len: Some(1) },
293 Token::Struct {
294 name: "Activity",
295 len: 5,
296 },
297 Token::Str("created_at"),
298 Token::Some,
299 Token::U64(1_571_048_061_237),
300 Token::Str("emoji"),
301 Token::Some,
302 Token::Struct {
303 name: "ActivityEmoji",
304 len: 1,
305 },
306 Token::Str("name"),
307 Token::Str("Test"),
308 Token::StructEnd,
309 Token::Str("id"),
310 Token::Some,
311 Token::Str("aaaaaaaaaaaaaaaa"),
312 Token::Str("type"),
313 Token::U8(4),
314 Token::Str("name"),
315 Token::Str("foo"),
316 Token::StructEnd,
317 Token::SeqEnd,
318 Token::Str("client_status"),
319 Token::Struct {
320 name: "ClientStatus",
321 len: 1,
322 },
323 Token::Str("desktop"),
324 Token::Some,
325 Token::Enum { name: "Status" },
326 Token::Str("online"),
327 Token::Unit,
328 Token::StructEnd,
329 Token::Str("guild_id"),
330 Token::Some,
331 Token::NewtypeStruct { name: "GuildId" },
332 Token::Str("2"),
333 Token::Str("status"),
334 Token::Enum { name: "Status" },
335 Token::Str("online"),
336 Token::Unit,
337 Token::Str("user"),
338 Token::Struct {
339 name: "UserOrId",
340 len: 1,
341 },
342 Token::Str("id"),
343 Token::NewtypeStruct { name: "UserId" },
344 Token::Str("3"),
345 Token::StructEnd,
346 Token::StructEnd,
347 Token::Str("user_id"),
348 Token::Some,
349 Token::NewtypeStruct { name: "UserId" },
350 Token::Str("3"),
351 Token::StructEnd,
352 Token::SeqEnd,
353 Token::Str("guild_id"),
354 Token::NewtypeStruct { name: "GuildId" },
355 Token::Str("2"),
356 Token::Str("id"),
357 Token::NewtypeStruct { name: "ChannelId" },
358 Token::Str("4"),
359 Token::Str("member_count"),
360 Token::I32(8),
361 Token::Str("removed_member_ids"),
362 Token::Seq { len: Some(0) },
363 Token::SeqEnd,
364 Token::StructEnd,
365 ],
366 );
367 }
368}