titanium_model/
user.rs

1use crate::snowflake::Snowflake;
2use crate::TitanString;
3use serde::{Deserialize, Serialize};
4
5/// Discord User representation.
6#[derive(Debug, Clone, Deserialize, Serialize)]
7pub struct User<'a> {
8    /// User ID.
9    pub id: Snowflake,
10    /// Username (not unique per se post-pomelo).
11    pub username: TitanString<'a>,
12    /// User's 4-digit Discord tag (deprecated, "0" for pomelo users).
13    pub discriminator: TitanString<'a>,
14    /// User's display name.
15    #[serde(default)]
16    pub global_name: Option<TitanString<'a>>,
17    /// Avatar hash.
18    #[serde(default)]
19    pub avatar: Option<TitanString<'a>>,
20    /// Whether the user is a bot.
21    #[serde(default)]
22    pub bot: bool,
23    /// Whether the user is a system user.
24    #[serde(default)]
25    pub system: bool,
26    /// Whether the user has MFA enabled.
27    #[serde(default)]
28    pub mfa_enabled: Option<bool>,
29    /// Banner hash.
30    #[serde(default)]
31    pub banner: Option<TitanString<'a>>,
32    /// Banner color as integer.
33    #[serde(default)]
34    pub accent_color: Option<u32>,
35    /// User's locale.
36    #[serde(default)]
37    pub locale: Option<TitanString<'a>>,
38    /// Whether email is verified.
39    #[serde(default)]
40    pub verified: Option<bool>,
41    /// User's email (requires email scope).
42    #[serde(default)]
43    pub email: Option<TitanString<'a>>,
44    /// User flags.
45    #[serde(default)]
46    pub flags: Option<u64>,
47    /// Nitro subscription type.
48    #[serde(default)]
49    pub premium_type: Option<u8>,
50    /// Public flags on the user.
51    #[serde(default)]
52    pub public_flags: Option<u64>,
53    /// Avatar decoration data.
54    #[serde(default)]
55    pub avatar_decoration_data: Option<crate::json::Value>,
56}
57
58impl User<'_> {
59    /// Returns the URL of the user's avatar.
60    #[must_use]
61    pub fn avatar_url(&self) -> Option<String> {
62        self.avatar.as_ref().map(|hash| {
63            let ext = if hash.starts_with("a_") { "gif" } else { "png" };
64            format!(
65                "https://cdn.discordapp.com/avatars/{}/{}.{}",
66                self.id, hash, ext
67            )
68        })
69    }
70
71    /// Returns the URL of the user's default avatar.
72    #[must_use]
73    pub fn default_avatar_url(&self) -> String {
74        let index = if self.discriminator == "0" {
75            (self.id.0 >> 22) % 6
76        } else {
77            self.discriminator.parse::<u64>().unwrap_or(0) % 5
78        };
79        format!("https://cdn.discordapp.com/embed/avatars/{index}.png")
80    }
81
82    /// Returns the user's displayed avatar URL (avatar or default).
83    #[must_use]
84    pub fn face(&self) -> String {
85        self.avatar_url()
86            .unwrap_or_else(|| self.default_avatar_url())
87    }
88}
89
90impl crate::Mention for User<'_> {
91    fn mention(&self) -> String {
92        format!("<@{}>", self.id.0)
93    }
94}
95
96/// Partial user for presence updates.
97#[derive(Debug, Clone, Deserialize, Serialize)]
98pub struct PartialUser {
99    pub id: Snowflake,
100}
101
102/// Client status for presence.
103#[derive(Debug, Clone, Deserialize, Serialize)]
104pub struct ClientStatus {
105    #[serde(default)]
106    pub desktop: Option<String>,
107    #[serde(default)]
108    pub mobile: Option<String>,
109    #[serde(default)]
110    pub web: Option<String>,
111}
112
113/// Presence update event.
114#[derive(Debug, Clone, Deserialize, Serialize)]
115pub struct PresenceUpdateEvent {
116    pub user: PartialUser,
117    #[serde(default)]
118    pub guild_id: Option<Snowflake>,
119    pub status: String,
120    #[serde(default)]
121    pub activities: Vec<crate::json::Value>,
122    #[serde(default)]
123    pub client_status: Option<ClientStatus>,
124}