diff --git a/src/Client.cs b/src/Client.cs index 5877014..39dc0cd 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -1056,7 +1056,7 @@ namespace WTelegram return user; } - #region TL-Helpers +#region TL-Helpers /// Helper function to upload a file to Telegram /// Path to the file to upload /// (optional) Callback for tracking the progression of the transfer @@ -1336,7 +1336,67 @@ namespace WTelegram outputStream.Seek(streamStartPos + maxOffsetSeen, SeekOrigin.Begin); return fileType; } -#endregion + + /// Helper method that tries to fetch all participants from a Channel (beyond Telegram server-side limitations) + /// The channel to query + /// Also detch the kicked/banned members? + /// Field count indicates the total count of members. Field participants contains those that were successfully fetched + /// This method can take a few minutes to complete on big channels. It likely won't be able to obtain the full total count of members + public async Task Channels_GetAllParticipants(InputChannelBase channel, bool includeKickBan = false) + { + var result = new Channels_ChannelParticipants { chats = new(), users = new() }; + + var sem = new SemaphoreSlim(10); // prevents flooding Telegram with requests + var user_ids = new HashSet(); + var participants = new List(); + + var tasks = new List + { + GetWithFilter(new ChannelParticipantsAdmins()), + GetWithFilter(new ChannelParticipantsBots()), + GetWithFilter(new ChannelParticipantsSearch { q = "" }, (f, c) => new ChannelParticipantsSearch { q = f.q + c }), + }; + var mcf = this.Channels_GetFullChannel(channel); + tasks.Add(mcf); + if (includeKickBan) + { + tasks.Add(GetWithFilter(new ChannelParticipantsKicked { q = "" }, (f, c) => new ChannelParticipantsKicked { q = f.q + c })); + tasks.Add(GetWithFilter(new ChannelParticipantsBanned { q = "" }, (f, c) => new ChannelParticipantsBanned { q = f.q + c })); + } + await Task.WhenAll(tasks); + + result.count = ((ChannelFull)mcf.Result.full_chat).participants_count; + result.participants = participants.ToArray(); + return result; + + async Task GetWithFilter(T filter, Func recurse = null) where T : ChannelParticipantsFilter + { + Channels_ChannelParticipants ccp; + for (int offset = 0; ;) + { + await sem.WaitAsync(); + try + { + ccp = await this.Channels_GetParticipants(channel, filter, offset, 2000, 0); + } + finally + { + sem.Release(); + } + foreach (var kvp in ccp.chats) result.chats[kvp.Key] = kvp.Value; + foreach (var kvp in ccp.users) result.users[kvp.Key] = kvp.Value; + lock (participants) + foreach (var participant in ccp.participants) + if (user_ids.Add(participant.UserID)) + participants.Add(participant); + offset += ccp.participants.Length; + if (offset >= ccp.count) break; + } + if (recurse != null && (ccp.count == 200 || ccp.count == 1000)) + await Task.WhenAll(Enumerable.Range('a', 26).Select(c => GetWithFilter(recurse(filter, (char)c), recurse))); + } + } + #endregion /// Enable the collection of id/access_hash pairs (experimental) public bool CollectAccessHash { get; set; } diff --git a/src/TL.Helpers.cs b/src/TL.Helpers.cs index 34e83cc..d9425fa 100644 --- a/src/TL.Helpers.cs +++ b/src/TL.Helpers.cs @@ -316,9 +316,25 @@ namespace TL public override int Timeout => timeout; } - partial class ChannelParticipantBase { public virtual bool IsAdmin => false; } - partial class ChannelParticipantCreator { public override bool IsAdmin => true; } - partial class ChannelParticipantAdmin { public override bool IsAdmin => true; } + partial class ChannelParticipantBase + { + public virtual bool IsAdmin => false; + public abstract long UserID { get; } + } + partial class ChannelParticipantCreator + { + public override bool IsAdmin => true; + public override long UserID => user_id; + } + partial class ChannelParticipantAdmin + { + public override bool IsAdmin => true; + public override long UserID => user_id; + } + partial class ChannelParticipant { public override long UserID => user_id; } + partial class ChannelParticipantSelf { public override long UserID => user_id; } + partial class ChannelParticipantBanned { public override long UserID => peer is PeerUser pu ? pu.user_id : 0; } + partial class ChannelParticipantLeft { public override long UserID => peer is PeerUser pu ? pu.user_id : 0; } partial class UpdatesBase { diff --git a/src/TL.Schema.cs b/src/TL.Schema.cs index 167d6ce..96db3d0 100644 --- a/src/TL.Schema.cs +++ b/src/TL.Schema.cs @@ -6328,7 +6328,7 @@ namespace TL public abstract partial class ChannelParticipantBase : IObject { } /// Channel/supergroup participant See [TLDef(0xC00C07C0)] - public class ChannelParticipant : ChannelParticipantBase + public partial class ChannelParticipant : ChannelParticipantBase { /// Pariticipant user ID public long user_id; @@ -6337,7 +6337,7 @@ namespace TL } /// Myself See [TLDef(0x35A8BFA7)] - public class ChannelParticipantSelf : ChannelParticipantBase + public partial class ChannelParticipantSelf : ChannelParticipantBase { public Flags flags; /// User ID @@ -6402,7 +6402,7 @@ namespace TL } /// Banned/kicked user See [TLDef(0x6DF8014E)] - public class ChannelParticipantBanned : ChannelParticipantBase + public partial class ChannelParticipantBanned : ChannelParticipantBase { /// Flags, see TL conditional fields public Flags flags; @@ -6423,7 +6423,7 @@ namespace TL } /// A participant that left the channel/supergroup See [TLDef(0x1B03F006)] - public class ChannelParticipantLeft : ChannelParticipantBase + public partial class ChannelParticipantLeft : ChannelParticipantBase { /// The peer that left public Peer peer;