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}