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 layer_tl_types as tl;
6use layer_tl_types::{Cursor, Deserializable};
7
8use crate::{Client, InvocationError};
9
10// ─── Participant ──────────────────────────────────────────────────────────────
11
12/// A member of a chat, group or channel.
13#[derive(Debug, Clone)]
14pub struct Participant {
15    /// The user object.
16    pub user: tl::types::User,
17    /// Their role/status in the chat.
18    pub status: ParticipantStatus,
19}
20
21/// The role of a participant.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum ParticipantStatus {
24    /// Regular member.
25    Member,
26    /// The channel/group creator.
27    Creator,
28    /// Admin (may have custom title).
29    Admin,
30    /// Restricted / banned user.
31    Restricted,
32    /// Left the group.
33    Left,
34    /// Kicked (banned) from the group.
35    Banned,
36}
37
38// ─── Client methods ───────────────────────────────────────────────────────────
39
40impl Client {
41    /// Fetch all participants of a chat, group or channel.
42    ///
43    /// For channels this uses `channels.getParticipants`; for basic groups it
44    /// uses `messages.getFullChat`.
45    ///
46    /// Returns up to `limit` participants; pass `0` for the default (200 for channels).
47    pub async fn get_participants(
48        &self,
49        peer:  tl::enums::Peer,
50        limit: i32,
51    ) -> Result<Vec<Participant>, InvocationError> {
52        match &peer {
53            tl::enums::Peer::Channel(c) => {
54                let cache       = self.inner.peer_cache.lock().await;
55                let access_hash = cache.channels.get(&c.channel_id).copied().unwrap_or(0);
56                drop(cache);
57                self.get_channel_participants(c.channel_id, access_hash, limit).await
58            }
59            tl::enums::Peer::Chat(c) => {
60                self.get_chat_participants(c.chat_id).await
61            }
62            _ => Err(InvocationError::Deserialize("get_participants: peer must be a chat or channel".into())),
63        }
64    }
65
66    async fn get_channel_participants(
67        &self,
68        channel_id:  i64,
69        access_hash: i64,
70        limit:       i32,
71    ) -> Result<Vec<Participant>, InvocationError> {
72        let limit = if limit <= 0 { 200 } else { limit };
73        let req = tl::functions::channels::GetParticipants {
74            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
75                channel_id, access_hash,
76            }),
77            filter:  tl::enums::ChannelParticipantsFilter::ChannelParticipantsRecent,
78            offset:  0,
79            limit,
80            hash:    0,
81        };
82        let body    = self.rpc_call_raw_pub(&req).await?;
83        let mut cur = Cursor::from_slice(&body);
84        let raw     = match tl::enums::channels::ChannelParticipants::deserialize(&mut cur)? {
85            tl::enums::channels::ChannelParticipants::ChannelParticipants(p) => p,
86            tl::enums::channels::ChannelParticipants::NotModified => return Ok(vec![]),
87        };
88
89        // Build user map
90        let user_map: std::collections::HashMap<i64, tl::types::User> = raw.users.into_iter()
91            .filter_map(|u| match u { tl::enums::User::User(u) => Some((u.id, u)), _ => None })
92            .collect();
93
94        // Cache them
95        {
96            let mut cache = self.inner.peer_cache.lock().await;
97            for u in user_map.values() {
98                if let Some(h) = u.access_hash { cache.users.insert(u.id, h); }
99            }
100        }
101
102        let mut result = Vec::new();
103        for p in raw.participants {
104            let (user_id, status) = match &p {
105                tl::enums::ChannelParticipant::ChannelParticipant(x) => (x.user_id, ParticipantStatus::Member),
106                tl::enums::ChannelParticipant::ParticipantSelf(x)    => (x.user_id, ParticipantStatus::Member),
107                tl::enums::ChannelParticipant::Creator(x)            => (x.user_id, ParticipantStatus::Creator),
108                tl::enums::ChannelParticipant::Admin(x)              => (x.user_id, ParticipantStatus::Admin),
109                tl::enums::ChannelParticipant::Banned(x)             => (x.peer.user_id_or(0), ParticipantStatus::Banned),
110                tl::enums::ChannelParticipant::Left(x)               => (x.peer.user_id_or(0), ParticipantStatus::Left),
111            };
112            if let Some(user) = user_map.get(&user_id).cloned() {
113                result.push(Participant { user, status });
114            }
115        }
116        Ok(result)
117    }
118
119    async fn get_chat_participants(&self, chat_id: i64) -> Result<Vec<Participant>, InvocationError> {
120        let req  = tl::functions::messages::GetFullChat { chat_id };
121        let body = self.rpc_call_raw_pub(&req).await?;
122        let mut cur = Cursor::from_slice(&body);
123        let tl::enums::messages::ChatFull::ChatFull(full) = tl::enums::messages::ChatFull::deserialize(&mut cur)?;
124
125        let user_map: std::collections::HashMap<i64, tl::types::User> = full.users.into_iter()
126            .filter_map(|u| match u { tl::enums::User::User(u) => Some((u.id, u)), _ => None })
127            .collect();
128
129        {
130            let mut cache = self.inner.peer_cache.lock().await;
131            for u in user_map.values() {
132                if let Some(h) = u.access_hash { cache.users.insert(u.id, h); }
133            }
134        }
135
136        let participants = match &full.full_chat {
137            tl::enums::ChatFull::ChatFull(cf) => match &cf.participants {
138                tl::enums::ChatParticipants::ChatParticipants(p) => p.participants.clone(),
139                tl::enums::ChatParticipants::Forbidden(_)        => vec![],
140            },
141            tl::enums::ChatFull::ChannelFull(_) => {
142                return Err(InvocationError::Deserialize(
143                    "get_chat_participants: peer is a channel, use get_participants with a Channel peer instead".into()
144                ));
145            }
146        };
147
148        let mut result = Vec::new();
149        for p in participants {
150            let (user_id, status) = match p {
151                tl::enums::ChatParticipant::ChatParticipant(x) => (x.user_id, ParticipantStatus::Member),
152                tl::enums::ChatParticipant::Creator(x)          => (x.user_id, ParticipantStatus::Creator),
153                tl::enums::ChatParticipant::Admin(x)            => (x.user_id, ParticipantStatus::Admin),
154            };
155            if let Some(user) = user_map.get(&user_id).cloned() {
156                result.push(Participant { user, status });
157            }
158        }
159        Ok(result)
160    }
161
162    /// Kick a user from a basic group (chat). For channels, use [`ban_participant`].
163    pub async fn kick_participant(
164        &self,
165        chat_id: i64,
166        user_id: i64,
167    ) -> Result<(), InvocationError> {
168        let cache       = self.inner.peer_cache.lock().await;
169        let access_hash = cache.users.get(&user_id).copied().unwrap_or(0);
170        drop(cache);
171        let req = tl::functions::messages::DeleteChatUser {
172            revoke_history: false,
173            chat_id,
174            user_id: tl::enums::InputUser::InputUser(tl::types::InputUser { user_id, access_hash }),
175        };
176        self.rpc_call_raw_pub(&req).await?;
177        Ok(())
178    }
179
180    /// Ban a user from a channel or supergroup.
181    ///
182    /// Pass `until_date = 0` for a permanent ban.
183    pub async fn ban_participant(
184        &self,
185        channel:    tl::enums::Peer,
186        user_id:    i64,
187        until_date: i32,
188    ) -> Result<(), InvocationError> {
189        let (channel_id, ch_hash) = match &channel {
190            tl::enums::Peer::Channel(c) => {
191                let h = self.inner.peer_cache.lock().await.channels.get(&c.channel_id).copied().unwrap_or(0);
192                (c.channel_id, h)
193            }
194            _ => return Err(InvocationError::Deserialize("ban_participant: peer must be a channel".into())),
195        };
196        let user_hash = self.inner.peer_cache.lock().await.users.get(&user_id).copied().unwrap_or(0);
197
198        let req = tl::functions::channels::EditBanned {
199            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
200                channel_id, access_hash: ch_hash,
201            }),
202            participant: tl::enums::InputPeer::User(tl::types::InputPeerUser {
203                user_id, access_hash: user_hash,
204            }),
205            banned_rights: tl::enums::ChatBannedRights::ChatBannedRights(tl::types::ChatBannedRights {
206                view_messages:   true,
207                send_messages:   true,
208                send_media:      true,
209                send_stickers:   true,
210                send_gifs:       true,
211                send_games:      true,
212                send_inline:     true,
213                embed_links:     true,
214                send_polls:      true,
215                change_info:     true,
216                invite_users:    true,
217                pin_messages:    true,
218                manage_topics:   false,
219                send_photos:     false,
220                send_videos:     false,
221                send_roundvideos: false,
222                send_audios:     false,
223                send_voices:     false,
224                send_docs:       false,
225                send_plain:      false,
226                edit_rank:       false,
227                until_date,
228            }),
229        };
230        self.rpc_call_raw_pub(&req).await?;
231        Ok(())
232    }
233
234    /// Promote (or demote) a user to admin in a channel or supergroup.
235    ///
236    /// Pass `promote = true` to grant admin rights, `false` to remove them.
237    pub async fn promote_participant(
238        &self,
239        channel: tl::enums::Peer,
240        user_id: i64,
241        promote: bool,
242    ) -> Result<(), InvocationError> {
243        let (channel_id, ch_hash) = match &channel {
244            tl::enums::Peer::Channel(c) => {
245                let h = self.inner.peer_cache.lock().await.channels.get(&c.channel_id).copied().unwrap_or(0);
246                (c.channel_id, h)
247            }
248            _ => return Err(InvocationError::Deserialize("promote_participant: peer must be a channel".into())),
249        };
250        let user_hash = self.inner.peer_cache.lock().await.users.get(&user_id).copied().unwrap_or(0);
251
252        let rights = if promote {
253            tl::types::ChatAdminRights {
254                change_info:            true,
255                post_messages:          true,
256                edit_messages:          true,
257                delete_messages:        true,
258                ban_users:              true,
259                invite_users:           true,
260                pin_messages:           true,
261                add_admins:             false,
262                anonymous:              false,
263                manage_call:            true,
264                other:                  false,
265                manage_topics:          false,
266                post_stories:           false,
267                edit_stories:           false,
268                delete_stories:         false,
269                manage_direct_messages: false,
270                manage_ranks:           false,
271            }
272        } else {
273            tl::types::ChatAdminRights {
274                change_info:            false,
275                post_messages:          false,
276                edit_messages:          false,
277                delete_messages:        false,
278                ban_users:              false,
279                invite_users:           false,
280                pin_messages:           false,
281                add_admins:             false,
282                anonymous:              false,
283                manage_call:            false,
284                other:                  false,
285                manage_topics:          false,
286                post_stories:           false,
287                edit_stories:           false,
288                delete_stories:         false,
289                manage_direct_messages: false,
290                manage_ranks:           false,
291            }
292        };
293
294        let req = tl::functions::channels::EditAdmin {
295            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
296                channel_id, access_hash: ch_hash,
297            }),
298            user_id: tl::enums::InputUser::InputUser(tl::types::InputUser { user_id, access_hash: user_hash }),
299            admin_rights: tl::enums::ChatAdminRights::ChatAdminRights(rights),
300            rank: None,
301        };
302        self.rpc_call_raw_pub(&req).await?;
303        Ok(())
304    }
305
306    /// Iterate profile photos of a user or channel.
307    ///
308    /// Returns a list of photo objects (up to `limit`).
309    pub async fn get_profile_photos(
310        &self,
311        peer:  tl::enums::Peer,
312        limit: i32,
313    ) -> Result<Vec<tl::enums::Photo>, InvocationError> {
314        let input_peer = {
315            let cache = self.inner.peer_cache.lock().await;
316            cache.peer_to_input(&peer)
317        };
318
319        let req = tl::functions::photos::GetUserPhotos {
320            user_id: match &input_peer {
321                tl::enums::InputPeer::User(u) => tl::enums::InputUser::InputUser(
322                    tl::types::InputUser { user_id: u.user_id, access_hash: u.access_hash }
323                ),
324                tl::enums::InputPeer::PeerSelf => tl::enums::InputUser::UserSelf,
325                _ => return Err(InvocationError::Deserialize("get_profile_photos: peer must be a user".into())),
326            },
327            offset: 0,
328            max_id: 0,
329            limit,
330        };
331        let body    = self.rpc_call_raw_pub(&req).await?;
332        let mut cur = Cursor::from_slice(&body);
333        match tl::enums::photos::Photos::deserialize(&mut cur)? {
334            tl::enums::photos::Photos::Photos(p)  => Ok(p.photos),
335            tl::enums::photos::Photos::Slice(p)   => Ok(p.photos),
336        }
337    }
338
339    /// Search for a peer (user, group, or channel) by name prefix.
340    ///
341    /// Searches contacts, dialogs, and globally. Returns combined results.
342    pub async fn search_peer(
343        &self,
344        query: &str,
345    ) -> Result<Vec<tl::enums::Peer>, InvocationError> {
346        let req  = tl::functions::contacts::Search {
347            q:   query.to_string(),
348            limit: 20,
349        };
350        let body = self.rpc_call_raw_pub(&req).await?;
351        let mut cur = Cursor::from_slice(&body);
352        let tl::enums::contacts::Found::Found(found) = tl::enums::contacts::Found::deserialize(&mut cur)?;
353
354        self.cache_users_slice_pub(&found.users).await;
355        self.cache_chats_slice_pub(&found.chats).await;
356
357        let mut peers = Vec::new();
358        for r in found.my_results.iter().chain(found.results.iter()) {
359            peers.push(r.clone());
360        }
361        Ok(peers)
362    }
363
364    /// Send a reaction to a message.
365    ///
366    /// `reaction` should be an emoji string like `"👍"` or an empty string to remove.
367    pub async fn send_reaction(
368        &self,
369        peer:       tl::enums::Peer,
370        message_id: i32,
371        reaction:   &str,
372    ) -> Result<(), InvocationError> {
373        let input_peer = {
374            let cache = self.inner.peer_cache.lock().await;
375            cache.peer_to_input(&peer)
376        };
377
378        let reactions = if reaction.is_empty() {
379            vec![]
380        } else {
381            vec![tl::enums::Reaction::Emoji(tl::types::ReactionEmoji {
382                emoticon: reaction.to_string(),
383            })]
384        };
385
386        let req = tl::functions::messages::SendReaction {
387            big:        false,
388            add_to_recent: false,
389            peer:       input_peer,
390            msg_id:     message_id,
391            reaction:   Some(reactions),
392        };
393        self.rpc_call_raw_pub(&req).await?;
394        Ok(())
395    }
396}
397
398// ─── Helper extension for Peer ────────────────────────────────────────────────
399
400trait PeerUserIdExt {
401    fn user_id_or(&self, default: i64) -> i64;
402}
403
404impl PeerUserIdExt for tl::enums::Peer {
405    fn user_id_or(&self, default: i64) -> i64 {
406        match self {
407            tl::enums::Peer::User(u) => u.user_id,
408            _ => default,
409        }
410    }
411}
412
413// ─── G-26: BannedRightsBuilder ────────────────────────────────────────────────
414
415/// Fluent builder for granular channel ban rights.
416///
417/// ```rust,no_run
418/// # async fn f(client: layer_client::Client, channel: layer_tl_types::enums::Peer) -> Result<(), Box<dyn std::error::Error>> {
419/// client.set_banned_rights(channel, 12345678, |b| b
420///     .send_messages(true)
421///     .send_media(true)
422///     .until_date(0))
423///     .await?;
424/// # Ok(()) }
425/// ```
426#[derive(Debug, Clone, Default)]
427pub struct BannedRightsBuilder {
428    pub view_messages:  bool,
429    pub send_messages:  bool,
430    pub send_media:     bool,
431    pub send_stickers:  bool,
432    pub send_gifs:      bool,
433    pub send_games:     bool,
434    pub send_inline:    bool,
435    pub embed_links:    bool,
436    pub send_polls:     bool,
437    pub change_info:    bool,
438    pub invite_users:   bool,
439    pub pin_messages:   bool,
440    pub until_date:     i32,
441}
442
443impl BannedRightsBuilder {
444    pub fn new() -> Self { Self::default() }
445    pub fn view_messages(mut self, v: bool)  -> Self { self.view_messages  = v; self }
446    pub fn send_messages(mut self, v: bool)  -> Self { self.send_messages  = v; self }
447    pub fn send_media(mut self, v: bool)     -> Self { self.send_media     = v; self }
448    pub fn send_stickers(mut self, v: bool)  -> Self { self.send_stickers  = v; self }
449    pub fn send_gifs(mut self, v: bool)      -> Self { self.send_gifs      = v; self }
450    pub fn send_games(mut self, v: bool)     -> Self { self.send_games     = v; self }
451    pub fn send_inline(mut self, v: bool)    -> Self { self.send_inline    = v; self }
452    pub fn embed_links(mut self, v: bool)    -> Self { self.embed_links    = v; self }
453    pub fn send_polls(mut self, v: bool)     -> Self { self.send_polls     = v; self }
454    pub fn change_info(mut self, v: bool)    -> Self { self.change_info    = v; self }
455    pub fn invite_users(mut self, v: bool)   -> Self { self.invite_users   = v; self }
456    pub fn pin_messages(mut self, v: bool)   -> Self { self.pin_messages   = v; self }
457    /// Ban until a Unix timestamp. `0` = permanent.
458    pub fn until_date(mut self, ts: i32)     -> Self { self.until_date     = ts; self }
459
460    /// Full ban (all rights revoked, permanent).
461    pub fn full_ban() -> Self {
462        Self {
463            view_messages: true, send_messages: true, send_media: true,
464            send_stickers: true, send_gifs: true, send_games: true,
465            send_inline: true, embed_links: true, send_polls: true,
466            change_info: true, invite_users: true, pin_messages: true,
467            until_date: 0,
468        }
469    }
470
471    pub(crate) fn into_tl(self) -> tl::enums::ChatBannedRights {
472        tl::enums::ChatBannedRights::ChatBannedRights(tl::types::ChatBannedRights {
473            view_messages:   self.view_messages,
474            send_messages:   self.send_messages,
475            send_media:      self.send_media,
476            send_stickers:   self.send_stickers,
477            send_gifs:       self.send_gifs,
478            send_games:      self.send_games,
479            send_inline:     self.send_inline,
480            embed_links:     self.embed_links,
481            send_polls:      self.send_polls,
482            change_info:     self.change_info,
483            invite_users:    self.invite_users,
484            pin_messages:    self.pin_messages,
485            manage_topics:   false,
486            send_photos:     false,
487            send_videos:     false,
488            send_roundvideos: false,
489            send_audios:     false,
490            send_voices:     false,
491            send_docs:       false,
492            send_plain:      false,
493            edit_rank:       false,
494            until_date:      self.until_date,
495        })
496    }
497}
498
499// ─── G-27: AdminRightsBuilder ─────────────────────────────────────────────────
500
501/// Fluent builder for granular admin rights.
502#[derive(Debug, Clone, Default)]
503pub struct AdminRightsBuilder {
504    pub change_info:     bool,
505    pub post_messages:   bool,
506    pub edit_messages:   bool,
507    pub delete_messages: bool,
508    pub ban_users:       bool,
509    pub invite_users:    bool,
510    pub pin_messages:    bool,
511    pub add_admins:      bool,
512    pub anonymous:       bool,
513    pub manage_call:     bool,
514    pub manage_topics:   bool,
515    pub rank:            Option<String>,
516}
517
518impl AdminRightsBuilder {
519    pub fn new() -> Self { Self::default() }
520    pub fn change_info(mut self, v: bool)     -> Self { self.change_info     = v; self }
521    pub fn post_messages(mut self, v: bool)   -> Self { self.post_messages   = v; self }
522    pub fn edit_messages(mut self, v: bool)   -> Self { self.edit_messages   = v; self }
523    pub fn delete_messages(mut self, v: bool) -> Self { self.delete_messages = v; self }
524    pub fn ban_users(mut self, v: bool)       -> Self { self.ban_users       = v; self }
525    pub fn invite_users(mut self, v: bool)    -> Self { self.invite_users    = v; self }
526    pub fn pin_messages(mut self, v: bool)    -> Self { self.pin_messages    = v; self }
527    pub fn add_admins(mut self, v: bool)      -> Self { self.add_admins      = v; self }
528    pub fn anonymous(mut self, v: bool)       -> Self { self.anonymous       = v; self }
529    pub fn manage_call(mut self, v: bool)     -> Self { self.manage_call     = v; self }
530    pub fn manage_topics(mut self, v: bool)   -> Self { self.manage_topics   = v; self }
531    /// Custom admin title (max 16 chars).
532    pub fn rank(mut self, r: impl Into<String>) -> Self { self.rank = Some(r.into()); self }
533
534    /// Full admin (all standard rights).
535    pub fn full_admin() -> Self {
536        Self {
537            change_info: true, post_messages: true, edit_messages: true,
538            delete_messages: true, ban_users: true, invite_users: true,
539            pin_messages: true, add_admins: false, anonymous: false,
540            manage_call: true, manage_topics: true, rank: None,
541        }
542    }
543
544    pub(crate) fn into_tl_rights(self) -> tl::enums::ChatAdminRights {
545        tl::enums::ChatAdminRights::ChatAdminRights(tl::types::ChatAdminRights {
546            change_info:            self.change_info,
547            post_messages:          self.post_messages,
548            edit_messages:          self.edit_messages,
549            delete_messages:        self.delete_messages,
550            ban_users:              self.ban_users,
551            invite_users:           self.invite_users,
552            pin_messages:           self.pin_messages,
553            add_admins:             self.add_admins,
554            anonymous:              self.anonymous,
555            manage_call:            self.manage_call,
556            other:                  false,
557            manage_topics:          self.manage_topics,
558            post_stories:           false,
559            edit_stories:           false,
560            delete_stories:         false,
561            manage_direct_messages: false,
562            manage_ranks:           false,
563        })
564    }
565}
566
567// ─── G-30: ParticipantPermissions ────────────────────────────────────────────
568
569/// The effective permissions/rights of a specific participant.
570#[derive(Debug, Clone)]
571pub struct ParticipantPermissions {
572    pub is_creator:  bool,
573    pub is_admin:    bool,
574    pub is_banned:   bool,
575    pub is_left:     bool,
576    pub can_send_messages: bool,
577    pub can_send_media:    bool,
578    pub can_pin_messages:  bool,
579    pub can_add_admins:    bool,
580    pub admin_rank:        Option<String>,
581}
582
583impl ParticipantPermissions {
584    pub fn is_creator(&self)  -> bool { self.is_creator }
585    pub fn is_admin(&self)    -> bool { self.is_admin }
586    pub fn is_banned(&self)   -> bool { self.is_banned }
587    pub fn is_member(&self)   -> bool { !self.is_banned && !self.is_left }
588}
589
590// ─── Client: new participant methods ──────────────────────────────────────────
591
592impl Client {
593    // ── G-26: set_banned_rights ───────────────────────────────────────────
594
595    /// Apply granular ban rights to a user in a channel or supergroup.
596    ///
597    /// Use [`BannedRightsBuilder`] to specify which rights to restrict.
598    pub async fn set_banned_rights(
599        &self,
600        channel: tl::enums::Peer,
601        user_id: i64,
602        build:   impl FnOnce(BannedRightsBuilder) -> BannedRightsBuilder,
603    ) -> Result<(), InvocationError> {
604        let rights = build(BannedRightsBuilder::new()).into_tl();
605        let (channel_id, ch_hash) = match &channel {
606            tl::enums::Peer::Channel(c) => {
607                let h = self.inner.peer_cache.lock().await.channels.get(&c.channel_id).copied().unwrap_or(0);
608                (c.channel_id, h)
609            }
610            _ => return Err(InvocationError::Deserialize("set_banned_rights: must be a channel".into())),
611        };
612        let user_hash = self.inner.peer_cache.lock().await.users.get(&user_id).copied().unwrap_or(0);
613        let req = tl::functions::channels::EditBanned {
614            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
615                channel_id, access_hash: ch_hash,
616            }),
617            participant: tl::enums::InputPeer::User(tl::types::InputPeerUser {
618                user_id, access_hash: user_hash,
619            }),
620            banned_rights: rights,
621        };
622        self.rpc_call_raw_pub(&req).await?;
623        Ok(())
624    }
625
626    // ── G-27: set_admin_rights ────────────────────────────────────────────
627
628    /// Apply granular admin rights to a user in a channel or supergroup.
629    ///
630    /// Use [`AdminRightsBuilder`] to specify which rights to grant.
631    pub async fn set_admin_rights(
632        &self,
633        channel: tl::enums::Peer,
634        user_id: i64,
635        build:   impl FnOnce(AdminRightsBuilder) -> AdminRightsBuilder,
636    ) -> Result<(), InvocationError> {
637        let b = build(AdminRightsBuilder::new());
638        let rank = b.rank.clone();
639        let rights = b.into_tl_rights();
640        let (channel_id, ch_hash) = match &channel {
641            tl::enums::Peer::Channel(c) => {
642                let h = self.inner.peer_cache.lock().await.channels.get(&c.channel_id).copied().unwrap_or(0);
643                (c.channel_id, h)
644            }
645            _ => return Err(InvocationError::Deserialize("set_admin_rights: must be a channel".into())),
646        };
647        let user_hash = self.inner.peer_cache.lock().await.users.get(&user_id).copied().unwrap_or(0);
648        let req = tl::functions::channels::EditAdmin {
649            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
650                channel_id, access_hash: ch_hash,
651            }),
652            user_id: tl::enums::InputUser::InputUser(tl::types::InputUser { user_id, access_hash: user_hash }),
653            admin_rights: rights,
654            rank,
655        };
656        self.rpc_call_raw_pub(&req).await?;
657        Ok(())
658    }
659
660    // ── G-28: iter_participants with filter ───────────────────────────────
661
662    /// Fetch participants with an optional filter, paginated.
663    ///
664    /// `filter` defaults to `ChannelParticipantsRecent` when `None`.
665    pub async fn iter_participants(
666        &self,
667        peer:   tl::enums::Peer,
668        filter: Option<tl::enums::ChannelParticipantsFilter>,
669        limit:  i32,
670    ) -> Result<Vec<Participant>, InvocationError> {
671        match &peer {
672            tl::enums::Peer::Channel(c) => {
673                let access_hash = self.inner.peer_cache.lock().await
674                    .channels.get(&c.channel_id).copied().unwrap_or(0);
675                let filter = filter.unwrap_or(tl::enums::ChannelParticipantsFilter::ChannelParticipantsRecent);
676                let limit  = if limit <= 0 { 200 } else { limit };
677                let req = tl::functions::channels::GetParticipants {
678                    channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
679                        channel_id: c.channel_id, access_hash,
680                    }),
681                    filter,
682                    offset: 0,
683                    limit,
684                    hash: 0,
685                };
686                let body = self.rpc_call_raw_pub(&req).await?;
687                let mut cur = Cursor::from_slice(&body);
688                let raw = match tl::enums::channels::ChannelParticipants::deserialize(&mut cur)? {
689                    tl::enums::channels::ChannelParticipants::ChannelParticipants(p) => p,
690                    tl::enums::channels::ChannelParticipants::NotModified => return Ok(vec![]),
691                };
692                let user_map: std::collections::HashMap<i64, tl::types::User> = raw.users.into_iter()
693                    .filter_map(|u| match u { tl::enums::User::User(u) => Some((u.id, u)), _ => None })
694                    .collect();
695                {
696                    let mut cache = self.inner.peer_cache.lock().await;
697                    for u in user_map.values() {
698                        if let Some(h) = u.access_hash { cache.users.insert(u.id, h); }
699                    }
700                }
701                Ok(raw.participants.into_iter().filter_map(|p| {
702                    let (uid, status) = match &p {
703                        tl::enums::ChannelParticipant::ChannelParticipant(x) => (x.user_id, ParticipantStatus::Member),
704                        tl::enums::ChannelParticipant::ParticipantSelf(x) => (x.user_id, ParticipantStatus::Member),
705                        tl::enums::ChannelParticipant::Creator(x) => (x.user_id, ParticipantStatus::Creator),
706                        tl::enums::ChannelParticipant::Admin(x) => (x.user_id, ParticipantStatus::Admin),
707                        tl::enums::ChannelParticipant::Banned(x) => {
708                            if let tl::enums::Peer::User(u) = &x.peer { (u.user_id, ParticipantStatus::Banned) } else { return None; }
709                        }
710                        tl::enums::ChannelParticipant::Left(x) => {
711                            if let tl::enums::Peer::User(u) = &x.peer { (u.user_id, ParticipantStatus::Left) } else { return None; }
712                        }
713                    };
714                    user_map.get(&uid).map(|u| Participant { user: u.clone(), status })
715                }).collect())
716            }
717            tl::enums::Peer::Chat(c) => self.get_chat_participants(c.chat_id).await,
718            _ => Err(InvocationError::Deserialize("iter_participants: must be chat or channel".into())),
719        }
720    }
721
722    // ── G-30: get_permissions ─────────────────────────────────────────────
723
724    /// Get the effective permissions of a specific user in a channel.
725    pub async fn get_permissions(
726        &self,
727        channel: tl::enums::Peer,
728        user_id: i64,
729    ) -> Result<ParticipantPermissions, InvocationError> {
730        let (channel_id, ch_hash) = match &channel {
731            tl::enums::Peer::Channel(c) => {
732                let h = self.inner.peer_cache.lock().await.channels.get(&c.channel_id).copied().unwrap_or(0);
733                (c.channel_id, h)
734            }
735            _ => return Err(InvocationError::Deserialize("get_permissions: must be a channel".into())),
736        };
737        let user_hash = self.inner.peer_cache.lock().await.users.get(&user_id).copied().unwrap_or(0);
738        let req = tl::functions::channels::GetParticipant {
739            channel: tl::enums::InputChannel::InputChannel(tl::types::InputChannel {
740                channel_id, access_hash: ch_hash,
741            }),
742            participant: tl::enums::InputPeer::User(tl::types::InputPeerUser {
743                user_id, access_hash: user_hash,
744            }),
745        };
746        let body = self.rpc_call_raw_pub(&req).await?;
747        let mut cur = Cursor::from_slice(&body);
748        let tl::enums::channels::ChannelParticipant::ChannelParticipant(raw) = tl::enums::channels::ChannelParticipant::deserialize(&mut cur)?;
749
750        let perms = match raw.participant {
751            tl::enums::ChannelParticipant::Creator(_) => ParticipantPermissions {
752                is_creator: true, is_admin: true, is_banned: false, is_left: false,
753                can_send_messages: true, can_send_media: true, can_pin_messages: true,
754                can_add_admins: true, admin_rank: None,
755            },
756            tl::enums::ChannelParticipant::Admin(a) => {
757                let tl::enums::ChatAdminRights::ChatAdminRights(rights) = a.admin_rights;
758                ParticipantPermissions {
759                    is_creator: false, is_admin: true, is_banned: false, is_left: false,
760                    can_send_messages: true, can_send_media: true,
761                    can_pin_messages: rights.pin_messages,
762                    can_add_admins: rights.add_admins,
763                    admin_rank: a.rank,
764                }
765            }
766            tl::enums::ChannelParticipant::Banned(b) => {
767                let tl::enums::ChatBannedRights::ChatBannedRights(rights) = b.banned_rights;
768                ParticipantPermissions {
769                    is_creator: false, is_admin: false, is_banned: true, is_left: false,
770                    can_send_messages: !rights.send_messages,
771                    can_send_media: !rights.send_media,
772                    can_pin_messages: !rights.pin_messages,
773                    can_add_admins: false, admin_rank: None,
774                }
775            }
776            tl::enums::ChannelParticipant::Left(_) => ParticipantPermissions {
777                is_creator: false, is_admin: false, is_banned: false, is_left: true,
778                can_send_messages: false, can_send_media: false, can_pin_messages: false,
779                can_add_admins: false, admin_rank: None,
780            },
781            _ => ParticipantPermissions {
782                is_creator: false, is_admin: false, is_banned: false, is_left: false,
783                can_send_messages: true, can_send_media: true, can_pin_messages: false,
784                can_add_admins: false, admin_rank: None,
785            },
786        };
787
788        Ok(perms)
789    }
790}