Skip to main content

ferogram/
peer_cache.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2//
3// ferogram: async Telegram MTProto client in Rust
4// https://github.com/ankit-chaubey/ferogram
5//
6// Licensed under either the MIT License or the Apache License 2.0.
7// See the LICENSE-MIT or LICENSE-APACHE file in this repository:
8// https://github.com/ankit-chaubey/ferogram
9//
10// Feel free to use, modify, and share this code.
11// Please keep this notice when redistributing.
12
13use std::collections::{HashMap, HashSet};
14use std::sync::Arc;
15
16use ferogram_tl_types as tl;
17
18use crate::errors::InvocationError;
19pub use crate::types::ChannelKind;
20
21impl From<ferogram_session::ChannelKind> for ChannelKind {
22    fn from(k: ferogram_session::ChannelKind) -> Self {
23        match k {
24            ferogram_session::ChannelKind::Broadcast => ChannelKind::Broadcast,
25            ferogram_session::ChannelKind::Megagroup => ChannelKind::Megagroup,
26            ferogram_session::ChannelKind::Gigagroup => ChannelKind::Gigagroup,
27        }
28    }
29}
30
31impl From<ChannelKind> for ferogram_session::ChannelKind {
32    fn from(k: ChannelKind) -> Self {
33        match k {
34            ChannelKind::Broadcast => ferogram_session::ChannelKind::Broadcast,
35            ChannelKind::Megagroup => ferogram_session::ChannelKind::Megagroup,
36            ChannelKind::Gigagroup => ferogram_session::ChannelKind::Gigagroup,
37        }
38    }
39}
40
41/// A batch-scoped, read-only map from channel ID to the raw TL chat object.
42///
43/// Built once per update batch from the `chats` vec and shared (cheaply via
44/// `Arc` refcount) across every `IncomingMessage` produced in that batch.
45/// When the last message is dropped the map is freed automatically.
46pub type PeerMap = Arc<HashMap<i64, tl::enums::Chat>>;
47
48/// Build a `PeerMap` from a slice of TL chat objects.
49///
50/// Silently ignores `Chat::Empty` and any entry without an ID.
51pub fn build_peer_map(chats: &[tl::enums::Chat]) -> Option<PeerMap> {
52    if chats.is_empty() {
53        return None;
54    }
55    let mut map = HashMap::with_capacity(chats.len());
56    for chat in chats {
57        let id = match chat {
58            tl::enums::Chat::Channel(c) => c.id,
59            tl::enums::Chat::ChannelForbidden(c) => c.id,
60            tl::enums::Chat::Chat(c) => c.id,
61            tl::enums::Chat::Forbidden(c) => c.id,
62            tl::enums::Chat::Community(c) => c.id,
63            tl::enums::Chat::CommunityForbidden(c) => c.id,
64            tl::enums::Chat::Empty(_) => continue,
65        };
66        map.insert(id, chat.clone());
67    }
68    if map.is_empty() {
69        None
70    } else {
71        Some(Arc::new(map))
72    }
73}
74
75/// Opt-in experimental behaviours that deviate from strict Telegram spec.
76///
77/// All flags default to `false` (safe / spec-correct).  Enable only what you
78/// need after reading the per-field warnings.
79///
80/// # Example
81/// ```rust,no_run
82/// use ferogram::{Client, ExperimentalFeatures};
83///
84/// # #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> {
85/// let (client, _sd) = Client::builder()
86///     .api_id(12345)
87///     .api_hash("abc")
88///     .experimental_features(ExperimentalFeatures {
89///         allow_zero_hash: true,   // bot-only; omit for user accounts
90///         ..Default::default()
91///     })
92///     .connect().await?;
93/// # Ok(()) }
94/// ```
95#[derive(Clone, Debug, Default)]
96pub struct ExperimentalFeatures {
97    /// When no `access_hash` is cached for a user or channel, fall back to
98    /// `access_hash = 0` instead of returning [`InvocationError::PeerNotCached`].
99    ///
100    /// **Bot accounts only.** The Telegram spec explicitly permits `hash = 0`
101    /// for bots when only a min-hash is available.  On user accounts this
102    /// produces `USER_ID_INVALID` / `CHANNEL_INVALID`.
103    pub allow_zero_hash: bool,
104
105    /// When resolving a min-user via `InputPeerUserFromMessage`, if the
106    /// containing channel's hash is not cached, proceed with
107    /// `channel access_hash = 0` instead of returning
108    /// [`InvocationError::PeerNotCached`].
109    ///
110    /// Almost always wrong.  The inner `InputPeerChannel { access_hash: 0 }`
111    /// makes the whole `InputPeerUserFromMessage` invalid and Telegram will
112    /// reject it.  Only useful for debugging / testing.
113    pub allow_missing_channel_hash: bool,
114
115    /// When `access_hash` is missing for a channel during `getChannelDifference`,
116    /// call `channels.getChannels` with `access_hash = 0` to fetch it, cache it,
117    /// and retry the diff in the same loop iteration.
118    ///
119    /// When false (the default), the diff is deferred: the entry stays alive and
120    /// the diff retries naturally once the hash arrives via a future update's
121    /// entity list.
122    ///
123    /// **Bot accounts only** for reliable operation. On user accounts
124    /// `channels.getChannels { access_hash: 0 }` succeeds only for public channels
125    /// and channels you are currently a member of.
126    pub auto_resolve_peers: bool,
127
128    /// Enable resumable uploads and downloads.
129    ///
130    /// When `true`, interrupted transfers save a checkpoint under
131    /// `checkpoint_dir` (defaults to `.ferogram-transfers/` next to the
132    /// session file). The next call with the same media / file automatically
133    /// resumes from where it left off.
134    ///
135    /// Upload sessions are valid for ~1 hour on Telegram's side; if more time
136    /// has passed the upload restarts from scratch automatically.
137    ///
138    /// Default: `false`.
139    pub resumable_transfers: bool,
140
141    /// Directory for transfer checkpoints when `resumable_transfers` is enabled.
142    /// If `None`, defaults to `.ferogram-transfers/` next to the session file.
143    pub checkpoint_dir: Option<std::path::PathBuf>,
144
145    /// Cache min-user message contexts (`InputPeerUserFromMessage` entries).
146    ///
147    /// When `false` (the default), users seen with `min=true` are silently
148    /// ignored instead of being stored. This keeps session files lean and is
149    /// the right choice for gateway / proxy servers that never need to address
150    /// min users directly.
151    ///
152    /// Enable only if your application needs to send messages or make API
153    /// calls targeting users that arrive exclusively as min-users (i.e. users
154    /// you have never seen with a full access_hash).
155    ///
156    /// Default: `false`.
157    pub cache_min_peers: bool,
158}
159
160/// Caches access hashes for users and channels so every API call carries the
161/// correct hash without re-resolving peers.
162/// A snapshot of what [`PeerCache`] currently holds.
163///
164/// Returned by [`PeerCache::stats`]. Useful for logging, monitoring, and
165/// deciding whether to call [`PeerCache::clear_min_contexts`].
166#[derive(Clone, Debug)]
167pub struct PeerCacheStats {
168    /// Full users with a valid access_hash.
169    pub users: usize,
170    /// Full channels with a valid access_hash.
171    pub channels: usize,
172    /// Regular group chats tracked by existence (no hash needed).
173    pub chats: usize,
174    /// Channels seen with `min=true` (no usable access_hash yet).
175    pub min_channels: usize,
176    /// Min-user message contexts (`InputPeerUserFromMessage` entries).
177    /// Always 0 when `cache_min_peers` is disabled.
178    pub min_contexts: usize,
179    /// Username reverse-index entries.
180    pub usernames: usize,
181    /// Phone number reverse-index entries.
182    pub phones: usize,
183}
184
185/// Discriminates the kind of peer stored in `PeerCache::username_to_peer`.
186#[derive(Clone, Debug, PartialEq, Eq)]
187pub enum PeerType {
188    User,
189    Channel,
190    Chat,
191}
192
193///
194/// All fields are `pub` so that `save_session` / `connect` can read/write them
195/// directly, and so that advanced callers can inspect the cache.
196pub struct PeerCache {
197    /// user_id -> access_hash (full users only, min=false)
198    pub users: HashMap<i64, i64>,
199    /// channel_id -> (access_hash, `Option<ChannelKind>`) (full channels only, min=false)
200    pub channels: HashMap<i64, (i64, Option<ChannelKind>)>,
201    /// Regular group chat IDs (Chat::Chat / ChatForbidden).
202    /// Groups need no access_hash; track existence for peer validation.
203    pub chats: HashSet<i64>,
204    /// Channel IDs seen with min=true. These are real channels but have no
205    /// valid access_hash. Stored separately so they are NEVER confused with
206    /// regular groups. DO NOT put min channels in `chats`. A min channel must
207    /// never become InputPeerChat  - that causes fatal RPC failures.
208    pub channels_min: HashSet<i64>,
209    /// user_id -> (peer_id, msg_id) for min users seen in a message context.
210    /// Min users have an invalid access_hash; they must be referenced via
211    /// InputPeerUserFromMessage using the peer and message where they appeared.
212    pub min_contexts: HashMap<i64, (i64, i32)>,
213    /// Reverse index: lowercase username → (id, PeerType).
214    /// Populated by cache_user / cache_chat; always overwritten on update
215    /// (usernames can change).
216    pub username_to_peer: HashMap<String, (i64, PeerType)>,
217    /// Reverse index: E.164 phone → user_id.
218    pub phone_to_user: HashMap<String, i64>,
219    /// Experimental opt-ins that change error-vs-fallback behaviour.
220    pub(crate) experimental: ExperimentalFeatures,
221}
222
223impl Default for PeerCache {
224    fn default() -> Self {
225        Self::new(ExperimentalFeatures::default())
226    }
227}
228
229impl PeerCache {
230    /// Create a new empty cache with the given experimental-feature flags.
231    pub fn new(experimental: ExperimentalFeatures) -> Self {
232        Self {
233            users: HashMap::new(),
234            channels: HashMap::new(),
235            chats: HashSet::new(),
236            channels_min: HashSet::new(),
237            min_contexts: HashMap::new(),
238            username_to_peer: HashMap::new(),
239            phone_to_user: HashMap::new(),
240            experimental,
241        }
242    }
243
244    pub fn cache_user(&mut self, user: &tl::enums::User) {
245        if let tl::enums::User::User(u) = user {
246            if u.min {
247                // min=true: access_hash is not valid; requires a message context.
248            } else if let Some(hash) = u.access_hash {
249                // Never overwrite a valid non-zero hash with zero.
250                if hash != 0 {
251                    self.users.insert(u.id, hash);
252                } else {
253                    self.users.entry(u.id).or_insert(0);
254                }
255                // Full user always supersedes any min context.
256                self.min_contexts.remove(&u.id);
257            }
258            // Reverse indices (update even for min users so username lookup works)
259            if let Some(ref uname) = u.username {
260                self.username_to_peer
261                    .insert(uname.to_lowercase(), (u.id, PeerType::User));
262            }
263            if let Some(ref phone) = u.phone {
264                self.phone_to_user.insert(phone.clone(), u.id);
265            }
266        }
267    }
268
269    /// Cache a user that arrived in a message context.
270    ///
271    /// For min users (access_hash is invalid), stores the peer+msg context so
272    /// they can later be referenced via `InputPeerUserFromMessage`.
273    ///
274    /// Uses **latest-wins** semantics: a newer message context replaces the
275    /// stored one.  Recent messages are less likely to have been deleted.
276    pub fn cache_user_with_context(&mut self, user: &tl::enums::User, peer_id: i64, msg_id: i32) {
277        if let tl::enums::User::User(u) = user {
278            if u.min {
279                // Never downgrade a cached full user to a min context.
280                if self.experimental.cache_min_peers && !self.users.contains_key(&u.id) {
281                    // Latest-wins: overwrite with the most recent message context.
282                    self.min_contexts.insert(u.id, (peer_id, msg_id));
283                }
284            } else if let Some(hash) = u.access_hash {
285                // Never overwrite a non-zero hash with zero.
286                if hash != 0 {
287                    self.users.insert(u.id, hash);
288                } else {
289                    self.users.entry(u.id).or_insert(0);
290                }
291                self.min_contexts.remove(&u.id);
292            }
293            // Reverse indices
294            if let Some(ref uname) = u.username {
295                self.username_to_peer
296                    .insert(uname.to_lowercase(), (u.id, PeerType::User));
297            }
298            if let Some(ref phone) = u.phone {
299                self.phone_to_user.insert(phone.clone(), u.id);
300            }
301        }
302    }
303
304    pub fn cache_chat(&mut self, chat: &tl::enums::Chat) {
305        match chat {
306            tl::enums::Chat::Channel(c) => {
307                let kind = if c.megagroup {
308                    Some(ChannelKind::Megagroup)
309                } else if c.gigagroup {
310                    Some(ChannelKind::Gigagroup)
311                } else {
312                    Some(ChannelKind::Broadcast)
313                };
314                if c.min {
315                    // min channel: no access_hash available.
316                    // Store in channels_min; never put in chats (InputPeerChat fails).
317                    if !self.channels.contains_key(&c.id) {
318                        self.channels_min.insert(c.id);
319                    }
320                } else if let Some(hash) = c.access_hash {
321                    // Never overwrite a valid non-zero hash with zero.
322                    if hash != 0 {
323                        self.channels.insert(c.id, (hash, kind));
324                    } else {
325                        self.channels.entry(c.id).or_insert((0, kind));
326                    }
327                    // Full channel supersedes any min tracking.
328                    self.channels_min.remove(&c.id);
329                }
330                // Reverse username index for channels (update regardless of min)
331                if let Some(ref uname) = c.username {
332                    self.username_to_peer
333                        .insert(uname.to_lowercase(), (c.id, PeerType::Channel));
334                }
335            }
336            tl::enums::Chat::ChannelForbidden(c) => {
337                // ChannelForbidden has no flags; treat as Broadcast kind.
338                if c.access_hash != 0 {
339                    self.channels
340                        .insert(c.id, (c.access_hash, Some(ChannelKind::Broadcast)));
341                } else {
342                    self.channels
343                        .entry(c.id)
344                        .or_insert((0, Some(ChannelKind::Broadcast)));
345                }
346                self.channels_min.remove(&c.id);
347            }
348            tl::enums::Chat::Chat(c) => {
349                // Regular groups need no access_hash; track existence only.
350                self.chats.insert(c.id);
351            }
352            tl::enums::Chat::Forbidden(c) => {
353                self.chats.insert(c.id);
354            }
355            _ => {}
356        }
357    }
358
359    /// Look up the cached [`ChannelKind`] for a channel ID.
360    ///
361    /// Returns `None` when the channel is not in the cache or was loaded from a
362    /// pre-v6 session file that predates kind tracking.
363    pub fn channel_kind_of(&self, channel_id: i64) -> Option<ChannelKind> {
364        self.channels.get(&channel_id).and_then(|&(_, k)| k)
365    }
366
367    pub fn cache_users(&mut self, users: &[tl::enums::User]) {
368        for u in users {
369            self.cache_user(u);
370        }
371    }
372
373    pub fn cache_chats(&mut self, chats: &[tl::enums::Chat]) {
374        for c in chats {
375            self.cache_chat(c);
376        }
377    }
378
379    /// Store an already-resolved `InputPeer`'s access hash into the cache.
380    ///
381    /// Called when a caller provides a `PeerRef::Input` so that the subsequent
382    /// `peer_to_input` lookup succeeds without an RPC.
383    pub fn cache_input_peer(&mut self, ip: &tl::enums::InputPeer) {
384        match ip {
385            tl::enums::InputPeer::User(u) => {
386                if u.access_hash != 0 {
387                    self.users.insert(u.user_id, u.access_hash);
388                } else {
389                    self.users.entry(u.user_id).or_insert(0);
390                }
391                self.min_contexts.remove(&u.user_id);
392            }
393            tl::enums::InputPeer::Channel(c) => {
394                if c.access_hash != 0 {
395                    self.channels
396                        .entry(c.channel_id)
397                        .and_modify(|e| e.0 = c.access_hash)
398                        .or_insert((c.access_hash, None));
399                } else {
400                    self.channels.entry(c.channel_id).or_insert((0, None));
401                }
402                self.channels_min.remove(&c.channel_id);
403            }
404            tl::enums::InputPeer::Chat(c) => {
405                self.chats.insert(c.chat_id);
406            }
407            // UserFromMessage: cache the container peer's hash AND record the
408            // min_context so peer_to_input() can rebuild InputPeerUserFromMessage.
409            tl::enums::InputPeer::UserFromMessage(u) => {
410                // Cache the container peer's access hash
411                self.cache_input_peer(&u.peer);
412                // Extract container peer_id for the min_context entry
413                let container_peer_id = match &u.peer {
414                    tl::enums::InputPeer::Channel(c) => Some(c.channel_id),
415                    tl::enums::InputPeer::Chat(c) => Some(c.chat_id),
416                    tl::enums::InputPeer::User(pu) => Some(pu.user_id),
417                    tl::enums::InputPeer::PeerSelf => Some(0i64),
418                    _ => None,
419                };
420                if let Some(peer_id) = container_peer_id {
421                    // Only set min_context if there is no full hash cached yet.
422                    if self.experimental.cache_min_peers && !self.users.contains_key(&u.user_id) {
423                        self.min_contexts.insert(u.user_id, (peer_id, u.msg_id));
424                    }
425                }
426            }
427            // ChannelFromMessage: cache the container peer hash and channel entry.
428            tl::enums::InputPeer::ChannelFromMessage(c) => {
429                self.cache_input_peer(&c.peer);
430                // The channel itself has no standalone hash here; mark as known
431                // via channels_min so we don't lose track of it.
432                self.channels_min.insert(c.channel_id);
433            }
434            tl::enums::InputPeer::Empty | tl::enums::InputPeer::PeerSelf => {}
435        }
436    }
437
438    /// Remove stale cache entries when Telegram rejects them with
439    /// `PEER_ID_INVALID`, `CHANNEL_INVALID`, `USER_ID_INVALID`, or
440    /// `CHANNEL_PRIVATE`.  The caller should then retry the operation.
441    pub fn invalidate_peer(&mut self, peer: &tl::enums::Peer) {
442        match peer {
443            tl::enums::Peer::User(u) => {
444                self.users.remove(&u.user_id);
445                self.min_contexts.remove(&u.user_id);
446            }
447            tl::enums::Peer::Channel(c) => {
448                self.channels.remove(&c.channel_id);
449                self.channels_min.remove(&c.channel_id);
450            }
451            tl::enums::Peer::Chat(_) => {} // basic groups have no hash to invalidate
452        }
453    }
454
455    pub(crate) fn user_input_peer(
456        &self,
457        user_id: i64,
458    ) -> Result<tl::enums::InputPeer, InvocationError> {
459        if user_id == 0 {
460            return Ok(tl::enums::InputPeer::PeerSelf);
461        }
462
463        // Full hash: best case.
464        if let Some(&hash) = self.users.get(&user_id) {
465            return Ok(tl::enums::InputPeer::User(tl::types::InputPeerUser {
466                user_id,
467                access_hash: hash,
468            }));
469        }
470
471        // Min user: resolve via the message context where they were seen.
472        if let Some(&(peer_id, msg_id)) = self.min_contexts.get(&user_id) {
473            // The containing peer can be a channel, a basic group, or a DM user.
474            // Build the correct InputPeer variant for each case.
475            let container = if let Some(&(hash, _)) = self.channels.get(&peer_id) {
476                tl::enums::InputPeer::Channel(tl::types::InputPeerChannel {
477                    channel_id: peer_id,
478                    access_hash: hash,
479                })
480            } else if self.channels_min.contains(&peer_id) {
481                if self.experimental.allow_missing_channel_hash {
482                    tracing::warn!(
483                        "[ferogram::peer_cache] channel {peer_id} is a min peer \
484                         (seen inside message for user {user_id}), using access_hash=0. \
485                         This will likely cause CHANNEL_INVALID on user accounts. \
486                         Call client.resolve_peer() to get a full access_hash first."
487                    );
488                    tl::enums::InputPeer::Channel(tl::types::InputPeerChannel {
489                        channel_id: peer_id,
490                        access_hash: 0,
491                    })
492                } else {
493                    return Err(InvocationError::PeerNotCached(format!(
494                        "min user {user_id} was seen in channel {peer_id}, \
495                         but that channel is only known as a min channel (no access_hash). \
496                         Resolve the channel first, or enable \
497                         ExperimentalFeatures::allow_missing_channel_hash."
498                    )));
499                }
500            } else if self.chats.contains(&peer_id) {
501                // Basic group: no access_hash needed.
502                tl::enums::InputPeer::Chat(tl::types::InputPeerChat { chat_id: peer_id })
503            } else if let Some(&hash) = self.users.get(&peer_id) {
504                // DM: min user was seen in a direct message with another user.
505                tl::enums::InputPeer::User(tl::types::InputPeerUser {
506                    user_id: peer_id,
507                    access_hash: hash,
508                })
509            } else {
510                return Err(InvocationError::PeerNotCached(format!(
511                    "min user {user_id} was seen in peer {peer_id}, \
512                     but that peer is not cached (not a known channel, chat, or user). \
513                     Ensure the containing chat flows through the update loop first."
514                )));
515            };
516            return Ok(tl::enums::InputPeer::UserFromMessage(Box::new(
517                tl::types::InputPeerUserFromMessage {
518                    peer: container,
519                    msg_id,
520                    user_id,
521                },
522            )));
523        }
524
525        // No hash at all.
526        if self.experimental.allow_zero_hash {
527            tracing::warn!(
528                "[ferogram::peer_cache] no access_hash cached for user {user_id}, using 0. \
529                 This is valid for bots but will cause USER_ID_INVALID on user accounts. \
530                 Disable ExperimentalFeatures::allow_zero_hash or call resolve_peer() first."
531            );
532            Ok(tl::enums::InputPeer::User(tl::types::InputPeerUser {
533                user_id,
534                access_hash: 0,
535            }))
536        } else {
537            Err(InvocationError::PeerNotCached(format!(
538                "no access_hash cached for user {user_id}. \
539                 Ensure at least one message from this user flows through the \
540                 update loop before using them as a peer, or call \
541                 client.resolve_peer() first."
542            )))
543        }
544    }
545
546    fn channel_input_peer(&self, channel_id: i64) -> Result<tl::enums::InputPeer, InvocationError> {
547        if let Some(&(hash, _)) = self.channels.get(&channel_id) {
548            return Ok(tl::enums::InputPeer::Channel(tl::types::InputPeerChannel {
549                channel_id,
550                access_hash: hash,
551            }));
552        }
553
554        if self.experimental.allow_zero_hash {
555            tracing::warn!(
556                "[ferogram::peer_cache] no access_hash cached for channel {channel_id}, using 0. \
557                 This is valid for bots but will cause CHANNEL_INVALID on user accounts. \
558                 Disable ExperimentalFeatures::allow_zero_hash or call resolve_peer() first."
559            );
560            Ok(tl::enums::InputPeer::Channel(tl::types::InputPeerChannel {
561                channel_id,
562                access_hash: 0,
563            }))
564        } else {
565            Err(InvocationError::PeerNotCached(format!(
566                "no access_hash cached for channel {channel_id}. \
567                 Ensure the channel flows through the update loop before using \
568                 it as a peer, or call client.resolve_peer() first."
569            )))
570        }
571    }
572
573    /// Drop all cached min-user contexts.
574    ///
575    /// Safe to call at any time. After this, any min user that has no full
576    /// access_hash returns [`InvocationError::PeerNotCached`] until they
577    /// appear again in an update (and `cache_min_peers` is enabled) or until
578    /// `resolve_peer()` fetches their full hash.
579    ///
580    /// Call this periodically on gateway servers to bound memory growth from
581    /// min-user entries that accumulate over long uptimes.
582    pub fn clear_min_contexts(&mut self) {
583        self.min_contexts.clear();
584    }
585
586    /// A point-in-time snapshot of entry counts in this cache.
587    ///
588    /// Cheap to call, no allocation beyond the returned struct itself.
589    pub fn stats(&self) -> PeerCacheStats {
590        PeerCacheStats {
591            users: self.users.len(),
592            channels: self.channels.len(),
593            chats: self.chats.len(),
594            min_channels: self.channels_min.len(),
595            min_contexts: self.min_contexts.len(),
596            usernames: self.username_to_peer.len(),
597            phones: self.phone_to_user.len(),
598        }
599    }
600
601    pub fn peer_to_input(
602        &self,
603        peer: &tl::enums::Peer,
604    ) -> Result<tl::enums::InputPeer, InvocationError> {
605        match peer {
606            tl::enums::Peer::User(u) => self.user_input_peer(u.user_id),
607            tl::enums::Peer::Chat(c) => Ok(tl::enums::InputPeer::Chat(tl::types::InputPeerChat {
608                chat_id: c.chat_id,
609            })),
610            tl::enums::Peer::Channel(c) => self.channel_input_peer(c.channel_id),
611        }
612    }
613}