diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs
index ebca0120c..09681dafc 100644
--- a/src/Discord.Net.Core/DiscordConfig.cs
+++ b/src/Discord.Net.Core/DiscordConfig.cs
@@ -214,5 +214,13 @@ namespace Discord
/// If set to , this value will be "Discord#1234".
///
public bool FormatUsersInBidirectionalUnicode { get; set; } = true;
+
+ ///
+ /// Returns the max thread members allowed to be in a request.
+ ///
+ ///
+ /// The maximum number of thread members that can be gotten per-batch.
+ ///
+ public const int MaxThreadMembersPerBatch = 100;
}
}
diff --git a/src/Discord.Net.Core/Entities/Users/IThreadUser.cs b/src/Discord.Net.Core/Entities/Users/IThreadUser.cs
index f94ca8bb6..81cbf89d4 100644
--- a/src/Discord.Net.Core/Entities/Users/IThreadUser.cs
+++ b/src/Discord.Net.Core/Entities/Users/IThreadUser.cs
@@ -21,5 +21,10 @@ namespace Discord
/// Gets the guild this thread was created in.
///
IGuild Guild { get; }
+
+ ///
+ /// Gets the on the server this thread was created in.
+ ///
+ IGuildUser GuildUser { get; }
}
}
diff --git a/src/Discord.Net.Rest/API/Common/ThreadMember.cs b/src/Discord.Net.Rest/API/Common/ThreadMember.cs
index 30249ee44..fb89f349d 100644
--- a/src/Discord.Net.Rest/API/Common/ThreadMember.cs
+++ b/src/Discord.Net.Rest/API/Common/ThreadMember.cs
@@ -16,5 +16,8 @@ namespace Discord.API
[JsonProperty("flags")]
public int Flags { get; set; } // No enum type (yet?)
+
+ [JsonProperty("member")]
+ public Optional GuildMember { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index c759119c2..955952d00 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -563,16 +563,23 @@ namespace Discord.API
await SendAsync("DELETE", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false);
}
-
- public async Task ListThreadMembersAsync(ulong channelId, RequestOptions options = null)
+
+ public async Task ListThreadMembersAsync(ulong channelId, ulong? after = null, int? limit = null, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
+ var query = "?with_member=true";
+
+ if (limit.HasValue)
+ query += $"&limit={limit}";
+ if (after.HasValue)
+ query += $"&after={after}";
+
options = RequestOptions.CreateOrClone(options);
var bucket = new BucketIds(channelId: channelId);
- return await SendAsync("GET", () => $"channels/{channelId}/thread-members", bucket, options: options).ConfigureAwait(false);
+ return await SendAsync("GET", () => $"channels/{channelId}/thread-members{query}", bucket, options: options).ConfigureAwait(false);
}
public async Task GetThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null)
@@ -583,8 +590,9 @@ namespace Discord.API
options = RequestOptions.CreateOrClone(options);
var bucket = new BucketIds(channelId: channelId);
+ var query = "?with_member=true";
- return await SendAsync("GET", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false);
+ return await SendAsync("GET", () => $"channels/{channelId}/thread-members/{userId}{query}", bucket, options: options).ConfigureAwait(false);
}
public async Task GetActiveThreadsAsync(ulong guildId, RequestOptions options = null)
@@ -2524,7 +2532,7 @@ namespace Discord.API
=> await SendAsync("GET", () => $"applications/{CurrentApplicationId}/role-connections/metadata", new BucketIds(), options: options).ConfigureAwait(false);
public async Task UpdateApplicationRoleConnectionMetadataRecordsAsync(RoleConnectionMetadata[] roleConnections, RequestOptions options = null)
- => await SendJsonAsync ("PUT", () => $"applications/{CurrentApplicationId}/role-connections/metadata", roleConnections, new BucketIds(), options: options).ConfigureAwait(false);
+ => await SendJsonAsync("PUT", () => $"applications/{CurrentApplicationId}/role-connections/metadata", roleConnections, new BucketIds(), options: options).ConfigureAwait(false);
public async Task GetUserApplicationRoleConnectionAsync(ulong applicationId, RequestOptions options = null)
=> await SendAsync("GET", () => $"users/@me/applications/{applicationId}/role-connection", new BucketIds(), options: options);
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs
index 1555b2093..7889c8b41 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestThreadChannel.cs
@@ -101,6 +101,17 @@ namespace Discord.Rest
public new Task GetUserAsync(ulong userId, RequestOptions options = null)
=> ThreadHelper.GetUserAsync(userId, this, Discord, options);
+ ///
+ /// Gets a collection of users within this thread.
+ ///
+ /// Sets the limit of the user count for each request. 100 by default.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a collection of thread
+ /// users found within this thread channel.
+ ///
+ public IAsyncEnumerable> GetThreadUsersAsync(int limit = DiscordConfig.MaxThreadMembersPerBatch, RequestOptions options = null)
+ => ThreadHelper.GetUsersAsync(this, Discord, limit, null, options);
+
///
/// Gets a collection of users within this thread.
///
@@ -109,8 +120,9 @@ namespace Discord.Rest
/// A task representing the asynchronous get operation. The task returns a
/// of 's.
///
+ [Obsolete("Please use GetThreadUsersAsync instead of this. Will be removed in next major version.", false)]
public new async Task> GetUsersAsync(RequestOptions options = null)
- => (await ThreadHelper.GetUsersAsync(this, Discord, options).ConfigureAwait(false)).ToImmutableArray();
+ => (await GetThreadUsersAsync(options: options).FlattenAsync()).ToImmutableArray();
///
public override async Task ModifyAsync(Action func, RequestOptions options = null)
diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
index f1c6c0efb..7b6d42e22 100644
--- a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs
@@ -85,11 +85,27 @@ namespace Discord.Rest
return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
}
- public static async Task GetUsersAsync(IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
+ public static IAsyncEnumerable> GetUsersAsync(IThreadChannel channel, BaseDiscordClient client, int limit = DiscordConfig.MaxThreadMembersPerBatch, ulong? afterId = null, RequestOptions options = null)
{
- var users = await client.ApiClient.ListThreadMembersAsync(channel.Id, options);
-
- return users.Select(x => RestThreadUser.Create(client, channel.Guild, x, channel)).ToArray();
+ return new PagedAsyncEnumerable(
+ limit,
+ async (info, ct) =>
+ {
+ if (info.Position != null)
+ afterId = info.Position.Value;
+ var users = await client.ApiClient.ListThreadMembersAsync(channel.Id, afterId, limit, options);
+ return users.Select(x => RestThreadUser.Create(client, channel.Guild, x, channel)).ToImmutableArray();
+ },
+ nextPage: (info, lastPage) =>
+ {
+ if (lastPage.Count != limit)
+ return false;
+ info.Position = lastPage.Max(x => x.Id);
+ return true;
+ },
+ start: afterId,
+ count: limit
+ );
}
public static async Task GetUserAsync(ulong userId, IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
diff --git a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
index fe362e16e..0187e5f71 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs
@@ -18,6 +18,9 @@ namespace Discord.Rest
///
public IGuild Guild { get; }
+ ///
+ public RestGuildUser GuildUser { get; private set; }
+
///
public string Mention => MentionUtils.MentionUser(Id);
@@ -38,8 +41,13 @@ namespace Discord.Rest
internal void Update(Model model)
{
ThreadJoinedAt = model.JoinTimestamp;
+ if(model.GuildMember.IsSpecified)
+ GuildUser = RestGuildUser.Create(Discord, Guild, model.GuildMember.Value);
}
+ ///
+ IGuildUser IThreadUser.GuildUser => GuildUser;
+
///
/// Gets the guild user for this thread user.
///
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
index 5c739c857..baf3e7529 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketThreadChannel.cs
@@ -172,7 +172,7 @@ namespace Discord.WebSocket
return threadUsers.ToImmutableArray();
}
- internal SocketThreadUser AddOrUpdateThreadMember(ThreadMember model, SocketGuildUser guildMember)
+ internal SocketThreadUser AddOrUpdateThreadMember(ThreadMember model, SocketGuildUser guildMember = null)
{
if (_members.TryGetValue(model.UserId.Value, out SocketThreadUser member))
member.Update(model);
@@ -219,15 +219,21 @@ namespace Discord.WebSocket
/// A task representing the asynchronous download operation.
public async Task DownloadUsersAsync(RequestOptions options = null)
{
- var users = await Discord.ApiClient.ListThreadMembersAsync(Id, options);
+ var prevBatchCount = DiscordConfig.MaxThreadMembersPerBatch;
+ ulong? maxId = null;
- lock (_downloadLock)
+ while (prevBatchCount == DiscordConfig.MaxThreadMembersPerBatch)
{
- foreach (var threadMember in users)
- {
- var guildUser = Guild.GetUser(threadMember.UserId.Value);
+ var users = await Discord.ApiClient.ListThreadMembersAsync(Id, maxId, DiscordConfig.MaxThreadMembersPerBatch, options);
+ prevBatchCount = users.Length;
+ maxId = users.Max(x => x.UserId.GetValueOrDefault());
- AddOrUpdateThreadMember(threadMember, guildUser);
+ lock (_downloadLock)
+ {
+ foreach (var threadMember in users)
+ {
+ AddOrUpdateThreadMember(threadMember);
+ }
}
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
index 290681d4d..96eee47a4 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
@@ -134,19 +134,21 @@ namespace Discord.WebSocket
public DateTimeOffset? RequestToSpeakTimestamp
=> GuildUser.RequestToSpeakTimestamp;
- private SocketGuildUser GuildUser { get; set; }
+ ///
+ public SocketGuildUser GuildUser { get; private set; }
- internal SocketThreadUser(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser member, ulong userId)
+ internal SocketThreadUser(SocketGuild guild, SocketThreadChannel thread, ulong userId, SocketGuildUser member = null)
: base(guild.Discord, userId)
{
Thread = thread;
Guild = guild;
- GuildUser = member;
+ if(member is not null)
+ GuildUser = member;
}
- internal static SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, Model model, SocketGuildUser member)
+ internal static SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, Model model, SocketGuildUser guildUser = null)
{
- var entity = new SocketThreadUser(guild, thread, member, model.UserId.Value);
+ var entity = new SocketThreadUser(guild, thread, model.UserId.Value, guildUser);
entity.Update(model);
return entity;
}
@@ -154,7 +156,7 @@ namespace Discord.WebSocket
internal static SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser owner)
{
// this is used for creating the owner of the thread.
- var entity = new SocketThreadUser(guild, thread, owner, owner.Id);
+ var entity = new SocketThreadUser(guild, thread, owner.Id, owner);
entity.Update(new Model
{
JoinTimestamp = thread.CreatedAt,
@@ -165,6 +167,8 @@ namespace Discord.WebSocket
internal void Update(Model model)
{
ThreadJoinedAt = model.JoinTimestamp;
+ if(model.GuildMember.IsSpecified)
+ GuildUser = Guild.AddOrUpdateUser(model.GuildMember.Value);
}
///
@@ -214,6 +218,9 @@ namespace Discord.WebSocket
///
IGuild IGuildUser.Guild => Guild;
+ ///
+ IGuildUser IThreadUser.GuildUser => GuildUser;
+
///
ulong IGuildUser.GuildId => Guild.Id;