twilight_cache_inmemory/model/
member.rs

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/// 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) 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    /// Member's guild avatar.
61    pub const fn avatar(&self) -> Option<ImageHash> {
62        self.avatar
63    }
64
65    /// When the user can resume communication in a guild again.
66    ///
67    /// Checking if this value is [`Some`] is not enough to know if a used is currently
68    /// timed out as Discord doesn't send any events when the timeout expires, and
69    /// therefore the cache is not updated accordingly. You should ensure that the
70    /// provided [`Timestamp`] is not in the past. See [discord-api-docs#4269].
71    ///
72    /// [discord-api-docs#4269]: https://github.com/discord/discord-api-docs/issues/4269
73    pub const fn communication_disabled_until(&self) -> Option<Timestamp> {
74        self.communication_disabled_until
75    }
76
77    /// Whether the member is deafened in a voice channel.
78    pub const fn deaf(&self) -> Option<bool> {
79        self.deaf
80    }
81
82    /// Flags for the member.
83    ///
84    /// Defaults to an empty bitfield.
85    pub const fn flags(&self) -> MemberFlags {
86        self.flags
87    }
88
89    /// [`Timestamp`] of this member's join date.
90    pub const fn joined_at(&self) -> Option<Timestamp> {
91        self.joined_at
92    }
93
94    /// Whether the member is muted in a voice channel.
95    pub const fn mute(&self) -> Option<bool> {
96        self.mute
97    }
98
99    /// Nickname of the member.
100    pub fn nick(&self) -> Option<&str> {
101        self.nick.as_deref()
102    }
103
104    /// Whether the member has not yet passed the guild's Membership Screening
105    /// requirements.
106    pub const fn pending(&self) -> bool {
107        self.pending
108    }
109
110    /// [`Timestamp`] of the date the member boosted the guild.
111    pub const fn premium_since(&self) -> Option<Timestamp> {
112        self.premium_since
113    }
114
115    /// List of role IDs this member has.
116    pub fn roles(&self) -> &[Id<RoleMarker>] {
117        &self.roles
118    }
119
120    /// ID of the user relating to the member.
121    pub const fn user_id(&self) -> Id<UserMarker> {
122        self.user_id
123    }
124}
125
126impl From<Member> for CachedMember {
127    fn from(member: Member) -> Self {
128        let Member {
129            avatar,
130            communication_disabled_until,
131            deaf,
132            flags,
133            joined_at,
134            mute,
135            nick,
136            pending,
137            premium_since,
138            roles,
139            user,
140        } = member;
141
142        Self {
143            avatar,
144            communication_disabled_until,
145            deaf: Some(deaf),
146            flags,
147            joined_at,
148            mute: Some(mute),
149            nick,
150            pending,
151            premium_since,
152            roles,
153            user_id: user.id,
154        }
155    }
156}
157
158impl From<ComputedInteractionMember> for CachedMember {
159    fn from(member: ComputedInteractionMember) -> Self {
160        let ComputedInteractionMember {
161            avatar,
162            deaf,
163            mute,
164            user_id,
165            interaction_member,
166        } = member;
167        let InteractionMember {
168            avatar: _,
169            communication_disabled_until,
170            flags,
171            joined_at,
172            nick,
173            pending,
174            permissions: _,
175            premium_since,
176            roles,
177        } = interaction_member;
178
179        Self {
180            avatar,
181            communication_disabled_until,
182            deaf,
183            flags,
184            joined_at,
185            mute,
186            nick,
187            pending,
188            premium_since,
189            roles,
190            user_id,
191        }
192    }
193}
194
195impl From<(Id<UserMarker>, PartialMember)> for CachedMember {
196    fn from((user_id, member): (Id<UserMarker>, PartialMember)) -> Self {
197        let PartialMember {
198            avatar,
199            communication_disabled_until,
200            deaf,
201            flags,
202            joined_at,
203            mute,
204            nick,
205            permissions: _,
206            premium_since,
207            roles,
208            user,
209        } = member;
210
211        Self {
212            avatar,
213            communication_disabled_until,
214            deaf: Some(deaf),
215            flags,
216            joined_at,
217            mute: Some(mute),
218            nick,
219            pending: false,
220            premium_since,
221            roles,
222            user_id: user.map_or(user_id, |user| user.id),
223        }
224    }
225}
226
227impl PartialEq<Member> for CachedMember {
228    fn eq(&self, other: &Member) -> bool {
229        self.avatar == other.avatar
230            && self.communication_disabled_until == other.communication_disabled_until
231            && self.deaf == Some(other.deaf)
232            && self.joined_at == other.joined_at
233            && self.mute == Some(other.mute)
234            && self.nick == other.nick
235            && self.pending == other.pending
236            && self.premium_since == other.premium_since
237            && self.roles == other.roles
238            && self.user_id == other.user.id
239    }
240}
241
242impl PartialEq<PartialMember> for CachedMember {
243    fn eq(&self, other: &PartialMember) -> bool {
244        self.communication_disabled_until == other.communication_disabled_until
245            && self.deaf == Some(other.deaf)
246            && self.joined_at == other.joined_at
247            && self.mute == Some(other.mute)
248            && self.nick == other.nick
249            && self.premium_since == other.premium_since
250            && self.roles == other.roles
251    }
252}
253
254impl PartialEq<InteractionMember> for CachedMember {
255    fn eq(&self, other: &InteractionMember) -> bool {
256        self.joined_at == other.joined_at
257            && self.nick == other.nick
258            && self.premium_since == other.premium_since
259            && self.roles == other.roles
260    }
261}
262
263impl CacheableMember for CachedMember {
264    fn roles(&self) -> &[Id<RoleMarker>] {
265        &self.roles
266    }
267
268    #[cfg(feature = "permission-calculator")]
269    fn communication_disabled_until(&self) -> Option<Timestamp> {
270        self.communication_disabled_until
271    }
272
273    fn avatar(&self) -> Option<ImageHash> {
274        self.avatar
275    }
276
277    fn deaf(&self) -> Option<bool> {
278        self.deaf
279    }
280
281    fn mute(&self) -> Option<bool> {
282        self.mute
283    }
284
285    fn update_with_member_update(&mut self, member_update: &MemberUpdate) {
286        self.avatar = member_update.avatar;
287        self.deaf = member_update.deaf.or_else(|| self.deaf());
288        self.mute = member_update.mute.or_else(|| self.mute());
289        self.nick.clone_from(&member_update.nick);
290        self.roles.clone_from(&member_update.roles);
291        self.joined_at = member_update.joined_at;
292        self.pending = member_update.pending;
293        self.communication_disabled_until = member_update.communication_disabled_until;
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::CachedMember;
300    use static_assertions::assert_fields;
301    use twilight_model::{
302        guild::{Member, MemberFlags, PartialMember},
303        id::Id,
304        user::User,
305        util::Timestamp,
306    };
307
308    assert_fields!(
309        CachedMember: deaf,
310        joined_at,
311        mute,
312        nick,
313        pending,
314        premium_since,
315        roles,
316        user_id
317    );
318
319    fn cached_member() -> CachedMember {
320        let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
321        let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
322        CachedMember {
323            avatar: None,
324            communication_disabled_until: None,
325            deaf: Some(false),
326            flags,
327            joined_at,
328            mute: Some(true),
329            nick: Some("member nick".to_owned()),
330            pending: false,
331            premium_since: None,
332            roles: Vec::new(),
333            user_id: user().id,
334        }
335    }
336
337    fn user() -> User {
338        User {
339            accent_color: None,
340            avatar: None,
341            avatar_decoration: None,
342            avatar_decoration_data: None,
343            banner: None,
344            bot: false,
345            discriminator: 1,
346            email: None,
347            flags: None,
348            global_name: Some("test".to_owned()),
349            id: Id::new(1),
350            locale: None,
351            mfa_enabled: None,
352            name: "bar".to_owned(),
353            premium_type: None,
354            public_flags: None,
355            system: None,
356            verified: None,
357        }
358    }
359
360    #[test]
361    fn eq_member() {
362        let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
363        let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
364
365        let member = Member {
366            avatar: None,
367            communication_disabled_until: None,
368            deaf: false,
369            flags,
370            joined_at,
371            mute: true,
372            nick: Some("member nick".to_owned()),
373            pending: false,
374            premium_since: None,
375            roles: Vec::new(),
376            user: user(),
377        };
378
379        assert_eq!(cached_member(), member);
380    }
381
382    #[test]
383    fn eq_partial_member() {
384        let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
385        let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
386
387        let member = PartialMember {
388            avatar: None,
389            communication_disabled_until: None,
390            deaf: false,
391            flags,
392            joined_at,
393            mute: true,
394            nick: Some("member nick".to_owned()),
395            permissions: None,
396            premium_since: None,
397            roles: Vec::new(),
398            user: None,
399        };
400
401        assert_eq!(cached_member(), member);
402    }
403}