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