Skip to content

Commit

Permalink
feat: bulk ban (#482)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lulalaby authored Apr 21, 2024
1 parent 6a65f0c commit df7e2ec
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 23 deletions.
30 changes: 30 additions & 0 deletions DisCatSharp/Entities/Guild/DiscordBulkBanResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;

using Newtonsoft.Json;

namespace DisCatSharp.Entities;

/// <summary>
/// Represents a bulk ban response
/// </summary>
public sealed class DiscordBulkBanResponse : ObservableApiObject
{
/// <summary>
/// Gets the list of user ids, that were successfully banned.
/// </summary>
[JsonProperty("banned_users", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<ulong> BannedUsers { get; internal set; } = [];

/// <summary>
/// <para>Gets the list of user ids, that were not banned.</para>
/// <para>These users were either already banned or could not be banned due to various reasons.</para>
/// </summary>
[JsonProperty("failed_users", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<ulong> FailedUsers { get; internal set; } = [];

/// <summary>
/// Initializes a new instance of the <see cref="DiscordBulkBanResponse"/> class.
/// </summary>
internal DiscordBulkBanResponse()
{ }
}
71 changes: 59 additions & 12 deletions DisCatSharp/Entities/Guild/DiscordGuild.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1026,43 +1026,90 @@ public Task RemoveTimeoutAsync(ulong memberId, string? reason = null)
=> this.Discord.ApiClient.ModifyTimeoutAsync(this.Id, memberId, null, reason);

/// <summary>
/// Bans a specified member from this guild.
/// Bans a specified <see cref="DiscordMember"/> from this guild.
/// </summary>
/// <param name="member">Member to ban.</param>
/// <param name="deleteMessageDays">How many days to remove messages from.</param>
/// <param name="deleteMessageSeconds">How many seconds to remove messages from the users. Minimum <c>0</c> seconds and maximum <c>604800 </c> seconds (7 days).</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task BanMemberAsync(DiscordMember member, int deleteMessageDays = 0, string? reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, member.Id, deleteMessageDays, reason);
public Task BanMemberAsync(DiscordMember member, [DiscordDeprecated("This is now in seconds, we convert it until the next minor release.")] int deleteMessageSeconds = 0, string? reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, member.Id, deleteMessageSeconds < 8 && deleteMessageSeconds > 0 ? this.DaysToSeconds(deleteMessageSeconds) : deleteMessageSeconds, reason);

/// <summary>
/// Bans a specified user by ID. This doesn't require the user to be in this guild.
/// Bans a specified <see cref="DiscordUser"/>. This doesn't require the user to be in this guild.
/// </summary>
/// <param name="user">The user to ban.</param>
/// <param name="deleteMessageDays">How many days to remove messages from.</param>
/// <param name="deleteMessageSeconds">How many seconds to remove messages from the users. Minimum <c>0</c> seconds and maximum <c>604800 </c> seconds (7 days).</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task BanMemberAsync(DiscordUser user, int deleteMessageDays = 0, string? reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, user.Id, deleteMessageDays, reason);
public Task BanMemberAsync(DiscordUser user, [DiscordDeprecated("This is now in seconds, we convert it until the next minor release.")] int deleteMessageSeconds = 0, string? reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, user.Id, deleteMessageSeconds < 8 && deleteMessageSeconds > 0 ? this.DaysToSeconds(deleteMessageSeconds) : deleteMessageSeconds, reason);

/// <summary>
/// Bans a specified user by ID. This doesn't require the user to be in this guild.
/// Bans a specified user ID from this guild. This doesn't require the user to be in this guild.
/// </summary>
/// <param name="userId">ID of the user to ban.</param>
/// <param name="deleteMessageDays">How many days to remove messages from.</param>
/// <param name="deleteMessageSeconds">How many seconds to remove messages from the users. Minimum <c>0</c> seconds and maximum <c>604800 </c> seconds (7 days).</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task BanMemberAsync(ulong userId, int deleteMessageDays = 0, string reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, userId, deleteMessageDays, reason);
public Task BanMemberAsync(ulong userId, [DiscordDeprecated("This is now in seconds, we convert it until the next minor release.")] int deleteMessageSeconds = 0, string reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, userId, deleteMessageSeconds < 8 && deleteMessageSeconds > 0 ? this.DaysToSeconds(deleteMessageSeconds) : deleteMessageSeconds, reason);

/// <summary>
/// Converts days to seconds to help users transition from <c>deleteMessageDays</c> to <c>deleteMessageSeconds</c>.
/// </summary>
/// <param name="days">The days to convert to seconds.</param>
/// <returns>The days in seconds.</returns>
private int DaysToSeconds(int days)
=> days * 24 * 60 * 60;

/// <summary>
/// Bulk bans a list of <see cref="DiscordMember"/>s from this guild.
/// </summary>
/// <param name="members">The members to ban.</param>
/// <param name="deleteMessageSeconds">How many seconds to remove messages from the users. Minimum <c>0</c> seconds and maximum <c>604800 </c> seconds (7 days).</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="ArgumentException">Thrown when <paramref name="deleteMessageSeconds"/> was too low or too high, or when <paramref name="members"/> contains more than <c>200</c> members.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> or <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordBulkBanResponse> BulkBanMembersAsync(List<DiscordMember> members, int deleteMessageSeconds = 0, string? reason = null)
=> this.Discord.ApiClient.CreateGuildBulkBanAsync(this.Id, members.Select(x => x.Id).ToList(), deleteMessageSeconds, reason);

/// <summary>
/// Bulk bans a list of <see cref="DiscordUser"/>s from this guild. This doesn't require the users to be in this guild.
/// </summary>
/// <param name="users">The users to ban.</param>
/// <param name="deleteMessageSeconds">How many seconds to remove messages from the users. Minimum <c>0</c> seconds and maximum <c>604800 </c> seconds (7 days).</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="ArgumentException">Thrown when <paramref name="deleteMessageSeconds"/> was too low or too high, or when <paramref name="users"/> contains more than <c>200</c> users.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> or <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordBulkBanResponse> BulkBanMembersAsync(List<DiscordUser> users, int deleteMessageSeconds = 0, string? reason = null)
=> this.Discord.ApiClient.CreateGuildBulkBanAsync(this.Id, users.Select(x => x.Id).ToList(), deleteMessageSeconds, reason);

/// <summary>
/// Bans a list of user IDs from this guild. This doesn't require the users to be in this guild.
/// </summary>
/// <param name="userIds">The user IDs to ban.</param>
/// <param name="deleteMessageSeconds">How many seconds to remove messages from the users. Minimum <c>0</c> seconds and maximum <c>604800 </c> seconds (7 days).</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="ArgumentException">Thrown when <paramref name="deleteMessageSeconds"/> was too low or too high, or when <paramref name="userIds"/> contains more than <c>200</c> user ids.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> or <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordBulkBanResponse> BulkBanMembersAsync(List<ulong> userIds, int deleteMessageSeconds = 0, string reason = null)
=> this.Discord.ApiClient.CreateGuildBulkBanAsync(this.Id, userIds, deleteMessageSeconds, reason);

/// <summary>
/// Unbans a user from this guild.
Expand Down
31 changes: 31 additions & 0 deletions DisCatSharp/Net/Abstractions/Rest/RestGuildPayloads.cs
Original file line number Diff line number Diff line change
Expand Up @@ -840,3 +840,34 @@ internal class RestGuildUpdateUserVoiceStatePayload : ObservableApiObject
[JsonProperty("suppress", NullValueHandling = NullValueHandling.Ignore)]
public bool? Suppress { get; set; }
}

/// <summary>
/// Represents a guild ban payload.
/// </summary>
internal sealed class RestGuildBanPayload : ObservableApiObject
{
/// <summary>
/// Gets or sets the delete message seconds.
/// </summary>
[JsonProperty("delete_message_seconds", NullValueHandling = NullValueHandling.Include)]
public int DeleteMessageSeconds { get; set; }
}


/// <summary>
/// Represents a guild bulk ban payload.
/// </summary>
internal sealed class RestGuildBulkBanPayload : ObservableApiObject
{
/// <summary>
/// Gets or sets the user ids.
/// </summary>
[JsonProperty("user_ids", NullValueHandling = NullValueHandling.Ignore)]
public List<ulong> UserIds { get; set; }

/// <summary>
/// Gets or sets the delete message seconds.
/// </summary>
[JsonProperty("delete_message_seconds", NullValueHandling = NullValueHandling.Include)]
public int DeleteMessageSeconds { get; set; }
}
57 changes: 46 additions & 11 deletions DisCatSharp/Net/Rest/DiscordApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1060,20 +1060,20 @@ internal async Task<IReadOnlyList<DiscordBan>> GetGuildBansAsync(ulong guildId,
}

/// <summary>
/// Creates the guild ban async.
/// Creates a guild ban.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="deleteMessageDays">The delete_message_days.</param>
/// <param name="guildId">The guild id to ban from.</param>
/// <param name="userId">The user id to ban.</param>
/// <param name="deleteMessageSeconds">The delete message seconds.</param>
/// <param name="reason">The reason.</param>
internal Task CreateGuildBanAsync(ulong guildId, ulong userId, int deleteMessageDays, string? reason)
internal Task CreateGuildBanAsync(ulong guildId, ulong userId, int deleteMessageSeconds, string? reason)
{
if (deleteMessageDays < 0 || deleteMessageDays > 7)
throw new ArgumentException("Delete message days must be a number between 0 and 7.", nameof(deleteMessageDays));
if (deleteMessageSeconds < 0 || deleteMessageSeconds > 7)
throw new ArgumentException("Delete message seconds must be a number between 0 and 604800.", nameof(deleteMessageSeconds));

var urlParams = new Dictionary<string, string>
var pld = new RestGuildBanPayload()
{
["delete_message_days"] = deleteMessageDays.ToString(CultureInfo.InvariantCulture)
DeleteMessageSeconds = deleteMessageSeconds
};

var headers = Utilities.GetBaseHeaders();
Expand All @@ -1087,8 +1087,43 @@ internal Task CreateGuildBanAsync(ulong guildId, ulong userId, int deleteMessage
user_id = userId
}, out var path);

var url = Utilities.GetApiUriFor(path, BuildQueryString(urlParams), this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, headers);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, headers, DiscordJson.SerializeObject(pld));
}

/// <summary>
/// Creates a guild bulk ban.
/// </summary>
/// <param name="guildId">The guild id to ban from.</param>
/// <param name="userIds">The user ids to ban.</param>
/// <param name="deleteMessageSeconds">The delete message seconds.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordBulkBanResponse> CreateGuildBulkBanAsync(ulong guildId, List<ulong> userIds, int deleteMessageSeconds, string? reason)
{
if (deleteMessageSeconds < 0 || deleteMessageSeconds > 7)
throw new ArgumentException("Delete message seconds must be a number between 0 and 604800.", nameof(deleteMessageSeconds));
if (userIds.Count > 200)
throw new ArgumentException("Can only bulk-ban up to 200 users.", nameof(userIds));

var pld = new RestGuildBulkBanPayload()
{
UserIds = userIds.ToList(),
DeleteMessageSeconds = deleteMessageSeconds,
};

var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);

var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BULK_BAN}";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new
{
guild_id = guildId
}, out var path);

var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var response = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
return DiscordJson.DeserializeObject<DiscordBulkBanResponse>(response.Response, this.Discord);
}

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions DisCatSharp/Net/Rest/Endpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ public static class Endpoints
/// </summary>
public const string BANS = "/bans";

/// <summary>
/// The bulk ban endpoint.
/// </summary>
public const string BULK_BAN = "/bulk-ban";

/// <summary>
/// The webhook endpoint.
/// </summary>
Expand Down

0 comments on commit df7e2ec

Please sign in to comment.