Skip to main content

serenity/model/
user.rs

1//! User information-related models.
2
3use std::fmt;
4#[cfg(feature = "model")]
5use std::fmt::Write;
6use std::num::NonZeroU16;
7use std::ops::{Deref, DerefMut};
8
9use serde::{Deserialize, Serialize};
10
11use super::prelude::*;
12#[cfg(feature = "model")]
13use crate::builder::{Builder, CreateMessage, EditProfile};
14#[cfg(all(feature = "cache", feature = "model"))]
15use crate::cache::{Cache, UserRef};
16#[cfg(feature = "collector")]
17use crate::collector::{MessageCollector, ReactionCollector};
18#[cfg(feature = "collector")]
19use crate::gateway::ShardMessenger;
20#[cfg(feature = "model")]
21use crate::http::CacheHttp;
22#[cfg(feature = "model")]
23use crate::internal::prelude::*;
24#[cfg(feature = "model")]
25use crate::json::json;
26#[cfg(feature = "model")]
27use crate::model::utils::{avatar_url, user_banner_url};
28
29/// Used with `#[serde(with|deserialize_with|serialize_with)]`
30///
31/// # Examples
32///
33/// ```rust,ignore
34/// use std::num::NonZeroU16;
35///
36/// #[derive(Deserialize, Serialize)]
37/// struct A {
38///     #[serde(with = "discriminator")]
39///     id: Option<NonZeroU16>,
40/// }
41///
42/// #[derive(Deserialize)]
43/// struct B {
44///     #[serde(deserialize_with = "discriminator::deserialize")]
45///     id: Option<NonZeroU16>,
46/// }
47///
48/// #[derive(Serialize)]
49/// struct C {
50///     #[serde(serialize_with = "discriminator::serialize")]
51///     id: Option<NonZeroU16>,
52/// }
53/// ```
54pub(crate) mod discriminator {
55    use std::fmt;
56
57    use serde::de::{Error, Visitor};
58
59    struct DiscriminatorVisitor;
60
61    impl Visitor<'_> for DiscriminatorVisitor {
62        type Value = u16;
63
64        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65            formatter.write_str("string or integer discriminator")
66        }
67
68        fn visit_u64<E: Error>(self, value: u64) -> Result<Self::Value, E> {
69            u16::try_from(value).map_err(Error::custom)
70        }
71
72        fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
73            s.parse().map_err(Error::custom)
74        }
75    }
76
77    use std::num::NonZeroU16;
78
79    use serde::{Deserializer, Serializer};
80
81    pub fn deserialize<'de, D: Deserializer<'de>>(
82        deserializer: D,
83    ) -> Result<Option<NonZeroU16>, D::Error> {
84        deserializer.deserialize_option(OptionalDiscriminatorVisitor)
85    }
86
87    #[allow(clippy::trivially_copy_pass_by_ref, clippy::ref_option)]
88    pub fn serialize<S: Serializer>(
89        value: &Option<NonZeroU16>,
90        serializer: S,
91    ) -> Result<S::Ok, S::Error> {
92        match value {
93            Some(value) => serializer.serialize_some(&format_args!("{value:04}")),
94            None => serializer.serialize_none(),
95        }
96    }
97
98    struct OptionalDiscriminatorVisitor;
99
100    impl<'de> Visitor<'de> for OptionalDiscriminatorVisitor {
101        type Value = Option<NonZeroU16>;
102
103        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
104            formatter.write_str("optional string or integer discriminator")
105        }
106
107        fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
108            Ok(None)
109        }
110
111        fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
112            Ok(None)
113        }
114
115        fn visit_some<D: Deserializer<'de>>(
116            self,
117            deserializer: D,
118        ) -> Result<Self::Value, D::Error> {
119            deserializer.deserialize_any(DiscriminatorVisitor).map(NonZeroU16::new)
120        }
121    }
122}
123
124/// Information about the current user.
125///
126/// [Discord docs](https://discord.com/developers/docs/resources/user#user-object).
127#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
128#[derive(Clone, Debug, Default, Deserialize, Serialize)]
129#[serde(transparent)]
130pub struct CurrentUser(User);
131
132impl Deref for CurrentUser {
133    type Target = User;
134
135    fn deref(&self) -> &Self::Target {
136        &self.0
137    }
138}
139
140impl DerefMut for CurrentUser {
141    fn deref_mut(&mut self) -> &mut Self::Target {
142        &mut self.0
143    }
144}
145
146impl From<CurrentUser> for User {
147    fn from(user: CurrentUser) -> Self {
148        user.0
149    }
150}
151
152#[cfg(feature = "model")]
153impl CurrentUser {
154    /// Edits the current user's profile settings.
155    ///
156    /// This mutates the current user in-place.
157    ///
158    /// Refer to [`EditProfile`]'s documentation for its methods.
159    ///
160    /// # Examples
161    ///
162    /// Change the avatar:
163    ///
164    /// ```rust,no_run
165    /// # use serenity::builder::{EditProfile, CreateAttachment};
166    /// # use serenity::http::Http;
167    /// # use serenity::model::user::CurrentUser;
168    /// #
169    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
170    /// # let http: Http = unimplemented!();
171    /// # let mut user = CurrentUser::default();
172    /// let avatar = CreateAttachment::path("./avatar.png").await?;
173    /// user.edit(&http, EditProfile::new().avatar(&avatar)).await;
174    /// # Ok(())
175    /// # }
176    /// ```
177    ///
178    /// # Errors
179    ///
180    /// Returns an [`Error::Http`] if an invalid value is set. May also return an [`Error::Json`]
181    /// if there is an error in deserializing the API response.
182    pub async fn edit(&mut self, cache_http: impl CacheHttp, builder: EditProfile) -> Result<()> {
183        *self = builder.execute(cache_http, ()).await?;
184        Ok(())
185    }
186}
187
188/// The representation of a user's status.
189///
190/// [Discord docs](https://discord.com/developers/docs/topics/gateway-events#update-presence-status-types).
191#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
192#[derive(
193    Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize,
194)]
195#[non_exhaustive]
196pub enum OnlineStatus {
197    #[serde(rename = "dnd")]
198    DoNotDisturb,
199    #[serde(rename = "idle")]
200    Idle,
201    #[serde(rename = "invisible")]
202    Invisible,
203    #[serde(rename = "offline")]
204    Offline,
205    #[serde(rename = "online")]
206    #[default]
207    Online,
208}
209
210impl OnlineStatus {
211    #[must_use]
212    pub fn name(&self) -> &str {
213        match *self {
214            OnlineStatus::DoNotDisturb => "dnd",
215            OnlineStatus::Idle => "idle",
216            OnlineStatus::Invisible => "invisible",
217            OnlineStatus::Offline => "offline",
218            OnlineStatus::Online => "online",
219        }
220    }
221}
222
223/// Information about a user.
224///
225/// [Discord docs](https://discord.com/developers/docs/resources/user#user-object), existence of
226/// additional partial member field documented [here](https://discord.com/developers/docs/topics/gateway-events#message-create).
227#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
228#[derive(Clone, Debug, Default, Deserialize, Serialize)]
229#[non_exhaustive]
230pub struct User {
231    /// The unique Id of the user. Can be used to calculate the account's creation date.
232    pub id: UserId,
233    /// The account's username. Changing username will trigger a discriminator
234    /// change if the username+discriminator pair becomes non-unique. Unless the account has
235    /// migrated to a next generation username, which does not have a discriminant.
236    #[serde(rename = "username")]
237    pub name: String,
238    /// The account's discriminator to differentiate the user from others with
239    /// the same [`Self::name`]. The name+discriminator pair is always unique.
240    /// If the discriminator is not present, then this is a next generation username
241    /// which is implicitly unique.
242    #[serde(default, skip_serializing_if = "Option::is_none", with = "discriminator")]
243    pub discriminator: Option<NonZeroU16>,
244    /// The account's display name, if it is set.
245    /// For bots this is the application name.
246    pub global_name: Option<String>,
247    /// Optional avatar hash.
248    pub avatar: Option<ImageHash>,
249    /// Indicator of whether the user is a bot.
250    #[serde(default)]
251    pub bot: bool,
252    /// Whether the user is an Official Discord System user (part of the urgent message system).
253    #[serde(default)]
254    pub system: bool,
255    /// Whether the user has two factor enabled on their account
256    #[serde(default)]
257    pub mfa_enabled: bool,
258    /// Optional banner hash.
259    ///
260    /// **Note**: This will only be present if the user is fetched via Rest API, e.g. with
261    /// [`crate::http::Http::get_user`].
262    pub banner: Option<ImageHash>,
263    /// The user's banner colour encoded as an integer representation of hexadecimal colour code
264    ///
265    /// **Note**: This will only be present if the user is fetched via Rest API, e.g. with
266    /// [`crate::http::Http::get_user`].
267    #[serde(rename = "accent_color")]
268    pub accent_colour: Option<Colour>,
269    /// The user's chosen language option
270    pub locale: Option<String>,
271    /// Whether the email on this account has been verified
272    ///
273    /// Requires [`Scope::Email`]
274    pub verified: Option<bool>,
275    /// The user's email
276    ///
277    /// Requires [`Scope::Email`]
278    pub email: Option<String>,
279    /// The flags on a user's account
280    #[serde(default)]
281    pub flags: UserPublicFlags,
282    /// The type of Nitro subscription on a user's account
283    #[serde(default)]
284    pub premium_type: PremiumType,
285    /// The public flags on a user's account
286    pub public_flags: Option<UserPublicFlags>,
287    /// Only included in [`Message::mentions`] for messages from the gateway.
288    ///
289    /// [Discord docs](https://discord.com/developers/docs/topics/gateway-events#message-create-message-create-extra-fields).
290    // Box required to avoid infinitely recursive types
291    pub member: Option<Box<PartialMember>>,
292    /// The primary guild and tag the user has active.
293    ///
294    /// Note: just because this guild is populated does not mean the tag is visible.
295    pub primary_guild: Option<PrimaryGuild>,
296    /// Information about this user's avatar decoration.
297    pub avatar_decoration_data: Option<AvatarDecorationData>,
298    /// The collectibles the user currently has active, excluding avatar decorations and profile
299    /// effects.
300    pub collectibles: Option<Collectibles>,
301}
302
303enum_number! {
304    /// Premium types denote the level of premium a user has. Visit the [Nitro](https://discord.com/nitro)
305    /// page to learn more about the premium plans Discord currently offers.
306    ///
307    /// [Discord docs](https://discord.com/developers/docs/resources/user#user-object-premium-types).
308    #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
309    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
310    #[serde(from = "u8", into = "u8")]
311    #[non_exhaustive]
312    pub enum PremiumType {
313        #[default]
314        None = 0,
315        NitroClassic = 1,
316        Nitro = 2,
317        NitroBasic = 3,
318        _ => Unknown(u8),
319    }
320}
321
322bitflags! {
323    /// User's public flags
324    ///
325    /// [Discord docs](https://discord.com/developers/docs/resources/user#user-object-user-flags).
326    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
327    #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
328    pub struct UserPublicFlags: u32 {
329        /// User's flag as discord employee
330        const DISCORD_EMPLOYEE = 1 << 0;
331        /// User's flag as partnered server owner
332        const PARTNERED_SERVER_OWNER = 1 << 1;
333        /// User's flag as hypesquad events
334        const HYPESQUAD_EVENTS = 1 << 2;
335        /// User's flag as bug hunter level 1
336        const BUG_HUNTER_LEVEL_1 = 1 << 3;
337        /// User's flag as house bravery
338        const HOUSE_BRAVERY = 1 << 6;
339        /// User's flag as house brilliance
340        const HOUSE_BRILLIANCE = 1 << 7;
341        /// User's flag as house balance
342        const HOUSE_BALANCE = 1 << 8;
343        /// User's flag as early supporter
344        const EARLY_SUPPORTER = 1 << 9;
345        /// User's flag as team user
346        const TEAM_USER = 1 << 10;
347        /// User's flag as system
348        const SYSTEM = 1 << 12;
349        /// User's flag as bug hunter level 2
350        const BUG_HUNTER_LEVEL_2 = 1 << 14;
351        /// User's flag as verified bot
352        const VERIFIED_BOT = 1 << 16;
353        /// User's flag as early verified bot developer
354        const EARLY_VERIFIED_BOT_DEVELOPER = 1 << 17;
355        /// User's flag as discord certified moderator
356        const DISCORD_CERTIFIED_MODERATOR = 1 << 18;
357        /// Bot's running with HTTP interactions
358        const BOT_HTTP_INTERACTIONS = 1 << 19;
359        /// User's flag for suspected spam activity.
360        #[cfg(feature = "unstable_discord_api")]
361        const SPAMMER = 1 << 20;
362        /// User's flag as active developer
363        const ACTIVE_DEVELOPER = 1 << 22;
364    }
365}
366
367/// User's Primary Guild object
368///
369/// [Discord docs](https://discord.com/developers/docs/resources/user#user-object-user-primary-guild)
370#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
371#[derive(Clone, Debug, Default, Deserialize, Serialize)]
372#[non_exhaustive]
373pub struct PrimaryGuild {
374    /// The id of the user's primary guild.
375    pub identity_guild_id: Option<GuildId>,
376    /// Whether the user is displaying the primary guild's server tag. This can be null if the
377    /// system clears the identity, e.g. because the server no longer supports tags.
378    pub identity_enabled: Option<bool>,
379    /// The text of the [`User`]'s server tag.
380    pub tag: Option<String>,
381    /// The hash of the server badge.
382    pub badge: Option<ImageHash>,
383}
384
385#[cfg(feature = "model")]
386impl PrimaryGuild {
387    #[must_use]
388    /// Returns the formatted URL of the badge's icon, if one exists.
389    pub fn badge_url(&self) -> Option<String> {
390        primary_guild_badge_url(self.identity_guild_id, self.badge.as_ref())
391    }
392}
393
394#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
395#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
396#[non_exhaustive]
397/// The data for a [`User`]'s avatar decoration.
398///
399/// [Discord docs](https://discord.com/developers/docs/resources/user#avatar-decoration-data-object).
400pub struct AvatarDecorationData {
401    /// The avatar decoration hash
402    pub asset: ImageHash,
403    /// id of the avatar decoration's SKU
404    pub sku_id: SkuId,
405}
406
407#[cfg(feature = "model")]
408impl AvatarDecorationData {
409    #[must_use]
410    /// Returns the formatted URL of the decoration.
411    pub fn decoration_url(&self) -> String {
412        avatar_decoration_url(&self.asset)
413    }
414}
415
416/// The collectibles the user has, excluding Avatar Decorations and Profile Effects.
417///
418/// [Discord docs](https://discord.com/developers/docs/resources/user#collectibles).
419#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
420#[derive(Clone, Debug, Deserialize, Serialize)]
421#[non_exhaustive]
422pub struct Collectibles {
423    /// The [`User`]'s nameplate, if they have one.
424    pub nameplate: Option<Nameplate>,
425}
426
427/// A nameplate, shown on the member list on official clients.
428///
429/// [Discord docs](https://discord.com/developers/docs/resources/user#nameplate-nameplate-structure).
430#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
431#[derive(Clone, Debug, Deserialize, Serialize)]
432#[non_exhaustive]
433pub struct Nameplate {
434    /// Id of the nameplate SKU
435    pub sku_id: SkuId,
436    /// Path to the nameplate asset.
437    pub asset: String,
438    /// The label of this nameplate.
439    pub label: String,
440    /// Background color of the nameplate, one of: `crimson`, `berry`, `sky`, `teal`, `forest`,
441    /// `bubble_gum`, `violet`, `cobalt`, `clover`, `lemon`, `white`
442    pub palette: String,
443}
444
445#[cfg(all(feature = "unstable_discord_api", feature = "model"))]
446impl Nameplate {
447    /// Gets the static version of the nameplate's url.
448    #[must_use]
449    pub fn static_url(&self) -> String {
450        static_nameplate_url(&self.asset)
451    }
452
453    /// Gets the animated version of the nameplate's url.
454    #[must_use]
455    pub fn url(&self) -> String {
456        nameplate_url(&self.asset)
457    }
458}
459
460use std::hash::{Hash, Hasher};
461
462impl PartialEq for User {
463    fn eq(&self, other: &Self) -> bool {
464        self.id == other.id
465    }
466}
467
468impl Eq for User {}
469
470impl Hash for User {
471    fn hash<H: Hasher>(&self, hasher: &mut H) {
472        self.id.hash(hasher);
473    }
474}
475
476#[cfg(feature = "model")]
477impl User {
478    /// Returns the formatted URL of the user's icon, if one exists.
479    ///
480    /// This will produce a WEBP image URL, or GIF if the user has a GIF avatar.
481    #[inline]
482    #[must_use]
483    pub fn avatar_url(&self) -> Option<String> {
484        avatar_url(None, self.id, self.avatar.as_ref())
485    }
486
487    /// Returns the formatted URL of the user's banner, if one exists.
488    ///
489    /// This will produce a WEBP image URL, or GIF if the user has a GIF banner.
490    #[inline]
491    #[must_use]
492    pub fn banner_url(&self) -> Option<String> {
493        user_banner_url(None, self.id, self.banner.as_ref())
494    }
495
496    /// Creates a direct message channel between the [current user] and the user. This can also
497    /// retrieve the channel if one already exists.
498    ///
499    /// [current user]: CurrentUser
500    ///
501    /// # Errors
502    ///
503    /// See [`UserId::create_dm_channel`] for what errors may be returned.
504    #[inline]
505    pub async fn create_dm_channel(&self, cache_http: impl CacheHttp) -> Result<PrivateChannel> {
506        if self.bot {
507            return Err(Error::Model(ModelError::MessagingBot));
508        }
509
510        self.id.create_dm_channel(cache_http).await
511    }
512
513    /// Retrieves the time that this user was created at.
514    #[inline]
515    #[must_use]
516    pub fn created_at(&self) -> Timestamp {
517        self.id.created_at()
518    }
519
520    /// Returns the formatted URL to the user's default avatar URL.
521    ///
522    /// This will produce a PNG URL.
523    #[inline]
524    #[must_use]
525    pub fn default_avatar_url(&self) -> String {
526        default_avatar_url(self)
527    }
528
529    /// Sends a message to a user through a direct message channel. This is a channel that can only
530    /// be accessed by you and the recipient.
531    ///
532    /// # Examples
533    ///
534    /// See [`UserId::direct_message`] for examples.
535    ///
536    /// # Errors
537    ///
538    /// See [`UserId::direct_message`] for errors.
539    pub async fn direct_message(
540        &self,
541        cache_http: impl CacheHttp,
542        builder: CreateMessage,
543    ) -> Result<Message> {
544        self.id.direct_message(cache_http, builder).await
545    }
546
547    /// Calculates the user's display name.
548    ///
549    /// The global name takes priority over the user's username if it exists.
550    ///
551    /// Note: Guild specific information is not included as this is only available on the [Member].
552    #[inline]
553    #[must_use]
554    pub fn display_name(&self) -> &str {
555        self.global_name.as_deref().unwrap_or(&self.name)
556    }
557
558    /// This is an alias of [`Self::direct_message`].
559    #[allow(clippy::missing_errors_doc)]
560    #[inline]
561    pub async fn dm(&self, cache_http: impl CacheHttp, builder: CreateMessage) -> Result<Message> {
562        self.direct_message(cache_http, builder).await
563    }
564
565    /// Retrieves the URL to the user's avatar, falling back to the default avatar if needed.
566    ///
567    /// This will call [`Self::avatar_url`] first, and if that returns [`None`], it then falls back
568    /// to [`Self::default_avatar_url`].
569    #[must_use]
570    pub fn face(&self) -> String {
571        self.avatar_url().unwrap_or_else(|| self.default_avatar_url())
572    }
573
574    /// Retrieves the URL to the static version of the user's avatar, falling back to the default
575    /// avatar if needed.
576    ///
577    /// This will call [`Self::static_avatar_url`] first, and if that returns [`None`], it then
578    /// falls back to [`Self::default_avatar_url`].
579    #[must_use]
580    pub fn static_face(&self) -> String {
581        self.static_avatar_url().unwrap_or_else(|| self.default_avatar_url())
582    }
583
584    /// Check if a user has a [`Role`]. This will retrieve the [`Guild`] from the [`Cache`] if it
585    /// is available, and then check if that guild has the given [`Role`].
586    ///
587    /// # Examples
588    ///
589    /// Check if a guild has a [`Role`] by Id:
590    ///
591    /// ```rust,ignore
592    /// // Assumes a 'guild_id' and `role_id` have already been bound
593    /// let _ = message.author.has_role(guild_id, role_id);
594    /// ```
595    ///
596    /// [`Cache`]: crate::cache::Cache
597    ///
598    /// # Errors
599    ///
600    /// Returns an [`Error::Http`] if the given [`Guild`] is unavailable, if that [`Role`] does not
601    /// exist in the given [`Guild`], or if the given [`User`] is not in that [`Guild`].
602    ///
603    /// May also return an [`Error::Json`] if there is an error in deserializing the API response.
604    #[inline]
605    pub async fn has_role(
606        &self,
607        cache_http: impl CacheHttp,
608        guild_id: impl Into<GuildId>,
609        role: impl Into<RoleId>,
610    ) -> Result<bool> {
611        guild_id.into().member(cache_http, self).await.map(|m| m.roles.contains(&role.into()))
612    }
613
614    /// Refreshes the information about the user.
615    ///
616    /// Replaces the instance with the data retrieved over the REST API.
617    ///
618    /// # Errors
619    ///
620    /// See [`UserId::to_user`] for what errors may be returned.
621    #[inline]
622    pub async fn refresh(&mut self, cache_http: impl CacheHttp) -> Result<()> {
623        *self = self.id.to_user(cache_http).await?;
624
625        Ok(())
626    }
627
628    /// Returns a static formatted URL of the user's icon, if one exists.
629    ///
630    /// This will always produce a WEBP image URL.
631    #[inline]
632    #[must_use]
633    pub fn static_avatar_url(&self) -> Option<String> {
634        static_avatar_url(self.id, self.avatar.as_ref())
635    }
636
637    /// Returns the "tag" for the user.
638    ///
639    /// The "tag" is defined as "username#discriminator", such as "zeyla#5479".
640    ///
641    /// # Examples
642    ///
643    /// Make a command to tell the user what their tag is:
644    ///
645    /// ```rust,no_run
646    /// # use serenity::prelude::*;
647    /// # use serenity::model::prelude::*;
648    /// # struct Handler;
649    ///
650    /// #[serenity::async_trait]
651    /// # #[cfg(feature = "client")]
652    /// impl EventHandler for Handler {
653    ///     async fn message(&self, context: Context, msg: Message) {
654    ///         if msg.content == "!mytag" {
655    ///             let content = format!("Your tag is: {}", msg.author.tag());
656    ///             let _ = msg.channel_id.say(&context.http, &content).await;
657    ///         }
658    ///     }
659    /// }
660    /// ```
661    #[inline]
662    #[must_use]
663    pub fn tag(&self) -> String {
664        tag(&self.name, self.discriminator)
665    }
666
667    /// Returns the user's nickname in the given `guild_id`.
668    ///
669    /// If none is used, it returns [`None`].
670    #[inline]
671    pub async fn nick_in(
672        &self,
673        cache_http: impl CacheHttp,
674        guild_id: impl Into<GuildId>,
675    ) -> Option<String> {
676        let guild_id = guild_id.into();
677
678        // This can't be removed because `GuildId::member` clones the entire `Member` struct if
679        // it's present in the cache, which is expensive.
680        #[cfg(feature = "cache")]
681        {
682            if let Some(cache) = cache_http.cache() {
683                if let Some(guild) = guild_id.to_guild_cached(cache) {
684                    if let Some(member) = guild.members.get(&self.id) {
685                        return member.nick.clone();
686                    }
687                }
688            }
689        }
690
691        // At this point we're guaranteed to do an API call.
692        guild_id.member(cache_http, &self.id).await.ok().and_then(|member| member.nick)
693    }
694
695    /// Returns a builder which can be awaited to obtain a message or stream of messages sent by
696    /// this user.
697    #[cfg(feature = "collector")]
698    pub fn await_reply(&self, shard_messenger: impl AsRef<ShardMessenger>) -> MessageCollector {
699        MessageCollector::new(shard_messenger).author_id(self.id)
700    }
701
702    /// Same as [`Self::await_reply`].
703    #[cfg(feature = "collector")]
704    pub fn await_replies(&self, shard_messenger: impl AsRef<ShardMessenger>) -> MessageCollector {
705        self.await_reply(shard_messenger)
706    }
707
708    /// Returns a builder which can be awaited to obtain a reaction or stream of reactions sent by
709    /// this user.
710    #[cfg(feature = "collector")]
711    pub fn await_reaction(&self, shard_messenger: impl AsRef<ShardMessenger>) -> ReactionCollector {
712        ReactionCollector::new(shard_messenger).author_id(self.id)
713    }
714
715    /// Same as [`Self::await_reaction`].
716    #[cfg(feature = "collector")]
717    pub fn await_reactions(
718        &self,
719        shard_messenger: impl AsRef<ShardMessenger>,
720    ) -> ReactionCollector {
721        self.await_reaction(shard_messenger)
722    }
723}
724
725impl fmt::Display for User {
726    /// Formats a string which will mention the user.
727    // This is in the format of: `<@USER_ID>`
728    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
729        fmt::Display::fmt(&self.id.mention(), f)
730    }
731}
732
733#[cfg(feature = "model")]
734impl UserId {
735    /// Creates a direct message channel between the [current user] and the user. This can also
736    /// retrieve the channel if one already exists.
737    ///
738    /// # Errors
739    ///
740    /// Returns [`Error::Http`] if a [`User`] with that [`UserId`] does not exist, or is otherwise
741    /// unavailable.
742    ///
743    /// May also return an [`Error::Json`] if there is an error in deserializing the channel data
744    /// returned by the Discord API.
745    ///
746    /// [current user]: CurrentUser
747    pub async fn create_dm_channel(self, cache_http: impl CacheHttp) -> Result<PrivateChannel> {
748        #[cfg(feature = "temp_cache")]
749        if let Some(cache) = cache_http.cache() {
750            if let Some(private_channel) = cache.temp_private_channels.get(&self) {
751                return Ok(PrivateChannel::clone(&private_channel));
752            }
753        }
754
755        let map = json!({
756            "recipient_id": self,
757        });
758
759        let channel = cache_http.http().create_private_channel(&map).await?;
760
761        #[cfg(feature = "temp_cache")]
762        if let Some(cache) = cache_http.cache() {
763            use crate::cache::MaybeOwnedArc;
764
765            let cached_channel = MaybeOwnedArc::new(channel.clone());
766            cache.temp_private_channels.insert(self, cached_channel);
767        }
768
769        Ok(channel)
770    }
771
772    /// Sends a message to a user through a direct message channel. This is a channel that can only
773    /// be accessed by you and the recipient.
774    ///
775    /// # Examples
776    ///
777    /// When a user sends a message with a content of `"~help"`, DM the author a help message
778    ///
779    /// ```rust,no_run
780    /// # use serenity::prelude::*;
781    /// # use serenity::model::prelude::*;
782    /// # struct Handler;
783    /// use serenity::builder::CreateMessage;
784    ///
785    /// #[serenity::async_trait]
786    /// # #[cfg(feature = "client")]
787    /// impl EventHandler for Handler {
788    ///     async fn message(&self, ctx: Context, msg: Message) {
789    ///         if msg.content == "~help" {
790    ///             let builder = CreateMessage::new().content("Helpful info here.");
791    ///
792    ///             if let Err(why) = msg.author.id.direct_message(&ctx, builder).await {
793    ///                 println!("Err sending help: {why:?}");
794    ///                 let _ = msg.reply(&ctx, "There was an error DMing you help.").await;
795    ///             };
796    ///         }
797    ///     }
798    /// }
799    /// ```
800    ///
801    /// # Errors
802    ///
803    /// Returns a [`ModelError::MessagingBot`] if the user being direct messaged is a bot user.
804    ///
805    /// May also return an [`Error::Http`] if the user cannot be sent a direct message.
806    ///
807    /// Returns an [`Error::Json`] if there is an error deserializing the API response.
808    pub async fn direct_message(
809        self,
810        cache_http: impl CacheHttp,
811        builder: CreateMessage,
812    ) -> Result<Message> {
813        self.create_dm_channel(&cache_http).await?.send_message(cache_http, builder).await
814    }
815
816    /// This is an alias of [`Self::direct_message`].
817    #[allow(clippy::missing_errors_doc)]
818    #[inline]
819    pub async fn dm(self, cache_http: impl CacheHttp, builder: CreateMessage) -> Result<Message> {
820        self.direct_message(cache_http, builder).await
821    }
822
823    /// Attempts to find a [`User`] by its Id in the cache.
824    #[cfg(feature = "cache")]
825    #[inline]
826    pub fn to_user_cached(self, cache: &impl AsRef<Cache>) -> Option<UserRef<'_>> {
827        cache.as_ref().user(self)
828    }
829
830    /// First attempts to find a [`User`] by its Id in the cache, upon failure requests it via the
831    /// REST API.
832    ///
833    /// **Note**: If the cache is not enabled, REST API will be used only.
834    ///
835    /// **Note**: If the cache is enabled, you might want to enable the `temp_cache` feature to
836    /// cache user data retrieved by this function for a short duration.
837    ///
838    /// # Errors
839    ///
840    /// May return an [`Error::Http`] if a [`User`] with that [`UserId`] does not exist, or
841    /// otherwise cannot be fetched.
842    ///
843    /// May also return an [`Error::Json`] if there is an error in deserializing the user.
844    #[inline]
845    pub async fn to_user(self, cache_http: impl CacheHttp) -> Result<User> {
846        #[cfg(feature = "cache")]
847        {
848            if let Some(cache) = cache_http.cache() {
849                if let Some(user) = cache.user(self) {
850                    return Ok(user.clone());
851                }
852            }
853        }
854
855        let user = cache_http.http().get_user(self).await?;
856
857        #[cfg(all(feature = "cache", feature = "temp_cache"))]
858        {
859            if let Some(cache) = cache_http.cache() {
860                use crate::cache::MaybeOwnedArc;
861
862                let cached_user = MaybeOwnedArc::new(user.clone());
863                cache.temp_users.insert(cached_user.id, cached_user);
864            }
865        }
866
867        Ok(user)
868    }
869}
870
871impl From<Member> for UserId {
872    /// Gets the Id of a [`Member`].
873    fn from(member: Member) -> UserId {
874        member.user.id
875    }
876}
877
878impl From<&Member> for UserId {
879    /// Gets the Id of a [`Member`].
880    fn from(member: &Member) -> UserId {
881        member.user.id
882    }
883}
884
885impl From<User> for UserId {
886    /// Gets the Id of a [`User`].
887    fn from(user: User) -> UserId {
888        user.id
889    }
890}
891
892impl From<&User> for UserId {
893    /// Gets the Id of a [`User`].
894    fn from(user: &User) -> UserId {
895        user.id
896    }
897}
898
899#[cfg(feature = "model")]
900fn default_avatar_url(user: &User) -> String {
901    let avatar_id = if let Some(discriminator) = user.discriminator {
902        discriminator.get() % 5 // Legacy username system
903    } else {
904        ((user.id.get() >> 22) % 6) as u16 // New username system
905    };
906
907    cdn!("/embed/avatars/{}.png", avatar_id)
908}
909
910#[cfg(feature = "model")]
911fn static_avatar_url(user_id: UserId, hash: Option<&ImageHash>) -> Option<String> {
912    hash.map(|hash| cdn!("/avatars/{}/{}.webp?size=1024", user_id, hash))
913}
914
915#[cfg(feature = "model")]
916fn tag(name: &str, discriminator: Option<NonZeroU16>) -> String {
917    // 32: max length of username
918    // 1: `#`
919    // 4: max length of discriminator
920    let mut tag = String::with_capacity(37);
921    tag.push_str(name);
922    if let Some(discriminator) = discriminator {
923        tag.push('#');
924        write!(tag, "{discriminator:04}").expect("writing to a string should never fail");
925    }
926    tag
927}
928
929#[cfg(feature = "model")]
930fn primary_guild_badge_url(guild_id: Option<GuildId>, hash: Option<&ImageHash>) -> Option<String> {
931    if let Some(guild_id) = guild_id {
932        return hash.map(|hash| cdn!("/guild-tag-badges/{}/{}.png?size=1024", guild_id, hash));
933    }
934
935    None
936}
937
938#[cfg(feature = "model")]
939fn avatar_decoration_url(hash: &ImageHash) -> String {
940    cdn!("/avatar-decoration-presets/{}.png?size=1024", hash)
941}
942
943#[cfg(all(feature = "unstable_discord_api", feature = "model"))]
944fn nameplate_url(path: &str) -> String {
945    cdn!("https://cdn.discordapp.com/assets/collectibles/{}/asset.webm", path)
946}
947
948#[cfg(all(feature = "unstable_discord_api", feature = "model"))]
949#[cfg(feature = "model")]
950fn static_nameplate_url(path: &str) -> String {
951    cdn!("https://cdn.discordapp.com/assets/collectibles/{}/static.png", path)
952}
953
954#[cfg(test)]
955mod test {
956    use std::num::NonZeroU16;
957
958    #[test]
959    fn test_discriminator_serde() {
960        use serde::{Deserialize, Serialize};
961
962        use super::discriminator;
963        use crate::json::{assert_json, json};
964
965        #[derive(Debug, PartialEq, Deserialize, Serialize)]
966        struct User {
967            #[serde(default, skip_serializing_if = "Option::is_none", with = "discriminator")]
968            discriminator: Option<NonZeroU16>,
969        }
970
971        let user = User {
972            discriminator: NonZeroU16::new(123),
973        };
974        assert_json(&user, json!({"discriminator": "0123"}));
975
976        let user_no_discriminator = User {
977            discriminator: None,
978        };
979        assert_json(&user_no_discriminator, json!({}));
980    }
981
982    #[cfg(feature = "model")]
983    mod model {
984        use std::num::NonZeroU16;
985        use std::str::FromStr;
986
987        use crate::model::id::UserId;
988        use crate::model::misc::ImageHash;
989        use crate::model::user::User;
990
991        #[test]
992        fn test_core() {
993            let mut user = User {
994                id: UserId::new(210),
995                avatar: Some(ImageHash::from_str("fb211703bcc04ee612c88d494df0272f").unwrap()),
996                discriminator: NonZeroU16::new(1432),
997                name: "test".to_string(),
998                ..Default::default()
999            };
1000
1001            let expected = "/avatars/210/fb211703bcc04ee612c88d494df0272f.webp?size=1024";
1002            assert!(user.avatar_url().unwrap().ends_with(expected));
1003            assert!(user.static_avatar_url().unwrap().ends_with(expected));
1004
1005            user.avatar = Some(ImageHash::from_str("a_fb211703bcc04ee612c88d494df0272f").unwrap());
1006            let expected = "/avatars/210/a_fb211703bcc04ee612c88d494df0272f.gif?size=1024";
1007            assert!(user.avatar_url().unwrap().ends_with(expected));
1008            let expected = "/avatars/210/a_fb211703bcc04ee612c88d494df0272f.webp?size=1024";
1009            assert!(user.static_avatar_url().unwrap().ends_with(expected));
1010
1011            user.avatar = None;
1012            assert!(user.avatar_url().is_none());
1013
1014            assert_eq!(user.tag(), "test#1432");
1015        }
1016
1017        #[test]
1018        fn default_avatars() {
1019            let mut user = User {
1020                discriminator: None,
1021                id: UserId::new(737323631117598811),
1022                ..Default::default()
1023            };
1024
1025            // New username system
1026            assert!(user.default_avatar_url().ends_with("5.png"));
1027
1028            // Legacy username system
1029            user.discriminator = NonZeroU16::new(1);
1030            assert!(user.default_avatar_url().ends_with("1.png"));
1031            user.discriminator = NonZeroU16::new(2);
1032            assert!(user.default_avatar_url().ends_with("2.png"));
1033            user.discriminator = NonZeroU16::new(3);
1034            assert!(user.default_avatar_url().ends_with("3.png"));
1035            user.discriminator = NonZeroU16::new(4);
1036            assert!(user.default_avatar_url().ends_with("4.png"));
1037        }
1038    }
1039}