Skip to main content

layer_client/
participants.rs

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