grammers_session/
peer.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_tl_types as tl;
12
13/// A compact peer identifier.
14/// ```
15/// use std::mem::size_of;
16/// assert_eq!(size_of::<grammers_session::defs::PeerId>(), size_of::<i64>());
17/// ```
18/// The [`PeerInfo`] cached by the session for this `PeerId` may be retrieved via [`crate::Session::peer`].
19///
20/// The internal representation uses the Bot API Dialog ID format to
21/// bit-pack both the peer's true identifier and type in a single integer.
22///
23/// Internally, arbitrary values outside the valid range of Bot API Dialog ID
24/// may be used to represent special peer identifiers.
25#[repr(transparent)]
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27pub struct PeerId(i64);
28
29/// Witness to the session's authority from Telegram to interact with a peer.
30///
31/// If Telegram deems the session to already have such authority, the session may
32/// be allowed to present [`PeerAuth::default`] instead of this witness. This can
33/// happen when the logged-in user is a bot account, or, for user accounts, when
34/// the peer being interacted with is one of its contacts.
35#[repr(transparent)]
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37pub struct PeerAuth(i64);
38
39/// Ocap-style reference to a peer object, a peer object capability, bundling the identity
40/// of a peer (its [`PeerId`]) with authority over it (as [`PeerAuth`]), to allow fluent use.
41///
42/// This type implements conversion to [`tl::enums::InputPeer`] and derivatives.
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44pub struct PeerRef {
45    /// The peer identity.
46    pub id: PeerId,
47    /// The authority bound to both the sibling identity and the session of the logged-in user.
48    pub auth: PeerAuth,
49}
50
51/// [`PeerId`]'s kind.
52///
53/// The `PeerId` bitpacks this information for size reasons.
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
55pub enum PeerKind {
56    /// The peer identity belongs to a [`tl::enums::User`]. May also represent [`PeerKind::UserSelf`].
57    User,
58    /// The peer identity belongs to a user with its [`tl::types::User::is_self`] flag set to `true`.
59    UserSelf,
60    /// The peer identity belongs to a [`tl::types::Chat`] or one of its derivatives.
61    Chat,
62    /// The peer identity belongs to a [`tl::types::Channel`] or one of its derivatives.
63    Channel,
64}
65
66/// An exploded peer reference along with any known useful information about the peer.
67#[derive(Clone, Debug, PartialEq, Eq)]
68pub enum PeerInfo {
69    User {
70        /// Bare user identifier.
71        ///
72        /// Despite being `i64`, Telegram only uses strictly positive values.
73        id: i64,
74        /// Non-ambient authority bound to both the user itself and the session.
75        auth: Option<PeerAuth>,
76        /// Whether this user represents a bot or not.
77        bot: Option<bool>,
78        /// Whether this user represents the logged-in user authorized by this session or not.
79        is_self: Option<bool>,
80    },
81    Chat {
82        /// Bare chat identifier.
83        ///
84        /// Note that the HTTP Bot API negates this identifier to signal that it is a chat,
85        /// but the true value used by Telegram's API is always strictly-positive.
86        id: i64,
87    },
88    Channel {
89        /// Bare channel identifier.
90        ///
91        /// Note that the HTTP Bot API prefixes this identifier with `-100` to signal that it is a channel,
92        /// but the true value used by Telegram's API is always strictly-positive.
93        id: i64,
94        /// Non-ambient authority bound to both the user itself and the session.
95        auth: Option<PeerAuth>,
96        /// Channel kind, useful to determine what the possible permissions on it are.
97        kind: Option<ChannelKind>,
98    },
99}
100
101/// Additional information about a [`PeerInfo::Channel`].
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub enum ChannelKind {
104    /// Value used for a channel with its [`tl::types::Channel::megagroup`] flag set to `true`.
105    Megagroup,
106    /// Value used for a channel with its [`tl::types::Channel::broadcast`] flag set to `true`.
107    Broadcast,
108    /// Value used for a channel with its [`tl::types::Channel::gigagroup`] flag set to `true`.
109    Gigagroup,
110}
111
112/// Sentinel value used to represent the self-user when its true `PeerId` is unknown.
113///
114/// Per https://core.telegram.org/api/bots/ids:
115/// > a bot API dialog ID ranges from -4000000000000 to 1099511627775
116///
117/// This value is not intended to be visible or persisted, so it can be changed as needed in the future.
118const SELF_USER_ID: PeerId = PeerId(1 << 40);
119
120/// Sentinel value used to represent empty chats.
121///
122/// Per https://core.telegram.org/api/bots/ids:
123/// > \[…] transformed range for bot API chat dialog IDs is -999999999999 to -1 inclusively
124/// >
125/// > \[…] transformed range for bot API channel dialog IDs is -1997852516352 to -1000000000001 inclusively
126///
127/// `chat_id` parameters are in Telegram's API use the bare identifier, so there's no
128/// empty constructor, but it can be mimicked by picking the value in the correct range hole.
129/// This value is closer to "channel with ID 0" than "chat with ID 0", but there's no distinct
130/// `-0` integer, and channels have a proper constructor for empty already
131const EMPTY_CHAT_ID: i64 = -1000000000000;
132
133impl PeerId {
134    /// Creates a peer identity for the currently-logged-in user or bot account.
135    ///
136    /// Internally, this will use a special sentinel value outside of any valid Bot API Dialog ID range.
137    pub fn self_user() -> Self {
138        SELF_USER_ID
139    }
140
141    /// Creates a peer identity for a user or bot account.
142    pub fn user(id: i64) -> Self {
143        // https://core.telegram.org/api/bots/ids#user-ids
144        if !(1 <= id && id <= 0xffffffffff) {
145            panic!("user ID out of range");
146        }
147
148        Self(id)
149    }
150
151    /// Creates a peer identity for a small group chat.
152    pub fn chat(id: i64) -> Self {
153        // https://core.telegram.org/api/bots/ids#chat-ids
154        if !(1 <= id && id <= 999999999999) {
155            panic!("chat ID out of range");
156        }
157
158        Self(-id)
159    }
160
161    /// Creates a peer identity for a broadcast channel, megagroup, gigagroup or monoforum.
162    pub fn channel(id: i64) -> Self {
163        // https://core.telegram.org/api/bots/ids#supergroup-channel-ids and #monoforum-ids
164        if !((1 <= id && id <= 997852516352) || (1002147483649 <= id && id <= 3000000000000)) {
165            panic!("channel ID out of range");
166        }
167
168        Self(-(1000000000000 + id))
169    }
170
171    /// Peer kind.
172    pub fn kind(self) -> PeerKind {
173        if 1 <= self.0 && self.0 <= 0xffffffffff {
174            PeerKind::User
175        } else if self.0 == SELF_USER_ID.0 {
176            PeerKind::UserSelf
177        } else if -999999999999 <= self.0 && self.0 <= -1 {
178            PeerKind::Chat
179        } else if -1997852516352 <= self.0 && self.0 <= -1000000000001
180            || (-2002147483649 <= self.0 && self.0 <= -4000000000000)
181        {
182            PeerKind::Channel
183        } else {
184            unreachable!()
185        }
186    }
187
188    /// Returns the identity using the Bot API Dialog ID format.
189    ///
190    /// Will return an arbitrary value if [`Self::kind`] is [`PeerKind::UserSelf`].
191    /// This value should not be relied on and may change between releases.
192    pub fn bot_api_dialog_id(&self) -> i64 {
193        self.0
194    }
195
196    /// Unpacked peer identifier. Panics if [`Self::kind`] is [`PeerKind::UserSelf`].
197    pub fn bare_id(&self) -> i64 {
198        match self.kind() {
199            PeerKind::User => self.0,
200            PeerKind::UserSelf => panic!("self-user ID not known"),
201            PeerKind::Chat => -self.0,
202            PeerKind::Channel => -self.0 - 1000000000000,
203        }
204    }
205}
206
207impl PeerAuth {
208    /// Construct a new peer authentication using Telegram's `access_hash` value.
209    pub fn from_hash(access_hash: i64) -> Self {
210        PeerAuth(access_hash)
211    }
212
213    /// Grants access to the internal access hash.
214    pub fn hash(&self) -> i64 {
215        self.0
216    }
217}
218
219impl Default for PeerAuth {
220    /// Returns the ambient authority to authorize peers only when Telegram considers it valid.
221    ///
222    /// The internal representation uses `0` to signal the ambient authority,
223    /// although this might happen to be the actual witness used by some peers.
224    fn default() -> Self {
225        Self(0)
226    }
227}
228
229impl PeerInfo {
230    /// Returns the `PeerId` represented by this info.
231    ///
232    /// The returned [`PeerId::kind()`] will never be [`PeerKind::UserSelf`].
233    pub fn id(&self) -> PeerId {
234        match self {
235            PeerInfo::User { id, .. } => PeerId::user(*id),
236            PeerInfo::Chat { id } => PeerId::chat(*id),
237            PeerInfo::Channel { id, .. } => PeerId::channel(*id),
238        }
239    }
240
241    /// Returns the `PeerAuth` stored in this info, or [`PeerAuth::default()`] if that info is not known.
242    pub fn auth(&self) -> PeerAuth {
243        match self {
244            PeerInfo::User { auth, .. } => auth.unwrap_or_default(),
245            PeerInfo::Chat { .. } => PeerAuth::default(),
246            PeerInfo::Channel { auth, .. } => auth.unwrap_or_default(),
247        }
248    }
249}
250
251impl fmt::Display for PeerId {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        self.bot_api_dialog_id().fmt(f)
254    }
255}
256
257impl From<PeerInfo> for PeerRef {
258    fn from(peer: PeerInfo) -> Self {
259        PeerRef {
260            id: peer.id(),
261            auth: peer.auth(),
262        }
263    }
264}
265
266impl From<tl::enums::Peer> for PeerId {
267    fn from(peer: tl::enums::Peer) -> Self {
268        match peer {
269            tl::enums::Peer::User(user) => PeerId::from(user),
270            tl::enums::Peer::Chat(chat) => PeerId::from(chat),
271            tl::enums::Peer::Channel(channel) => PeerId::from(channel),
272        }
273    }
274}
275
276impl From<tl::types::PeerUser> for PeerId {
277    fn from(user: tl::types::PeerUser) -> Self {
278        PeerId::user(user.user_id)
279    }
280}
281
282impl From<tl::types::PeerChat> for PeerId {
283    fn from(chat: tl::types::PeerChat) -> Self {
284        PeerId::chat(chat.chat_id)
285    }
286}
287
288impl From<tl::types::PeerChannel> for PeerId {
289    fn from(channel: tl::types::PeerChannel) -> Self {
290        PeerId::channel(channel.channel_id)
291    }
292}
293
294impl From<tl::enums::InputPeer> for PeerRef {
295    fn from(peer: tl::enums::InputPeer) -> Self {
296        match peer {
297            tl::enums::InputPeer::Empty => {
298                panic!("InputPeer::Empty cannot be converted to any Peer");
299            }
300            tl::enums::InputPeer::PeerSelf => PeerRef {
301                id: SELF_USER_ID,
302                auth: PeerAuth::default(),
303            },
304            tl::enums::InputPeer::User(user) => PeerRef::from(user),
305            tl::enums::InputPeer::Chat(chat) => PeerRef::from(chat),
306            tl::enums::InputPeer::Channel(channel) => PeerRef::from(channel),
307            tl::enums::InputPeer::UserFromMessage(user) => PeerRef::from(*user),
308            tl::enums::InputPeer::ChannelFromMessage(channel) => PeerRef::from(*channel),
309        }
310    }
311}
312
313impl From<tl::types::InputPeerSelf> for PeerRef {
314    fn from(_: tl::types::InputPeerSelf) -> Self {
315        PeerRef {
316            id: SELF_USER_ID,
317            auth: PeerAuth::default(),
318        }
319    }
320}
321
322impl From<tl::types::InputPeerUser> for PeerRef {
323    fn from(user: tl::types::InputPeerUser) -> Self {
324        PeerRef {
325            id: PeerId::user(user.user_id),
326            auth: PeerAuth::from_hash(user.access_hash),
327        }
328    }
329}
330
331impl From<tl::types::InputPeerChat> for PeerRef {
332    fn from(chat: tl::types::InputPeerChat) -> Self {
333        PeerRef {
334            id: PeerId::chat(chat.chat_id),
335            auth: PeerAuth::default(),
336        }
337    }
338}
339
340impl From<tl::types::InputPeerChannel> for PeerRef {
341    fn from(channel: tl::types::InputPeerChannel) -> Self {
342        PeerRef {
343            id: PeerId::channel(channel.channel_id),
344            auth: PeerAuth::from_hash(channel.access_hash),
345        }
346    }
347}
348
349impl From<tl::types::InputPeerUserFromMessage> for PeerRef {
350    fn from(user: tl::types::InputPeerUserFromMessage) -> Self {
351        // Not currently willing to make PeerRef significantly larger to accomodate for this uncommon type.
352        PeerRef {
353            id: PeerId::user(user.user_id),
354            auth: PeerAuth::default(),
355        }
356    }
357}
358
359impl From<tl::types::InputPeerChannelFromMessage> for PeerRef {
360    fn from(channel: tl::types::InputPeerChannelFromMessage) -> Self {
361        // Not currently willing to make PeerRef significantly larger to accomodate for this uncommon type.
362        PeerRef {
363            id: PeerId::channel(channel.channel_id),
364            auth: PeerAuth::default(),
365        }
366    }
367}
368
369impl From<tl::enums::User> for PeerRef {
370    fn from(user: tl::enums::User) -> Self {
371        match user {
372            grammers_tl_types::enums::User::Empty(user) => PeerRef::from(user),
373            grammers_tl_types::enums::User::User(user) => PeerRef::from(user),
374        }
375    }
376}
377
378impl From<tl::types::UserEmpty> for PeerRef {
379    fn from(user: tl::types::UserEmpty) -> Self {
380        PeerRef {
381            id: PeerId::user(user.id),
382            auth: PeerAuth::default(),
383        }
384    }
385}
386
387impl From<tl::types::User> for PeerRef {
388    fn from(user: tl::types::User) -> Self {
389        PeerRef {
390            id: if user.is_self {
391                PeerId::self_user()
392            } else {
393                PeerId::user(user.id)
394            },
395            auth: user
396                .access_hash
397                .map(PeerAuth::from_hash)
398                .unwrap_or(PeerAuth::default()),
399        }
400    }
401}
402
403impl From<tl::enums::Chat> for PeerRef {
404    fn from(chat: tl::enums::Chat) -> Self {
405        match chat {
406            grammers_tl_types::enums::Chat::Empty(chat) => PeerRef::from(chat),
407            grammers_tl_types::enums::Chat::Chat(chat) => PeerRef::from(chat),
408            grammers_tl_types::enums::Chat::Forbidden(chat) => PeerRef::from(chat),
409            grammers_tl_types::enums::Chat::Channel(channel) => PeerRef::from(channel),
410            grammers_tl_types::enums::Chat::ChannelForbidden(channel) => PeerRef::from(channel),
411        }
412    }
413}
414
415impl From<tl::types::ChatEmpty> for PeerRef {
416    fn from(chat: tl::types::ChatEmpty) -> Self {
417        PeerRef {
418            id: PeerId::chat(chat.id),
419            auth: PeerAuth::default(),
420        }
421    }
422}
423
424impl From<tl::types::Chat> for PeerRef {
425    fn from(chat: tl::types::Chat) -> Self {
426        PeerRef {
427            id: PeerId::chat(chat.id),
428            auth: PeerAuth::default(),
429        }
430    }
431}
432
433impl From<tl::types::ChatForbidden> for PeerRef {
434    fn from(chat: tl::types::ChatForbidden) -> Self {
435        PeerRef {
436            id: PeerId::chat(chat.id),
437            auth: PeerAuth::default(),
438        }
439    }
440}
441
442impl From<tl::types::Channel> for PeerRef {
443    fn from(channel: tl::types::Channel) -> Self {
444        PeerRef {
445            id: PeerId::channel(channel.id),
446            auth: channel
447                .access_hash
448                .map(PeerAuth::from_hash)
449                .unwrap_or(PeerAuth::default()),
450        }
451    }
452}
453
454impl From<tl::types::ChannelForbidden> for PeerRef {
455    fn from(channel: tl::types::ChannelForbidden) -> Self {
456        PeerRef {
457            id: PeerId::channel(channel.id),
458            auth: PeerAuth::from_hash(channel.access_hash),
459        }
460    }
461}
462
463impl From<PeerId> for tl::enums::Peer {
464    fn from(peer: PeerId) -> Self {
465        match peer.kind() {
466            PeerKind::User => tl::enums::Peer::User(tl::types::PeerUser {
467                user_id: peer.bare_id(),
468            }),
469            PeerKind::UserSelf => panic!("self-user ID not known"),
470            PeerKind::Chat => tl::enums::Peer::Chat(tl::types::PeerChat {
471                chat_id: peer.bare_id(),
472            }),
473            PeerKind::Channel => tl::enums::Peer::Channel(tl::types::PeerChannel {
474                channel_id: peer.bare_id(),
475            }),
476        }
477    }
478}
479
480impl From<PeerRef> for tl::enums::InputPeer {
481    fn from(peer: PeerRef) -> Self {
482        match peer.id.kind() {
483            PeerKind::User => tl::enums::InputPeer::User(tl::types::InputPeerUser {
484                user_id: peer.id.bare_id(),
485                access_hash: peer.auth.hash(),
486            }),
487            PeerKind::UserSelf => tl::enums::InputPeer::PeerSelf,
488            PeerKind::Chat => tl::enums::InputPeer::Chat(tl::types::InputPeerChat {
489                chat_id: peer.id.bare_id(),
490            }),
491            PeerKind::Channel => tl::enums::InputPeer::Channel(tl::types::InputPeerChannel {
492                channel_id: peer.id.bare_id(),
493                access_hash: peer.auth.hash(),
494            }),
495        }
496    }
497}
498
499impl From<PeerRef> for tl::enums::InputUser {
500    fn from(peer: PeerRef) -> Self {
501        match peer.id.kind() {
502            PeerKind::User => tl::enums::InputUser::User(tl::types::InputUser {
503                user_id: peer.id.bare_id(),
504                access_hash: peer.auth.hash(),
505            }),
506            PeerKind::UserSelf => tl::enums::InputUser::UserSelf,
507            PeerKind::Chat => tl::enums::InputUser::Empty,
508            PeerKind::Channel => tl::enums::InputUser::Empty,
509        }
510    }
511}
512
513impl From<PeerRef> for i64 {
514    fn from(peer: PeerRef) -> Self {
515        match peer.id.kind() {
516            PeerKind::User => EMPTY_CHAT_ID,
517            PeerKind::UserSelf => EMPTY_CHAT_ID,
518            PeerKind::Chat => peer.id.bare_id(),
519            PeerKind::Channel => EMPTY_CHAT_ID,
520        }
521    }
522}
523
524impl From<PeerRef> for tl::enums::InputChannel {
525    fn from(peer: PeerRef) -> Self {
526        match peer.id.kind() {
527            PeerKind::User => tl::enums::InputChannel::Empty,
528            PeerKind::UserSelf => tl::enums::InputChannel::Empty,
529            PeerKind::Chat => tl::enums::InputChannel::Empty,
530            PeerKind::Channel => tl::enums::InputChannel::Channel(tl::types::InputChannel {
531                channel_id: peer.id.bare_id(),
532                access_hash: peer.auth.hash(),
533            }),
534        }
535    }
536}