Skip to main content

layer_client/
participants.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// NOTE:
5// The "Layer" project is no longer maintained or supported.
6// Its original purpose for personal SDK/APK experimentation and learning
7// has been fulfilled.
8//
9// Please use Ferogram instead:
10// https://github.com/ankit-chaubey/ferogram
11// Ferogram will receive future updates and development, although progress
12// may be slower.
13//
14// Ferogram is an async Telegram MTProto client library written in Rust.
15// Its implementation follows the behaviour of the official Telegram clients,
16// particularly Telegram Desktop and TDLib, and aims to provide a clean and
17// modern async interface for building Telegram clients and tools.
18
19//! Chat participant and member management.
20//!
21//! Provides [`Client::get_participants`], kick, ban, and admin rights management.
22
23use std::collections::VecDeque;
24
25use layer_tl_types as tl;
26use layer_tl_types::{Cursor, Deserializable};
27
28use crate::{Client, InvocationError, PeerRef};
29
30// Participant
31
32/// A member of a chat, group or channel.
33#[derive(Debug, Clone)]
34pub struct Participant {
35    /// The user object.
36    pub user: tl::types::User,
37    /// Their role/status in the chat.
38    pub status: ParticipantStatus,
39}
40
41/// The role of a participant.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum ParticipantStatus {
44    /// Regular member.
45    Member,
46    /// The channel/group creator.
47    Creator,
48    /// Admin (may have custom title).
49    Admin,
50    /// Restricted / banned user.
51    Restricted,
52    /// Left the group.
53    Left,
54    /// Kicked (banned) from the group.
55    Banned,
56}
57
58// Client methods
59
60impl Client {
61    /// Fetch all participants of a chat, group or channel.
62    ///
63    /// For channels this uses `channels.getParticipants`; for basic groups it
64    /// uses `messages.getFullChat`.
65    ///
66    /// Returns up to `limit` participants; pass `0` for the default (200 for channels).
67    pub async fn get_participants(
68        &self,
69        peer: impl Into<PeerRef>,
70        limit: i32,
71    ) -> Result<Vec<Participant>, InvocationError> {
72        let peer = peer.into().resolve(self).await?;
73        match &peer {
74            tl::enums::Peer::Channel(c) => {
75                let cache = self.inner.peer_cache.read().await;
76                let access_hash = cache.channels.get(&c.channel_id).copied().unwrap_or(0);
77                drop(cache);
78                self.get_channel_participants(c.channel_id, access_hash, limit)
79                    .await
80            }
81            tl::enums::Peer::Chat(c) => self.get_chat_participants(c.chat_id).await,
82            _ => Err(InvocationError::Deserialize(
83                "get_participants: peer must be a chat or channel".into(),
84            )),
85        }
86    }
87
88    async fn get_channel_participants(
89        &self,
90        channel_id: i64,
91        access_hash: i64,
92        limit: i32,
93    ) -> Result<Vec<Participant>, InvocationError> {
94        let limit = if limit <= 0 { 200 } else { limit };
95        let req = tl::functions::channels::GetParticipants {
96            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
97                channel_id,
98                access_hash,
99            }),
100            filter: tl::enums::ChannelParticipantsFilter::ChannelParticipantsRecent,
101            offset: 0,
102            limit,
103            hash: 0,
104        };
105        let body = self.rpc_call_raw_pub(&req).await?;
106        let mut cur = Cursor::from_slice(&body);
107        let raw = match tl::enums::channels::ChannelParticipants::deserialize(&mut cur)? {
108            tl::enums::channels::ChannelParticipants::ChannelParticipants(p) => p,
109            tl::enums::channels::ChannelParticipants::NotModified => return Ok(vec![]),
110        };
111
112        // Build user map
113        let user_map: std::collections::HashMap<i64, tl::types::User> = raw
114            .users
115            .into_iter()
116            .filter_map(|u| match u {
117                tl::enums::User::User(u) => Some((u.id, u)),
118                _ => None,
119            })
120            .collect();
121
122        // Cache them
123        {
124            let mut cache = self.inner.peer_cache.write().await;
125            for u in user_map.values() {
126                if let Some(h) = u.access_hash {
127                    cache.users.insert(u.id, h);
128                }
129            }
130        }
131
132        let mut result = Vec::new();
133        for p in raw.participants {
134            let (user_id, status) = match &p {
135                tl::enums::ChannelParticipant::ChannelParticipant(x) => {
136                    (x.user_id, ParticipantStatus::Member)
137                }
138                tl::enums::ChannelParticipant::ParticipantSelf(x) => {
139                    (x.user_id, ParticipantStatus::Member)
140                }
141                tl::enums::ChannelParticipant::Creator(x) => {
142                    (x.user_id, ParticipantStatus::Creator)
143                }
144                tl::enums::ChannelParticipant::Admin(x) => (x.user_id, ParticipantStatus::Admin),
145                tl::enums::ChannelParticipant::Banned(x) => {
146                    (x.peer.user_id_or(0), ParticipantStatus::Banned)
147                }
148                tl::enums::ChannelParticipant::Left(x) => {
149                    (x.peer.user_id_or(0), ParticipantStatus::Left)
150                }
151            };
152            if let Some(user) = user_map.get(&user_id).cloned() {
153                result.push(Participant { user, status });
154            }
155        }
156        Ok(result)
157    }
158
159    async fn get_chat_participants(
160        &self,
161        chat_id: i64,
162    ) -> Result<Vec<Participant>, InvocationError> {
163        let req = tl::functions::messages::GetFullChat { chat_id };
164        let body = self.rpc_call_raw_pub(&req).await?;
165        let mut cur = Cursor::from_slice(&body);
166        let tl::enums::messages::ChatFull::ChatFull(full) =
167            tl::enums::messages::ChatFull::deserialize(&mut cur)?;
168
169        let user_map: std::collections::HashMap<i64, tl::types::User> = full
170            .users
171            .into_iter()
172            .filter_map(|u| match u {
173                tl::enums::User::User(u) => Some((u.id, u)),
174                _ => None,
175            })
176            .collect();
177
178        {
179            let mut cache = self.inner.peer_cache.write().await;
180            for u in user_map.values() {
181                if let Some(h) = u.access_hash {
182                    cache.users.insert(u.id, h);
183                }
184            }
185        }
186
187        let participants = match &full.full_chat {
188            tl::enums::ChatFull::ChatFull(cf) => match &cf.participants {
189                tl::enums::ChatParticipants::ChatParticipants(p) => p.participants.clone(),
190                tl::enums::ChatParticipants::Forbidden(_) => vec![],
191            },
192            tl::enums::ChatFull::ChannelFull(_) => {
193                return Err(InvocationError::Deserialize(
194                    "get_chat_participants: peer is a channel, use get_participants with a Channel peer instead".into()
195                ));
196            }
197        };
198
199        let mut result = Vec::new();
200        for p in participants {
201            let (user_id, status) = match p {
202                tl::enums::ChatParticipant::ChatParticipant(x) => {
203                    (x.user_id, ParticipantStatus::Member)
204                }
205                tl::enums::ChatParticipant::Creator(x) => (x.user_id, ParticipantStatus::Creator),
206                tl::enums::ChatParticipant::Admin(x) => (x.user_id, ParticipantStatus::Admin),
207            };
208            if let Some(user) = user_map.get(&user_id).cloned() {
209                result.push(Participant { user, status });
210            }
211        }
212        Ok(result)
213    }
214
215    /// Kick a user from a basic group (chat). For channels, use [`ban_participant`].
216    pub async fn kick_participant(
217        &self,
218        chat_id: i64,
219        user_id: i64,
220    ) -> Result<(), InvocationError> {
221        let cache = self.inner.peer_cache.read().await;
222        let access_hash = cache.users.get(&user_id).copied().unwrap_or(0);
223        drop(cache);
224        let req = tl::functions::messages::DeleteChatUser {
225            revoke_history: false,
226            chat_id,
227            user_id: tl::enums::InputUser::InputUser(tl::types::InputUser {
228                user_id,
229                access_hash,
230            }),
231        };
232        self.rpc_call_raw_pub(&req).await?;
233        Ok(())
234    }
235
236    /// Ban a user from a channel or supergroup.
237    ///
238    /// Pass `until_date = 0` for a permanent ban.
239    pub async fn ban_participant(
240        &self,
241        channel: impl Into<PeerRef>,
242        user_id: i64,
243        until_date: i32,
244    ) -> Result<(), InvocationError> {
245        let channel = channel.into().resolve(self).await?;
246        let (channel_id, ch_hash) = match &channel {
247            tl::enums::Peer::Channel(c) => {
248                let h = self
249                    .inner
250                    .peer_cache
251                    .read()
252                    .await
253                    .channels
254                    .get(&c.channel_id)
255                    .copied()
256                    .unwrap_or(0);
257                (c.channel_id, h)
258            }
259            _ => {
260                return Err(InvocationError::Deserialize(
261                    "ban_participant: peer must be a channel".into(),
262                ));
263            }
264        };
265        let user_hash = self
266            .inner
267            .peer_cache
268            .read()
269            .await
270            .users
271            .get(&user_id)
272            .copied()
273            .unwrap_or(0);
274
275        let req = tl::functions::channels::EditBanned {
276            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
277                channel_id,
278                access_hash: ch_hash,
279            }),
280            participant: tl::enums::InputPeer::User(tl::types::InputPeerUser {
281                user_id,
282                access_hash: user_hash,
283            }),
284            banned_rights: tl::enums::ChatBannedRights::ChatBannedRights(
285                tl::types::ChatBannedRights {
286                    view_messages: true,
287                    send_messages: true,
288                    send_media: true,
289                    send_stickers: true,
290                    send_gifs: true,
291                    send_games: true,
292                    send_inline: true,
293                    embed_links: true,
294                    send_polls: true,
295                    change_info: true,
296                    invite_users: true,
297                    pin_messages: true,
298                    manage_topics: false,
299                    send_photos: false,
300                    send_videos: false,
301                    send_roundvideos: false,
302                    send_audios: false,
303                    send_voices: false,
304                    send_docs: false,
305                    send_plain: false,
306                    edit_rank: false,
307                    until_date,
308                },
309            ),
310        };
311        self.rpc_call_raw_pub(&req).await?;
312        Ok(())
313    }
314
315    /// Promote (or demote) a user to admin in a channel or supergroup.
316    ///
317    /// Pass `promote = true` to grant admin rights, `false` to remove them.
318    pub async fn promote_participant(
319        &self,
320        channel: impl Into<PeerRef>,
321        user_id: i64,
322        promote: bool,
323    ) -> Result<(), InvocationError> {
324        let channel = channel.into().resolve(self).await?;
325        let (channel_id, ch_hash) = match &channel {
326            tl::enums::Peer::Channel(c) => {
327                let h = self
328                    .inner
329                    .peer_cache
330                    .read()
331                    .await
332                    .channels
333                    .get(&c.channel_id)
334                    .copied()
335                    .unwrap_or(0);
336                (c.channel_id, h)
337            }
338            _ => {
339                return Err(InvocationError::Deserialize(
340                    "promote_participant: peer must be a channel".into(),
341                ));
342            }
343        };
344        let user_hash = self
345            .inner
346            .peer_cache
347            .read()
348            .await
349            .users
350            .get(&user_id)
351            .copied()
352            .unwrap_or(0);
353
354        let rights = if promote {
355            tl::types::ChatAdminRights {
356                change_info: true,
357                post_messages: true,
358                edit_messages: true,
359                delete_messages: true,
360                ban_users: true,
361                invite_users: true,
362                pin_messages: true,
363                add_admins: false,
364                anonymous: false,
365                manage_call: true,
366                other: false,
367                manage_topics: false,
368                post_stories: false,
369                edit_stories: false,
370                delete_stories: false,
371                manage_direct_messages: false,
372                manage_ranks: false,
373            }
374        } else {
375            tl::types::ChatAdminRights {
376                change_info: false,
377                post_messages: false,
378                edit_messages: false,
379                delete_messages: false,
380                ban_users: false,
381                invite_users: false,
382                pin_messages: false,
383                add_admins: false,
384                anonymous: false,
385                manage_call: false,
386                other: false,
387                manage_topics: false,
388                post_stories: false,
389                edit_stories: false,
390                delete_stories: false,
391                manage_direct_messages: false,
392                manage_ranks: false,
393            }
394        };
395
396        let req = tl::functions::channels::EditAdmin {
397            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
398                channel_id,
399                access_hash: ch_hash,
400            }),
401            user_id: tl::enums::InputUser::InputUser(tl::types::InputUser {
402                user_id,
403                access_hash: user_hash,
404            }),
405            admin_rights: tl::enums::ChatAdminRights::ChatAdminRights(rights),
406            rank: None,
407        };
408        self.rpc_call_raw_pub(&req).await?;
409        Ok(())
410    }
411
412    /// Iterate profile photos of a user or channel.
413    ///
414    /// Returns a list of photo objects (up to `limit`).
415    pub async fn get_profile_photos(
416        &self,
417        peer: impl Into<PeerRef>,
418        limit: i32,
419    ) -> Result<Vec<tl::enums::Photo>, InvocationError> {
420        let peer = peer.into().resolve(self).await?;
421        let input_peer = {
422            let cache = self.inner.peer_cache.read().await;
423            cache.peer_to_input(&peer)
424        };
425
426        let req = tl::functions::photos::GetUserPhotos {
427            user_id: match &input_peer {
428                tl::enums::InputPeer::User(u) => {
429                    tl::enums::InputUser::InputUser(tl::types::InputUser {
430                        user_id: u.user_id,
431                        access_hash: u.access_hash,
432                    })
433                }
434                tl::enums::InputPeer::PeerSelf => tl::enums::InputUser::UserSelf,
435                _ => {
436                    return Err(InvocationError::Deserialize(
437                        "get_profile_photos: peer must be a user".into(),
438                    ));
439                }
440            },
441            offset: 0,
442            max_id: 0,
443            limit,
444        };
445        let body = self.rpc_call_raw_pub(&req).await?;
446        let mut cur = Cursor::from_slice(&body);
447        match tl::enums::photos::Photos::deserialize(&mut cur)? {
448            tl::enums::photos::Photos::Photos(p) => Ok(p.photos),
449            tl::enums::photos::Photos::Slice(p) => Ok(p.photos),
450        }
451    }
452
453    /// Stream profile photos of a user lazily, one page at a time.
454    ///
455    /// Returns a [`ProfilePhotoIter`] that fetches photos in pages of
456    /// `chunk_size` and exposes them one-by-one via `.next().await`.
457    /// Set `chunk_size` to `0` to use the default (100).
458    ///
459    /// Only works for users: channels use `messages.search` with a photo
460    /// filter instead.
461    ///
462    /// # Example
463    /// ```rust,no_run
464    /// # use layer_client::Client;
465    /// # async fn example(client: Client, peer: layer_client::tl::enums::Peer) -> anyhow::Result<()> {
466    /// let mut iter = client.iter_profile_photos(peer, 0);
467    /// while let Some(photo) = iter.next().await? {
468    /// println!("{photo:?}");
469    /// }
470    /// # Ok(()) }
471    /// ```
472    pub async fn iter_profile_photos(
473        &self,
474        peer: impl Into<PeerRef>,
475        chunk_size: i32,
476    ) -> Result<ProfilePhotoIter, InvocationError> {
477        let chunk_size = if chunk_size <= 0 { 100 } else { chunk_size };
478        let peer = peer.into().resolve(self).await?;
479        let input_peer = {
480            let cache = self.inner.peer_cache.read().await;
481            cache.peer_to_input(&peer)
482        };
483        let input_user = match &input_peer {
484            tl::enums::InputPeer::User(u) => {
485                tl::enums::InputUser::InputUser(tl::types::InputUser {
486                    user_id: u.user_id,
487                    access_hash: u.access_hash,
488                })
489            }
490            tl::enums::InputPeer::PeerSelf => tl::enums::InputUser::UserSelf,
491            _ => {
492                return Err(InvocationError::Deserialize(
493                    "iter_profile_photos: peer must be a user".into(),
494                ));
495            }
496        };
497
498        Ok(ProfilePhotoIter {
499            client: self.clone(),
500            input_user,
501            chunk_size,
502            offset: 0,
503            buffer: VecDeque::new(),
504            done: false,
505        })
506    }
507
508    /// Search for a peer (user, group, or channel) by name prefix.
509    ///
510    /// Searches contacts, dialogs, and globally. Returns combined results.
511    pub async fn search_peer(&self, query: &str) -> Result<Vec<tl::enums::Peer>, InvocationError> {
512        let req = tl::functions::contacts::Search {
513            q: query.to_string(),
514            limit: 20,
515        };
516        let body = self.rpc_call_raw_pub(&req).await?;
517        let mut cur = Cursor::from_slice(&body);
518        let tl::enums::contacts::Found::Found(found) =
519            tl::enums::contacts::Found::deserialize(&mut cur)?;
520
521        self.cache_users_slice_pub(&found.users).await;
522        self.cache_chats_slice_pub(&found.chats).await;
523
524        let mut peers = Vec::new();
525        for r in found.my_results.iter().chain(found.results.iter()) {
526            peers.push(r.clone());
527        }
528        Ok(peers)
529    }
530
531    /// Send a reaction to a message.
532    ///
533    /// Accepts anything that converts to [`InputReactions`]:
534    ///
535    /// ```rust,no_run
536    /// // emoji shorthand
537    /// client.send_reaction(peer, msg_id, "👍").await?;
538    ///
539    /// // fluent builder
540    /// use layer_client::reactions::InputReactions;
541    /// client.send_reaction(peer, msg_id, InputReactions::custom_emoji(123).big()).await?;
542    ///
543    /// // remove all reactions
544    /// client.send_reaction(peer, msg_id, InputReactions::remove()).await?;
545    /// ```
546    pub async fn send_reaction(
547        &self,
548        peer: impl Into<PeerRef>,
549        message_id: i32,
550        reaction: impl Into<crate::reactions::InputReactions>,
551    ) -> Result<(), InvocationError> {
552        let peer = peer.into().resolve(self).await?;
553        let input_peer = {
554            let cache = self.inner.peer_cache.read().await;
555            cache.peer_to_input(&peer)
556        };
557
558        let r: crate::reactions::InputReactions = reaction.into();
559        let req = tl::functions::messages::SendReaction {
560            big: r.big,
561            add_to_recent: r.add_to_recent,
562            peer: input_peer,
563            msg_id: message_id,
564            reaction: if r.reactions.is_empty() {
565                None
566            } else {
567                Some(r.reactions)
568            },
569        };
570        self.rpc_call_raw_pub(&req).await?;
571        Ok(())
572    }
573}
574
575// Helper extension for Peer
576
577trait PeerUserIdExt {
578    fn user_id_or(&self, default: i64) -> i64;
579}
580
581impl PeerUserIdExt for tl::enums::Peer {
582    fn user_id_or(&self, default: i64) -> i64 {
583        match self {
584            tl::enums::Peer::User(u) => u.user_id,
585            _ => default,
586        }
587    }
588}
589
590// BannedRightsBuilder
591
592/// Fluent builder for granular channel ban rights.
593///
594/// ```rust,no_run
595/// # async fn f(client: layer_client::Client, channel: layer_tl_types::enums::Peer) -> Result<(), Box<dyn std::error::Error>> {
596/// client.set_banned_rights(channel, 12345678, |b| b
597/// .send_messages(true)
598/// .send_media(true)
599/// .until_date(0))
600/// .await?;
601/// # Ok(()) }
602/// ```
603#[derive(Debug, Clone, Default)]
604pub struct BannedRightsBuilder {
605    pub view_messages: bool,
606    pub send_messages: bool,
607    pub send_media: bool,
608    pub send_stickers: bool,
609    pub send_gifs: bool,
610    pub send_games: bool,
611    pub send_inline: bool,
612    pub embed_links: bool,
613    pub send_polls: bool,
614    pub change_info: bool,
615    pub invite_users: bool,
616    pub pin_messages: bool,
617    pub until_date: i32,
618}
619
620impl BannedRightsBuilder {
621    pub fn new() -> Self {
622        Self::default()
623    }
624    pub fn view_messages(mut self, v: bool) -> Self {
625        self.view_messages = v;
626        self
627    }
628    pub fn send_messages(mut self, v: bool) -> Self {
629        self.send_messages = v;
630        self
631    }
632    pub fn send_media(mut self, v: bool) -> Self {
633        self.send_media = v;
634        self
635    }
636    pub fn send_stickers(mut self, v: bool) -> Self {
637        self.send_stickers = v;
638        self
639    }
640    pub fn send_gifs(mut self, v: bool) -> Self {
641        self.send_gifs = v;
642        self
643    }
644    pub fn send_games(mut self, v: bool) -> Self {
645        self.send_games = v;
646        self
647    }
648    pub fn send_inline(mut self, v: bool) -> Self {
649        self.send_inline = v;
650        self
651    }
652    pub fn embed_links(mut self, v: bool) -> Self {
653        self.embed_links = v;
654        self
655    }
656    pub fn send_polls(mut self, v: bool) -> Self {
657        self.send_polls = v;
658        self
659    }
660    pub fn change_info(mut self, v: bool) -> Self {
661        self.change_info = v;
662        self
663    }
664    pub fn invite_users(mut self, v: bool) -> Self {
665        self.invite_users = v;
666        self
667    }
668    pub fn pin_messages(mut self, v: bool) -> Self {
669        self.pin_messages = v;
670        self
671    }
672    /// Ban until a Unix timestamp. `0` = permanent.
673    pub fn until_date(mut self, ts: i32) -> Self {
674        self.until_date = ts;
675        self
676    }
677
678    /// Full ban (all rights revoked, permanent).
679    pub fn full_ban() -> Self {
680        Self {
681            view_messages: true,
682            send_messages: true,
683            send_media: true,
684            send_stickers: true,
685            send_gifs: true,
686            send_games: true,
687            send_inline: true,
688            embed_links: true,
689            send_polls: true,
690            change_info: true,
691            invite_users: true,
692            pin_messages: true,
693            until_date: 0,
694        }
695    }
696
697    pub(crate) fn into_tl(self) -> tl::enums::ChatBannedRights {
698        tl::enums::ChatBannedRights::ChatBannedRights(tl::types::ChatBannedRights {
699            view_messages: self.view_messages,
700            send_messages: self.send_messages,
701            send_media: self.send_media,
702            send_stickers: self.send_stickers,
703            send_gifs: self.send_gifs,
704            send_games: self.send_games,
705            send_inline: self.send_inline,
706            embed_links: self.embed_links,
707            send_polls: self.send_polls,
708            change_info: self.change_info,
709            invite_users: self.invite_users,
710            pin_messages: self.pin_messages,
711            manage_topics: false,
712            send_photos: false,
713            send_videos: false,
714            send_roundvideos: false,
715            send_audios: false,
716            send_voices: false,
717            send_docs: false,
718            send_plain: false,
719            edit_rank: false,
720            until_date: self.until_date,
721        })
722    }
723}
724
725// AdminRightsBuilder
726
727/// Fluent builder for granular admin rights.
728#[derive(Debug, Clone, Default)]
729pub struct AdminRightsBuilder {
730    pub change_info: bool,
731    pub post_messages: bool,
732    pub edit_messages: bool,
733    pub delete_messages: bool,
734    pub ban_users: bool,
735    pub invite_users: bool,
736    pub pin_messages: bool,
737    pub add_admins: bool,
738    pub anonymous: bool,
739    pub manage_call: bool,
740    pub manage_topics: bool,
741    pub rank: Option<String>,
742}
743
744impl AdminRightsBuilder {
745    pub fn new() -> Self {
746        Self::default()
747    }
748    pub fn change_info(mut self, v: bool) -> Self {
749        self.change_info = v;
750        self
751    }
752    pub fn post_messages(mut self, v: bool) -> Self {
753        self.post_messages = v;
754        self
755    }
756    pub fn edit_messages(mut self, v: bool) -> Self {
757        self.edit_messages = v;
758        self
759    }
760    pub fn delete_messages(mut self, v: bool) -> Self {
761        self.delete_messages = v;
762        self
763    }
764    pub fn ban_users(mut self, v: bool) -> Self {
765        self.ban_users = v;
766        self
767    }
768    pub fn invite_users(mut self, v: bool) -> Self {
769        self.invite_users = v;
770        self
771    }
772    pub fn pin_messages(mut self, v: bool) -> Self {
773        self.pin_messages = v;
774        self
775    }
776    pub fn add_admins(mut self, v: bool) -> Self {
777        self.add_admins = v;
778        self
779    }
780    pub fn anonymous(mut self, v: bool) -> Self {
781        self.anonymous = v;
782        self
783    }
784    pub fn manage_call(mut self, v: bool) -> Self {
785        self.manage_call = v;
786        self
787    }
788    pub fn manage_topics(mut self, v: bool) -> Self {
789        self.manage_topics = v;
790        self
791    }
792    /// Custom admin title (max 16 chars).
793    pub fn rank(mut self, r: impl Into<String>) -> Self {
794        self.rank = Some(r.into());
795        self
796    }
797
798    /// Full admin (all standard rights).
799    pub fn full_admin() -> Self {
800        Self {
801            change_info: true,
802            post_messages: true,
803            edit_messages: true,
804            delete_messages: true,
805            ban_users: true,
806            invite_users: true,
807            pin_messages: true,
808            add_admins: false,
809            anonymous: false,
810            manage_call: true,
811            manage_topics: true,
812            rank: None,
813        }
814    }
815
816    pub(crate) fn into_tl_rights(self) -> tl::enums::ChatAdminRights {
817        tl::enums::ChatAdminRights::ChatAdminRights(tl::types::ChatAdminRights {
818            change_info: self.change_info,
819            post_messages: self.post_messages,
820            edit_messages: self.edit_messages,
821            delete_messages: self.delete_messages,
822            ban_users: self.ban_users,
823            invite_users: self.invite_users,
824            pin_messages: self.pin_messages,
825            add_admins: self.add_admins,
826            anonymous: self.anonymous,
827            manage_call: self.manage_call,
828            other: false,
829            manage_topics: self.manage_topics,
830            post_stories: false,
831            edit_stories: false,
832            delete_stories: false,
833            manage_direct_messages: false,
834            manage_ranks: false,
835        })
836    }
837}
838
839// ParticipantPermissions
840
841/// The effective permissions/rights of a specific participant.
842#[derive(Debug, Clone)]
843pub struct ParticipantPermissions {
844    pub is_creator: bool,
845    pub is_admin: bool,
846    pub is_banned: bool,
847    pub is_left: bool,
848    pub can_send_messages: bool,
849    pub can_send_media: bool,
850    pub can_pin_messages: bool,
851    pub can_add_admins: bool,
852    pub admin_rank: Option<String>,
853}
854
855impl ParticipantPermissions {
856    pub fn is_creator(&self) -> bool {
857        self.is_creator
858    }
859    pub fn is_admin(&self) -> bool {
860        self.is_admin
861    }
862    pub fn is_banned(&self) -> bool {
863        self.is_banned
864    }
865    pub fn is_member(&self) -> bool {
866        !self.is_banned && !self.is_left
867    }
868}
869
870// Client: new participant methods
871
872impl Client {
873    // set_banned_rights
874
875    /// Apply granular ban rights to a user in a channel or supergroup.
876    ///
877    /// Use [`BannedRightsBuilder`] to specify which rights to restrict.
878    pub async fn set_banned_rights(
879        &self,
880        channel: impl Into<PeerRef>,
881        user_id: i64,
882        build: impl FnOnce(BannedRightsBuilder) -> BannedRightsBuilder,
883    ) -> Result<(), InvocationError> {
884        let rights = build(BannedRightsBuilder::new()).into_tl();
885        let channel = channel.into().resolve(self).await?;
886        let (channel_id, ch_hash) = match &channel {
887            tl::enums::Peer::Channel(c) => {
888                let h = self
889                    .inner
890                    .peer_cache
891                    .read()
892                    .await
893                    .channels
894                    .get(&c.channel_id)
895                    .copied()
896                    .unwrap_or(0);
897                (c.channel_id, h)
898            }
899            _ => {
900                return Err(InvocationError::Deserialize(
901                    "set_banned_rights: must be a channel".into(),
902                ));
903            }
904        };
905        let user_hash = self
906            .inner
907            .peer_cache
908            .read()
909            .await
910            .users
911            .get(&user_id)
912            .copied()
913            .unwrap_or(0);
914        let req = tl::functions::channels::EditBanned {
915            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
916                channel_id,
917                access_hash: ch_hash,
918            }),
919            participant: tl::enums::InputPeer::User(tl::types::InputPeerUser {
920                user_id,
921                access_hash: user_hash,
922            }),
923            banned_rights: rights,
924        };
925        self.rpc_call_raw_pub(&req).await?;
926        Ok(())
927    }
928
929    // set_admin_rights
930
931    /// Apply granular admin rights to a user in a channel or supergroup.
932    ///
933    /// Use [`AdminRightsBuilder`] to specify which rights to grant.
934    pub async fn set_admin_rights(
935        &self,
936        channel: impl Into<PeerRef>,
937        user_id: i64,
938        build: impl FnOnce(AdminRightsBuilder) -> AdminRightsBuilder,
939    ) -> Result<(), InvocationError> {
940        let b = build(AdminRightsBuilder::new());
941        let rank = b.rank.clone();
942        let rights = b.into_tl_rights();
943        let channel = channel.into().resolve(self).await?;
944        let (channel_id, ch_hash) = match &channel {
945            tl::enums::Peer::Channel(c) => {
946                let h = self
947                    .inner
948                    .peer_cache
949                    .read()
950                    .await
951                    .channels
952                    .get(&c.channel_id)
953                    .copied()
954                    .unwrap_or(0);
955                (c.channel_id, h)
956            }
957            _ => {
958                return Err(InvocationError::Deserialize(
959                    "set_admin_rights: must be a channel".into(),
960                ));
961            }
962        };
963        let user_hash = self
964            .inner
965            .peer_cache
966            .read()
967            .await
968            .users
969            .get(&user_id)
970            .copied()
971            .unwrap_or(0);
972        let req = tl::functions::channels::EditAdmin {
973            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
974                channel_id,
975                access_hash: ch_hash,
976            }),
977            user_id: tl::enums::InputUser::InputUser(tl::types::InputUser {
978                user_id,
979                access_hash: user_hash,
980            }),
981            admin_rights: rights,
982            rank,
983        };
984        self.rpc_call_raw_pub(&req).await?;
985        Ok(())
986    }
987
988    // iter_participants with filter
989
990    /// Fetch participants with an optional filter, paginated.
991    ///
992    /// `filter` defaults to `ChannelParticipantsRecent` when `None`.
993    pub async fn iter_participants(
994        &self,
995        peer: impl Into<PeerRef>,
996        filter: Option<tl::enums::ChannelParticipantsFilter>,
997        limit: i32,
998    ) -> Result<Vec<Participant>, InvocationError> {
999        let peer = peer.into().resolve(self).await?;
1000        match &peer {
1001            tl::enums::Peer::Channel(c) => {
1002                let access_hash = self
1003                    .inner
1004                    .peer_cache
1005                    .read()
1006                    .await
1007                    .channels
1008                    .get(&c.channel_id)
1009                    .copied()
1010                    .unwrap_or(0);
1011                let filter = filter
1012                    .unwrap_or(tl::enums::ChannelParticipantsFilter::ChannelParticipantsRecent);
1013                let limit = if limit <= 0 { 200 } else { limit };
1014                let req = tl::functions::channels::GetParticipants {
1015                    channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
1016                        channel_id: c.channel_id,
1017                        access_hash,
1018                    }),
1019                    filter,
1020                    offset: 0,
1021                    limit,
1022                    hash: 0,
1023                };
1024                let body = self.rpc_call_raw_pub(&req).await?;
1025                let mut cur = Cursor::from_slice(&body);
1026                let raw = match tl::enums::channels::ChannelParticipants::deserialize(&mut cur)? {
1027                    tl::enums::channels::ChannelParticipants::ChannelParticipants(p) => p,
1028                    tl::enums::channels::ChannelParticipants::NotModified => return Ok(vec![]),
1029                };
1030                let user_map: std::collections::HashMap<i64, tl::types::User> = raw
1031                    .users
1032                    .into_iter()
1033                    .filter_map(|u| match u {
1034                        tl::enums::User::User(u) => Some((u.id, u)),
1035                        _ => None,
1036                    })
1037                    .collect();
1038                {
1039                    let mut cache = self.inner.peer_cache.write().await;
1040                    for u in user_map.values() {
1041                        if let Some(h) = u.access_hash {
1042                            cache.users.insert(u.id, h);
1043                        }
1044                    }
1045                }
1046                Ok(raw
1047                    .participants
1048                    .into_iter()
1049                    .filter_map(|p| {
1050                        let (uid, status) = match &p {
1051                            tl::enums::ChannelParticipant::ChannelParticipant(x) => {
1052                                (x.user_id, ParticipantStatus::Member)
1053                            }
1054                            tl::enums::ChannelParticipant::ParticipantSelf(x) => {
1055                                (x.user_id, ParticipantStatus::Member)
1056                            }
1057                            tl::enums::ChannelParticipant::Creator(x) => {
1058                                (x.user_id, ParticipantStatus::Creator)
1059                            }
1060                            tl::enums::ChannelParticipant::Admin(x) => {
1061                                (x.user_id, ParticipantStatus::Admin)
1062                            }
1063                            tl::enums::ChannelParticipant::Banned(x) => {
1064                                if let tl::enums::Peer::User(u) = &x.peer {
1065                                    (u.user_id, ParticipantStatus::Banned)
1066                                } else {
1067                                    return None;
1068                                }
1069                            }
1070                            tl::enums::ChannelParticipant::Left(x) => {
1071                                if let tl::enums::Peer::User(u) = &x.peer {
1072                                    (u.user_id, ParticipantStatus::Left)
1073                                } else {
1074                                    return None;
1075                                }
1076                            }
1077                        };
1078                        user_map.get(&uid).map(|u| Participant {
1079                            user: u.clone(),
1080                            status,
1081                        })
1082                    })
1083                    .collect())
1084            }
1085            tl::enums::Peer::Chat(c) => self.get_chat_participants(c.chat_id).await,
1086            _ => Err(InvocationError::Deserialize(
1087                "iter_participants: must be chat or channel".into(),
1088            )),
1089        }
1090    }
1091
1092    // get_permissions
1093
1094    /// Get the effective permissions of a specific user in a channel.
1095    pub async fn get_permissions(
1096        &self,
1097        channel: impl Into<PeerRef>,
1098        user_id: i64,
1099    ) -> Result<ParticipantPermissions, InvocationError> {
1100        let channel = channel.into().resolve(self).await?;
1101        let (channel_id, ch_hash) = match &channel {
1102            tl::enums::Peer::Channel(c) => {
1103                let h = self
1104                    .inner
1105                    .peer_cache
1106                    .read()
1107                    .await
1108                    .channels
1109                    .get(&c.channel_id)
1110                    .copied()
1111                    .unwrap_or(0);
1112                (c.channel_id, h)
1113            }
1114            _ => {
1115                return Err(InvocationError::Deserialize(
1116                    "get_permissions: must be a channel".into(),
1117                ));
1118            }
1119        };
1120        let user_hash = self
1121            .inner
1122            .peer_cache
1123            .read()
1124            .await
1125            .users
1126            .get(&user_id)
1127            .copied()
1128            .unwrap_or(0);
1129        let req = tl::functions::channels::GetParticipant {
1130            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
1131                channel_id,
1132                access_hash: ch_hash,
1133            }),
1134            participant: tl::enums::InputPeer::User(tl::types::InputPeerUser {
1135                user_id,
1136                access_hash: user_hash,
1137            }),
1138        };
1139        let body = self.rpc_call_raw_pub(&req).await?;
1140        let mut cur = Cursor::from_slice(&body);
1141        let tl::enums::channels::ChannelParticipant::ChannelParticipant(raw) =
1142            tl::enums::channels::ChannelParticipant::deserialize(&mut cur)?;
1143
1144        let perms = match raw.participant {
1145            tl::enums::ChannelParticipant::Creator(_) => ParticipantPermissions {
1146                is_creator: true,
1147                is_admin: true,
1148                is_banned: false,
1149                is_left: false,
1150                can_send_messages: true,
1151                can_send_media: true,
1152                can_pin_messages: true,
1153                can_add_admins: true,
1154                admin_rank: None,
1155            },
1156            tl::enums::ChannelParticipant::Admin(a) => {
1157                let tl::enums::ChatAdminRights::ChatAdminRights(rights) = a.admin_rights;
1158                ParticipantPermissions {
1159                    is_creator: false,
1160                    is_admin: true,
1161                    is_banned: false,
1162                    is_left: false,
1163                    can_send_messages: true,
1164                    can_send_media: true,
1165                    can_pin_messages: rights.pin_messages,
1166                    can_add_admins: rights.add_admins,
1167                    admin_rank: a.rank,
1168                }
1169            }
1170            tl::enums::ChannelParticipant::Banned(b) => {
1171                let tl::enums::ChatBannedRights::ChatBannedRights(rights) = b.banned_rights;
1172                ParticipantPermissions {
1173                    is_creator: false,
1174                    is_admin: false,
1175                    is_banned: true,
1176                    is_left: false,
1177                    can_send_messages: !rights.send_messages,
1178                    can_send_media: !rights.send_media,
1179                    can_pin_messages: !rights.pin_messages,
1180                    can_add_admins: false,
1181                    admin_rank: None,
1182                }
1183            }
1184            tl::enums::ChannelParticipant::Left(_) => ParticipantPermissions {
1185                is_creator: false,
1186                is_admin: false,
1187                is_banned: false,
1188                is_left: true,
1189                can_send_messages: false,
1190                can_send_media: false,
1191                can_pin_messages: false,
1192                can_add_admins: false,
1193                admin_rank: None,
1194            },
1195            _ => ParticipantPermissions {
1196                is_creator: false,
1197                is_admin: false,
1198                is_banned: false,
1199                is_left: false,
1200                can_send_messages: true,
1201                can_send_media: true,
1202                can_pin_messages: false,
1203                can_add_admins: false,
1204                admin_rank: None,
1205            },
1206        };
1207
1208        Ok(perms)
1209    }
1210}
1211
1212/// Lazy async iterator over a user's profile photos.
1213///
1214/// Obtained from [`Client::iter_profile_photos`].
1215///
1216/// Fetches photos in pages and yields them one at a time.
1217/// Returns `Ok(None)` when all photos have been consumed.
1218///
1219/// # Example
1220/// ```rust,no_run
1221/// # use layer_client::Client;
1222/// # async fn example(client: Client, peer: layer_client::tl::enums::Peer) -> anyhow::Result<()> {
1223/// let mut iter = client.iter_profile_photos(peer, 0).await?;
1224/// while let Some(photo) = iter.next().await? {
1225/// println!("{photo:?}");
1226/// }
1227/// # Ok(()) }
1228/// ```
1229pub struct ProfilePhotoIter {
1230    client: Client,
1231    input_user: tl::enums::InputUser,
1232    chunk_size: i32,
1233    /// Next offset to request from the server.
1234    offset: i32,
1235    /// Buffered photos from the last fetched page.
1236    buffer: VecDeque<tl::enums::Photo>,
1237    /// `true` once the server has no more photos to return.
1238    done: bool,
1239}
1240
1241impl ProfilePhotoIter {
1242    /// Yield the next profile photo, fetching a new page from Telegram when
1243    /// the local buffer is empty.
1244    ///
1245    /// Returns `Ok(None)` when iteration is complete.
1246    pub async fn next(&mut self) -> Result<Option<tl::enums::Photo>, InvocationError> {
1247        // Serve from buffer first.
1248        if let Some(photo) = self.buffer.pop_front() {
1249            return Ok(Some(photo));
1250        }
1251
1252        // Buffer empty: if we already know there are no more pages, stop.
1253        if self.done {
1254            return Ok(None);
1255        }
1256
1257        // Fetch next page.
1258        let req = tl::functions::photos::GetUserPhotos {
1259            user_id: self.input_user.clone(),
1260            offset: self.offset,
1261            max_id: 0,
1262            limit: self.chunk_size,
1263        };
1264        let body = self.client.rpc_call_raw_pub(&req).await?;
1265        let mut cur = Cursor::from_slice(&body);
1266
1267        let (photos, total): (Vec<tl::enums::Photo>, Option<i32>) =
1268            match tl::enums::photos::Photos::deserialize(&mut cur)? {
1269                tl::enums::photos::Photos::Photos(p) => {
1270                    // Server returned everything at once: no more pages.
1271                    self.done = true;
1272                    (p.photos, None)
1273                }
1274                tl::enums::photos::Photos::Slice(p) => (p.photos, Some(p.count)),
1275            };
1276
1277        let returned = photos.len() as i32;
1278        self.offset += returned;
1279
1280        // If we got fewer than requested, or we've reached the total, we're done.
1281        if returned < self.chunk_size {
1282            self.done = true;
1283        }
1284        if let Some(total) = total
1285            && self.offset >= total
1286        {
1287            self.done = true;
1288        }
1289
1290        self.buffer.extend(photos);
1291        Ok(self.buffer.pop_front())
1292    }
1293
1294    /// Collect all remaining photos into a `Vec`.
1295    ///
1296    /// Convenience wrapper around repeated `.next()` calls.
1297    pub async fn collect(mut self) -> Result<Vec<tl::enums::Photo>, InvocationError> {
1298        let mut out = Vec::new();
1299        while let Some(photo) = self.next().await? {
1300            out.push(photo);
1301        }
1302        Ok(out)
1303    }
1304
1305    /// Total number of photos reported by the server on the first page.
1306    ///
1307    /// Returns `None` until the first page has been fetched, or if the server
1308    /// returned a non-slice response (meaning all photos fit in one page).
1309    pub fn total_count(&self) -> Option<i32> {
1310        // Exposed as a future extension point: currently the total is only
1311        // available after the first network round-trip, so callers should
1312        // call `.next()` once before querying this if they need the count.
1313        // For now, we expose offset as a proxy.
1314        if self.offset > 0 {
1315            Some(self.offset)
1316        } else {
1317            None
1318        }
1319    }
1320}