Skip to main content

layer_client/
types.rs

1//! Typed wrappers over raw TL user and chat types.
2//!
3//! The raw TL layer has `tl::enums::User` (two variants: `Empty` / `User`) and
4//! `tl::enums::Chat` (five variants: `Empty`, `Chat`, `Forbidden`,
5//! `Channel`, `ChannelForbidden`).  Working with them directly requires constant
6//! pattern-matching.  This module provides three thin wrappers: [`User`],
7//! [`Group`], and [`Channel`]: with uniform accessor APIs.
8
9use layer_tl_types as tl;
10
11// User
12
13/// Typed wrapper over `tl::enums::User`.
14#[derive(Debug, Clone)]
15pub struct User {
16    pub raw: tl::enums::User,
17}
18
19impl User {
20    /// Wrap a raw TL user.
21    pub fn from_raw(raw: tl::enums::User) -> Option<Self> {
22        match &raw {
23            tl::enums::User::Empty(_) => None,
24            tl::enums::User::User(_) => Some(Self { raw }),
25        }
26    }
27
28    fn inner(&self) -> &tl::types::User {
29        match &self.raw {
30            tl::enums::User::User(u) => u,
31            tl::enums::User::Empty(_) => unreachable!("User::Empty filtered in from_raw"),
32        }
33    }
34
35    /// Telegram user ID.
36    pub fn id(&self) -> i64 {
37        self.inner().id
38    }
39
40    /// Access hash needed for API calls.
41    pub fn access_hash(&self) -> Option<i64> {
42        self.inner().access_hash
43    }
44
45    /// First name.
46    pub fn first_name(&self) -> Option<&str> {
47        self.inner().first_name.as_deref()
48    }
49
50    /// Last name.
51    pub fn last_name(&self) -> Option<&str> {
52        self.inner().last_name.as_deref()
53    }
54
55    /// Username (without `@`).
56    pub fn username(&self) -> Option<&str> {
57        self.inner().username.as_deref()
58    }
59
60    /// Phone number, if visible.
61    pub fn phone(&self) -> Option<&str> {
62        self.inner().phone.as_deref()
63    }
64
65    /// `true` if this is a verified account.
66    pub fn verified(&self) -> bool {
67        self.inner().verified
68    }
69
70    /// `true` if this is a bot account.
71    pub fn bot(&self) -> bool {
72        self.inner().bot
73    }
74
75    /// `true` if the account is deleted.
76    pub fn deleted(&self) -> bool {
77        self.inner().deleted
78    }
79
80    /// `true` if the current user has blocked this user.
81    pub fn blocked(&self) -> bool {
82        false
83    }
84
85    /// `true` if this is a premium account.
86    pub fn premium(&self) -> bool {
87        self.inner().premium
88    }
89
90    /// Full display name (`first_name [last_name]`).
91    pub fn full_name(&self) -> String {
92        match (self.first_name(), self.last_name()) {
93            (Some(f), Some(l)) => format!("{f} {l}"),
94            (Some(f), None) => f.to_string(),
95            (None, Some(l)) => l.to_string(),
96            (None, None) => String::new(),
97        }
98    }
99
100    /// All active usernames (including the primary username).
101    pub fn usernames(&self) -> Vec<&str> {
102        let mut names = Vec::new();
103        // Primary username
104        if let Some(u) = self.inner().username.as_deref() {
105            names.push(u);
106        }
107        // Additional usernames
108        if let Some(extras) = &self.inner().usernames {
109            for u in extras {
110                let tl::enums::Username::Username(un) = u;
111                if un.active {
112                    names.push(un.username.as_str());
113                }
114            }
115        }
116        names
117    }
118
119    /// The user's current online status.
120    pub fn status(&self) -> Option<&tl::enums::UserStatus> {
121        self.inner().status.as_ref()
122    }
123
124    /// Profile photo, if set.
125    pub fn photo(&self) -> Option<&tl::types::UserProfilePhoto> {
126        match self.inner().photo.as_ref()? {
127            tl::enums::UserProfilePhoto::UserProfilePhoto(p) => Some(p),
128            _ => None,
129        }
130    }
131
132    /// `true` if this is the currently logged-in user.
133    pub fn is_self(&self) -> bool {
134        self.inner().is_self
135    }
136
137    /// `true` if this user is in the logged-in user's contact list.
138    pub fn contact(&self) -> bool {
139        self.inner().contact
140    }
141
142    /// `true` if the logged-in user is also in this user's contact list.
143    pub fn mutual_contact(&self) -> bool {
144        self.inner().mutual_contact
145    }
146
147    /// `true` if this account has been flagged as a scam.
148    pub fn scam(&self) -> bool {
149        self.inner().scam
150    }
151
152    /// `true` if this account has been restricted (e.g. spam-banned).
153    pub fn restricted(&self) -> bool {
154        self.inner().restricted
155    }
156
157    /// `true` if the bot does not display in inline mode publicly.
158    pub fn bot_privacy(&self) -> bool {
159        self.inner().bot_nochats
160    }
161
162    /// `true` if the bot supports being added to groups.
163    pub fn bot_supports_chats(&self) -> bool {
164        !self.inner().bot_nochats
165    }
166
167    /// `true` if the bot can be used inline even without a location share.
168    pub fn bot_inline_geo(&self) -> bool {
169        self.inner().bot_inline_geo
170    }
171
172    /// `true` if this account belongs to Telegram support staff.
173    pub fn support(&self) -> bool {
174        self.inner().support
175    }
176
177    /// Language code reported by the user's client.
178    pub fn lang_code(&self) -> Option<&str> {
179        self.inner().lang_code.as_deref()
180    }
181
182    /// Restriction reasons (why this account is unavailable in certain regions).
183    pub fn restriction_reason(&self) -> Vec<&tl::enums::RestrictionReason> {
184        self.inner()
185            .restriction_reason
186            .as_deref()
187            .unwrap_or(&[])
188            .iter()
189            .collect()
190    }
191
192    /// Bot inline placeholder text (shown in the compose bar when the user activates inline mode).
193    pub fn bot_inline_placeholder(&self) -> Option<&str> {
194        self.inner().bot_inline_placeholder.as_deref()
195    }
196
197    /// Convert to a `Peer` for use in API calls.
198    pub fn as_peer(&self) -> tl::enums::Peer {
199        tl::enums::Peer::User(tl::types::PeerUser { user_id: self.id() })
200    }
201
202    /// Convert to an `InputPeer` for API calls (requires access hash).
203    pub fn as_input_peer(&self) -> tl::enums::InputPeer {
204        match self.inner().access_hash {
205            Some(ah) => tl::enums::InputPeer::User(tl::types::InputPeerUser {
206                user_id: self.id(),
207                access_hash: ah,
208            }),
209            None => tl::enums::InputPeer::PeerSelf,
210        }
211    }
212}
213
214impl std::fmt::Display for User {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        let name = self.full_name();
217        if let Some(uname) = self.username() {
218            write!(f, "{name} (@{uname})")
219        } else {
220            write!(f, "{name} [{}]", self.id())
221        }
222    }
223}
224
225// Group
226
227/// Typed wrapper over `tl::types::Chat`.
228#[derive(Debug, Clone)]
229pub struct Group {
230    pub raw: tl::types::Chat,
231}
232
233impl Group {
234    /// Wrap from a raw `tl::enums::Chat`, returning `None` if it is not a
235    /// basic group (i.e. empty, forbidden, or a channel).
236    pub fn from_raw(raw: tl::enums::Chat) -> Option<Self> {
237        match raw {
238            tl::enums::Chat::Chat(c) => Some(Self { raw: c }),
239            tl::enums::Chat::Empty(_)
240            | tl::enums::Chat::Forbidden(_)
241            | tl::enums::Chat::Channel(_)
242            | tl::enums::Chat::ChannelForbidden(_) => None,
243        }
244    }
245
246    /// Group ID.
247    pub fn id(&self) -> i64 {
248        self.raw.id
249    }
250
251    /// Group title.
252    pub fn title(&self) -> &str {
253        &self.raw.title
254    }
255
256    /// Member count.
257    pub fn participants_count(&self) -> i32 {
258        self.raw.participants_count
259    }
260
261    /// `true` if the logged-in user is the creator.
262    pub fn creator(&self) -> bool {
263        self.raw.creator
264    }
265
266    /// `true` if the group has been migrated to a supergroup.
267    pub fn migrated_to(&self) -> Option<&tl::enums::InputChannel> {
268        self.raw.migrated_to.as_ref()
269    }
270
271    /// Convert to a `Peer`.
272    pub fn as_peer(&self) -> tl::enums::Peer {
273        tl::enums::Peer::Chat(tl::types::PeerChat { chat_id: self.id() })
274    }
275
276    /// Convert to an `InputPeer`.
277    pub fn as_input_peer(&self) -> tl::enums::InputPeer {
278        tl::enums::InputPeer::Chat(tl::types::InputPeerChat { chat_id: self.id() })
279    }
280}
281
282impl std::fmt::Display for Group {
283    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284        write!(f, "{} [group {}]", self.title(), self.id())
285    }
286}
287
288// Channel
289
290/// The kind of a channel or supergroup.
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum ChannelKind {
293    /// A broadcast channel (posts only, no member replies by default).
294    Broadcast,
295    /// A supergroup (all members can post).
296    Megagroup,
297    /// A gigagroup / broadcast group (large public broadcast supergroup).
298    Gigagroup,
299}
300
301/// Typed wrapper over `tl::types::Channel`.
302#[derive(Debug, Clone)]
303pub struct Channel {
304    pub raw: tl::types::Channel,
305}
306
307impl Channel {
308    /// Wrap from a raw `tl::enums::Chat`, returning `None` if it is not a channel.
309    pub fn from_raw(raw: tl::enums::Chat) -> Option<Self> {
310        match raw {
311            tl::enums::Chat::Channel(c) => Some(Self { raw: c }),
312            _ => None,
313        }
314    }
315
316    /// Channel ID.
317    pub fn id(&self) -> i64 {
318        self.raw.id
319    }
320
321    /// Access hash.
322    pub fn access_hash(&self) -> Option<i64> {
323        self.raw.access_hash
324    }
325
326    /// Channel / supergroup title.
327    pub fn title(&self) -> &str {
328        &self.raw.title
329    }
330
331    /// Username (without `@`), if public.
332    pub fn username(&self) -> Option<&str> {
333        self.raw.username.as_deref()
334    }
335
336    /// `true` if this is a supergroup (not a broadcast channel).
337    pub fn megagroup(&self) -> bool {
338        self.raw.megagroup
339    }
340
341    /// `true` if this is a broadcast channel.
342    pub fn broadcast(&self) -> bool {
343        self.raw.broadcast
344    }
345
346    /// `true` if this is a verified channel.
347    pub fn verified(&self) -> bool {
348        self.raw.verified
349    }
350
351    /// `true` if the channel is restricted.
352    pub fn restricted(&self) -> bool {
353        self.raw.restricted
354    }
355
356    /// `true` if the channel has signatures on posts.
357    pub fn signatures(&self) -> bool {
358        self.raw.signatures
359    }
360
361    /// Approximate member count (may be `None` for private channels).
362    pub fn participants_count(&self) -> Option<i32> {
363        self.raw.participants_count
364    }
365
366    /// The kind of this channel.
367    ///
368    /// Returns `ChannelKind::Megagroup` for supergroups, `ChannelKind::Broadcast` for
369    /// broadcast channels, and `ChannelKind::Gigagroup` for large broadcast groups.
370    pub fn kind(&self) -> ChannelKind {
371        if self.raw.megagroup {
372            ChannelKind::Megagroup
373        } else if self.raw.gigagroup {
374            ChannelKind::Gigagroup
375        } else {
376            ChannelKind::Broadcast
377        }
378    }
379
380    /// All active usernames (including the primary username).
381    pub fn usernames(&self) -> Vec<&str> {
382        let mut names = Vec::new();
383        if let Some(u) = self.raw.username.as_deref() {
384            names.push(u);
385        }
386        if let Some(extras) = &self.raw.usernames {
387            for u in extras {
388                let tl::enums::Username::Username(un) = u;
389                if un.active {
390                    names.push(un.username.as_str());
391                }
392            }
393        }
394        names
395    }
396
397    /// Profile photo, if set.
398    pub fn photo(&self) -> Option<&tl::types::ChatPhoto> {
399        match &self.raw.photo {
400            tl::enums::ChatPhoto::ChatPhoto(p) => Some(p),
401            _ => None,
402        }
403    }
404
405    /// Admin rights granted to the logged-in user in this channel, if any.
406    pub fn admin_rights(&self) -> Option<&tl::types::ChatAdminRights> {
407        match self.raw.admin_rights.as_ref()? {
408            tl::enums::ChatAdminRights::ChatAdminRights(r) => Some(r),
409        }
410    }
411
412    /// Restriction reasons (why this channel is unavailable in certain regions).
413    pub fn restriction_reason(&self) -> Vec<&tl::enums::RestrictionReason> {
414        self.raw
415            .restriction_reason
416            .as_deref()
417            .unwrap_or(&[])
418            .iter()
419            .collect()
420    }
421
422    /// Convert to a `Peer`.
423    pub fn as_peer(&self) -> tl::enums::Peer {
424        tl::enums::Peer::Channel(tl::types::PeerChannel {
425            channel_id: self.id(),
426        })
427    }
428
429    /// Convert to an `InputPeer` (requires access hash).
430    pub fn as_input_peer(&self) -> tl::enums::InputPeer {
431        match self.raw.access_hash {
432            Some(ah) => tl::enums::InputPeer::Channel(tl::types::InputPeerChannel {
433                channel_id: self.id(),
434                access_hash: ah,
435            }),
436            None => tl::enums::InputPeer::Empty,
437        }
438    }
439
440    /// Convert to an `InputChannel` for channel-specific RPCs.
441    pub fn as_input_channel(&self) -> tl::enums::InputChannel {
442        match self.raw.access_hash {
443            Some(ah) => tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
444                channel_id: self.id(),
445                access_hash: ah,
446            }),
447            None => tl::enums::InputChannel::Empty,
448        }
449    }
450}
451
452impl std::fmt::Display for Channel {
453    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454        if let Some(uname) = self.username() {
455            write!(f, "{} (@{uname})", self.title())
456        } else {
457            write!(f, "{} [channel {}]", self.title(), self.id())
458        }
459    }
460}
461
462// Chat enum (unified)
463
464/// A unified chat type: either a basic [`Group`] or a [`Channel`]/supergroup.
465#[derive(Debug, Clone)]
466pub enum Chat {
467    Group(Group),
468    Channel(Box<Channel>),
469}
470
471impl Chat {
472    /// Attempt to construct from a raw `tl::enums::Chat`.
473    pub fn from_raw(raw: tl::enums::Chat) -> Option<Self> {
474        match &raw {
475            tl::enums::Chat::Chat(_) => Group::from_raw(raw).map(Chat::Group),
476            tl::enums::Chat::Channel(_) => {
477                Channel::from_raw(raw).map(|c| Chat::Channel(Box::new(c)))
478            }
479            _ => None,
480        }
481    }
482
483    /// Common ID regardless of variant.
484    pub fn id(&self) -> i64 {
485        match self {
486            Chat::Group(g) => g.id(),
487            Chat::Channel(c) => c.id(),
488        }
489    }
490
491    /// Common title regardless of variant.
492    pub fn title(&self) -> &str {
493        match self {
494            Chat::Group(g) => g.title(),
495            Chat::Channel(c) => c.title(),
496        }
497    }
498
499    /// Convert to a `Peer`.
500    pub fn as_peer(&self) -> tl::enums::Peer {
501        match self {
502            Chat::Group(g) => g.as_peer(),
503            Chat::Channel(c) => c.as_peer(),
504        }
505    }
506
507    /// Convert to an `InputPeer`.
508    pub fn as_input_peer(&self) -> tl::enums::InputPeer {
509        match self {
510            Chat::Group(g) => g.as_input_peer(),
511            Chat::Channel(c) => c.as_input_peer(),
512        }
513    }
514}