* initial commit * return & update old method * mark old method as obsoletepull/2594/head
| @@ -214,5 +214,13 @@ namespace Discord | |||
| /// If set to <see langword="false"/>, this value will be "Discord#1234". | |||
| /// </remarks> | |||
| 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. | |||
| /// </summary> | |||
| 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")] | |||
| public int Flags { get; set; } // No enum type (yet?) | |||
| [JsonProperty("member")] | |||
| public Optional<GuildMember> GuildMember { get; set; } | |||
| } | |||
| } | |||
| @@ -563,16 +563,23 @@ namespace Discord.API | |||
| 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)); | |||
| 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<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) | |||
| @@ -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<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) | |||
| @@ -2524,7 +2532,7 @@ namespace Discord.API | |||
| => 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) | |||
| => 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) | |||
| => 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) | |||
| => 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> | |||
| /// Gets a collection of users within this thread. | |||
| /// </summary> | |||
| @@ -109,8 +120,9 @@ namespace Discord.Rest | |||
| /// A task representing the asynchronous get operation. The task returns a | |||
| /// <see cref="IReadOnlyCollection{T}"/> of <see cref="RestThreadUser"/>'s. | |||
| /// </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) | |||
| => (await ThreadHelper.GetUsersAsync(this, Discord, options).ConfigureAwait(false)).ToImmutableArray(); | |||
| => (await GetThreadUsersAsync(options: options).FlattenAsync()).ToImmutableArray(); | |||
| /// <inheritdoc/> | |||
| 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(); | |||
| } | |||
| 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 users.Select(x => RestThreadUser.Create(client, channel.Guild, x, channel)).ToArray(); | |||
| return new PagedAsyncEnumerable<RestThreadUser>( | |||
| 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<RestThreadUser> GetUserAsync(ulong userId, IThreadChannel channel, BaseDiscordClient client, RequestOptions options = null) | |||
| @@ -18,6 +18,9 @@ namespace Discord.Rest | |||
| /// <inheritdoc/> | |||
| public IGuild Guild { get; } | |||
| /// <inheritdoc cref="IThreadUser.GuildUser"/> | |||
| public RestGuildUser GuildUser { get; private set; } | |||
| /// <inheritdoc/> | |||
| 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); | |||
| } | |||
| /// <inheritdoc /> | |||
| IGuildUser IThreadUser.GuildUser => GuildUser; | |||
| /// <summary> | |||
| /// Gets the guild user for this thread user. | |||
| /// </summary> | |||
| @@ -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 | |||
| /// <returns>A task representing the asynchronous download operation.</returns> | |||
| 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 | |||
| => 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) | |||
| { | |||
| 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); | |||
| } | |||
| /// <inheritdoc/> | |||
| @@ -214,6 +218,9 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc/> | |||
| IGuild IGuildUser.Guild => Guild; | |||
| /// <inheritdoc /> | |||
| IGuildUser IThreadUser.GuildUser => GuildUser; | |||
| /// <inheritdoc/> | |||
| ulong IGuildUser.GuildId => Guild.Id; | |||