* 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>tags/3.5.0
| @@ -97,6 +97,13 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| public const int MaxUsersPerBatch = 1000; | public const int MaxUsersPerBatch = 1000; | ||||
| /// <summary> | /// <summary> | ||||
| /// Returns the max bans allowed to be in a request. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The maximum number of bans that can be gotten per-batch. | |||||
| /// </returns> | |||||
| public const int MaxBansPerBatch = 1000; | |||||
| /// <summary> | |||||
| /// Returns the max users allowed to be in a request for guild event users. | /// Returns the max users allowed to be in a request for guild event users. | ||||
| /// </summary> | /// </summary> | ||||
| /// <returns> | /// <returns> | ||||
| @@ -1,10 +1,10 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Specifies the direction of where message(s) should be retrieved from. | |||||
| /// Specifies the direction of where entities (e.g. bans/messages) should be retrieved from. | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// This enum is used to specify the direction for retrieving messages. | |||||
| /// This enum is used to specify the direction for retrieving entities. | |||||
| /// <note type="important"> | /// <note type="important"> | ||||
| /// At the time of writing, <see cref="Around"/> is not yet implemented into | /// At the time of writing, <see cref="Around"/> is not yet implemented into | ||||
| /// <see cref="IMessageChannel.GetMessagesAsync(int, CacheMode, RequestOptions)"/>. | /// <see cref="IMessageChannel.GetMessagesAsync(int, CacheMode, RequestOptions)"/>. | ||||
| @@ -15,15 +15,15 @@ namespace Discord | |||||
| public enum Direction | public enum Direction | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// The message(s) should be retrieved before a message. | |||||
| /// The entity(s) should be retrieved before an entity. | |||||
| /// </summary> | /// </summary> | ||||
| Before, | Before, | ||||
| /// <summary> | /// <summary> | ||||
| /// The message(s) should be retrieved after a message. | |||||
| /// The entity(s) should be retrieved after an entity. | |||||
| /// </summary> | /// </summary> | ||||
| After, | After, | ||||
| /// <summary> | /// <summary> | ||||
| /// The message(s) should be retrieved around a message. | |||||
| /// The entity(s) should be retrieved around an entity. | |||||
| /// </summary> | /// </summary> | ||||
| Around | Around | ||||
| } | } | ||||
| @@ -409,17 +409,70 @@ namespace Discord | |||||
| /// A task that represents the asynchronous leave operation. | /// A task that represents the asynchronous leave operation. | ||||
| /// </returns> | /// </returns> | ||||
| Task LeaveAsync(RequestOptions options = null); | Task LeaveAsync(RequestOptions options = null); | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of all users banned in this guild. | |||||
| /// Gets <paramref name="limit"/> amount of bans from the guild ordered by user ID. | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// The returned collection is an asynchronous enumerable object; one must call | |||||
| /// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a | |||||
| /// collection. | |||||
| /// </note> | |||||
| /// <note type="warning"> | |||||
| /// Do not fetch too many bans at once! This may cause unwanted preemptive rate limit or even actual | |||||
| /// rate limit, causing your bot to freeze! | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| /// <param name="limit">The amount of bans to get from the guild.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| /// <returns> | /// <returns> | ||||
| /// A task that represents the asynchronous get operation. The task result contains a read-only collection of | |||||
| /// ban objects that this guild currently possesses, with each object containing the user banned and reason | |||||
| /// behind the ban. | |||||
| /// A paged collection of bans. | |||||
| /// </returns> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IBan>> GetBansAsync(int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null); | |||||
| /// <summary> | |||||
| /// Gets <paramref name="limit"/> amount of bans from the guild starting at the provided <paramref name="fromUserId"/> ordered by user ID. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// The returned collection is an asynchronous enumerable object; one must call | |||||
| /// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a | |||||
| /// collection. | |||||
| /// </note> | |||||
| /// <note type="warning"> | |||||
| /// Do not fetch too many bans at once! This may cause unwanted preemptive rate limit or even actual | |||||
| /// rate limit, causing your bot to freeze! | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| /// <param name="fromUserId">The ID of the user to start to get bans from.</param> | |||||
| /// <param name="dir">The direction of the bans to be gotten.</param> | |||||
| /// <param name="limit">The number of bans to get.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A paged collection of bans. | |||||
| /// </returns> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IBan>> GetBansAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null); | |||||
| /// <summary> | |||||
| /// Gets <paramref name="limit"/> amount of bans from the guild starting at the provided <paramref name="fromUser"/> ordered by user ID. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// <note type="important"> | |||||
| /// The returned collection is an asynchronous enumerable object; one must call | |||||
| /// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a | |||||
| /// collection. | |||||
| /// </note> | |||||
| /// <note type="warning"> | |||||
| /// Do not fetch too many bans at once! This may cause unwanted preemptive rate limit or even actual | |||||
| /// rate limit, causing your bot to freeze! | |||||
| /// </note> | |||||
| /// </remarks> | |||||
| /// <param name="fromUser">The user to start to get bans from.</param> | |||||
| /// <param name="dir">The direction of the bans to be gotten.</param> | |||||
| /// <param name="limit">The number of bans to get.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A paged collection of bans. | |||||
| /// </returns> | /// </returns> | ||||
| Task<IReadOnlyCollection<IBan>> GetBansAsync(RequestOptions options = null); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IBan>> GetBansAsync(IUser fromUser, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a ban object for a banned user. | /// Gets a ban object for a banned user. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -0,0 +1,9 @@ | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| internal class GetGuildBansParams | |||||
| { | |||||
| public Optional<int> Limit { get; set; } | |||||
| public Optional<Direction> RelativeDirection { get; set; } | |||||
| public Optional<ulong> RelativeUserId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -1545,13 +1545,29 @@ namespace Discord.API | |||||
| #endregion | #endregion | ||||
| #region Guild Bans | #region Guild Bans | ||||
| public async Task<IReadOnlyCollection<Ban>> GetGuildBansAsync(ulong guildId, RequestOptions options = null) | |||||
| public async Task<IReadOnlyCollection<Ban>> GetGuildBansAsync(ulong guildId, GetGuildBansParams args, RequestOptions options = null) | |||||
| { | { | ||||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | Preconditions.NotEqual(guildId, 0, nameof(guildId)); | ||||
| Preconditions.NotNull(args, nameof(args)); | |||||
| Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit)); | |||||
| Preconditions.AtMost(args.Limit, DiscordConfig.MaxBansPerBatch, nameof(args.Limit)); | |||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxBansPerBatch); | |||||
| ulong? relativeId = args.RelativeUserId.IsSpecified ? args.RelativeUserId.Value : (ulong?)null; | |||||
| var relativeDir = args.RelativeDirection.GetValueOrDefault(Direction.Before) switch | |||||
| { | |||||
| Direction.After => "after", | |||||
| Direction.Around => "around", | |||||
| _ => "before", | |||||
| }; | |||||
| var ids = new BucketIds(guildId: guildId); | var ids = new BucketIds(guildId: guildId); | ||||
| return await SendAsync<IReadOnlyCollection<Ban>>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false); | |||||
| Expression<Func<string>> endpoint; | |||||
| if (relativeId != null) | |||||
| endpoint = () => $"guilds/{guildId}/bans?limit={limit}&{relativeDir}={relativeId}"; | |||||
| else | |||||
| endpoint = () => $"guilds/{guildId}/bans?limit={limit}"; | |||||
| return await SendAsync<IReadOnlyCollection<Ban>>("GET", endpoint, ids, options: options).ConfigureAwait(false); | |||||
| } | } | ||||
| public async Task<Ban> GetGuildBanAsync(ulong guildId, ulong userId, RequestOptions options) | public async Task<Ban> GetGuildBanAsync(ulong guildId, ulong userId, RequestOptions options) | ||||
| { | { | ||||
| @@ -142,12 +142,54 @@ namespace Discord.Rest | |||||
| #endregion | #endregion | ||||
| #region Bans | #region Bans | ||||
| public static async Task<IReadOnlyCollection<RestBan>> GetBansAsync(IGuild guild, BaseDiscordClient client, | |||||
| RequestOptions options) | |||||
| public static IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(IGuild guild, BaseDiscordClient client, | |||||
| ulong? fromUserId, Direction dir, int limit, RequestOptions options) | |||||
| { | { | ||||
| var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options).ConfigureAwait(false); | |||||
| return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); | |||||
| if (dir == Direction.Around && limit > DiscordConfig.MaxBansPerBatch) | |||||
| { | |||||
| int around = limit / 2; | |||||
| if (fromUserId.HasValue) | |||||
| return GetBansAsync(guild, client, fromUserId.Value + 1, Direction.Before, around + 1, options) | |||||
| .Concat(GetBansAsync(guild, client, fromUserId.Value, Direction.After, around, options)); | |||||
| else | |||||
| return GetBansAsync(guild, client, null, Direction.Before, around + 1, options); | |||||
| } | |||||
| return new PagedAsyncEnumerable<RestBan>( | |||||
| DiscordConfig.MaxBansPerBatch, | |||||
| async (info, ct) => | |||||
| { | |||||
| var args = new GetGuildBansParams | |||||
| { | |||||
| RelativeDirection = dir, | |||||
| Limit = info.PageSize | |||||
| }; | |||||
| if (info.Position != null) | |||||
| args.RelativeUserId = info.Position.Value; | |||||
| var models = await client.ApiClient.GetGuildBansAsync(guild.Id, args, options).ConfigureAwait(false); | |||||
| var builder = ImmutableArray.CreateBuilder<RestBan>(); | |||||
| foreach (var model in models) | |||||
| builder.Add(RestBan.Create(client, model)); | |||||
| return builder.ToImmutable(); | |||||
| }, | |||||
| nextPage: (info, lastPage) => | |||||
| { | |||||
| if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) | |||||
| return false; | |||||
| if (dir == Direction.Before) | |||||
| info.Position = lastPage.Min(x => x.User.Id); | |||||
| else | |||||
| info.Position = lastPage.Max(x => x.User.Id); | |||||
| return true; | |||||
| }, | |||||
| start: fromUserId, | |||||
| count: limit | |||||
| ); | |||||
| } | } | ||||
| public static async Task<RestBan> GetBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, RequestOptions options) | public static async Task<RestBan> GetBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, RequestOptions options) | ||||
| { | { | ||||
| var model = await client.ApiClient.GetGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false); | var model = await client.ApiClient.GetGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false); | ||||
| @@ -333,17 +333,18 @@ namespace Discord.Rest | |||||
| #endregion | #endregion | ||||
| #region Bans | #region Bans | ||||
| /// <summary> | |||||
| /// Gets a collection of all users banned in this guild. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous get operation. The task result contains a read-only collection of | |||||
| /// ban objects that this guild currently possesses, with each object containing the user banned and reason | |||||
| /// behind the ban. | |||||
| /// </returns> | |||||
| public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null) | |||||
| => GuildHelper.GetBansAsync(this, Discord, options); | |||||
| /// <inheritdoc cref="IGuild.GetBansAsync(int, RequestOptions)" /> | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null) | |||||
| => GuildHelper.GetBansAsync(this, Discord, null, Direction.Before, limit, options); | |||||
| /// <inheritdoc cref="IGuild.GetBansAsync(ulong, Direction, int, RequestOptions)" /> | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null) | |||||
| => GuildHelper.GetBansAsync(this, Discord, fromUserId, dir, limit, options); | |||||
| /// <inheritdoc cref="IGuild.GetBansAsync(IUser, Direction, int, RequestOptions)" /> | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(IUser fromUser, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null) | |||||
| => GuildHelper.GetBansAsync(this, Discord, fromUser.Id, dir, limit, options); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a ban object for a banned user. | /// Gets a ban object for a banned user. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -1193,22 +1194,24 @@ namespace Discord.Rest | |||||
| IReadOnlyCollection<IRole> IGuild.Roles => Roles; | IReadOnlyCollection<IRole> IGuild.Roles => Roles; | ||||
| IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers; | IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers; | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| 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) | 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) | ||||
| => await CreateEventAsync(name, startTime, type, privacyLevel, description, endTime, channelId, location, coverImage, options).ConfigureAwait(false); | => await CreateEventAsync(name, startTime, type, privacyLevel, description, endTime, channelId, location, coverImage, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions options) | async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions options) | ||||
| => await GetEventAsync(id, options).ConfigureAwait(false); | => await GetEventAsync(id, options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IReadOnlyCollection<IGuildScheduledEvent>> IGuild.GetEventsAsync(RequestOptions options) | async Task<IReadOnlyCollection<IGuildScheduledEvent>> IGuild.GetEventsAsync(RequestOptions options) | ||||
| => await GetEventsAsync(options).ConfigureAwait(false); | => await GetEventsAsync(options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) | |||||
| => await GetBansAsync(options).ConfigureAwait(false); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(int limit, RequestOptions options) | |||||
| => GetBansAsync(limit, options); | |||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options) | |||||
| => GetBansAsync(fromUserId, dir, limit, options); | |||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(IUser fromUser, Direction dir, int limit, RequestOptions options) | |||||
| => GetBansAsync(fromUser, dir, limit, options); | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| async Task<IBan> IGuild.GetBanAsync(IUser user, RequestOptions options) | async Task<IBan> IGuild.GetBanAsync(IUser user, RequestOptions options) | ||||
| => await GetBanAsync(user, options).ConfigureAwait(false); | => await GetBanAsync(user, options).ConfigureAwait(false); | ||||
| @@ -621,17 +621,19 @@ namespace Discord.WebSocket | |||||
| #endregion | #endregion | ||||
| #region Bans | #region Bans | ||||
| /// <summary> | |||||
| /// Gets a collection of all users banned in this guild. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous get operation. The task result contains a read-only collection of | |||||
| /// ban objects that this guild currently possesses, with each object containing the user banned and reason | |||||
| /// behind the ban. | |||||
| /// </returns> | |||||
| public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null) | |||||
| => GuildHelper.GetBansAsync(this, Discord, options); | |||||
| /// <inheritdoc cref="IGuild.GetBansAsync(int, RequestOptions)" /> | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null) | |||||
| => GuildHelper.GetBansAsync(this, Discord, null, Direction.Before, limit, options); | |||||
| /// <inheritdoc cref="IGuild.GetBansAsync(ulong, Direction, int, RequestOptions)" /> | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null) | |||||
| => GuildHelper.GetBansAsync(this, Discord, fromUserId, dir, limit, options); | |||||
| /// <inheritdoc cref="IGuild.GetBansAsync(IUser, Direction, int, RequestOptions)" /> | |||||
| public IAsyncEnumerable<IReadOnlyCollection<RestBan>> GetBansAsync(IUser fromUser, Direction dir, int limit = DiscordConfig.MaxBansPerBatch, RequestOptions options = null) | |||||
| => GuildHelper.GetBansAsync(this, Discord, fromUser.Id, dir, limit, options); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a ban object for a banned user. | /// Gets a ban object for a banned user. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -1810,8 +1812,14 @@ namespace Discord.WebSocket | |||||
| async Task<IReadOnlyCollection<IGuildScheduledEvent>> IGuild.GetEventsAsync(RequestOptions options) | async Task<IReadOnlyCollection<IGuildScheduledEvent>> IGuild.GetEventsAsync(RequestOptions options) | ||||
| => await GetEventsAsync(options).ConfigureAwait(false); | => await GetEventsAsync(options).ConfigureAwait(false); | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) | |||||
| => await GetBansAsync(options).ConfigureAwait(false); | |||||
| IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(int limit, RequestOptions options) | |||||
| => GetBansAsync(limit, options); | |||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options) | |||||
| => GetBansAsync(fromUserId, dir, limit, options); | |||||
| /// <inheritdoc /> | |||||
| IAsyncEnumerable<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(IUser fromUser, Direction dir, int limit, RequestOptions options) | |||||
| => GetBansAsync(fromUser, dir, limit, options); | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| async Task<IBan> IGuild.GetBanAsync(IUser user, RequestOptions options) | async Task<IBan> IGuild.GetBanAsync(IUser user, RequestOptions options) | ||||
| => await GetBanAsync(user, options).ConfigureAwait(false); | => await GetBanAsync(user, options).ConfigureAwait(false); | ||||