tgbot/types/user/
mod.rs

1use std::{error::Error, fmt};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    api::{Method, Payload},
7    types::{Integer, ParseMode, PhotoSize},
8};
9
10#[cfg(test)]
11mod tests;
12
13/// Represents the date of birth of a user.
14#[serde_with::skip_serializing_none]
15#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
16pub struct Birthdate {
17    /// Day of the user's birth; 1-31
18    pub day: Integer,
19    /// Month of the user's birth; 1-12
20    pub month: Integer,
21    /// Year of the user's birth
22    pub year: Option<Integer>,
23}
24
25impl Birthdate {
26    /// Creates a new `Birthdate`.
27    ///
28    /// # Arguments
29    ///
30    /// * `day` - Day.
31    /// * `month` - Month.
32    pub fn new(day: Integer, month: Integer) -> Self {
33        Self { day, month, year: None }
34    }
35
36    /// Sets a new year.
37    ///
38    /// # Arguments
39    ///
40    /// * `year` - Year.
41    pub fn with_year(mut self, value: Integer) -> Self {
42        self.year = Some(value);
43        self
44    }
45}
46
47/// Contains information about a user that
48/// was shared with the bot using a [`crate::types::KeyboardButtonRequestUsers`] button.
49#[serde_with::skip_serializing_none]
50#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
51pub struct SharedUser {
52    /// Identifier of the shared user.
53    ///
54    /// The bot may not have access to the user and could be unable to use this identifier,
55    /// unless the user is already known to the bot by some other means.
56    pub user_id: Integer,
57    /// First name of the user, if the name was requested by the bot.
58    pub first_name: Option<String>,
59    /// Last name of the user, if the name was requested by the bot.
60    pub last_name: Option<String>,
61    /// Available sizes of the user photo, if the photo was requested by the bot
62    pub photo: Option<Vec<PhotoSize>>,
63    /// Username of the user, if the username was requested by the bot.
64    pub username: Option<String>,
65}
66
67impl SharedUser {
68    /// Creates a new `SharedUser`.
69    ///
70    /// # Arguments
71    ///
72    /// * `user_id` - Identifier of the shared user.
73    pub fn new(user_id: Integer) -> Self {
74        Self {
75            user_id,
76            first_name: None,
77            last_name: None,
78            photo: None,
79            username: None,
80        }
81    }
82
83    /// Sets a new first name.
84    ///
85    /// # Arguments
86    ///
87    /// * `value` - First name.
88    pub fn with_first_name<T>(mut self, value: T) -> Self
89    where
90        T: Into<String>,
91    {
92        self.first_name = Some(value.into());
93        self
94    }
95
96    /// Sets a new last name.
97    ///
98    /// # Arguments
99    ///
100    /// * `value` - Last name.
101    pub fn with_last_name<T>(mut self, value: T) -> Self
102    where
103        T: Into<String>,
104    {
105        self.last_name = Some(value.into());
106        self
107    }
108
109    /// Sets new photo sizes.
110    ///
111    /// # Arguments
112    ///
113    /// * `value` - Available sizes of photo.
114    pub fn with_photo<T>(mut self, value: T) -> Self
115    where
116        T: IntoIterator<Item = PhotoSize>,
117    {
118        self.photo = Some(value.into_iter().collect());
119        self
120    }
121
122    /// Sets a new username.
123    ///
124    /// # Arguments
125    ///
126    /// * `value` - Username.
127    pub fn with_username<T>(mut self, value: T) -> Self
128    where
129        T: Into<String>,
130    {
131        self.username = Some(value.into());
132        self
133    }
134}
135
136/// Represents a user.
137#[serde_with::skip_serializing_none]
138#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
139pub struct User {
140    /// First name of the user.
141    pub first_name: String,
142    /// Unique identifier of the user.
143    pub id: UserPeerId,
144    /// Indicates whether the user is a bot.
145    pub is_bot: bool,
146    /// Indicates whether the user added the bot to the attachment menu.
147    pub added_to_attachment_menu: Option<bool>,
148    /// Indicates whether the user is a Telegram Premium user.
149    pub is_premium: Option<bool>,
150    /// [IETF language tag][1] of the user's language.
151    ///
152    /// [1]: https://en.wikipedia.org/wiki/IETF_language_tag
153    pub language_code: Option<String>,
154    /// Last name of the user.
155    pub last_name: Option<String>,
156    /// Username of the user.
157    pub username: Option<UserUsername>,
158}
159
160impl User {
161    /// Creates a new `User`.
162    ///
163    /// # Arguments
164    ///
165    /// * `id` - Unique identifier of the user.
166    /// * `first_name` - First name of the user.
167    /// * `is_bot` - Indicates whether the user is a bot.
168    pub fn new<A, B>(id: A, first_name: B, is_bot: bool) -> Self
169    where
170        A: Into<UserPeerId>,
171        B: Into<String>,
172    {
173        Self {
174            first_name: first_name.into(),
175            id: id.into(),
176            is_bot,
177            added_to_attachment_menu: None,
178            is_premium: None,
179            language_code: None,
180            last_name: None,
181            username: None,
182        }
183    }
184
185    /// Returns the full name of the user (first name + last name).
186    pub fn get_full_name(&self) -> String {
187        let mut full_name = self.first_name.clone();
188        if let Some(ref last_name) = self.last_name {
189            full_name.push(' ');
190            full_name += last_name;
191        }
192        full_name
193    }
194
195    /// Returns the link to the user (`tg://user?id=xxx`).
196    ///
197    /// These links will work only if they are used inside an inline link.
198    /// For example, they will not work,
199    /// when used in an inline keyboard button or in a message text.
200    pub fn get_link(&self) -> String {
201        format!("tg://user?id={}", self.id.0)
202    }
203
204    /// Returns the mention for the user.
205    ///
206    /// # Arguments
207    ///
208    /// * `parse_mode` - A parse mode for formatting the mention.
209    ///
210    /// These mentions are only guaranteed to work if the user has contacted the bot in the past,
211    /// has sent a callback query to the bot via inline button or is a member
212    /// in the group where he was mentioned.
213    pub fn get_link_mention(&self, parse_mode: ParseMode) -> Result<String, MentionError> {
214        let full_name = parse_mode.escape(self.get_full_name());
215        let user_link = self.get_link();
216        Ok(match parse_mode {
217            ParseMode::Markdown => return Err(MentionError::UnsupportedParseMode(parse_mode)),
218            ParseMode::MarkdownV2 => format!(r#"[{}]({})"#, full_name, user_link),
219            ParseMode::Html => format!(r#"<a href="{}">{}</a>"#, user_link, full_name),
220        })
221    }
222
223    /// Sets a new value for the `added_to_attachment_menu` flag.
224    ///
225    /// # Arguments
226    ///
227    /// * `value` - Indicates whether the user added the bot to the attachment menu.
228    pub fn with_added_to_attachment_menu(mut self, value: bool) -> Self {
229        self.added_to_attachment_menu = Some(value);
230        self
231    }
232
233    /// Sets a new value for the `is_premium` flag.
234    ///
235    /// # Arguments
236    ///
237    /// * `value` - Indicates whether the user is a Telegram Premium user.
238    pub fn with_is_premium(mut self, value: bool) -> Self {
239        self.is_premium = Some(value);
240        self
241    }
242
243    /// Sets a new language code.
244    ///
245    /// # Arguments
246    ///
247    /// * `value` - Language code.
248    pub fn with_language_code<T>(mut self, value: T) -> Self
249    where
250        T: Into<String>,
251    {
252        self.language_code = Some(value.into());
253        self
254    }
255
256    /// Sets a new last name.
257    ///
258    /// # Arguments
259    ///
260    /// * `value` - Last name.
261    pub fn with_last_name<T>(mut self, value: T) -> Self
262    where
263        T: Into<String>,
264    {
265        self.last_name = Some(value.into());
266        self
267    }
268
269    /// Sets a new username.
270    ///
271    /// # Arguments
272    ///
273    /// * `value` - Username.
274    pub fn with_username<T>(mut self, value: T) -> Self
275    where
276        T: Into<UserUsername>,
277    {
278        self.username = Some(value.into());
279        self
280    }
281}
282
283/// Represents an error occurred when getting user mention.
284#[derive(Debug)]
285pub enum MentionError {
286    /// Parse mode is not supported
287    UnsupportedParseMode(ParseMode),
288}
289
290impl Error for MentionError {}
291
292impl fmt::Display for MentionError {
293    fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
294        match self {
295            MentionError::UnsupportedParseMode(parse_mode) => {
296                write!(out, "can not mention with {} parse mode", parse_mode)
297            }
298        }
299    }
300}
301
302/// Represents a list of profile pictures of a user.
303#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
304pub struct UserProfilePhotos {
305    /// Requested profile pictures (in up to 4 sizes each).
306    pub photos: Vec<Vec<PhotoSize>>,
307    /// Total number of profile pictures the target user has.
308    pub total_count: Integer,
309}
310
311impl UserProfilePhotos {
312    /// Creates a new `UserProfilePhotos`.
313    ///
314    /// # Arguments
315    ///
316    /// * `photos` - A list of photos.
317    /// * `total_count` - Total number of photos.
318    pub fn new<A, B>(photos: A, total_count: Integer) -> Self
319    where
320        A: IntoIterator<Item = B>,
321        B: IntoIterator<Item = PhotoSize>,
322    {
323        Self {
324            photos: photos.into_iter().map(|x| x.into_iter().collect()).collect(),
325            total_count,
326        }
327    }
328}
329
330/// ID of a user.
331#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
332#[serde(from = "Integer", into = "Integer")]
333pub struct UserPeerId(Integer);
334
335impl From<Integer> for UserPeerId {
336    fn from(value: Integer) -> Self {
337        Self(value)
338    }
339}
340
341impl From<UserPeerId> for Integer {
342    fn from(value: UserPeerId) -> Self {
343        value.0
344    }
345}
346
347impl fmt::Display for UserPeerId {
348    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349        self.0.fmt(f)
350    }
351}
352
353impl PartialEq<Integer> for UserPeerId {
354    fn eq(&self, other: &Integer) -> bool {
355        self.0.eq(other)
356    }
357}
358
359/// Username of a user in the format `@username`.
360#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
361#[serde(from = "String", into = "String")]
362pub struct UserUsername(String);
363
364impl From<&str> for UserUsername {
365    fn from(value: &str) -> Self {
366        Self(String::from(value))
367    }
368}
369
370impl From<String> for UserUsername {
371    fn from(value: String) -> Self {
372        Self(value)
373    }
374}
375
376impl From<UserUsername> for String {
377    fn from(value: UserUsername) -> Self {
378        value.0
379    }
380}
381
382impl fmt::Display for UserUsername {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        self.0.fmt(f)
385    }
386}
387
388impl PartialEq<String> for UserUsername {
389    fn eq(&self, other: &String) -> bool {
390        self.0.eq(other)
391    }
392}
393
394impl PartialEq<str> for UserUsername {
395    fn eq(&self, other: &str) -> bool {
396        self.0.eq(other)
397    }
398}
399
400/// Represents a user ID.
401#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
402#[serde(untagged)]
403pub enum UserId {
404    /// ID of a user.
405    Id(UserPeerId),
406    /// @username of a user.
407    Username(UserUsername),
408}
409
410impl From<&str> for UserId {
411    fn from(username: &str) -> UserId {
412        UserId::Username(String::from(username).into())
413    }
414}
415
416impl From<String> for UserId {
417    fn from(username: String) -> UserId {
418        UserId::Username(username.into())
419    }
420}
421
422impl From<Integer> for UserId {
423    fn from(id: Integer) -> UserId {
424        UserId::Id(id.into())
425    }
426}
427
428impl fmt::Display for UserId {
429    fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
430        match self {
431            UserId::Id(chat_id) => write!(out, "{}", chat_id.0),
432            UserId::Username(username) => write!(out, "{}", username.0),
433        }
434    }
435}
436
437impl From<UserPeerId> for UserId {
438    fn from(value: UserPeerId) -> Self {
439        UserId::Id(value)
440    }
441}
442
443impl From<UserUsername> for UserId {
444    fn from(value: UserUsername) -> Self {
445        UserId::Username(value)
446    }
447}
448
449/// Returns a list of profile pictures for a user.
450#[serde_with::skip_serializing_none]
451#[derive(Clone, Debug, Serialize)]
452pub struct GetUserProfilePhotos {
453    user_id: Integer,
454    limit: Option<Integer>,
455    offset: Option<Integer>,
456}
457
458impl GetUserProfilePhotos {
459    /// Creates a new `GetUserProfilePhotos`.
460    ///
461    /// # Arguments
462    ///
463    /// * `user_id` - Unique identifier of the target user.
464    pub fn new(user_id: Integer) -> Self {
465        GetUserProfilePhotos {
466            user_id,
467            offset: None,
468            limit: None,
469        }
470    }
471
472    /// Sets a new limit.
473    ///
474    /// # Arguments
475    ///
476    /// * `value` - Limit of the number of photos to be retrieved; 1—100; default - 100.
477    pub fn with_limit(mut self, limit: Integer) -> Self {
478        self.limit = Some(limit);
479        self
480    }
481
482    /// Sets a new offset.
483    ///
484    /// # Arguments
485    ///
486    /// * `value` - Sequential number of the first photo to be returned.
487    ///
488    /// By default, all photos are returned.
489    pub fn with_offset(mut self, offset: Integer) -> Self {
490        self.offset = Some(offset);
491        self
492    }
493}
494
495impl Method for GetUserProfilePhotos {
496    type Response = UserProfilePhotos;
497
498    fn into_payload(self) -> Payload {
499        Payload::json("getUserProfilePhotos", self)
500    }
501}
502
503/// Changes the emoji status for a given user
504/// that previously allowed the bot to manage their emoji status
505/// via the Mini App method `requestEmojiStatusAccess`.
506#[serde_with::skip_serializing_none]
507#[derive(Clone, Debug, Serialize)]
508pub struct SetUserEmojiStatus {
509    user_id: Integer,
510    emoji_status_custom_emoji_id: Option<String>,
511    emoji_status_expiration_date: Option<Integer>,
512}
513
514impl SetUserEmojiStatus {
515    /// Creates a new `SetUserEmojiStatus`.
516    ///
517    /// # Arguments
518    ///
519    /// * `user_id` - Unique identifier of the target user
520    pub fn new(user_id: Integer) -> Self {
521        Self {
522            user_id,
523            emoji_status_custom_emoji_id: None,
524            emoji_status_expiration_date: None,
525        }
526    }
527
528    /// Sets a new emoji ID.
529    ///
530    /// # Arguments
531    ///
532    /// * `value` - Custom emoji identifier of the emoji status to set.
533    ///   Pass an empty string to remove the status.
534    pub fn with_emoji_id<T>(mut self, value: T) -> Self
535    where
536        T: Into<String>,
537    {
538        self.emoji_status_custom_emoji_id = Some(value.into());
539        self
540    }
541
542    /// Sets a new expiration date.
543    ///
544    /// # Arguments
545    ///
546    /// * `value` - Expiration date of the emoji status, if any.
547    pub fn with_expiration_date(mut self, value: Integer) -> Self {
548        self.emoji_status_expiration_date = Some(value);
549        self
550    }
551}
552
553impl Method for SetUserEmojiStatus {
554    type Response = bool;
555
556    fn into_payload(self) -> Payload {
557        Payload::json("setUserEmojiStatus", self)
558    }
559}