grammers_client/peer/user.rs
1// Copyright 2020 - developers of the `grammers` project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::fmt;
10
11use grammers_session::types::{PeerAuth, PeerId, PeerInfo, PeerRef};
12use grammers_tl_types as tl;
13
14use crate::Client;
15
16/// Platform Identifier referenced only by [`RestrictionReason`].
17#[non_exhaustive]
18pub enum Platform {
19 All,
20 Android,
21 Ios,
22 WindowsPhone,
23 Other(String),
24}
25
26/// Reason why a user is globally restricted.
27pub struct RestrictionReason {
28 pub platforms: Vec<Platform>,
29 pub reason: String,
30 pub text: String,
31}
32
33impl RestrictionReason {
34 pub fn from_raw(reason: &tl::enums::RestrictionReason) -> Self {
35 let tl::enums::RestrictionReason::Reason(reason) = reason;
36 Self {
37 platforms: reason
38 .platform
39 .split('-')
40 .map(|p| match p {
41 // Taken from https://core.telegram.org/constructor/restrictionReason
42 "all" => Platform::All,
43 "android" => Platform::Android,
44 "ios" => Platform::Ios,
45 "wp" => Platform::WindowsPhone,
46 o => Platform::Other(o.to_string()),
47 })
48 .collect(),
49 reason: reason.reason.to_string(),
50 text: reason.text.to_string(),
51 }
52 }
53}
54
55/// A user.
56///
57/// Users include your contacts, members of a group, bot accounts created by [@BotFather], or
58/// anyone with a Telegram account.
59///
60/// A "normal" (non-bot) user may also behave like a "bot" without actually being one, for
61/// example, when controlled with a program as opposed to being controlled by a human through
62/// a Telegram application. These are commonly known as "userbots", and some people use them
63/// to enhance their Telegram experience (for example, creating "commands" so that the program
64/// automatically reacts to them, like translating messages).
65///
66/// [@BotFather]: https://t.me/BotFather
67#[derive(Clone)]
68pub struct User {
69 pub raw: tl::enums::User,
70 pub(crate) client: Client,
71}
72
73impl fmt::Debug for User {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 self.raw.fmt(f)
76 }
77}
78
79// TODO: photo
80impl User {
81 pub fn from_raw(client: &Client, user: tl::enums::User) -> Self {
82 Self {
83 raw: user,
84 client: client.clone(),
85 }
86 }
87
88 pub(crate) fn user(&self) -> Option<&tl::types::User> {
89 match &self.raw {
90 tl::enums::User::User(u) => Some(u),
91 tl::enums::User::Empty(_) => None,
92 }
93 }
94
95 /// Return the user presence status (also known as "last seen").
96 pub fn status(&self) -> &grammers_tl_types::enums::UserStatus {
97 self.user()
98 .and_then(|u| u.status.as_ref())
99 .unwrap_or(&grammers_tl_types::enums::UserStatus::Empty)
100 }
101
102 /// Return the unique identifier for this user.
103 pub fn id(&self) -> PeerId {
104 PeerId::user_unchecked(self.raw.id())
105 }
106
107 /// Non-min auth stored in the user, if any.
108 pub(crate) fn auth(&self) -> Option<PeerAuth> {
109 let user = self.user()?;
110 user.access_hash
111 .filter(|_| !user.min)
112 .map(PeerAuth::from_hash)
113 }
114
115 /// Convert the user to its reference.
116 ///
117 /// This is only possible if the peer would be usable on all methods or if it is in the session cache.
118 pub async fn to_ref(&self) -> Option<PeerRef> {
119 let id = self.id();
120 match self.auth() {
121 Some(auth) => Some(PeerRef { id, auth }),
122 None => self.client.0.session.peer_ref(id).await,
123 }
124 }
125
126 /// Return the first name of this user.
127 ///
128 /// The name will be `None` if the account was deleted. It may also be `None` if you received
129 /// it previously.
130 pub fn first_name(&self) -> Option<&str> {
131 self.user().and_then(|u| u.first_name.as_deref())
132 }
133
134 /// Return the last name of this user, if any.
135 pub fn last_name(&self) -> Option<&str> {
136 self.user().and_then(|u| {
137 u.last_name
138 .as_deref()
139 .and_then(|name| if name.is_empty() { None } else { Some(name) })
140 })
141 }
142
143 /// Return the full name of this user.
144 ///
145 /// This is equal to the user's first name concatenated with the user's last name, if this
146 /// is not empty. Otherwise, it equals the user's first name.
147 pub fn full_name(&self) -> String {
148 let first_name = self.first_name().unwrap_or_default();
149 if let Some(last_name) = self.last_name() {
150 let mut name = String::with_capacity(first_name.len() + 1 + last_name.len());
151 name.push_str(first_name);
152 name.push(' ');
153 name.push_str(last_name);
154 name
155 } else {
156 first_name.to_string()
157 }
158 }
159
160 /// Return the public @username of this user, if any.
161 ///
162 /// The returned username does not contain the "@" prefix.
163 ///
164 /// Outside of the application, people may link to this user with one of Telegram's URLs, such
165 /// as https://t.me/username.
166 pub fn username(&self) -> Option<&str> {
167 self.user().and_then(|u| u.username.as_deref())
168 }
169
170 /// Return collectible usernames of this user, if any.
171 ///
172 /// The returned usernames do not contain the "@" prefix.
173 ///
174 /// Outside of the application, people may link to this user with one of its username, such
175 /// as https://t.me/username.
176 pub fn usernames(&self) -> Vec<&str> {
177 self.user()
178 .and_then(|u| u.usernames.as_deref())
179 .map_or(Vec::new(), |usernames| {
180 usernames
181 .iter()
182 .map(|username| match username {
183 tl::enums::Username::Username(username) => username.username.as_ref(),
184 })
185 .collect()
186 })
187 }
188
189 /// Return the phone number of this user, if they are not a bot and their privacy settings
190 /// allow you to see it.
191 pub fn phone(&self) -> Option<&str> {
192 self.user().and_then(|u| u.phone.as_deref())
193 }
194
195 /// Return the photo of this user, if any.
196 pub fn photo(&self) -> Option<&tl::types::UserProfilePhoto> {
197 match self.user().and_then(|u| u.photo.as_ref()) {
198 Some(maybe_photo) => match maybe_photo {
199 tl::enums::UserProfilePhoto::Empty => None,
200 tl::enums::UserProfilePhoto::Photo(photo) => Some(photo),
201 },
202 None => None,
203 }
204 }
205
206 /// Does this user represent the account that's currently logged in?
207 pub fn is_self(&self) -> bool {
208 // TODO if is_self is false, check in peer cache if id == ourself
209 self.user().map(|u| u.is_self).unwrap_or(false)
210 }
211
212 /// Is this user in your account's contact list?
213 pub fn contact(&self) -> bool {
214 self.user().map(|u| u.contact).unwrap_or(false)
215 }
216
217 /// Is this user a mutual contact?
218 ///
219 /// Contacts are mutual if both the user of the current account and this user have eachother
220 /// in their respective contact list.
221 pub fn mutual_contact(&self) -> bool {
222 self.user().map(|u| u.mutual_contact).unwrap_or(false)
223 }
224
225 /// Has the account of this user been deleted?
226 pub fn deleted(&self) -> bool {
227 self.user().map(|u| u.deleted).unwrap_or(false)
228 }
229
230 /// Is the current account a bot?
231 ///
232 /// Bot accounts are those created by [@BotFather](https://t.me/BotFather).
233 pub fn is_bot(&self) -> bool {
234 self.user().map(|u| u.bot).unwrap_or(false)
235 }
236
237 /// If the current user is a bot, does it have [privacy mode] enabled?
238 ///
239 /// * Bots with privacy enabled won't see messages in groups unless they are replied or the
240 /// command includes their name (`/command@bot`).
241 /// * Bots with privacy disabled will be able to see all messages in a group.
242 ///
243 /// [privacy mode]: https://core.telegram.org/bots#privacy-mode
244 pub fn bot_privacy(&self) -> bool {
245 self.user().map(|u| !u.bot_chat_history).unwrap_or(false)
246 }
247
248 /// If the current user is a bot, can it be added to groups?
249 pub fn bot_supports_chats(self) -> bool {
250 self.user().map(|u| u.bot_nochats).unwrap_or(false)
251 }
252
253 /// Has the account of this user been verified?
254 ///
255 /// Verified accounts, such as [@BotFather](https://t.me/BotFather), have a special icon next
256 /// to their names in official applications (commonly a blue starred checkmark).
257 pub fn verified(&self) -> bool {
258 self.user().map(|u| u.verified).unwrap_or(false)
259 }
260
261 /// Does this user have restrictions applied to their account?
262 pub fn restricted(&self) -> bool {
263 self.user().map(|u| u.restricted).unwrap_or(false)
264 }
265
266 /// If the current user is a bot, does it want geolocation information on inline queries?
267 pub fn bot_inline_geo(&self) -> bool {
268 self.user().map(|u| u.bot_inline_geo).unwrap_or(false)
269 }
270
271 /// Is this user an official member of the support team?
272 pub fn support(&self) -> bool {
273 self.user().map(|u| u.support).unwrap_or(false)
274 }
275
276 /// Has this user been flagged for trying to scam other people?
277 pub fn scam(&self) -> bool {
278 self.user().map(|u| u.scam).unwrap_or(false)
279 }
280
281 /// Does this user have a Telegram Premium subscription?
282 pub fn is_premium(&self) -> bool {
283 self.user().map(|u| u.premium).unwrap_or(false)
284 }
285
286 /// Has this user been flagged as a fake account?
287 pub fn fake(&self) -> bool {
288 self.user().map(|u| u.fake).unwrap_or(false)
289 }
290
291 /// The reason(s) why this user is restricted, could be empty.
292 pub fn restriction_reason(&self) -> Vec<RestrictionReason> {
293 if let Some(reasons) = self.user().and_then(|u| u.restriction_reason.as_ref()) {
294 reasons.iter().map(RestrictionReason::from_raw).collect()
295 } else {
296 Vec::new()
297 }
298 }
299
300 /// Return the placeholder for inline queries if the current user is a bot and has said
301 /// placeholder configured.
302 pub fn bot_inline_placeholder(&self) -> Option<&str> {
303 self.user()
304 .and_then(|u| u.bot_inline_placeholder.as_deref())
305 }
306
307 /// Language code of the user, if any.
308 pub fn lang_code(&self) -> Option<&str> {
309 self.user().and_then(|u| u.lang_code.as_deref())
310 }
311}
312
313impl From<User> for PeerInfo {
314 #[inline]
315 fn from(user: User) -> Self {
316 <Self as From<&User>>::from(&user)
317 }
318}
319impl<'a> From<&'a User> for PeerInfo {
320 fn from(user: &'a User) -> Self {
321 <PeerInfo as From<&'a tl::enums::User>>::from(&user.raw)
322 }
323}