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