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    #[allow(clippy::missing_const_for_fn)]
117    pub fn roles(&self) -> &[Id<RoleMarker>] {
118        &self.roles
119    }
120
121    /// ID of the user relating to the member.
122    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}