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;