Skip to main content

serenity/model/guild/
member.rs

1#[cfg(feature = "cache")]
2use std::cmp::Reverse;
3use std::fmt;
4
5#[cfg(feature = "model")]
6use crate::builder::EditMember;
7#[cfg(feature = "cache")]
8use crate::cache::Cache;
9#[cfg(feature = "model")]
10use crate::http::{CacheHttp, Http};
11#[cfg(all(feature = "cache", feature = "model"))]
12use crate::internal::prelude::*;
13use crate::model::prelude::*;
14#[cfg(feature = "model")]
15use crate::model::utils::{avatar_url, user_banner_url};
16
17/// Information about a member of a guild.
18///
19/// [Discord docs](https://discord.com/developers/docs/resources/guild#guild-member-object),
20/// [extra fields](https://discord.com/developers/docs/topics/gateway-events#guild-member-add-guild-member-add-extra-fields).
21#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
22#[derive(Clone, Debug, Default, Deserialize, Serialize)]
23#[non_exhaustive]
24pub struct Member {
25    /// Attached User struct.
26    pub user: User,
27    /// The member's nickname, if present.
28    ///
29    /// Can't be longer than 32 characters.
30    pub nick: Option<String>,
31    /// The member's guild avatar hash
32    pub avatar: Option<ImageHash>,
33    /// The member's guild banner hash
34    pub banner: Option<ImageHash>,
35    /// Vector of Ids of [`Role`]s given to the member.
36    pub roles: Vec<RoleId>,
37    /// Timestamp representing the date when the member joined.
38    pub joined_at: Option<Timestamp>,
39    /// Timestamp representing the date since the member is boosting the guild.
40    pub premium_since: Option<Timestamp>,
41    /// Indicator of whether the member can hear in voice channels.
42    pub deaf: bool,
43    /// Indicator of whether the member can speak in voice channels.
44    pub mute: bool,
45    /// Guild member flags.
46    pub flags: GuildMemberFlags,
47    /// Indicator that the member hasn't accepted the rules of the guild yet.
48    #[serde(default)]
49    pub pending: bool,
50    /// The total permissions of the member in a channel, including overrides.
51    ///
52    /// This is only [`Some`] when returned in an [`Interaction`] object.
53    ///
54    /// [`Interaction`]: crate::model::application::Interaction
55    pub permissions: Option<Permissions>,
56    /// When the user's timeout will expire and the user will be able to communicate in the guild
57    /// again.
58    ///
59    /// Will be None or a time in the past if the user is not timed out.
60    pub communication_disabled_until: Option<Timestamp>,
61    /// The unique Id of the guild that the member is a part of.
62    #[serde(default)]
63    pub guild_id: GuildId,
64    /// If the member is currently flagged for sending excessive DMs to non-friend server members
65    /// in the last 24 hours.
66    ///
67    /// Will be None or a time in the past if the user is not flagged.
68    pub unusual_dm_activity_until: Option<Timestamp>,
69    /// Information about this member's guild specific avatar decoration.
70    pub avatar_decoration_data: Option<AvatarDecorationData>,
71}
72
73bitflags! {
74    /// Flags for a guild member.
75    ///
76    /// [Discord docs](https://discord.com/developers/docs/resources/guild#guild-member-object-guild-member-flags).
77    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
78    #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
79    pub struct GuildMemberFlags: u32 {
80        /// Member has left and rejoined the guild. Not editable
81        const DID_REJOIN = 1 << 0;
82        /// Member has completed onboarding. Not editable
83        const COMPLETED_ONBOARDING = 1 << 1;
84        /// Member is exempt from guild verification requirements. Editable
85        const BYPASSES_VERIFICATION = 1 << 2;
86        /// Member has started onboarding. Not editable
87        const STARTED_ONBOARDING = 1 << 3;
88        /// Member is a guest and can only access the voice channel they were invited to. Not
89        /// editable
90        const IS_GUEST = 1 << 4;
91        /// Member has started Server Guide new member actions. Not editable
92        const STARTED_HOME_ACTIONS = 1 << 5;
93        /// Member has completed Server Guide new member actions. Not editable
94        const COMPLETED_HOME_ACTIONS = 1 << 6;
95        /// Member's username, display name, or nickname is blocked by AutoMod. Not editable
96        const AUTOMOD_QUARANTINED_USERNAME = 1 << 7;
97        /// Member has dismissed the DM settings upsell. Not editable
98        const DM_SETTINGS_UPSELL_ACKNOWLEDGED = 1 << 9;
99        /// Member's guild tag is blocked by AutoMod. Not editable
100        const AUTOMOD_QUARANTINED_GUILD_TAG = 1 << 10;
101    }
102}
103
104#[cfg(feature = "model")]
105impl Member {
106    /// Adds a [`Role`] to the member.
107    ///
108    /// **Note**: Requires the [Manage Roles] permission.
109    ///
110    /// # Errors
111    ///
112    /// Returns [`Error::Http`] if the current user lacks permission, or if a role with the given
113    /// Id does not exist.
114    ///
115    /// [Manage Roles]: Permissions::MANAGE_ROLES
116    #[inline]
117    pub async fn add_role(&self, http: impl AsRef<Http>, role_id: impl Into<RoleId>) -> Result<()> {
118        http.as_ref().add_member_role(self.guild_id, self.user.id, role_id.into(), None).await
119    }
120
121    /// Adds one or multiple [`Role`]s to the member.
122    ///
123    /// **Note**: Requires the [Manage Roles] permission.
124    ///
125    /// # Errors
126    ///
127    /// Returns [`Error::Http`] if the current user lacks permission, or if a role with a given Id
128    /// does not exist.
129    ///
130    /// [Manage Roles]: Permissions::MANAGE_ROLES
131    pub async fn add_roles(&self, http: impl AsRef<Http>, role_ids: &[RoleId]) -> Result<()> {
132        for role_id in role_ids {
133            self.add_role(http.as_ref(), role_id).await?;
134        }
135
136        Ok(())
137    }
138
139    /// Ban a [`User`] from the guild, deleting a number of days' worth of messages (`dmd`) between
140    /// the range 0 and 7.
141    ///
142    /// **Note**: Requires the [Ban Members] permission.
143    ///
144    /// # Errors
145    ///
146    /// Returns a [`ModelError::DeleteMessageDaysAmount`] if the `dmd` is greater than 7. Can also
147    /// return [`Error::Http`] if the current user lacks permission to ban this member.
148    ///
149    /// [Ban Members]: Permissions::BAN_MEMBERS
150    #[inline]
151    pub async fn ban(&self, http: impl AsRef<Http>, dmd: u8) -> Result<()> {
152        self.ban_with_reason(http, dmd, "").await
153    }
154
155    /// Ban the member from the guild with a reason. Refer to [`Self::ban`] to further
156    /// documentation.
157    ///
158    /// # Errors
159    ///
160    /// In addition to the errors [`Self::ban`] may return, can also return
161    /// [`Error::ExceededLimit`] if the length of the reason is greater than 512.
162    #[inline]
163    pub async fn ban_with_reason(
164        &self,
165        http: impl AsRef<Http>,
166        dmd: u8,
167        reason: impl AsRef<str>,
168    ) -> Result<()> {
169        self.guild_id.ban_with_reason(http, self.user.id, dmd, reason).await
170    }
171
172    /// Determines the member's colour.
173    #[cfg(feature = "cache")]
174    pub fn colour(&self, cache: impl AsRef<Cache>) -> Option<Colour> {
175        let guild = cache.as_ref().guild(self.guild_id)?;
176
177        let mut roles = self
178            .roles
179            .iter()
180            .filter_map(|role_id| guild.roles.get(role_id))
181            .collect::<Vec<&Role>>();
182
183        roles.sort_by_key(|&b| Reverse(b));
184
185        let default = Colour::default();
186
187        roles.iter().find(|r| r.colour.0 != default.0).map(|r| r.colour)
188    }
189
190    /// Returns the "default channel" of the guild for the member. (This returns the first channel
191    /// that can be read by the member, if there isn't one returns [`None`])
192    #[cfg(feature = "cache")]
193    pub fn default_channel(&self, cache: impl AsRef<Cache>) -> Option<GuildChannel> {
194        let guild = self.guild_id.to_guild_cached(&cache)?;
195
196        let member = guild.members.get(&self.user.id)?;
197
198        for channel in guild.channels.values() {
199            if channel.kind != ChannelType::Category
200                && guild.user_permissions_in(channel, member).view_channel()
201            {
202                return Some(channel.clone());
203            }
204        }
205
206        None
207    }
208
209    /// Times the user out until `time`.
210    ///
211    /// Requires the [Moderate Members] permission.
212    ///
213    /// **Note**: [Moderate Members]: crate::model::permission::Permissions::MODERATE_MEMBERS
214    ///
215    /// # Errors
216    ///
217    /// Returns [`Error::Http`] if the current user lacks permission or if `time` is greater than
218    /// 28 days from the current time.
219    ///
220    /// [Moderate Members]: Permissions::MODERATE_MEMBERS
221    #[doc(alias = "timeout")]
222    pub async fn disable_communication_until_datetime(
223        &mut self,
224        cache_http: impl CacheHttp,
225        time: Timestamp,
226    ) -> Result<()> {
227        let builder = EditMember::new().disable_communication_until_datetime(time);
228        match self.guild_id.edit_member(cache_http, self.user.id, builder).await {
229            Ok(_) => {
230                self.communication_disabled_until = Some(time);
231                Ok(())
232            },
233            Err(why) => Err(why),
234        }
235    }
236
237    /// Calculates the member's display name.
238    ///
239    /// The nickname takes priority over the member's username if it exists.
240    #[inline]
241    #[must_use]
242    pub fn display_name(&self) -> &str {
243        self.nick.as_ref().or(self.user.global_name.as_ref()).unwrap_or(&self.user.name)
244    }
245
246    /// Returns the DiscordTag of a Member, taking possible nickname into account.
247    #[inline]
248    #[must_use]
249    #[deprecated = "Use User::tag to get the correct Discord username format or Self::display_name for the name that users will see."]
250    pub fn distinct(&self) -> String {
251        if let Some(discriminator) = self.user.discriminator {
252            format!("{}#{:04}", self.display_name(), discriminator.get())
253        } else {
254            self.display_name().to_string()
255        }
256    }
257
258    /// Edits the member in place with the given data.
259    ///
260    /// See [`EditMember`] for the permission(s) required for separate builder methods, as well as
261    /// usage of this.
262    ///
263    /// # Examples
264    ///
265    /// See [`GuildId::edit_member`] for details.
266    ///
267    /// # Errors
268    ///
269    /// Returns [`Error::Http`] if the current user lacks necessary permissions.
270    pub async fn edit(
271        &mut self,
272        cache_http: impl CacheHttp,
273        builder: EditMember<'_>,
274    ) -> Result<()> {
275        *self = self.guild_id.edit_member(cache_http, self.user.id, builder).await?;
276        Ok(())
277    }
278
279    /// Allow a user to communicate, removing their timeout, if there is one.
280    ///
281    /// **Note**: Requires the [Moderate Members] permission.
282    ///
283    /// # Errors
284    ///
285    /// Returns [`Error::Http`] if the current user lacks permission.
286    ///
287    /// [Moderate Members]: Permissions::MODERATE_MEMBERS
288    #[doc(alias = "timeout")]
289    pub async fn enable_communication(&mut self, cache_http: impl CacheHttp) -> Result<()> {
290        let builder = EditMember::new().enable_communication();
291        *self = self.guild_id.edit_member(cache_http, self.user.id, builder).await?;
292        Ok(())
293    }
294
295    /// Retrieves the ID and position of the member's highest role in the hierarchy, if they have
296    /// one.
297    ///
298    /// This _may_ return [`None`] if the user has roles, but they are not present in the cache for
299    /// cache inconsistency reasons.
300    ///
301    /// The "highest role in hierarchy" is defined as the role with the highest position. If two or
302    /// more roles have the same highest position, then the role with the lowest ID is the highest.
303    #[cfg(feature = "cache")]
304    #[deprecated = "Use Guild::member_highest_role"]
305    pub fn highest_role_info(&self, cache: impl AsRef<Cache>) -> Option<(RoleId, u16)> {
306        cache
307            .as_ref()
308            .guild(self.guild_id)
309            .as_ref()
310            .and_then(|g| g.member_highest_role(self))
311            .map(|r| (r.id, r.position))
312    }
313
314    /// Kick the member from the guild.
315    ///
316    /// **Note**: Requires the [Kick Members] permission.
317    ///
318    /// # Examples
319    ///
320    /// Kick a member from its guild:
321    ///
322    /// ```rust,ignore
323    /// // assuming a `member` has already been bound
324    /// match member.kick().await {
325    ///     Ok(()) => println!("Successfully kicked member"),
326    ///     Err(Error::Model(ModelError::GuildNotFound)) => {
327    ///         println!("Couldn't determine guild of member");
328    ///     },
329    ///     Err(Error::Model(ModelError::InvalidPermissions(missing_perms))) => {
330    ///         println!("Didn't have permissions; missing: {:?}", missing_perms);
331    ///     },
332    ///     _ => {},
333    /// }
334    /// ```
335    ///
336    /// # Errors
337    ///
338    /// Returns a [`ModelError::GuildNotFound`] if the Id of the member's guild could not be
339    /// determined.
340    ///
341    /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
342    /// does not have permission to perform the kick.
343    ///
344    /// Otherwise will return [`Error::Http`] if the current user lacks permission.
345    ///
346    /// [Kick Members]: Permissions::KICK_MEMBERS
347    #[inline]
348    pub async fn kick(&self, cache_http: impl CacheHttp) -> Result<()> {
349        self.kick_with_reason(cache_http, "").await
350    }
351
352    /// Kicks the member from the guild, with a reason.
353    ///
354    /// **Note**: Requires the [Kick Members] permission.
355    ///
356    /// # Examples
357    ///
358    /// Kicks a member from it's guild, with an optional reason:
359    ///
360    /// ```rust,ignore
361    /// match member.kick(&ctx.http, "A Reason").await {
362    ///     Ok(()) => println!("Successfully kicked member"),
363    ///     Err(Error::Model(ModelError::GuildNotFound)) => {
364    ///         println!("Couldn't determine guild of member");
365    ///     },
366    ///     Err(Error::Model(ModelError::InvalidPermissions(missing_perms))) => {
367    ///         println!("Didn't have permissions; missing: {:?}", missing_perms);
368    ///     },
369    ///     _ => {},
370    /// }
371    /// ```
372    ///
373    /// # Errors
374    ///
375    /// In addition to the reasons [`Self::kick`] may return an error, can also return an error if
376    /// the given reason is too long.
377    ///
378    /// [Kick Members]: Permissions::KICK_MEMBERS
379    pub async fn kick_with_reason(&self, cache_http: impl CacheHttp, reason: &str) -> Result<()> {
380        #[cfg(feature = "cache")]
381        {
382            if let Some(cache) = cache_http.cache() {
383                let lookup = cache.guild(self.guild_id).as_deref().cloned();
384                if let Some(guild) = lookup {
385                    guild.require_perms(cache, Permissions::KICK_MEMBERS)?;
386
387                    guild.check_hierarchy(cache, self.user.id)?;
388                }
389            }
390        }
391
392        self.guild_id.kick_with_reason(cache_http.http(), self.user.id, reason).await
393    }
394
395    /// Moves the member to a voice channel.
396    ///
397    /// Requires the [Move Members] permission.
398    ///
399    /// # Errors
400    ///
401    /// Returns [`Error::Http`] if the member is not currently in a voice channel, or if the
402    /// current user lacks permission.
403    ///
404    /// [Move Members]: Permissions::MOVE_MEMBERS
405    pub async fn move_to_voice_channel(
406        &self,
407        cache_http: impl CacheHttp,
408        channel: impl Into<ChannelId>,
409    ) -> Result<Member> {
410        self.guild_id.move_member(cache_http, self.user.id, channel).await
411    }
412
413    /// Disconnects the member from their voice channel if any.
414    ///
415    /// Requires the [Move Members] permission.
416    ///
417    /// # Errors
418    ///
419    /// Returns [`Error::Http`] if the member is not currently in a voice channel, or if the
420    /// current user lacks permission.
421    ///
422    /// [Move Members]: Permissions::MOVE_MEMBERS
423    pub async fn disconnect_from_voice(&self, cache_http: impl CacheHttp) -> Result<Member> {
424        self.guild_id.disconnect_member(cache_http, self.user.id).await
425    }
426
427    /// Returns the guild-level permissions for the member.
428    ///
429    /// # Examples
430    ///
431    /// ```rust,ignore
432    /// // assuming there's a `member` variable gotten from anything.
433    /// println!("The permission bits for the member are: {}",
434    /// member.permissions(&cache).expect("permissions").bits());
435    /// ```
436    ///
437    /// # Note
438    ///
439    /// You likely want to use Guild::user_permissions_in instead as this function does not consider
440    /// permission overwrites.
441    ///
442    /// # Errors
443    ///
444    /// Returns a [`ModelError::GuildNotFound`] if the guild the member's in could not be
445    /// found in the cache.
446    ///
447    /// And/or returns [`ModelError::ItemMissing`] if the "default channel" of the guild is not
448    /// found.
449    #[cfg(feature = "cache")]
450    #[deprecated = "Use Guild::user_permissions_in, as this doesn't consider permission overwrites"]
451    pub fn permissions(&self, cache: impl AsRef<Cache>) -> Result<Permissions> {
452        let guild = cache.as_ref().guild(self.guild_id).ok_or(ModelError::GuildNotFound)?;
453
454        #[allow(deprecated)]
455        Ok(guild.member_permissions(self))
456    }
457
458    /// Removes a [`Role`] from the member.
459    ///
460    /// **Note**: Requires the [Manage Roles] permission.
461    ///
462    /// # Errors
463    ///
464    /// Returns [`Error::Http`] if a role with the given Id does not exist, or if the current user
465    /// lacks permission.
466    ///
467    /// [Manage Roles]: Permissions::MANAGE_ROLES
468    pub async fn remove_role(
469        &self,
470        http: impl AsRef<Http>,
471        role_id: impl Into<RoleId>,
472    ) -> Result<()> {
473        http.as_ref().remove_member_role(self.guild_id, self.user.id, role_id.into(), None).await
474    }
475
476    /// Removes one or multiple [`Role`]s from the member.
477    ///
478    /// **Note**: Requires the [Manage Roles] permission.
479    ///
480    /// # Errors
481    ///
482    /// Returns [`Error::Http`] if a role with a given Id does not exist, or if the current user
483    /// lacks permission.
484    ///
485    /// [Manage Roles]: Permissions::MANAGE_ROLES
486    pub async fn remove_roles(&self, http: impl AsRef<Http>, role_ids: &[RoleId]) -> Result<()> {
487        for role_id in role_ids {
488            self.remove_role(http.as_ref(), role_id).await?;
489        }
490
491        Ok(())
492    }
493
494    /// Retrieves the full role data for the user's roles.
495    ///
496    /// This is shorthand for manually searching through the Cache.
497    ///
498    /// If role data can not be found for the member, then [`None`] is returned.
499    #[cfg(feature = "cache")]
500    pub fn roles(&self, cache: impl AsRef<Cache>) -> Option<Vec<Role>> {
501        Some(
502            cache
503                .as_ref()
504                .guild(self.guild_id)?
505                .roles
506                .iter()
507                .filter(|(id, _)| self.roles.contains(id))
508                .map(|(_, role)| role.clone())
509                .collect(),
510        )
511    }
512
513    /// Unbans the [`User`] from the guild.
514    ///
515    /// **Note**: Requires the [Ban Members] permission.
516    ///
517    /// # Errors
518    ///
519    /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
520    /// does not have permission to perform bans.
521    ///
522    /// [Ban Members]: Permissions::BAN_MEMBERS
523    #[inline]
524    pub async fn unban(&self, http: impl AsRef<Http>) -> Result<()> {
525        http.as_ref().remove_ban(self.guild_id, self.user.id, None).await
526    }
527
528    /// Returns the formatted URL of the member's per guild avatar, if one exists.
529    ///
530    /// This will produce a WEBP image URL, or GIF if the member has a GIF avatar.
531    #[inline]
532    #[must_use]
533    pub fn avatar_url(&self) -> Option<String> {
534        avatar_url(Some(self.guild_id), self.user.id, self.avatar.as_ref())
535    }
536
537    /// Returns the formatted URL of the member's per guild banner, if one exists.
538    ///
539    /// This will produce a WEBP image URL, or GIF if the member has a GIF avatar.
540    #[must_use]
541    pub fn banner_url(&self) -> Option<String> {
542        user_banner_url(Some(self.guild_id), self.user.id, self.banner.as_ref())
543    }
544
545    /// Retrieves the URL to the current member's avatar, falling back to the user's avatar, then
546    /// default avatar if needed.
547    ///
548    /// This will call [`Self::avatar_url`] first, and if that returns [`None`], it then falls back
549    /// to [`User::face()`].
550    #[inline]
551    #[must_use]
552    pub fn face(&self) -> String {
553        self.avatar_url().unwrap_or_else(|| self.user.face())
554    }
555}
556
557impl fmt::Display for Member {
558    /// Mentions the user so that they receive a notification.
559    ///
560    /// # Examples
561    ///
562    /// ```rust,ignore
563    /// // assumes a `member` has already been bound
564    /// println!("{} is a member!", member);
565    /// ```
566    ///
567    /// This is in the format of `<@USER_ID>`.
568    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569        fmt::Display::fmt(&self.user.mention(), f)
570    }
571}
572
573/// A partial amount of data for a member.
574///
575/// This is used in [`Message`]s from [`Guild`]s.
576///
577/// [Discord docs](https://discord.com/developers/docs/resources/guild#guild-member-object),
578/// subset specification unknown (field type "partial member" is used in
579/// [link](https://discord.com/developers/docs/topics/gateway-events#message-create),
580/// [link](https://discord.com/developers/docs/resources/invite#invite-stage-instance-object),
581/// [link](https://discord.com/developers/docs/topics/gateway-events#message-create),
582/// [link](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure),
583/// [link](https://discord.com/developers/docs/interactions/receiving-and-responding#message-interaction-object))
584#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
585#[derive(Clone, Debug, Deserialize, Serialize)]
586#[non_exhaustive]
587pub struct PartialMember {
588    /// Indicator of whether the member can hear in voice channels.
589    #[serde(default)]
590    pub deaf: bool,
591    /// Timestamp representing the date when the member joined.
592    pub joined_at: Option<Timestamp>,
593    /// Indicator of whether the member can speak in voice channels
594    #[serde(default)]
595    pub mute: bool,
596    /// The member's nickname, if present.
597    ///
598    /// Can't be longer than 32 characters.
599    pub nick: Option<String>,
600    /// Vector of Ids of [`Role`]s given to the member.
601    pub roles: Vec<RoleId>,
602    /// Indicator that the member hasn't accepted the rules of the guild yet.
603    #[serde(default)]
604    pub pending: bool,
605    /// Timestamp representing the date since the member is boosting the guild.
606    pub premium_since: Option<Timestamp>,
607    /// The unique Id of the guild that the member is a part of.
608    ///
609    /// Manually inserted in [`Reaction::deserialize`].
610    pub guild_id: Option<GuildId>,
611    /// Attached User struct.
612    pub user: Option<User>,
613    /// The total permissions of the member in a channel, including overrides.
614    ///
615    /// This is only [`Some`] when returned in an [`Interaction`] object.
616    ///
617    /// [`Interaction`]: crate::model::application::Interaction
618    pub permissions: Option<Permissions>,
619    /// If the member is currently flagged for sending excessive DMs to non-friend server members
620    /// in the last 24 hours.
621    ///
622    /// Will be None or a time in the past if the user is not flagged.
623    pub unusual_dm_activity_until: Option<Timestamp>,
624    /// The member's guild avatar hash
625    pub avatar: Option<ImageHash>,
626    /// The member's guild banner hash
627    pub banner: Option<ImageHash>,
628    /// Information about this member's avatar decoration.
629    pub avatar_decoration_data: Option<AvatarDecorationData>,
630}
631
632impl From<PartialMember> for Member {
633    fn from(partial: PartialMember) -> Self {
634        Member {
635            user: partial.user.unwrap_or_default(),
636            nick: partial.nick,
637            avatar: partial.avatar,
638            banner: partial.banner,
639            roles: partial.roles,
640            joined_at: partial.joined_at,
641            premium_since: partial.premium_since,
642            deaf: partial.deaf,
643            mute: partial.mute,
644            flags: GuildMemberFlags::default(),
645            pending: partial.pending,
646            permissions: partial.permissions,
647            communication_disabled_until: None,
648            guild_id: partial.guild_id.unwrap_or_default(),
649            unusual_dm_activity_until: partial.unusual_dm_activity_until,
650            avatar_decoration_data: partial.avatar_decoration_data,
651        }
652    }
653}
654
655impl From<Member> for PartialMember {
656    fn from(member: Member) -> Self {
657        PartialMember {
658            deaf: member.deaf,
659            joined_at: member.joined_at,
660            mute: member.mute,
661            nick: member.nick,
662            roles: member.roles,
663            pending: member.pending,
664            premium_since: member.premium_since,
665            guild_id: Some(member.guild_id),
666            user: Some(member.user),
667            permissions: member.permissions,
668            unusual_dm_activity_until: member.unusual_dm_activity_until,
669            avatar: member.avatar,
670            banner: member.banner,
671            avatar_decoration_data: member.avatar_decoration_data,
672        }
673    }
674}
675
676#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
677#[derive(Clone, Debug, Deserialize, Serialize)]
678#[non_exhaustive]
679pub struct PartialThreadMember {
680    /// The time the current user last joined the thread.
681    pub join_timestamp: Timestamp,
682    /// Any user-thread settings, currently only used for notifications
683    pub flags: ThreadMemberFlags,
684}
685
686/// A model representing a user in a Guild Thread.
687///
688/// [Discord docs](https://discord.com/developers/docs/resources/channel#thread-member-object),
689/// [extra fields](https://discord.com/developers/docs/topics/gateway-events#thread-member-update-thread-member-update-event-extra-fields).
690#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
691#[derive(Clone, Debug, Deserialize, Serialize)]
692#[non_exhaustive]
693pub struct ThreadMember {
694    #[serde(flatten)]
695    pub inner: PartialThreadMember,
696    /// The id of the thread.
697    pub id: ChannelId,
698    /// The id of the user.
699    pub user_id: UserId,
700    /// Additional information about the user.
701    ///
702    /// This field is only present when `with_member` is set to `true` when calling
703    /// List Thread Members or Get Thread Member, or inside [`ThreadMembersUpdateEvent`].
704    pub member: Option<Member>,
705    /// ID of the guild.
706    ///
707    /// Always present in [`ThreadMemberUpdateEvent`], otherwise `None`.
708    pub guild_id: Option<GuildId>,
709    // According to https://discord.com/developers/docs/topics/gateway-events#thread-members-update,
710    // > the thread member objects will also include the guild member and nullable presence objects
711    // > for each added thread member
712    // Which implies that ThreadMember has a presence field. But https://discord.com/developers/docs/resources/channel#thread-member-object
713    // says that's not true. I'm not adding the presence field here for now
714}
715
716bitflags! {
717    /// Describes extra features of the message.
718    ///
719    /// Discord docs: flags field on [Thread Member](https://discord.com/developers/docs/resources/channel#thread-member-object).
720    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
721    #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
722    pub struct ThreadMemberFlags: u64 {
723        // Not documented.
724        const NOTIFICATIONS = 1 << 0;
725    }
726}