Skip to main content

layer_client/
types.rs

1//! Ergonomic high-level wrappers over raw TL user/chat types (G-37).
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/// Wrapper around `tl::enums::User` with ergonomic accessors.
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 { self.inner().id }
37
38    /// Access hash needed for API calls.
39    pub fn access_hash(&self) -> Option<i64> { self.inner().access_hash }
40
41    /// First name.
42    pub fn first_name(&self) -> Option<&str> { self.inner().first_name.as_deref() }
43
44    /// Last name.
45    pub fn last_name(&self) -> Option<&str> { self.inner().last_name.as_deref() }
46
47    /// Username (without `@`).
48    pub fn username(&self) -> Option<&str> { self.inner().username.as_deref() }
49
50    /// Phone number, if visible.
51    pub fn phone(&self) -> Option<&str> { self.inner().phone.as_deref() }
52
53    /// `true` if this is a verified account.
54    pub fn verified(&self) -> bool { self.inner().verified }
55
56    /// `true` if this is a bot account.
57    pub fn bot(&self) -> bool { self.inner().bot }
58
59    /// `true` if the account is deleted.
60    pub fn deleted(&self) -> bool { self.inner().deleted }
61
62    /// `true` if the current user has blocked this user.
63    pub fn blocked(&self) -> bool { false }
64
65    /// `true` if this is a premium account.
66    pub fn premium(&self) -> bool { self.inner().premium }
67
68    /// Full display name (`first_name [last_name]`).
69    pub fn full_name(&self) -> String {
70        match (self.first_name(), self.last_name()) {
71            (Some(f), Some(l)) => format!("{f} {l}"),
72            (Some(f), None)    => f.to_string(),
73            (None,    Some(l)) => l.to_string(),
74            (None,    None)    => String::new(),
75        }
76    }
77
78    /// Convert to a `Peer` for use in API calls.
79    pub fn as_peer(&self) -> tl::enums::Peer {
80        tl::enums::Peer::User(tl::types::PeerUser { user_id: self.id() })
81    }
82
83    /// Convert to an `InputPeer` for API calls (requires access hash).
84    pub fn as_input_peer(&self) -> tl::enums::InputPeer {
85        match self.inner().access_hash {
86            Some(ah) => tl::enums::InputPeer::User(tl::types::InputPeerUser {
87                user_id:     self.id(),
88                access_hash: ah,
89            }),
90            None => tl::enums::InputPeer::PeerSelf,
91        }
92    }
93}
94
95impl std::fmt::Display for User {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        let name = self.full_name();
98        if let Some(uname) = self.username() {
99            write!(f, "{name} (@{uname})")
100        } else {
101            write!(f, "{name} [{}]", self.id())
102        }
103    }
104}
105
106// ─── Group ────────────────────────────────────────────────────────────────────
107
108/// Wrapper around a basic Telegram group (`tl::types::Chat`).
109#[derive(Debug, Clone)]
110pub struct Group {
111    pub raw: tl::types::Chat,
112}
113
114impl Group {
115    /// Wrap from a raw `tl::enums::Chat`, returning `None` if it is not a
116    /// basic group (i.e. empty, forbidden, or a channel).
117    pub fn from_raw(raw: tl::enums::Chat) -> Option<Self> {
118        match raw {
119            tl::enums::Chat::Chat(c)            => Some(Self { raw: c }),
120            tl::enums::Chat::Empty(_)
121            | tl::enums::Chat::Forbidden(_)
122            | tl::enums::Chat::Channel(_)
123            | tl::enums::Chat::ChannelForbidden(_) => None,
124        }
125    }
126
127    /// Group ID.
128    pub fn id(&self) -> i64 { self.raw.id }
129
130    /// Group title.
131    pub fn title(&self) -> &str { &self.raw.title }
132
133    /// Member count.
134    pub fn participants_count(&self) -> i32 { self.raw.participants_count }
135
136    /// `true` if the logged-in user is the creator.
137    pub fn creator(&self) -> bool { self.raw.creator }
138
139    /// `true` if the group has been migrated to a supergroup.
140    pub fn migrated_to(&self) -> Option<&tl::enums::InputChannel> {
141        self.raw.migrated_to.as_ref()
142    }
143
144    /// Convert to a `Peer`.
145    pub fn as_peer(&self) -> tl::enums::Peer {
146        tl::enums::Peer::Chat(tl::types::PeerChat { chat_id: self.id() })
147    }
148
149    /// Convert to an `InputPeer`.
150    pub fn as_input_peer(&self) -> tl::enums::InputPeer {
151        tl::enums::InputPeer::Chat(tl::types::InputPeerChat { chat_id: self.id() })
152    }
153}
154
155impl std::fmt::Display for Group {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        write!(f, "{} [group {}]", self.title(), self.id())
158    }
159}
160
161// ─── Channel ──────────────────────────────────────────────────────────────────
162
163/// Wrapper around a Telegram channel or supergroup (`tl::types::Channel`).
164#[derive(Debug, Clone)]
165pub struct Channel {
166    pub raw: tl::types::Channel,
167}
168
169impl Channel {
170    /// Wrap from a raw `tl::enums::Chat`, returning `None` if it is not a channel.
171    pub fn from_raw(raw: tl::enums::Chat) -> Option<Self> {
172        match raw {
173            tl::enums::Chat::Channel(c) => Some(Self { raw: c }),
174            _ => None,
175        }
176    }
177
178    /// Channel ID.
179    pub fn id(&self) -> i64 { self.raw.id }
180
181    /// Access hash.
182    pub fn access_hash(&self) -> Option<i64> { self.raw.access_hash }
183
184    /// Channel / supergroup title.
185    pub fn title(&self) -> &str { &self.raw.title }
186
187    /// Username (without `@`), if public.
188    pub fn username(&self) -> Option<&str> { self.raw.username.as_deref() }
189
190    /// `true` if this is a supergroup (not a broadcast channel).
191    pub fn megagroup(&self) -> bool { self.raw.megagroup }
192
193    /// `true` if this is a broadcast channel.
194    pub fn broadcast(&self) -> bool { self.raw.broadcast }
195
196    /// `true` if this is a verified channel.
197    pub fn verified(&self) -> bool { self.raw.verified }
198
199    /// `true` if the channel is restricted.
200    pub fn restricted(&self) -> bool { self.raw.restricted }
201
202    /// `true` if the channel has signatures on posts.
203    pub fn signatures(&self) -> bool { self.raw.signatures }
204
205    /// Approximate member count (may be `None` for private channels).
206    pub fn participants_count(&self) -> Option<i32> { self.raw.participants_count }
207
208    /// Convert to a `Peer`.
209    pub fn as_peer(&self) -> tl::enums::Peer {
210        tl::enums::Peer::Channel(tl::types::PeerChannel { channel_id: self.id() })
211    }
212
213    /// Convert to an `InputPeer` (requires access hash).
214    pub fn as_input_peer(&self) -> tl::enums::InputPeer {
215        match self.raw.access_hash {
216            Some(ah) => tl::enums::InputPeer::Channel(tl::types::InputPeerChannel {
217                channel_id:  self.id(),
218                access_hash: ah,
219            }),
220            None => tl::enums::InputPeer::Empty,
221        }
222    }
223
224    /// Convert to an `InputChannel` for channel-specific RPCs.
225    pub fn as_input_channel(&self) -> tl::enums::InputChannel {
226        match self.raw.access_hash {
227            Some(ah) => tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
228                channel_id:  self.id(),
229                access_hash: ah,
230            }),
231            None => tl::enums::InputChannel::Empty,
232        }
233    }
234}
235
236impl std::fmt::Display for Channel {
237    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238        if let Some(uname) = self.username() {
239            write!(f, "{} (@{uname})", self.title())
240        } else {
241            write!(f, "{} [channel {}]", self.title(), self.id())
242        }
243    }
244}
245
246// ─── Chat enum (unified) ──────────────────────────────────────────────────────
247
248/// A unified chat type — either a basic [`Group`] or a [`Channel`]/supergroup.
249#[derive(Debug, Clone)]
250pub enum Chat {
251    Group(Group),
252    Channel(Box<Channel>),
253}
254
255impl Chat {
256    /// Attempt to construct from a raw `tl::enums::Chat`.
257    pub fn from_raw(raw: tl::enums::Chat) -> Option<Self> {
258        match &raw {
259            tl::enums::Chat::Chat(_)    => Group::from_raw(raw).map(Chat::Group),
260            tl::enums::Chat::Channel(_) => Channel::from_raw(raw).map(|c| Chat::Channel(Box::new(c))),
261            _ => None,
262        }
263    }
264
265    /// Common ID regardless of variant.
266    pub fn id(&self) -> i64 {
267        match self {
268            Chat::Group(g)   => g.id(),
269            Chat::Channel(c) => c.id(),
270        }
271    }
272
273    /// Common title regardless of variant.
274    pub fn title(&self) -> &str {
275        match self {
276            Chat::Group(g)   => g.title(),
277            Chat::Channel(c) => c.title(),
278        }
279    }
280
281    /// Convert to a `Peer`.
282    pub fn as_peer(&self) -> tl::enums::Peer {
283        match self {
284            Chat::Group(g)   => g.as_peer(),
285            Chat::Channel(c) => c.as_peer(),
286        }
287    }
288
289    /// Convert to an `InputPeer`.
290    pub fn as_input_peer(&self) -> tl::enums::InputPeer {
291        match self {
292            Chat::Group(g)   => g.as_input_peer(),
293            Chat::Channel(c) => c.as_input_peer(),
294        }
295    }
296}