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<'a> User<'a> {
59    /// Returns the URL of the user's avatar.
60    pub fn avatar_url(&self) -> Option<String> {
61        self.avatar.as_ref().map(|hash| {
62            let ext = if hash.starts_with("a_") { "gif" } else { "png" };
63            format!(
64                "https://cdn.discordapp.com/avatars/{}/{}.{}",
65                self.id, hash, ext
66            )
67        })
68    }
69
70    /// Returns the URL of the user's default avatar.
71    pub fn default_avatar_url(&self) -> String {
72        let index = if self.discriminator == "0" {
73            (self.id.0 >> 22) % 6
74        } else {
75            self.discriminator.parse::<u64>().unwrap_or(0) % 5
76        };
77        format!("https://cdn.discordapp.com/embed/avatars/{}.png", index)
78    }
79
80    /// Returns the user's displayed avatar URL (avatar or default).
81    pub fn face(&self) -> String {
82        self.avatar_url()
83            .unwrap_or_else(|| self.default_avatar_url())
84    }
85}
86
87impl<'a> crate::Mention for User<'a> {
88    fn mention(&self) -> String {
89        format!("<@{}>", self.id.0)
90    }
91}
92
93/// Partial user for presence updates.
94#[derive(Debug, Clone, Deserialize, Serialize)]
95pub struct PartialUser {
96    pub id: Snowflake,
97}
98
99/// Client status for presence.
100#[derive(Debug, Clone, Deserialize, Serialize)]
101pub struct ClientStatus {
102    #[serde(default)]
103    pub desktop: Option<String>,
104    #[serde(default)]
105    pub mobile: Option<String>,
106    #[serde(default)]
107    pub web: Option<String>,
108}
109
110/// Presence update event.
111#[derive(Debug, Clone, Deserialize, Serialize)]
112pub struct PresenceUpdateEvent {
113    pub user: PartialUser,
114    #[serde(default)]
115    pub guild_id: Option<Snowflake>,
116    pub status: String,
117    #[serde(default)]
118    pub activities: Vec<crate::json::Value>,
119    #[serde(default)]
120    pub client_status: Option<ClientStatus>,
121}