From befd15dca846e79a253d5fcd64bf39d2739f1377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Spie=C3=9F?= Date: Sat, 16 Mar 2024 21:15:00 +0100 Subject: [PATCH 1/4] Add support for bulk banning users --- .../jda/api/entities/BulkBanResponse.java | 62 +++++++++++++++++ .../net/dv8tion/jda/api/entities/Guild.java | 66 ++++++++++++++++++- .../net/dv8tion/jda/api/requests/Route.java | 1 + .../jda/internal/entities/GuildImpl.java | 39 +++++++++++ 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/dv8tion/jda/api/entities/BulkBanResponse.java diff --git a/src/main/java/net/dv8tion/jda/api/entities/BulkBanResponse.java b/src/main/java/net/dv8tion/jda/api/entities/BulkBanResponse.java new file mode 100644 index 0000000000..d165bace9b --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/BulkBanResponse.java @@ -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)} + * + *

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 bannedUsers; + private final List failedUsers; + + public BulkBanResponse(@Nonnull List bannedUsers, @Nonnull List 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 getBannedUsers() + { + return bannedUsers; + } + + /** + * List of users which could not be banned. + * + * @return {@link List} of {@link UserSnowflake} + */ + @Nonnull + public List getFailedUsers() + { + return failedUsers; + } +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/Guild.java b/src/main/java/net/dv8tion/jda/api/entities/Guild.java index c409d0ab6e..b37614b33f 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java @@ -3629,8 +3629,8 @@ default AuditableRestAction kick(@Nonnull UserSnowflake user, @Nullable St *

  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} *
    The target Member cannot be banned due to a permission discrepancy
  • * - *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_MEMBER UNKNOWN_MEMBER} - *
    The specified Member was removed from the Guild before finishing the task
  • + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER UNKNOWN_USER} + *
    One of the users does not exist
  • * * * @param user @@ -3661,6 +3661,68 @@ default AuditableRestAction kick(@Nonnull UserSnowflake user, @Nullable St @CheckReturnValue AuditableRestAction ban(@Nonnull UserSnowflake user, int deletionTimeframe, @Nonnull TimeUnit unit); + /** + * Bans up to 200 of the provided users. + *
    To set a ban reason, use {@link AuditableRestAction#reason(String)}. + * + *

    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. + * + *

    Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by + * the returned {@link RestAction RestAction} include the following: + *

      + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} + *
      The target Member cannot be banned due to a permission discrepancy
    • + * + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER UNKNOWN_USER} + *
      One of the users does not exist
    • + *
    + * + * @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)}) + * + * @return {@link AuditableRestAction} - Type: {@link BulkBanResponse} + */ + @Nonnull + @CheckReturnValue + AuditableRestAction ban(@Nonnull Collection users, @Nullable Duration deletionTime); + + /** + * Bans up to 200 of the provided users. + *
    To set a ban reason, use {@link AuditableRestAction#reason(String)}. + * + *

    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. + * + *

    Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by + * the returned {@link RestAction RestAction} include the following: + *

      + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} + *
      The target Member cannot be banned due to a permission discrepancy
    • + * + *
    • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER UNKNOWN_USER} + *
      One of the users does not exist
    • + *
    + * + * @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)}). + * + * @return {@link AuditableRestAction} - Type: {@link BulkBanResponse} + */ + @Nonnull + @CheckReturnValue + default AuditableRestAction ban(@Nonnull Collection 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. * diff --git a/src/main/java/net/dv8tion/jda/api/requests/Route.java b/src/main/java/net/dv8tion/jda/api/requests/Route.java index f15462924c..f26a303c30 100644 --- a/src/main/java/net/dv8tion/jda/api/requests/Route.java +++ b/src/main/java/net/dv8tion/jda/api/requests/Route.java @@ -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-bans"); 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}"); diff --git a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java index c13416bf14..410697b3c3 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java @@ -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.*; @@ -1545,6 +1547,43 @@ public AuditableRestAction ban(@Nonnull UserSnowflake user, int duration, return new AuditableRestActionImpl<>(getJDA(), route, params); } + @Nonnull + @Override + public AuditableRestAction ban(@Nonnull Collection 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); + + for (UserSnowflake user : users) + { + checkOwner(user.getIdLong(), "ban"); + checkPosition(user); + } + + Set 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 bannedUsers = responseBody.getArray("banned_users") + .stream(DataArray::getLong) + .map(UserSnowflake::fromId) + .collect(Collectors.toList()); + List failedUsers = responseBody.getArray("failed_users") + .stream(DataArray::getLong) + .map(UserSnowflake::fromId) + .collect(Collectors.toList()); + return new BulkBanResponse(bannedUsers, failedUsers); + }); + } + @Nonnull @Override public AuditableRestAction unban(@Nonnull UserSnowflake user) From afb8f46f1c668433af768b1b0f9f85430b9a7ec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Spie=C3=9F?= Date: Sun, 17 Mar 2024 20:11:53 +0100 Subject: [PATCH 2/4] Update src/main/java/net/dv8tion/jda/api/entities/Guild.java --- src/main/java/net/dv8tion/jda/api/entities/Guild.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/dv8tion/jda/api/entities/Guild.java b/src/main/java/net/dv8tion/jda/api/entities/Guild.java index b37614b33f..2b59455611 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java @@ -3630,7 +3630,7 @@ default AuditableRestAction kick(@Nonnull UserSnowflake user, @Nullable St *
    The target Member cannot be banned due to a permission discrepancy * *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER UNKNOWN_USER} - *
    One of the users does not exist
  • + *
    The user does not exist * * * @param user From c52ea0f70ce621cedb6961b122ecef9e2a98198c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Spie=C3=9F?= Date: Sun, 17 Mar 2024 20:21:30 +0100 Subject: [PATCH 3/4] Fix typo and add missing error response code --- src/main/java/net/dv8tion/jda/api/entities/Guild.java | 10 ++++++---- .../net/dv8tion/jda/api/requests/ErrorResponse.java | 1 + src/main/java/net/dv8tion/jda/api/requests/Route.java | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/dv8tion/jda/api/entities/Guild.java b/src/main/java/net/dv8tion/jda/api/entities/Guild.java index 2b59455611..9a1bfa8773 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java @@ -3667,6 +3667,7 @@ default AuditableRestAction kick(@Nonnull UserSnowflake user, @Nullable St * *

    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. * *

    Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by * the returned {@link RestAction RestAction} include the following: @@ -3674,8 +3675,8 @@ default AuditableRestAction kick(@Nonnull UserSnowflake user, @Nullable St *

  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} *
    The target Member cannot be banned due to a permission discrepancy
  • * - *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER UNKNOWN_USER} - *
    One of the users does not exist
  • + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#FAILED_TO_BAN_USERS FAILED_TO_BAN_USERS} + *
    None of the users could be banned
  • * * * @param users @@ -3695,6 +3696,7 @@ default AuditableRestAction kick(@Nonnull UserSnowflake user, @Nullable St * *

    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. * *

    Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by * the returned {@link RestAction RestAction} include the following: @@ -3702,8 +3704,8 @@ default AuditableRestAction kick(@Nonnull UserSnowflake user, @Nullable St *

  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} *
    The target Member cannot be banned due to a permission discrepancy
  • * - *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER UNKNOWN_USER} - *
    One of the users does not exist
  • + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#FAILED_TO_BAN_USERS FAILED_TO_BAN_USERS} + *
    None of the users could be banned
  • * * * @param users diff --git a/src/main/java/net/dv8tion/jda/api/requests/ErrorResponse.java b/src/main/java/net/dv8tion/jda/api/requests/ErrorResponse.java index f59c3a5c65..450f4f9844 100644 --- a/src/main/java/net/dv8tion/jda/api/requests/ErrorResponse.java +++ b/src/main/java/net/dv8tion/jda/api/requests/ErrorResponse.java @@ -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!"); diff --git a/src/main/java/net/dv8tion/jda/api/requests/Route.java b/src/main/java/net/dv8tion/jda/api/requests/Route.java index f26a303c30..d454255048 100644 --- a/src/main/java/net/dv8tion/jda/api/requests/Route.java +++ b/src/main/java/net/dv8tion/jda/api/requests/Route.java @@ -103,7 +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-bans"); + 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}"); From 9e01febb1c6c5a23d9131421fcd3bb690936efb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Spie=C3=9F?= Date: Sun, 17 Mar 2024 21:33:29 +0100 Subject: [PATCH 4/4] Add missing permission check and update docs --- .../net/dv8tion/jda/api/entities/Guild.java | 20 +++++++++++++++++++ .../jda/internal/entities/GuildImpl.java | 1 + 2 files changed, 21 insertions(+) diff --git a/src/main/java/net/dv8tion/jda/api/entities/Guild.java b/src/main/java/net/dv8tion/jda/api/entities/Guild.java index 9a1bfa8773..2881b50295 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java @@ -3684,6 +3684,16 @@ default AuditableRestAction kick(@Nonnull UserSnowflake user, @Nullable St * @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 + *
      + *
    • If the users collection is null or contains null
    • + *
    • If the deletionTime is negative
    • + *
    + * * @return {@link AuditableRestAction} - Type: {@link BulkBanResponse} */ @Nonnull @@ -3715,6 +3725,16 @@ default AuditableRestAction kick(@Nonnull UserSnowflake user, @Nullable St * @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 + *
      + *
    • If null is provided
    • + *
    • If the deletionTimeframe is negative
    • + *
    + * * @return {@link AuditableRestAction} - Type: {@link BulkBanResponse} */ @Nonnull diff --git a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java index 410697b3c3..e0dedbf78b 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java @@ -1557,6 +1557,7 @@ public AuditableRestAction ban(@Nonnull Collection