[Feature] Thread user member & thread members pagination (#2580)
* initial commit * return & update old method * mark old method as obsolete
This commit is contained in:
@@ -214,5 +214,13 @@ namespace Discord
|
|||||||
/// If set to <see langword="false"/>, this value will be "Discord#1234".
|
/// If set to <see langword="false"/>, this value will be "Discord#1234".
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public bool FormatUsersInBidirectionalUnicode { get; set; } = true;
|
public bool FormatUsersInBidirectionalUnicode { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the max thread members allowed to be in a request.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The maximum number of thread members that can be gotten per-batch.
|
||||||
|
/// </returns>
|
||||||
|
public const int MaxThreadMembersPerBatch = 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,5 +21,10 @@ namespace Discord
|
|||||||
/// Gets the guild this thread was created in.
|
/// Gets the guild this thread was created in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IGuild Guild { get; }
|
IGuild Guild { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="IGuildUser"/> on the server this thread was created in.
|
||||||
|
/// </summary>
|
||||||
|
IGuildUser GuildUser { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,5 +16,8 @@ namespace Discord.API
|
|||||||
|
|
||||||
[JsonProperty("flags")]
|
[JsonProperty("flags")]
|
||||||
public int Flags { get; set; } // No enum type (yet?)
|
public int Flags { get; set; } // No enum type (yet?)
|
||||||
|
|
||||||
|
[JsonProperty("member")]
|
||||||
|
public Optional<GuildMember> GuildMember { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -564,15 +564,22 @@ namespace Discord.API
|
|||||||
await SendAsync("DELETE", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false);
|
await SendAsync("DELETE", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ThreadMember[]> ListThreadMembersAsync(ulong channelId, RequestOptions options = null)
|
public async Task<ThreadMember[]> ListThreadMembersAsync(ulong channelId, ulong? after = null, int? limit = null, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
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);
|
options = RequestOptions.CreateOrClone(options);
|
||||||
|
|
||||||
var bucket = new BucketIds(channelId: channelId);
|
var bucket = new BucketIds(channelId: channelId);
|
||||||
|
|
||||||
return await SendAsync<ThreadMember[]>("GET", () => $"channels/{channelId}/thread-members", bucket, options: options).ConfigureAwait(false);
|
return await SendAsync<ThreadMember[]>("GET", () => $"channels/{channelId}/thread-members{query}", bucket, options: options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ThreadMember> GetThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null)
|
public async Task<ThreadMember> GetThreadMemberAsync(ulong channelId, ulong userId, RequestOptions options = null)
|
||||||
@@ -583,8 +590,9 @@ namespace Discord.API
|
|||||||
options = RequestOptions.CreateOrClone(options);
|
options = RequestOptions.CreateOrClone(options);
|
||||||
|
|
||||||
var bucket = new BucketIds(channelId: channelId);
|
var bucket = new BucketIds(channelId: channelId);
|
||||||
|
var query = "?with_member=true";
|
||||||
|
|
||||||
return await SendAsync<ThreadMember>("GET", () => $"channels/{channelId}/thread-members/{userId}", bucket, options: options).ConfigureAwait(false);
|
return await SendAsync<ThreadMember>("GET", () => $"channels/{channelId}/thread-members/{userId}{query}", bucket, options: options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ChannelThreads> GetActiveThreadsAsync(ulong guildId, RequestOptions options = null)
|
public async Task<ChannelThreads> GetActiveThreadsAsync(ulong guildId, RequestOptions options = null)
|
||||||
@@ -2524,7 +2532,7 @@ namespace Discord.API
|
|||||||
=> await SendAsync<RoleConnectionMetadata[]>("GET", () => $"applications/{CurrentApplicationId}/role-connections/metadata", new BucketIds(), options: options).ConfigureAwait(false);
|
=> await SendAsync<RoleConnectionMetadata[]>("GET", () => $"applications/{CurrentApplicationId}/role-connections/metadata", new BucketIds(), options: options).ConfigureAwait(false);
|
||||||
|
|
||||||
public async Task<RoleConnectionMetadata[]> UpdateApplicationRoleConnectionMetadataRecordsAsync(RoleConnectionMetadata[] roleConnections, RequestOptions options = null)
|
public async Task<RoleConnectionMetadata[]> UpdateApplicationRoleConnectionMetadataRecordsAsync(RoleConnectionMetadata[] roleConnections, RequestOptions options = null)
|
||||||
=> await SendJsonAsync <RoleConnectionMetadata[]>("PUT", () => $"applications/{CurrentApplicationId}/role-connections/metadata", roleConnections, new BucketIds(), options: options).ConfigureAwait(false);
|
=> await SendJsonAsync<RoleConnectionMetadata[]>("PUT", () => $"applications/{CurrentApplicationId}/role-connections/metadata", roleConnections, new BucketIds(), options: options).ConfigureAwait(false);
|
||||||
|
|
||||||
public async Task<RoleConnection> GetUserApplicationRoleConnectionAsync(ulong applicationId, RequestOptions options = null)
|
public async Task<RoleConnection> GetUserApplicationRoleConnectionAsync(ulong applicationId, RequestOptions options = null)
|
||||||
=> await SendAsync<RoleConnection>("GET", () => $"users/@me/applications/{applicationId}/role-connection", new BucketIds(), options: options);
|
=> await SendAsync<RoleConnection>("GET", () => $"users/@me/applications/{applicationId}/role-connection", new BucketIds(), options: options);
|
||||||
|
|||||||
@@ -101,6 +101,17 @@ namespace Discord.Rest
|
|||||||
public new Task<RestThreadUser> GetUserAsync(ulong userId, RequestOptions options = null)
|
public new Task<RestThreadUser> GetUserAsync(ulong userId, RequestOptions options = null)
|
||||||
=> ThreadHelper.GetUserAsync(userId, this, Discord, options);
|
=> ThreadHelper.GetUserAsync(userId, this, Discord, options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a collection of users within this thread.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="limit">Sets the limit of the user count for each request. 100 by default.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A task that represents the asynchronous get operation. The task result contains a collection of thread
|
||||||
|
/// users found within this thread channel.
|
||||||
|
/// </returns>
|
||||||
|
public IAsyncEnumerable<IReadOnlyCollection<RestThreadUser>> GetThreadUsersAsync(int limit = DiscordConfig.MaxThreadMembersPerBatch, RequestOptions options = null)
|
||||||
|
=> ThreadHelper.GetUsersAsync(this, Discord, limit, null, options);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a collection of users within this thread.
|
/// Gets a collection of users within this thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -109,8 +120,9 @@ namespace Discord.Rest
|
|||||||
/// A task representing the asynchronous get operation. The task returns a
|
/// A task representing the asynchronous get operation. The task returns a
|
||||||
/// <see cref="IReadOnlyCollection{T}"/> of <see cref="RestThreadUser"/>'s.
|
/// <see cref="IReadOnlyCollection{T}"/> of <see cref="RestThreadUser"/>'s.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
|
[Obsolete("Please use GetThreadUsersAsync instead of this. Will be removed in next major version.", false)]
|
||||||
public new async Task<IReadOnlyCollection<RestThreadUser>> GetUsersAsync(RequestOptions options = null)
|
public new async Task<IReadOnlyCollection<RestThreadUser>> GetUsersAsync(RequestOptions options = null)
|
||||||
=> (await ThreadHelper.GetUsersAsync(this, Discord, options).ConfigureAwait(false)).ToImmutableArray();
|
=> (await GetThreadUsersAsync(options: options).FlattenAsync()).ToImmutableArray();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
|
public override async Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null)
|
||||||
|
|||||||
@@ -85,11 +85,27 @@ namespace Discord.Rest
|
|||||||
return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
|
return result.Threads.Select(x => RestThreadChannel.Create(client, channel.Guild, x)).ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<RestThreadUser[]> GetUsersAsync(IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
|
public static IAsyncEnumerable<IReadOnlyCollection<RestThreadUser>> 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 new PagedAsyncEnumerable<RestThreadUser>(
|
||||||
|
limit,
|
||||||
return users.Select(x => RestThreadUser.Create(client, channel.Guild, x, channel)).ToArray();
|
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<RestThreadUser> GetUserAsync(ulong userId, IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
|
public static async Task<RestThreadUser> GetUserAsync(ulong userId, IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null)
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ namespace Discord.Rest
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IGuild Guild { get; }
|
public IGuild Guild { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IThreadUser.GuildUser"/>
|
||||||
|
public RestGuildUser GuildUser { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Mention => MentionUtils.MentionUser(Id);
|
public string Mention => MentionUtils.MentionUser(Id);
|
||||||
|
|
||||||
@@ -38,8 +41,13 @@ namespace Discord.Rest
|
|||||||
internal void Update(Model model)
|
internal void Update(Model model)
|
||||||
{
|
{
|
||||||
ThreadJoinedAt = model.JoinTimestamp;
|
ThreadJoinedAt = model.JoinTimestamp;
|
||||||
|
if(model.GuildMember.IsSpecified)
|
||||||
|
GuildUser = RestGuildUser.Create(Discord, Guild, model.GuildMember.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IGuildUser IThreadUser.GuildUser => GuildUser;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the guild user for this thread user.
|
/// Gets the guild user for this thread user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ namespace Discord.WebSocket
|
|||||||
return threadUsers.ToImmutableArray();
|
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))
|
if (_members.TryGetValue(model.UserId.Value, out SocketThreadUser member))
|
||||||
member.Update(model);
|
member.Update(model);
|
||||||
@@ -219,15 +219,21 @@ namespace Discord.WebSocket
|
|||||||
/// <returns>A task representing the asynchronous download operation.</returns>
|
/// <returns>A task representing the asynchronous download operation.</returns>
|
||||||
public async Task DownloadUsersAsync(RequestOptions options = null)
|
public async Task DownloadUsersAsync(RequestOptions options = null)
|
||||||
{
|
{
|
||||||
var users = await Discord.ApiClient.ListThreadMembersAsync(Id, options);
|
var prevBatchCount = DiscordConfig.MaxThreadMembersPerBatch;
|
||||||
|
ulong? maxId = null;
|
||||||
|
|
||||||
|
while (prevBatchCount == DiscordConfig.MaxThreadMembersPerBatch)
|
||||||
|
{
|
||||||
|
var users = await Discord.ApiClient.ListThreadMembersAsync(Id, maxId, DiscordConfig.MaxThreadMembersPerBatch, options);
|
||||||
|
prevBatchCount = users.Length;
|
||||||
|
maxId = users.Max(x => x.UserId.GetValueOrDefault());
|
||||||
|
|
||||||
lock (_downloadLock)
|
lock (_downloadLock)
|
||||||
{
|
{
|
||||||
foreach (var threadMember in users)
|
foreach (var threadMember in users)
|
||||||
{
|
{
|
||||||
var guildUser = Guild.GetUser(threadMember.UserId.Value);
|
AddOrUpdateThreadMember(threadMember);
|
||||||
|
}
|
||||||
AddOrUpdateThreadMember(threadMember, guildUser);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,19 +134,21 @@ namespace Discord.WebSocket
|
|||||||
public DateTimeOffset? RequestToSpeakTimestamp
|
public DateTimeOffset? RequestToSpeakTimestamp
|
||||||
=> GuildUser.RequestToSpeakTimestamp;
|
=> GuildUser.RequestToSpeakTimestamp;
|
||||||
|
|
||||||
private SocketGuildUser GuildUser { get; set; }
|
/// <inheritdoc cref="IThreadUser.GuildUser"/>
|
||||||
|
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)
|
: base(guild.Discord, userId)
|
||||||
{
|
{
|
||||||
Thread = thread;
|
Thread = thread;
|
||||||
Guild = guild;
|
Guild = guild;
|
||||||
|
if(member is not null)
|
||||||
GuildUser = member;
|
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);
|
entity.Update(model);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
@@ -154,7 +156,7 @@ namespace Discord.WebSocket
|
|||||||
internal static SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser owner)
|
internal static SocketThreadUser Create(SocketGuild guild, SocketThreadChannel thread, SocketGuildUser owner)
|
||||||
{
|
{
|
||||||
// this is used for creating the owner of the thread.
|
// 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
|
entity.Update(new Model
|
||||||
{
|
{
|
||||||
JoinTimestamp = thread.CreatedAt,
|
JoinTimestamp = thread.CreatedAt,
|
||||||
@@ -165,6 +167,8 @@ namespace Discord.WebSocket
|
|||||||
internal void Update(Model model)
|
internal void Update(Model model)
|
||||||
{
|
{
|
||||||
ThreadJoinedAt = model.JoinTimestamp;
|
ThreadJoinedAt = model.JoinTimestamp;
|
||||||
|
if(model.GuildMember.IsSpecified)
|
||||||
|
GuildUser = Guild.AddOrUpdateUser(model.GuildMember.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -214,6 +218,9 @@ namespace Discord.WebSocket
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IGuild IGuildUser.Guild => Guild;
|
IGuild IGuildUser.Guild => Guild;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IGuildUser IThreadUser.GuildUser => GuildUser;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
ulong IGuildUser.GuildId => Guild.Id;
|
ulong IGuildUser.GuildId => Guild.Id;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user