1use std::ops::Deref;
2
3use crate::CacheableMember;
4use serde::Serialize;
5use twilight_model::user::AvatarDecorationData;
6use twilight_model::{
7 application::interaction::InteractionMember,
8 gateway::payload::incoming::MemberUpdate,
9 guild::{Member, MemberFlags, PartialMember},
10 id::{
11 Id,
12 marker::{RoleMarker, UserMarker},
13 },
14 util::{ImageHash, Timestamp},
15};
16
17#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct ComputedInteractionMember {
21 pub avatar: Option<ImageHash>,
23 pub deaf: Option<bool>,
25 pub interaction_member: InteractionMember,
27 pub mute: Option<bool>,
29 pub user_id: Id<UserMarker>,
31}
32
33impl Deref for ComputedInteractionMember {
34 type Target = InteractionMember;
35
36 fn deref(&self) -> &Self::Target {
37 &self.interaction_member
38 }
39}
40
41#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
45pub struct CachedMember {
46 pub(crate) avatar: Option<ImageHash>,
47 pub(crate) avatar_decoration_data: Option<AvatarDecorationData>,
48 pub(crate) banner: Option<ImageHash>,
49 pub(crate) communication_disabled_until: Option<Timestamp>,
50 pub(crate) deaf: Option<bool>,
51 pub(crate) flags: MemberFlags,
52 pub(crate) joined_at: Option<Timestamp>,
53 pub(crate) mute: Option<bool>,
54 pub(crate) nick: Option<String>,
55 pub(crate) pending: bool,
56 pub(crate) premium_since: Option<Timestamp>,
57 pub(crate) roles: Vec<Id<RoleMarker>>,
58 pub(crate) user_id: Id<UserMarker>,
59}
60
61impl CachedMember {
62 pub const fn avatar(&self) -> Option<ImageHash> {
64 self.avatar
65 }
66
67 pub const fn communication_disabled_until(&self) -> Option<Timestamp> {
76 self.communication_disabled_until
77 }
78
79 pub const fn deaf(&self) -> Option<bool> {
81 self.deaf
82 }
83
84 pub const fn flags(&self) -> MemberFlags {
88 self.flags
89 }
90
91 pub const fn joined_at(&self) -> Option<Timestamp> {
93 self.joined_at
94 }
95
96 pub const fn mute(&self) -> Option<bool> {
98 self.mute
99 }
100
101 pub fn nick(&self) -> Option<&str> {
103 self.nick.as_deref()
104 }
105
106 pub const fn pending(&self) -> bool {
109 self.pending
110 }
111
112 pub const fn premium_since(&self) -> Option<Timestamp> {
114 self.premium_since
115 }
116
117 #[allow(clippy::missing_const_for_fn)]
119 pub fn roles(&self) -> &[Id<RoleMarker>] {
120 &self.roles
121 }
122
123 pub const fn user_id(&self) -> Id<UserMarker> {
125 self.user_id
126 }
127}
128
129impl From<Member> for CachedMember {
130 fn from(member: Member) -> Self {
131 let Member {
132 avatar,
133 avatar_decoration_data,
134 banner,
135 communication_disabled_until,
136 deaf,
137 flags,
138 joined_at,
139 mute,
140 nick,
141 pending,
142 premium_since,
143 roles,
144 user,
145 } = member;
146
147 Self {
148 avatar,
149 avatar_decoration_data,
150 banner,
151 communication_disabled_until,
152 deaf: Some(deaf),
153 flags,
154 joined_at,
155 mute: Some(mute),
156 nick,
157 pending,
158 premium_since,
159 roles,
160 user_id: user.id,
161 }
162 }
163}
164
165impl From<ComputedInteractionMember> for CachedMember {
166 fn from(member: ComputedInteractionMember) -> Self {
167 let ComputedInteractionMember {
168 avatar,
169 deaf,
170 mute,
171 user_id,
172 interaction_member,
173 } = member;
174 let InteractionMember {
175 avatar: _,
176 avatar_decoration_data,
177 banner,
178 communication_disabled_until,
179 flags,
180 joined_at,
181 nick,
182 pending,
183 permissions: _,
184 premium_since,
185 roles,
186 } = interaction_member;
187
188 Self {
189 avatar,
190 avatar_decoration_data,
191 banner,
192 communication_disabled_until,
193 deaf,
194 flags,
195 joined_at,
196 mute,
197 nick,
198 pending,
199 premium_since,
200 roles,
201 user_id,
202 }
203 }
204}
205
206impl From<(Id<UserMarker>, PartialMember)> for CachedMember {
207 fn from((user_id, member): (Id<UserMarker>, PartialMember)) -> Self {
208 let PartialMember {
209 avatar,
210 avatar_decoration_data,
211 banner,
212 communication_disabled_until,
213 deaf,
214 flags,
215 joined_at,
216 mute,
217 nick,
218 permissions: _,
219 premium_since,
220 roles,
221 user,
222 } = member;
223
224 Self {
225 avatar,
226 avatar_decoration_data,
227 banner,
228 communication_disabled_until,
229 deaf: Some(deaf),
230 flags,
231 joined_at,
232 mute: Some(mute),
233 nick,
234 pending: false,
235 premium_since,
236 roles,
237 user_id: user.map_or(user_id, |user| user.id),
238 }
239 }
240}
241
242impl PartialEq<Member> for CachedMember {
243 fn eq(&self, other: &Member) -> bool {
244 self.avatar == other.avatar
245 && self.communication_disabled_until == other.communication_disabled_until
246 && self.deaf == Some(other.deaf)
247 && self.joined_at == other.joined_at
248 && self.mute == Some(other.mute)
249 && self.nick == other.nick
250 && self.pending == other.pending
251 && self.premium_since == other.premium_since
252 && self.roles == other.roles
253 && self.user_id == other.user.id
254 }
255}
256
257impl PartialEq<PartialMember> for CachedMember {
258 fn eq(&self, other: &PartialMember) -> bool {
259 self.communication_disabled_until == other.communication_disabled_until
260 && self.deaf == Some(other.deaf)
261 && self.joined_at == other.joined_at
262 && self.mute == Some(other.mute)
263 && self.nick == other.nick
264 && self.premium_since == other.premium_since
265 && self.roles == other.roles
266 }
267}
268
269impl PartialEq<InteractionMember> for CachedMember {
270 fn eq(&self, other: &InteractionMember) -> bool {
271 self.joined_at == other.joined_at
272 && self.nick == other.nick
273 && self.premium_since == other.premium_since
274 && self.roles == other.roles
275 }
276}
277
278impl CacheableMember for CachedMember {
279 fn roles(&self) -> &[Id<RoleMarker>] {
280 &self.roles
281 }
282
283 #[cfg(feature = "permission-calculator")]
284 fn communication_disabled_until(&self) -> Option<Timestamp> {
285 self.communication_disabled_until
286 }
287
288 fn avatar(&self) -> Option<ImageHash> {
289 self.avatar
290 }
291
292 fn deaf(&self) -> Option<bool> {
293 self.deaf
294 }
295
296 fn mute(&self) -> Option<bool> {
297 self.mute
298 }
299
300 fn update_with_member_update(&mut self, member_update: &MemberUpdate) {
301 self.avatar = member_update.avatar;
302 self.deaf = member_update.deaf.or_else(|| self.deaf());
303 self.mute = member_update.mute.or_else(|| self.mute());
304 self.nick.clone_from(&member_update.nick);
305 self.roles.clone_from(&member_update.roles);
306 self.joined_at = member_update.joined_at;
307 self.pending = member_update.pending;
308 self.communication_disabled_until = member_update.communication_disabled_until;
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::CachedMember;
315 use static_assertions::assert_fields;
316 use twilight_model::{
317 guild::{Member, MemberFlags, PartialMember},
318 id::Id,
319 user::User,
320 util::Timestamp,
321 };
322
323 assert_fields!(
324 CachedMember: deaf,
325 joined_at,
326 mute,
327 nick,
328 pending,
329 premium_since,
330 roles,
331 user_id
332 );
333
334 fn cached_member() -> CachedMember {
335 let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
336 let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
337 CachedMember {
338 avatar: None,
339 avatar_decoration_data: None,
340 banner: None,
341 communication_disabled_until: None,
342 deaf: Some(false),
343 flags,
344 joined_at,
345 mute: Some(true),
346 nick: Some("member nick".to_owned()),
347 pending: false,
348 premium_since: None,
349 roles: Vec::new(),
350 user_id: user().id,
351 }
352 }
353
354 fn user() -> User {
355 User {
356 accent_color: None,
357 avatar: None,
358 avatar_decoration: None,
359 avatar_decoration_data: None,
360 banner: None,
361 bot: false,
362 discriminator: 1,
363 email: None,
364 flags: None,
365 global_name: Some("test".to_owned()),
366 id: Id::new(1),
367 locale: None,
368 mfa_enabled: None,
369 name: "bar".to_owned(),
370 premium_type: None,
371 primary_guild: None,
372 public_flags: None,
373 system: None,
374 verified: None,
375 }
376 }
377
378 #[test]
379 fn eq_member() {
380 let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
381 let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
382
383 let member = Member {
384 avatar: None,
385 avatar_decoration_data: None,
386 banner: None,
387 communication_disabled_until: None,
388 deaf: false,
389 flags,
390 joined_at,
391 mute: true,
392 nick: Some("member nick".to_owned()),
393 pending: false,
394 premium_since: None,
395 roles: Vec::new(),
396 user: user(),
397 };
398
399 assert_eq!(cached_member(), member);
400 }
401
402 #[test]
403 fn eq_partial_member() {
404 let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
405 let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
406
407 let member = PartialMember {
408 avatar: None,
409 avatar_decoration_data: None,
410 banner: None,
411 communication_disabled_until: None,
412 deaf: false,
413 flags,
414 joined_at,
415 mute: true,
416 nick: Some("member nick".to_owned()),
417 permissions: None,
418 premium_since: None,
419 roles: Vec::new(),
420 user: None,
421 };
422
423 assert_eq!(cached_member(), member);
424 }
425}