twilight_cache_inmemory/model/
member.rs

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/// Computed components required to complete a full cached interaction member
18/// by implementing [`CacheableMember`].
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct ComputedInteractionMember {
21    /// Member's guild avatar.
22    pub avatar: Option<ImageHash>,
23    /// Whether the member is deafened in a voice channel.
24    pub deaf: Option<bool>,
25    /// Member that performed the interaction.
26    pub interaction_member: InteractionMember,
27    /// Whether the member is muted in a voice channel.
28    pub mute: Option<bool>,
29    /// ID of the user relating to the member.
30    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/// Represents a cached [`Member`].
42///
43/// [`Member`]: twilight_model::guild::Member
44#[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    /// Member's guild avatar.
63    pub const fn avatar(&self) -> Option<ImageHash> {
64        self.avatar
65    }
66
67    /// When the user can resume communication in a guild again.
68    ///
69    /// Checking if this value is [`Some`] is not enough to know if a used is currently
70    /// timed out as Discord doesn't send any events when the timeout expires, and
71    /// therefore the cache is not updated accordingly. You should ensure that the
72    /// provided [`Timestamp`] is not in the past. See [discord-api-docs#4269].
73    ///
74    /// [discord-api-docs#4269]: https://github.com/discord/discord-api-docs/issues/4269
75    pub const fn communication_disabled_until(&self) -> Option<Timestamp> {
76        self.communication_disabled_until
77    }
78
79    /// Whether the member is deafened in a voice channel.
80    pub const fn deaf(&self) -> Option<bool> {
81        self.deaf
82    }
83
84    /// Flags for the member.
85    ///
86    /// Defaults to an empty bitfield.
87    pub const fn flags(&self) -> MemberFlags {
88        self.flags
89    }
90
91    /// [`Timestamp`] of this member's join date.
92    pub const fn joined_at(&self) -> Option<Timestamp> {
93        self.joined_at
94    }
95
96    /// Whether the member is muted in a voice channel.
97    pub const fn mute(&self) -> Option<bool> {
98        self.mute
99    }
100
101    /// Nickname of the member.
102    pub fn nick(&self) -> Option<&str> {
103        self.nick.as_deref()
104    }
105
106    /// Whether the member has not yet passed the guild's Membership Screening
107    /// requirements.
108    pub const fn pending(&self) -> bool {
109        self.pending
110    }
111
112    /// [`Timestamp`] of the date the member boosted the guild.
113    pub const fn premium_since(&self) -> Option<Timestamp> {
114        self.premium_since
115    }
116
117    /// List of role IDs this member has.
118    #[allow(clippy::missing_const_for_fn)]
119    pub fn roles(&self) -> &[Id<RoleMarker>] {
120        &self.roles
121    }
122
123    /// ID of the user relating to the member.
124    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}