Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for bulk banning users #2630

Merged
merged 4 commits into from
Mar 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading