@@ -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,16 +564,23 @@ 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) | ||||
@@ -584,8 +591,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) | ||||
@@ -2517,7 +2525,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); | ||||
@@ -104,13 +104,12 @@ namespace Discord.Rest | |||||
/// <summary> | /// <summary> | ||||
/// Gets a collection of users within this thread. | /// Gets a collection of users within this thread. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | /// <returns> | ||||
/// A task representing the asynchronous get operation. The task returns a | |||||
/// <see cref="IReadOnlyCollection{T}"/> of <see cref="RestThreadUser"/>'s. | |||||
/// A task that represents the asynchronous get operation. The task result contains a collection of thread | |||||
/// users found within this thread channel. | |||||
/// </returns> | /// </returns> | ||||
public new async Task<IReadOnlyCollection<RestThreadUser>> GetUsersAsync(RequestOptions options = null) | |||||
=> (await ThreadHelper.GetUsersAsync(this, Discord, options).ConfigureAwait(false)).ToImmutableArray(); | |||||
public IAsyncEnumerable<IReadOnlyCollection<RestThreadUser>> GetUsersAsync(int usersPerBatch = DiscordConfig.MaxThreadMembersPerBatch, RequestOptions options = null) | |||||
=> ThreadHelper.GetUsersAsync(this, Discord, usersPerBatch, null, options); | |||||
/// <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 = null, 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<RestThreadUser>( | |||||
DiscordConfig.MaxUsersPerBatch, | |||||
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; | |||||
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); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -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; | ||||
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); | 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; | ||||