teloxide_core/types/
user.rs

1use serde::{Deserialize, Serialize};
2
3use crate::types::UserId;
4
5/// This object represents a Telegram user or bot.
6///
7/// [The official docs](https://core.telegram.org/bots/api#user).
8#[serde_with::skip_serializing_none]
9#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
10pub struct User {
11    /// Unique identifier for this user or bot.
12    pub id: UserId,
13
14    /// `true`, if this user is a bot.
15    pub is_bot: bool,
16
17    /// User‘s or bot’s first name.
18    pub first_name: String,
19
20    /// User‘s or bot’s last name.
21    pub last_name: Option<String>,
22
23    /// User‘s or bot’s username.
24    pub username: Option<String>,
25
26    /// [IETF language tag] of the user's language.
27    ///
28    /// [IETF language tag]: https://en.wikipedia.org/wiki/IETF_language_tag
29    pub language_code: Option<String>,
30
31    /// `true`, if this user is a Telegram Premium user.
32    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
33    pub is_premium: bool,
34
35    /// `true`, if this user added the bot to the attachment menu.
36    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
37    pub added_to_attachment_menu: bool,
38}
39
40impl User {
41    /// Returns full name of this user, ie first and last names joined with a
42    /// space.
43    #[must_use]
44    pub fn full_name(&self) -> String {
45        match &self.last_name {
46            Some(last_name) => format!("{0} {1}", self.first_name, last_name),
47            None => self.first_name.clone(),
48        }
49    }
50
51    /// Returns a username mention of this user. Returns `None` if
52    /// `self.username.is_none()`.
53    #[must_use]
54    pub fn mention(&self) -> Option<String> {
55        Some(format!("@{}", self.username.as_ref()?))
56    }
57
58    /// Returns an URL that links to this user in the form of
59    /// `tg://user/?id=<...>`.
60    #[must_use]
61    pub fn url(&self) -> reqwest::Url {
62        self.id.url()
63    }
64
65    /// Returns an URL that links to this user in the form of `t.me/<...>`.
66    /// Returns `None` if `self.username.is_none()`.
67    #[must_use]
68    pub fn tme_url(&self) -> Option<reqwest::Url> {
69        Some(format!("https://t.me/{}", self.username.as_ref()?).parse().unwrap())
70    }
71
72    /// Returns an URL that links to this user in the form of `t.me/<...>` or
73    /// `tg://user/?id=<...>`, preferring `t.me` one when possible.
74    #[must_use]
75    pub fn preferably_tme_url(&self) -> reqwest::Url {
76        self.tme_url().unwrap_or_else(|| self.url())
77    }
78
79    /// Returns `true` if this is the special user used by telegram bot API to
80    /// denote an anonymous user that sends messages on behalf of a group.
81    #[must_use]
82    pub fn is_anonymous(&self) -> bool {
83        // Sanity check
84        debug_assert!(
85            !self.id.is_anonymous()
86                || (self.is_bot
87                    && self.first_name == "Group"
88                    && self.last_name.is_none()
89                    && self.username.as_deref() == Some("GroupAnonymousBot"))
90        );
91
92        self.id.is_anonymous()
93    }
94
95    /// Returns `true` if this is the special user used by telegram bot API to
96    /// denote an anonymous user that sends messages on behalf of a channel.
97    #[must_use]
98    pub fn is_channel(&self) -> bool {
99        // Sanity check
100        debug_assert!(
101            !self.id.is_channel()
102                || (self.is_bot
103                    && self.first_name == "Channel"
104                    && self.last_name.is_none()
105                    && self.username.as_deref() == Some("Channel_Bot"))
106        );
107
108        self.id.is_channel()
109    }
110
111    /// Returns `true` if this is the special user used by telegram itself.
112    ///
113    /// It is sometimes also used as a fallback, for example when a channel post
114    /// is automatically forwarded to a group, bots in a group will get a
115    /// message where `from` is the Telegram user.
116    #[must_use]
117    pub fn is_telegram(&self) -> bool {
118        // Sanity check
119        debug_assert!(
120            !self.id.is_telegram()
121                || (!self.is_bot
122                    && self.first_name == "Telegram"
123                    && self.last_name.is_none()
124                    && self.username.is_none())
125        );
126
127        self.id.is_telegram()
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn deserialize() {
137        let json = r#"{
138            "id":12345,
139            "is_bot":false,
140            "first_name":"firstName",
141            "last_name":"lastName",
142            "username":"Username",
143            "language_code":"ru"
144        }"#;
145        let expected = User {
146            id: UserId(12345),
147            is_bot: false,
148            first_name: "firstName".to_string(),
149            last_name: Some("lastName".to_string()),
150            username: Some("Username".to_string()),
151            language_code: Some(String::from("ru")),
152            is_premium: false,
153            added_to_attachment_menu: false,
154        };
155        let actual = serde_json::from_str::<User>(json).unwrap();
156        assert_eq!(actual, expected)
157    }
158
159    #[test]
160    fn convenience_methods_work() {
161        let user_a = User {
162            id: UserId(43),
163            is_bot: false,
164            first_name: "First".to_owned(),
165            last_name: Some("Last".to_owned()),
166            username: Some("aaaaaaaaaaaaaaaa".to_owned()),
167            language_code: None,
168            is_premium: false,
169            added_to_attachment_menu: false,
170        };
171
172        let user_b = User {
173            id: UserId(44),
174            is_bot: false,
175            first_name: ".".to_owned(),
176            last_name: None,
177            username: None,
178            language_code: None,
179            is_premium: false,
180            added_to_attachment_menu: false,
181        };
182
183        assert_eq!(user_a.full_name(), "First Last");
184        assert_eq!(user_b.full_name(), ".");
185
186        assert_eq!(user_a.mention(), Some("@aaaaaaaaaaaaaaaa".to_owned()));
187        assert_eq!(user_b.mention(), None);
188
189        assert_eq!(user_a.tme_url(), Some("https://t.me/aaaaaaaaaaaaaaaa".parse().unwrap()));
190        assert_eq!(user_b.tme_url(), None);
191
192        assert_eq!(user_a.preferably_tme_url(), "https://t.me/aaaaaaaaaaaaaaaa".parse().unwrap());
193        assert_eq!(user_b.preferably_tme_url(), "tg://user/?id=44".parse().unwrap());
194    }
195}