Skip to content

Commit

Permalink
Add support for bulk banning users (#2630)
Browse files Browse the repository at this point in the history
  • Loading branch information
MinnDevelopment authored Mar 30, 2024
1 parent 711e4e7 commit 33ecfe9
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 2 deletions.
62 changes: 62 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/BulkBanResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.dv8tion.jda.api.entities;

import javax.annotation.Nonnull;
import java.time.Duration;
import java.util.Collections;
import java.util.List;

/**
* Response to {@link Guild#ban(java.util.Collection, Duration)}
*
* <p>This response includes a list of successfully banned users and users which could not be banned.
* Discord might fail to ban a user due to permission issues or an internal server error.
*/
public class BulkBanResponse
{
private final List<UserSnowflake> bannedUsers;
private final List<UserSnowflake> failedUsers;

public BulkBanResponse(@Nonnull List<UserSnowflake> bannedUsers, @Nonnull List<UserSnowflake> failedUsers)
{
this.bannedUsers = Collections.unmodifiableList(bannedUsers);
this.failedUsers = Collections.unmodifiableList(failedUsers);
}

/**
* List of successfully banned users.
*
* @return {@link List} of {@link UserSnowflake}
*/
@Nonnull
public List<UserSnowflake> getBannedUsers()
{
return bannedUsers;
}

/**
* List of users which could not be banned.
*
* @return {@link List} of {@link UserSnowflake}
*/
@Nonnull
public List<UserSnowflake> getFailedUsers()
{
return failedUsers;
}
}
88 changes: 86 additions & 2 deletions src/main/java/net/dv8tion/jda/api/entities/Guild.java
Original file line number Diff line number Diff line change
Expand Up @@ -3629,8 +3629,8 @@ default AuditableRestAction<Void> kick(@Nonnull UserSnowflake user, @Nullable St
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
* <br>The target Member cannot be banned due to a permission discrepancy</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_MEMBER UNKNOWN_MEMBER}
* <br>The specified Member was removed from the Guild before finishing the task</li>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER UNKNOWN_USER}
* <br>The user does not exist</li>
* </ul>
*
* @param user
Expand Down Expand Up @@ -3661,6 +3661,90 @@ default AuditableRestAction<Void> kick(@Nonnull UserSnowflake user, @Nullable St
@CheckReturnValue
AuditableRestAction<Void> ban(@Nonnull UserSnowflake user, int deletionTimeframe, @Nonnull TimeUnit unit);

/**
* Bans up to 200 of the provided users.
* <br>To set a ban reason, use {@link AuditableRestAction#reason(String)}.
*
* <p>The {@link BulkBanResponse} includes a list of {@link BulkBanResponse#getFailedUsers() failed users},
* which is populated with users that could not be banned, for instance due to some internal server error or permission issues.
* This list of failed users also includes all users that were already banned.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by
* the returned {@link RestAction RestAction} include the following:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
* <br>The target Member cannot be banned due to a permission discrepancy</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#FAILED_TO_BAN_USERS FAILED_TO_BAN_USERS}
* <br>None of the users could be banned</li>
* </ul>
*
* @param users
* The users to ban
* @param deletionTime
* Delete recent messages of the given timeframe (for instance the last hour with {@code Duration.ofHours(1)})
*
* @throws net.dv8tion.jda.api.exceptions.HierarchyException
* If any of the provided users is the guild owner or has a higher or equal role position
* @throws InsufficientPermissionException
* If the bot does not have {@link Permission#BAN_MEMBERS} or {@link Permission#MANAGE_SERVER}
* @throws IllegalArgumentException
* <ul>
* <li>If the users collection is null or contains null</li>
* <li>If the deletionTime is negative</li>
* </ul>
*
* @return {@link AuditableRestAction} - Type: {@link BulkBanResponse}
*/
@Nonnull
@CheckReturnValue
AuditableRestAction<BulkBanResponse> ban(@Nonnull Collection<UserSnowflake> users, @Nullable Duration deletionTime);

/**
* Bans up to 200 of the provided users.
* <br>To set a ban reason, use {@link AuditableRestAction#reason(String)}.
*
* <p>The {@link BulkBanResponse} includes a list of {@link BulkBanResponse#getFailedUsers() failed users},
* which is populated with users that could not be banned, for instance due to some internal server error or permission issues.
* This list of failed users also includes all users that were already banned.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by
* the returned {@link RestAction RestAction} include the following:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
* <br>The target Member cannot be banned due to a permission discrepancy</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#FAILED_TO_BAN_USERS FAILED_TO_BAN_USERS}
* <br>None of the users could be banned</li>
* </ul>
*
* @param users
* The users to ban
* @param deletionTimeframe
* The timeframe for the history of messages that will be deleted. (seconds precision)
* @param unit
* Timeframe unit as a {@link TimeUnit} (for example {@code ban(user, 7, TimeUnit.DAYS)}).
*
* @throws net.dv8tion.jda.api.exceptions.HierarchyException
* If any of the provided users is the guild owner or has a higher or equal role position
* @throws InsufficientPermissionException
* If the bot does not have {@link Permission#BAN_MEMBERS} or {@link Permission#MANAGE_SERVER}
* @throws IllegalArgumentException
* <ul>
* <li>If null is provided</li>
* <li>If the deletionTimeframe is negative</li>
* </ul>
*
* @return {@link AuditableRestAction} - Type: {@link BulkBanResponse}
*/
@Nonnull
@CheckReturnValue
default AuditableRestAction<BulkBanResponse> ban(@Nonnull Collection<UserSnowflake> users, int deletionTimeframe, @Nonnull TimeUnit unit)
{
Checks.notNull(unit, "TimeUnit");
return ban(users, Duration.ofSeconds(unit.toSeconds(deletionTimeframe)));
}

/**
* Unbans the specified {@link UserSnowflake} from this Guild.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ public enum ErrorResponse
MESSAGE_BLOCKED_BY_AUTOMOD( 200000, "Message was blocked by automatic moderation"),
TITLE_BLOCKED_BY_AUTOMOD( 200001, "Title was blocked by automatic moderation"),
MESSAGE_BLOCKED_BY_HARMFUL_LINK_FILTER( 240000, "Message blocked by harmful links filter"),
FAILED_TO_BAN_USERS( 500000, "Failed to ban users"),

SERVER_ERROR( 0, "Discord encountered an internal server error! Not good!");

Expand Down
1 change: 1 addition & 0 deletions src/main/java/net/dv8tion/jda/api/requests/Route.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public static class Guilds
public static final Route GET_BAN = new Route(GET, "guilds/{guild_id}/bans/{user_id}");
public static final Route UNBAN = new Route(DELETE, "guilds/{guild_id}/bans/{user_id}");
public static final Route BAN = new Route(PUT, "guilds/{guild_id}/bans/{user_id}");
public static final Route BULK_BAN = new Route(POST, "guilds/{guild_id}/bulk-ban");
public static final Route KICK_MEMBER = new Route(DELETE, "guilds/{guild_id}/members/{user_id}");
public static final Route MODIFY_MEMBER = new Route(PATCH, "guilds/{guild_id}/members/{user_id}");
public static final Route ADD_MEMBER = new Route(PUT, "guilds/{guild_id}/members/{user_id}");
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,12 @@
import net.dv8tion.jda.internal.utils.concurrent.task.GatewayTask;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import org.jetbrains.annotations.NotNull;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.*;
Expand Down Expand Up @@ -1545,6 +1547,44 @@ public AuditableRestAction<Void> ban(@Nonnull UserSnowflake user, int duration,
return new AuditableRestActionImpl<>(getJDA(), route, params);
}

@Nonnull
@Override
public AuditableRestAction<BulkBanResponse> ban(@Nonnull Collection<UserSnowflake> users, @Nullable Duration deletionTime)
{
deletionTime = deletionTime == null ? Duration.ZERO : deletionTime;
Checks.noneNull(users, "Users");
Checks.check(!deletionTime.isNegative(), "Deletion time cannot be negative");
Checks.check(deletionTime.getSeconds() <= TimeUnit.DAYS.toSeconds(7), "Deletion timeframe must not be larger than 7 days. Provided: %d seconds", deletionTime.getSeconds());
Checks.check(users.size() <= 200, "Cannot ban more than 200 users at once");
checkPermission(Permission.BAN_MEMBERS);
checkPermission(Permission.MANAGE_SERVER);

for (UserSnowflake user : users)
{
checkOwner(user.getIdLong(), "ban");
checkPosition(user);
}

Set<Long> userIds = users.stream().map(UserSnowflake::getIdLong).collect(Collectors.toSet());
DataObject body = DataObject.empty()
.put("user_ids", userIds)
.put("delete_message_seconds", deletionTime.getSeconds());
Route.CompiledRoute route = Route.Guilds.BULK_BAN.compile(getId());

return new AuditableRestActionImpl<>(getJDA(), route, body, (res, req) -> {
DataObject responseBody = res.getObject();
List<UserSnowflake> bannedUsers = responseBody.getArray("banned_users")
.stream(DataArray::getLong)
.map(UserSnowflake::fromId)
.collect(Collectors.toList());
List<UserSnowflake> failedUsers = responseBody.getArray("failed_users")
.stream(DataArray::getLong)
.map(UserSnowflake::fromId)
.collect(Collectors.toList());
return new BulkBanResponse(bannedUsers, failedUsers);
});
}

@Nonnull
@Override
public AuditableRestAction<Void> unban(@Nonnull UserSnowflake user)
Expand Down

0 comments on commit 33ecfe9

Please sign in to comment.