Skip to content

Commit d8757a5

Browse files
csmirquinchs
andauthored
feature: Update bans to support pagination (#2223)
* Cacheless impl * Ignore cache impl * Update src/Discord.Net.Core/Entities/Channels/Direction.cs Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Channels/Direction.cs Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Channels/Direction.cs Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Guilds/IGuild.cs Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Guilds/IGuild.cs Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Guilds/IGuild.cs Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Guilds/IGuild.cs Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> * Implement xmldoc consistency Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com>
1 parent d1cf1bf commit d8757a5

File tree

8 files changed

+185
-47
lines changed

8 files changed

+185
-47
lines changed

src/Discord.Net.Core/DiscordConfig.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ public class DiscordConfig
9797
/// </returns>
9898
public const int MaxUsersPerBatch = 1000;
9999
/// <summary>
100+
/// Returns the max bans allowed to be in a request.
101+
/// </summary>
102+
/// <returns>
103+
/// The maximum number of bans that can be gotten per-batch.
104+
/// </returns>
105+
public const int MaxBansPerBatch = 1000;
106+
/// <summary>
100107
/// Returns the max users allowed to be in a request for guild event users.
101108
/// </summary>
102109
/// <returns>

src/Discord.Net.Core/Entities/Channels/Direction.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
namespace Discord
22
{
33
/// <summary>
4-
/// Specifies the direction of where message(s) should be retrieved from.
4+
/// Specifies the direction of where entities (e.g. bans/messages) should be retrieved from.
55
/// </summary>
66
/// <remarks>
7-
/// This enum is used to specify the direction for retrieving messages.
7+
/// This enum is used to specify the direction for retrieving entities.
88
/// <note type="important">
99
/// At the time of writing, <see cref="Around"/> is not yet implemented into
1010
/// <see cref="IMessageChannel.GetMessagesAsync(int, CacheMode, RequestOptions)"/>.
@@ -15,15 +15,15 @@ namespace Discord
1515
public enum Direction
1616
{
1717
/// <summary>
18-
/// The message(s) should be retrieved before a message.
18+
/// The entity(s) should be retrieved before an entity.
1919
/// </summary>
2020
Before,
2121
/// <summary>
22-
/// The message(s) should be retrieved after a message.
22+
/// The entity(s) should be retrieved after an entity.
2323
/// </summary>
2424
After,
2525
/// <summary>
26-
/// The message(s) should be retrieved around a message.
26+
/// The entity(s) should be retrieved around an entity.
2727
/// </summary>
2828
Around
2929
}

src/Discord.Net.Core/Entities/Guilds/IGuild.cs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -409,17 +409,70 @@ public interface IGuild : IDeletable, ISnowflakeEntity
409409
/// A task that represents the asynchronous leave operation.
410410
/// </returns>
411411
Task LeaveAsync(RequestOptions options = null);
412-
413412
/// <summary>
414-
/// Gets a collection of all users banned in this guild.
413+
/// Gets <paramref name="limit"/> amount of bans from the guild ordered by user ID.
415414
/// </summary>
415+
/// <remarks>
416+
/// <note type="important">
417+
/// The returned collection is an asynchronous enumerable object; one must call
418+
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a
419+
/// collection.
420+
/// </note>
421+
/// <note type="warning">
422+
/// Do not fetch too many bans at once! This may cause unwanted preemptive rate limit or even actual
423+
/// rate limit, causing your bot to freeze!
424+
/// </note>
425+
/// </remarks>
426+
/// <param name="limit">The amount of bans to get from the guild.</param>
416427
/// <param name="options">The options to be used when sending the request.</param>
417428
/// <returns>
418-
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
419-
/// ban objects that this guild currently possesses, with each object containing the user banned and reason
420-
/// behind the ban.
429+
/// A paged collection of bans.
430+
/// </returns>
431+
IAsyncEnumerable<IReadOnlyCollection<IBan>> GetBansAsync(int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null);
432+
/// <summary>
433+
/// Gets <paramref name="limit"/> amount of bans from the guild starting at the provided <paramref name="fromUserId"/> ordered by user ID.
434+
/// </summary>
435+
/// <remarks>
436+
/// <note type="important">
437+
/// The returned collection is an asynchronous enumerable object; one must call
438+
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a
439+
/// collection.
440+
/// </note>
441+
/// <note type="warning">
442+
/// Do not fetch too many bans at once! This may cause unwanted preemptive rate limit or even actual
443+
/// rate limit, causing your bot to freeze!
444+
/// </note>
445+
/// </remarks>
446+
/// <param name="fromUserId">The ID of the user to start to get bans from.</param>
447+
/// <param name="dir">The direction of the bans to be gotten.</param>
448+
/// <param name="limit">The number of bans to get.</param>
449+
/// <param name="options">The options to be used when sending the request.</param>
450+
/// <returns>
451+
/// A paged collection of bans.
452+
/// </returns>
453+
IAsyncEnumerable<IReadOnlyCollection<IBan>> GetBansAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null);
454+
/// <summary>
455+
/// Gets <paramref name="limit"/> amount of bans from the guild starting at the provided <paramref name="fromUser"/> ordered by user ID.
456+
/// </summary>
457+
/// <remarks>
458+
/// <note type="important">
459+
/// The returned collection is an asynchronous enumerable object; one must call
460+
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a
461+
/// collection.
462+
/// </note>
463+
/// <note type="warning">
464+
/// Do not fetch too many bans at once! This may cause unwanted preemptive rate limit or even actual
465+
/// rate limit, causing your bot to freeze!
466+
/// </note>
467+
/// </remarks>
468+
/// <param name="fromUser">The user to start to get bans from.</param>
469+
/// <param name="dir">The direction of the bans to be gotten.</param>
470+
/// <param name="limit">The number of bans to get.</param>
471+
/// <param name="options">The options to be used when sending the request.</param>
472+
/// <returns>
473+
/// A paged collection of bans.
421474
/// </returns>
422-
Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null);
475+
IAsyncEnumerable<IReadOnlyCollection<IBan>> GetBansAsync(IUser fromUser, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null);
423476
/// <summary>
424477
/// Gets a ban object for a banned user.
425478
/// </summary>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Discord.API.Rest
2+
{
3+
internal class GetGuildBansParams
4+
{
5+
public Optional<int> Limit { get; set; }
6+
public Optional<Direction> RelativeDirection { get; set; }
7+
public Optional<ulong> RelativeUserId { get; set; }
8+
}
9+
}

src/Discord.Net.Rest/DiscordRestApiClient.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,13 +1545,29 @@ public async Task<GetGuildPruneCountResponse> GetGuildPruneCountAsync(ulong guil
15451545
#endregion
15461546

15471547
#region Guild Bans
1548-
public async Task<IReadOnlyCollection<Ban>> GetGuildBansAsync(ulong guildId, RequestOptions options = null)
1548+
public async Task<IReadOnlyCollection<Ban>> GetGuildBansAsync(ulong guildId, GetGuildBansParams args, RequestOptions options = null)
15491549
{
15501550
Preconditions.NotEqual(guildId, 0, nameof(guildId));
1551+
Preconditions.NotNull(args, nameof(args));
1552+
Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit));
1553+
Preconditions.AtMost(args.Limit, DiscordConfig.MaxBansPerBatch, nameof(args.Limit));
15511554
options = RequestOptions.CreateOrClone(options);
15521555

1556+
int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxBansPerBatch);
1557+
ulong? relativeId = args.RelativeUserId.IsSpecified ? args.RelativeUserId.Value : (ulong?)null;
1558+
var relativeDir = args.RelativeDirection.GetValueOrDefault(Direction.Before) switch
1559+
{
1560+
Direction.After => "after",
1561+
Direction.Around => "around",
1562+
_ => "before",
1563+
};
15531564
var ids = new BucketIds(guildId: guildId);
1554-
return await SendAsync<IReadOnlyCollection<Ban>>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false);
1565+
Expression<Func<string>> endpoint;
1566+
if (relativeId != null)
1567+
endpoint = () => $"guilds/{guildId}/bans?limit={limit}&{relativeDir}={relativeId}";
1568+
else
1569+
endpoint = () => $"guilds/{guildId}/bans?limit={limit}";
1570+
return await SendAsync<IReadOnlyCollection<Ban>>("GET", endpoint, ids, options: options).ConfigureAwait(false);
15551571
}
15561572
public async Task<Ban> GetGuildBanAsync(ulong guildId, ulong userId, RequestOptions options)
15571573
{

src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,54 @@ public static ulong GetUploadLimit(IGuild guild)
142142
#endregion
143143

144144
#region Bans
145-
public static async Task<IReadOnlyCollection<RestBan>> GetBansAsync(IGuild guild, BaseDiscordClient client,
146-
RequestOptions options)
145+
public static IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(IGuild guild, BaseDiscordClient client,
146+
ulong? fromUserId, Direction dir, int limit, RequestOptions options)
147147
{
148-
var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options).ConfigureAwait(false);
149-
return models.Select(x => RestBan.Create(client, x)).ToImmutableArray();
148+
if (dir == Direction.Around && limit > DiscordConfig.MaxBansPerBatch)
149+
{
150+
int around = limit / 2;
151+
if (fromUserId.HasValue)
152+
return GetBansAsync(guild, client, fromUserId.Value + 1, Direction.Before, around + 1, options)
153+
.Concat(GetBansAsync(guild, client, fromUserId.Value, Direction.After, around, options));
154+
else
155+
return GetBansAsync(guild, client, null, Direction.Before, around + 1, options);
156+
}
157+
158+
return new PagedAsyncEnumerable<RestBan>(
159+
DiscordConfig.MaxBansPerBatch,
160+
async (info, ct) =>
161+
{
162+
var args = new GetGuildBansParams
163+
{
164+
RelativeDirection = dir,
165+
Limit = info.PageSize
166+
};
167+
if (info.Position != null)
168+
args.RelativeUserId = info.Position.Value;
169+
170+
var models = await client.ApiClient.GetGuildBansAsync(guild.Id, args, options).ConfigureAwait(false);
171+
var builder = ImmutableArray.CreateBuilder<RestBan>();
172+
173+
foreach (var model in models)
174+
builder.Add(RestBan.Create(client, model));
175+
176+
return builder.ToImmutable();
177+
},
178+
nextPage: (info, lastPage) =>
179+
{
180+
if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch)
181+
return false;
182+
if (dir == Direction.Before)
183+
info.Position = lastPage.Min(x => x.User.Id);
184+
else
185+
info.Position = lastPage.Max(x => x.User.Id);
186+
return true;
187+
},
188+
start: fromUserId,
189+
count: limit
190+
);
150191
}
192+
151193
public static async Task<RestBan> GetBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, RequestOptions options)
152194
{
153195
var model = await client.ApiClient.GetGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false);

src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -333,17 +333,18 @@ public Task<RestGuildCommand> GetSlashCommandAsync(ulong id, RequestOptions opti
333333
#endregion
334334

335335
#region Bans
336-
/// <summary>
337-
/// Gets a collection of all users banned in this guild.
338-
/// </summary>
339-
/// <param name="options">The options to be used when sending the request.</param>
340-
/// <returns>
341-
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
342-
/// ban objects that this guild currently possesses, with each object containing the user banned and reason
343-
/// behind the ban.
344-
/// </returns>
345-
public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null)
346-
=> GuildHelper.GetBansAsync(this, Discord, options);
336+
337+
/// <inheritdoc cref="IGuild.GetBansAsync(int, RequestOptions)" />
338+
public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null)
339+
=> GuildHelper.GetBansAsync(this, Discord, null, Direction.Before, limit, options);
340+
341+
/// <inheritdoc cref="IGuild.GetBansAsync(ulong, Direction, int, RequestOptions)" />
342+
public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null)
343+
=> GuildHelper.GetBansAsync(this, Discord, fromUserId, dir, limit, options);
344+
345+
/// <inheritdoc cref="IGuild.GetBansAsync(IUser, Direction, int, RequestOptions)" />
346+
public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(IUser fromUser, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null)
347+
=> GuildHelper.GetBansAsync(this, Discord, fromUser.Id, dir, limit, options);
347348
/// <summary>
348349
/// Gets a ban object for a banned user.
349350
/// </summary>
@@ -1193,22 +1194,24 @@ public Task<RestGuildEvent> CreateEventAsync(
11931194
IReadOnlyCollection<IRole> IGuild.Roles => Roles;
11941195

11951196
IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers;
1196-
11971197
/// <inheritdoc />
11981198
async Task<IGuildScheduledEvent> IGuild.CreateEventAsync(string name, DateTimeOffset startTime, GuildScheduledEventType type, GuildScheduledEventPrivacyLevel privacyLevel, string description, DateTimeOffset? endTime, ulong? channelId, string location, Image? coverImage, RequestOptions options)
11991199
=> await CreateEventAsync(name, startTime, type, privacyLevel, description, endTime, channelId, location, coverImage, options).ConfigureAwait(false);
1200-
12011200
/// <inheritdoc />
12021201
async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions options)
12031202
=> await GetEventAsync(id, options).ConfigureAwait(false);
1204-
12051203
/// <inheritdoc />
12061204
async Task<IReadOnlyCollection<IGuildScheduledEvent>> IGuild.GetEventsAsync(RequestOptions options)
12071205
=> await GetEventsAsync(options).ConfigureAwait(false);
1208-
12091206
/// <inheritdoc />
1210-
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options)
1211-
=> await GetBansAsync(options).ConfigureAwait(false);
1207+
IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(int limit, RequestOptions options)
1208+
=> GetBansAsync(limit, options);
1209+
/// <inheritdoc />
1210+
IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options)
1211+
=> GetBansAsync(fromUserId, dir, limit, options);
1212+
/// <inheritdoc />
1213+
IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(IUser fromUser, Direction dir, int limit, RequestOptions options)
1214+
=> GetBansAsync(fromUser, dir, limit, options);
12121215
/// <inheritdoc/>
12131216
async Task<IBan> IGuild.GetBanAsync(IUser user, RequestOptions options)
12141217
=> await GetBanAsync(user, options).ConfigureAwait(false);

src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -621,17 +621,19 @@ public Task LeaveAsync(RequestOptions options = null)
621621
#endregion
622622

623623
#region Bans
624-
/// <summary>
625-
/// Gets a collection of all users banned in this guild.
626-
/// </summary>
627-
/// <param name="options">The options to be used when sending the request.</param>
628-
/// <returns>
629-
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
630-
/// ban objects that this guild currently possesses, with each object containing the user banned and reason
631-
/// behind the ban.
632-
/// </returns>
633-
public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null)
634-
=> GuildHelper.GetBansAsync(this, Discord, options);
624+
625+
/// <inheritdoc cref="IGuild.GetBansAsync(int, RequestOptions)" />
626+
public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null)
627+
=> GuildHelper.GetBansAsync(this, Discord, null, Direction.Before, limit, options);
628+
629+
/// <inheritdoc cref="IGuild.GetBansAsync(ulong, Direction, int, RequestOptions)" />
630+
public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null)
631+
=> GuildHelper.GetBansAsync(this, Discord, fromUserId, dir, limit, options);
632+
633+
/// <inheritdoc cref="IGuild.GetBansAsync(IUser, Direction, int, RequestOptions)" />
634+
public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(IUser fromUser, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null)
635+
=> GuildHelper.GetBansAsync(this, Discord, fromUser.Id, dir, limit, options);
636+
635637
/// <summary>
636638
/// Gets a ban object for a banned user.
637639
/// </summary>
@@ -1810,8 +1812,14 @@ async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions o
18101812
async Task<IReadOnlyCollection<IGuildScheduledEvent>> IGuild.GetEventsAsync(RequestOptions options)
18111813
=> await GetEventsAsync(options).ConfigureAwait(false);
18121814
/// <inheritdoc />
1813-
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options)
1814-
=> await GetBansAsync(options).ConfigureAwait(false);
1815+
IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(int limit, RequestOptions options)
1816+
=> GetBansAsync(limit, options);
1817+
/// <inheritdoc />
1818+
IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options)
1819+
=> GetBansAsync(fromUserId, dir, limit, options);
1820+
/// <inheritdoc />
1821+
IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(IUser fromUser, Direction dir, int limit, RequestOptions options)
1822+
=> GetBansAsync(fromUser, dir, limit, options);
18151823
/// <inheritdoc/>
18161824
async Task<IBan> IGuild.GetBanAsync(IUser user, RequestOptions options)
18171825
=> await GetBanAsync(user, options).ConfigureAwait(false);

0 commit comments

Comments
 (0)