removeReactionById(@Nonnull String messageId, @Nonnull
Checks.isSnowflake(messageId, "Message ID");
Checks.notNull(emoji, "Emoji");
- String encoded = EncodingUtil.encodeReaction(emoji.getAsReactionCode());
- Route.CompiledRoute route = Route.Messages.REMOVE_REACTION.compile(getId(), messageId, encoded, "@me");
+ Route.CompiledRoute route = Route.Messages.REMOVE_REACTION.compile(getId(), messageId, emoji.getAsReactionCode(), "@me");
return new RestActionImpl<>(getJDA(), route);
}
@@ -1882,7 +1879,7 @@ default ReactionPaginationAction retrieveReactionUsersById(@Nonnull String messa
Checks.isSnowflake(messageId, "Message ID");
Checks.notNull(emoji, "Emoji");
- return new ReactionPaginationActionImpl(this, messageId, EncodingUtil.encodeReaction(emoji.getAsReactionCode()));
+ return new ReactionPaginationActionImpl(this, messageId, emoji.getAsReactionCode());
}
/**
diff --git a/src/main/java/net/dv8tion/jda/api/entities/templates/Template.java b/src/main/java/net/dv8tion/jda/api/entities/templates/Template.java
index 1781432941..03629326ba 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/templates/Template.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/templates/Template.java
@@ -22,10 +22,10 @@
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.TemplateManager;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.managers.TemplateManagerImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.EntityString;
diff --git a/src/main/java/net/dv8tion/jda/api/events/http/HttpRequestEvent.java b/src/main/java/net/dv8tion/jda/api/events/http/HttpRequestEvent.java
index 15a03449eb..8714aa38f1 100644
--- a/src/main/java/net/dv8tion/jda/api/events/http/HttpRequestEvent.java
+++ b/src/main/java/net/dv8tion/jda/api/events/http/HttpRequestEvent.java
@@ -20,9 +20,9 @@
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route.CompiledRoute;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route.CompiledRoute;
import okhttp3.Headers;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/ratelimit/package-info.java b/src/main/java/net/dv8tion/jda/api/exceptions/InteractionExpiredException.java
similarity index 57%
rename from src/main/java/net/dv8tion/jda/internal/requests/ratelimit/package-info.java
rename to src/main/java/net/dv8tion/jda/api/exceptions/InteractionExpiredException.java
index 032e8ceb89..8da0f47311 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/ratelimit/package-info.java
+++ b/src/main/java/net/dv8tion/jda/api/exceptions/InteractionExpiredException.java
@@ -14,8 +14,18 @@
* limitations under the License.
*/
+package net.dv8tion.jda.api.exceptions;
+
+import net.dv8tion.jda.api.interactions.InteractionHook;
+
/**
- * Implementations of {@link net.dv8tion.jda.internal.requests.RateLimiter RateLimiter}
- * that handle the rate limit responses for the {@link net.dv8tion.jda.internal.requests.Requester Requester}!
+ * Indicates that an interaction has expired and can no longer be responded to.
+ *
This is used for follow-up requests sent via {@link InteractionHook}, which expire after 15 minutes.
*/
-package net.dv8tion.jda.internal.requests.ratelimit;
+public class InteractionExpiredException extends RuntimeException
+{
+ public InteractionExpiredException()
+ {
+ super("The interaction has expired and can no longer be responded to!");
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/exceptions/RateLimitedException.java b/src/main/java/net/dv8tion/jda/api/exceptions/RateLimitedException.java
index 589316df72..a70a5c76ba 100644
--- a/src/main/java/net/dv8tion/jda/api/exceptions/RateLimitedException.java
+++ b/src/main/java/net/dv8tion/jda/api/exceptions/RateLimitedException.java
@@ -16,7 +16,7 @@
package net.dv8tion.jda.api.exceptions;
-import net.dv8tion.jda.internal.requests.Route;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.internal.utils.Helpers;
/**
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/Method.java b/src/main/java/net/dv8tion/jda/api/requests/Method.java
similarity index 87%
rename from src/main/java/net/dv8tion/jda/internal/requests/Method.java
rename to src/main/java/net/dv8tion/jda/api/requests/Method.java
index d1f9326eb0..e091ae4d09 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/Method.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/Method.java
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package net.dv8tion.jda.internal.requests;
+package net.dv8tion.jda.api.requests;
+/**
+ * Enum used to specify the HTTP method to use for a request.
+ */
public enum Method
{
DELETE,
diff --git a/src/main/java/net/dv8tion/jda/api/requests/Request.java b/src/main/java/net/dv8tion/jda/api/requests/Request.java
index 5e3b5a4239..967aa87d21 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/Request.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/Request.java
@@ -25,7 +25,6 @@
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.CallbackContext;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.IOUtil;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
@@ -269,6 +268,8 @@ public boolean shouldQueue()
public void cancel()
{
+ if (!this.isCancelled)
+ onCancelled();
this.isCancelled = true;
}
diff --git a/src/main/java/net/dv8tion/jda/api/requests/RestConfig.java b/src/main/java/net/dv8tion/jda/api/requests/RestConfig.java
new file mode 100644
index 0000000000..0545798a1e
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/requests/RestConfig.java
@@ -0,0 +1,217 @@
+/*
+ * 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.requests;
+
+import net.dv8tion.jda.api.JDAInfo;
+import net.dv8tion.jda.internal.utils.Checks;
+import net.dv8tion.jda.internal.utils.Helpers;
+import okhttp3.Request;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Configuration for REST-request handling.
+ *
+ * This can be used to replace the {@link #setRateLimiterFactory(Function) rate-limit handling}
+ * or to use a different {@link #setBaseUrl(String) base url} for requests, e.g. for mocked HTTP responses or proxies.
+ */
+public class RestConfig
+{
+ /**
+ * The User-Agent used by JDA for all REST-api requests.
+ */
+ public static final String USER_AGENT = "DiscordBot (" + JDAInfo.GITHUB + ", " + JDAInfo.VERSION + ")";
+ /**
+ * The default base url used by JDA for all REST-api requests.
+ * This URL uses the API version defined by {@link JDAInfo#DISCORD_REST_VERSION} (v{@value JDAInfo#DISCORD_REST_VERSION}).
+ */
+ public static final String DEFAULT_BASE_URL = "https://discord.com/api/v" + JDAInfo.DISCORD_REST_VERSION + "/";
+
+ private String userAgent = USER_AGENT;
+ private String baseUrl = DEFAULT_BASE_URL;
+ private boolean relativeRateLimit = true;
+ private Consumer super Request.Builder> customBuilder;
+ private Function super RestRateLimiter.RateLimitConfig, ? extends RestRateLimiter> rateLimiter = SequentialRestRateLimiter::new;
+
+ /**
+ * Whether to use {@code X-RateLimit-Reset-After} to determine the rate-limit backoff.
+ *
If this is disabled, the default {@link RestRateLimiter} will use the {@code X-RateLimit-Reset} header timestamp to compute the relative backoff.
+ *
+ * @param relativeRateLimit
+ * True, to use relative reset after
+ *
+ * @return The current RestConfig for chaining convenience
+ */
+ @Nonnull
+ public RestConfig setRelativeRateLimit(boolean relativeRateLimit)
+ {
+ this.relativeRateLimit = relativeRateLimit;
+ return this;
+ }
+
+ /**
+ * Provide a custom implementation of {@link RestRateLimiter}.
+ *
By default, this will use the {@link SequentialRestRateLimiter}.
+ *
+ * @param rateLimiter
+ * The new implementation
+ *
+ * @throws IllegalArgumentException
+ * If the provided rate-limiter is null
+ *
+ * @return The current RestConfig for chaining convenience
+ */
+ @Nonnull
+ public RestConfig setRateLimiterFactory(@Nonnull Function super RestRateLimiter.RateLimitConfig, ? extends RestRateLimiter> rateLimiter)
+ {
+ Checks.notNull(rateLimiter, "RateLimiter");
+ this.rateLimiter = rateLimiter;
+ return this;
+ }
+
+ /**
+ * Provide a custom base URL for REST-api requests.
+ *
This uses {@link #DEFAULT_BASE_URL} by default.
+ *
+ *
It is important that the new URL uses the correct API version for JDA.
+ * The correct version is currently {@value JDAInfo#DISCORD_REST_VERSION}.
+ *
+ *
It is not required for this URL to be HTTPS, because local proxies do not require signed connections.
+ * However, if the URL points to an external server, it is highly recommended to use HTTPS for security.
+ *
+ * @param baseUrl
+ * The new base url
+ *
+ * @throws IllegalArgumentException
+ * If the provided base url is null, empty, or not an HTTP(s) url
+ *
+ * @return The current RestConfig for chaining convenience
+ */
+ @Nonnull
+ public RestConfig setBaseUrl(@Nonnull String baseUrl)
+ {
+ Checks.notEmpty(baseUrl, "URL");
+ Checks.check(baseUrl.length() > 4 && baseUrl.substring(0, 4).equalsIgnoreCase("http"), "URL must be HTTP");
+ if (baseUrl.endsWith("/"))
+ this.baseUrl = baseUrl;
+ else
+ this.baseUrl = baseUrl + "/";
+ return this;
+ }
+
+ /**
+ * Provide a custom User-Agent suffix which is appended to {@link #USER_AGENT}.
+ *
You can theoretically replace the User-Agent entirely with {@link #setCustomBuilder(Consumer)},
+ * however this is not recommended as Discord blocks requests with invalid or misbehaving User-Agents.
+ *
+ * @param suffix
+ * The suffix to append to the User-Agent, null to unset
+ *
+ * @return The current RestConfig for chaining convenience
+ */
+ @Nonnull
+ public RestConfig setUserAgentSuffix(@Nullable String suffix)
+ {
+ if (suffix == null || Helpers.isBlank(suffix))
+ this.userAgent = USER_AGENT;
+ else
+ this.userAgent = USER_AGENT + " " + suffix;
+ return this;
+ }
+
+ /**
+ * Provide an interceptor to update outgoing requests with custom headers or other modifications.
+ *
Be careful not to replace any important headers, like authorization or content-type.
+ * This is allowed by JDA, to allow proper use of {@link #setBaseUrl(String)} with any exotic proxy.
+ *
+ *
Example
+ *
{@code
+ * setCustomBuilder((request) -> {
+ * request.header("X-My-Header", "MyValue");
+ * })
+ * }
+ *
+ * @param customBuilder
+ * The request interceptor, or null to disable
+ *
+ * @return The current RestConfig for chaining convenience
+ */
+ @Nonnull
+ public RestConfig setCustomBuilder(@Nullable Consumer super Request.Builder> customBuilder)
+ {
+ this.customBuilder = customBuilder;
+ return this;
+ }
+
+ /**
+ * The adapted user-agent with the custom {@link #setUserAgentSuffix(String) suffix}.
+ *
+ * @return The user-agent
+ */
+ @Nonnull
+ public String getUserAgent()
+ {
+ return userAgent;
+ }
+
+ /**
+ * The configured base-url for REST-api requests.
+ *
+ * @return The base-url
+ */
+ @Nonnull
+ public String getBaseUrl()
+ {
+ return baseUrl;
+ }
+
+ /**
+ * The configured rate-limiter implementation.
+ *
+ * @return The rate-limiter
+ */
+ @Nonnull
+ public Function super RestRateLimiter.RateLimitConfig, ? extends RestRateLimiter> getRateLimiterFactory()
+ {
+ return rateLimiter;
+ }
+
+ /**
+ * The custom request interceptor.
+ *
+ * @return The custom interceptor, or null if none is configured
+ */
+ @Nullable
+ public Consumer super Request.Builder> getCustomBuilder()
+ {
+ return customBuilder;
+ }
+
+ /**
+ * Whether to use {@code X-RateLimit-Reset-After} to determine the rate-limit backoff.
+ *
If this is disabled, the default {@link RestRateLimiter} will use the {@code X-RateLimit-Reset} header timestamp to compute the relative backoff.
+ *
+ * @return True, if relative reset after is enabled
+ */
+ public boolean isRelativeRateLimit()
+ {
+ return relativeRateLimit;
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/requests/RestFuture.java b/src/main/java/net/dv8tion/jda/api/requests/RestFuture.java
index 465781e4ae..3fc9798d5b 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/RestFuture.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/RestFuture.java
@@ -18,7 +18,6 @@
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import okhttp3.RequestBody;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
diff --git a/src/main/java/net/dv8tion/jda/api/requests/RestRateLimiter.java b/src/main/java/net/dv8tion/jda/api/requests/RestRateLimiter.java
new file mode 100644
index 0000000000..0673dde00a
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/requests/RestRateLimiter.java
@@ -0,0 +1,295 @@
+/*
+ * 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.requests;
+
+import net.dv8tion.jda.api.JDA;
+import okhttp3.Response;
+import org.jetbrains.annotations.Blocking;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Interface used to handle requests to the Discord API.
+ * Requests are handed to the rate-limiter via {@link #enqueue(Work)} and executed using {@link Work#execute()}.
+ * The rate-limiter is responsible to ensure that requests do not exceed the rate-limit set by Discord.
+ */
+public interface RestRateLimiter
+{
+ /** Total time (in seconds) of when the current rate limit bucket will reset. Can have decimals to match previous millisecond ratelimit precision */
+ String RESET_AFTER_HEADER = "X-RateLimit-Reset-After";
+ /** Epoch time (seconds since 00:00:00 UTC on January 1, 1970) at which the rate limit resets */
+ String RESET_HEADER = "X-RateLimit-Reset";
+ /** The number of requests that can be made */
+ String LIMIT_HEADER = "X-RateLimit-Limit";
+ /** The number of remaining requests that can be made */
+ String REMAINING_HEADER = "X-RateLimit-Remaining";
+ /** Returned only on HTTP 429 responses if the rate limit encountered is the global rate limit (not per-route) */
+ String GLOBAL_HEADER = "X-RateLimit-Global";
+ /** A unique string denoting the rate limit being encountered (non-inclusive of top-level resources in the path) */
+ String HASH_HEADER = "X-RateLimit-Bucket";
+ /** The number of seconds to wait before submitting another request */
+ String RETRY_AFTER_HEADER = "Retry-After";
+ /** Returned only on HTTP 429 responses. Value can be user (per bot or user limit), global (per bot or user global limit), or shared (per resource limit) */
+ String SCOPE_HEADER = "X-RateLimit-Scope";
+
+ /**
+ * Enqueue a new request.
+ *
+ *
Use {@link Work#getRoute()} to determine the correct bucket.
+ *
+ * @param task
+ * The {@link Work} to enqueue
+ */
+ void enqueue(@Nonnull Work task);
+
+ /**
+ * Indication to stop accepting new requests.
+ *
+ * @param shutdown
+ * Whether to also cancel previously queued request
+ * @param callback
+ * Function to call once all requests are completed, used for final cleanup
+ */
+ void stop(boolean shutdown, @Nonnull Runnable callback);
+
+ /**
+ * Whether the queue has stopped accepting new requests.
+ *
+ * @return True, if the queue is stopped
+ */
+ boolean isStopped();
+
+ /**
+ * Cancel all currently queued requests, which are not marked as {@link Work#isPriority() priority}.
+ *
+ * @return The number of cancelled requests
+ */
+ int cancelRequests();
+
+ /**
+ * Type representing a pending request.
+ *
+ *
Use {@link #execute()} to run the request (on the calling thread) and {@link #isDone()} to discard it once completed.
+ */
+ interface Work
+ {
+ /**
+ * The {@link Route.CompiledRoute compiled route} of the request.
+ *
This is primarily used to handle rate-limit buckets.
+ *
+ *
To correctly handle rate-limits, it is recommended to use the {@link #HASH_HEADER bucket hash} header from the response.
+ *
+ * @return The {@link Route.CompiledRoute compiled route}
+ */
+ @Nonnull
+ Route.CompiledRoute getRoute();
+
+ /**
+ * The JDA instance which started the request.
+ *
+ * @return The JDA instance
+ */
+ @Nonnull
+ JDA getJDA();
+
+ /**
+ * Executes the request on the calling thread (blocking).
+ *
This might return null when the request has been skipped while executing.
+ * Retries for certain response codes are already handled by this method.
+ *
+ *
After completion, it is advised to use {@link #isDone()} to check whether the request should be retried.
+ *
+ * @return {@link Response} instance, used to update the rate-limit data
+ */
+ @Nullable
+ @Blocking
+ Response execute();
+
+ /**
+ * Whether the request should be skipped.
+ *
This can be caused by user cancellation.
+ *
+ *
The rate-limiter should handle by simply discarding the task without further action.
+ *
+ * @return True, if this request is skipped
+ */
+ boolean isSkipped();
+
+ /**
+ * Whether the request is completed.
+ *
This means you should not try using {@link #execute()} again.
+ *
+ * @return True, if the request has completed.
+ */
+ boolean isDone();
+
+ /**
+ * Requests marked as priority should not be cancelled.
+ *
+ * @return True, if this request is marked as priority
+ */
+ boolean isPriority();
+
+ /**
+ * Whether this request was cancelled.
+ *
Similar to {@link #isSkipped()}, but only checks cancellation.
+ *
+ * @return True, if this request was cancelled
+ */
+ boolean isCancelled();
+
+ /**
+ * Cancel the request.
+ *
Primarily used for {@link JDA#cancelRequests()}.
+ */
+ void cancel();
+ }
+
+ /**
+ * Global rate-limit store.
+ *
This can be used to share the global rate-limit information between multiple instances.
+ */
+ interface GlobalRateLimit
+ {
+ /**
+ * The current global rate-limit reset time.
+ *
This is the rate-limit applied on the bot token.
+ *
+ * @return The timestamp when the global rate-limit expires (unix timestamp in milliseconds)
+ */
+ long getClassic();
+
+ /**
+ * Set the current global rate-limit reset time.
+ *
This is the rate-limit applied on the bot token.
+ *
+ * @param timestamp
+ * The timestamp when the global rate-limit expires (unix timestamp in milliseconds)
+ */
+ void setClassic(long timestamp);
+
+ /**
+ * The current cloudflare rate-limit reset time.
+ *
This is the rate-limit applied on the current IP.
+ *
+ * @return The timestamp when the cloudflare rate-limit expires (unix timestamp in milliseconds)
+ */
+ long getCloudflare();
+
+ /**
+ * Set the current cloudflare rate-limit reset time.
+ *
This is the rate-limit applied on the current IP.
+ *
+ * @param timestamp
+ * The timestamp when the cloudflare rate-limit expires (unix timestamp in milliseconds)
+ */
+ void setCloudflare(long timestamp);
+
+ /**
+ * Creates a default instance of this interface.
+ *
This uses {@link AtomicLong} to keep track of rate-limits.
+ *
+ * @return The default implementation
+ */
+ @Nonnull
+ static GlobalRateLimit create()
+ {
+ return new GlobalRateLimit()
+ {
+ private final AtomicLong classic = new AtomicLong(-1);
+ private final AtomicLong cloudflare = new AtomicLong(-1);
+
+ @Override
+ public long getClassic()
+ {
+ return classic.get();
+ }
+
+ @Override
+ public void setClassic(long timestamp)
+ {
+ classic.set(timestamp);
+ }
+
+ @Override
+ public long getCloudflare()
+ {
+ return cloudflare.get();
+ }
+
+ @Override
+ public void setCloudflare(long timestamp)
+ {
+ cloudflare.set(timestamp);
+ }
+ };
+ }
+ }
+
+ /**
+ * Configuration for the rate-limiter.
+ */
+ class RateLimitConfig
+ {
+ private final ScheduledExecutorService pool;
+ private final GlobalRateLimit globalRateLimit;
+ private final boolean isRelative;
+
+ public RateLimitConfig(@Nonnull ScheduledExecutorService pool, @Nonnull GlobalRateLimit globalRateLimit, boolean isRelative)
+ {
+ this.pool = pool;
+ this.globalRateLimit = globalRateLimit;
+ this.isRelative = isRelative;
+ }
+
+ /**
+ * The {@link ScheduledExecutorService} used to schedule rate-limit tasks.
+ *
+ * @return The {@link ScheduledExecutorService}
+ */
+ @Nonnull
+ public ScheduledExecutorService getPool()
+ {
+ return pool;
+ }
+
+ /**
+ * The global rate-limit store.
+ *
+ * @return The global rate-limit store
+ */
+ @Nonnull
+ public GlobalRateLimit getGlobalRateLimit()
+ {
+ return globalRateLimit;
+ }
+
+ /**
+ * Whether to use {@link #RESET_AFTER_HEADER}.
+ *
This is primarily to avoid NTP sync issues.
+ *
+ * @return True, if {@link #RESET_AFTER_HEADER} should be used instead of {@link #RESET_HEADER}
+ */
+ public boolean isRelative()
+ {
+ return isRelative;
+ }
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/Route.java b/src/main/java/net/dv8tion/jda/api/requests/Route.java
similarity index 58%
rename from src/main/java/net/dv8tion/jda/internal/requests/Route.java
rename to src/main/java/net/dv8tion/jda/api/requests/Route.java
index f070d434cc..3c64d22762 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/Route.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/Route.java
@@ -14,25 +14,27 @@
* limitations under the License.
*/
-package net.dv8tion.jda.internal.requests;
+package net.dv8tion.jda.api.requests;
import net.dv8tion.jda.internal.utils.Checks;
+import net.dv8tion.jda.internal.utils.EncodingUtil;
import net.dv8tion.jda.internal.utils.EntityString;
import net.dv8tion.jda.internal.utils.Helpers;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
-import static net.dv8tion.jda.internal.requests.Method.*;
+import static net.dv8tion.jda.api.requests.Method.*;
+/**
+ * Routes for API endpoints.
+ */
@SuppressWarnings("unused")
public class Route
{
public static class Misc
{
- public static final Route TRACK = new Route(POST, "track");
public static final Route GET_VOICE_REGIONS = new Route(GET, "voice/regions");
public static final Route GATEWAY = new Route(GET, "gateway");
public static final Route GATEWAY_BOT = new Route(GET, "gateway/bot");
@@ -40,26 +42,9 @@ public static class Misc
public static class Applications
{
- // Bot only
public static final Route GET_BOT_APPLICATION = new Route(GET, "oauth2/applications/@me");
public static final Route GET_ROLE_CONNECTION_METADATA = new Route(GET, "applications/{application_id}/role-connections/metadata");
public static final Route UPDATE_ROLE_CONNECTION_METADATA = new Route(PUT, "applications/{application_id}/role-connections/metadata");
-
- // Client only
- public static final Route GET_APPLICATIONS = new Route(GET, "oauth2/applications");
- public static final Route CREATE_APPLICATION = new Route(POST, "oauth2/applications");
- public static final Route GET_APPLICATION = new Route(GET, "oauth2/applications/{application_id}");
- public static final Route MODIFY_APPLICATION = new Route(PUT, "oauth2/applications/{application_id}");
- public static final Route DELETE_APPLICATION = new Route(DELETE, "oauth2/applications/{application_id}");
-
- public static final Route CREATE_BOT = new Route(POST, "oauth2/applications/{application_id}/bot");
-
- public static final Route RESET_APPLICATION_SECRET = new Route(POST, "oauth2/applications/{application_id}/reset");
- public static final Route RESET_BOT_TOKEN = new Route(POST, "oauth2/applications/{application_id}/bot/reset");
-
- public static final Route GET_AUTHORIZED_APPLICATIONS = new Route(GET, "oauth2/tokens");
- public static final Route GET_AUTHORIZED_APPLICATION = new Route(GET, "oauth2/tokens/{auth_id}");
- public static final Route DELETE_AUTHORIZED_APPLICATION = new Route(DELETE, "oauth2/tokens/{auth_id}");
}
public static class Interactions
@@ -83,11 +68,11 @@ public static class Interactions
public static final Route GET_COMMAND_PERMISSIONS = new Route(GET, "applications/{application_id}/guilds/{guild_id}/commands/{command_id}/permissions");
public static final Route EDIT_COMMAND_PERMISSIONS = new Route(PUT, "applications/{application_id}/guilds/{guild_id}/commands/{command_id}/permissions");
- public static final Route CALLBACK = new Route(POST, "interactions/{interaction_id}/{interaction_token}/callback");
- public static final Route CREATE_FOLLOWUP = new Route(POST, "webhooks/{application_id}/{interaction_token}");
- public static final Route EDIT_FOLLOWUP = new Route(PATCH, "webhooks/{application_id}/{interaction_token}/messages/{message_id}");
- public static final Route DELETE_FOLLOWUP = new Route(DELETE, "webhooks/{application_id}/{interaction_token}/messages/{message_id}");
- public static final Route GET_ORIGINAL = new Route(GET, "webhooks/{application_id}/{interaction_token}/messages/@original");
+ public static final Route CALLBACK = new Route(POST, "interactions/{interaction_id}/{interaction_token}/callback", true);
+ public static final Route CREATE_FOLLOWUP = new Route(POST, "webhooks/{application_id}/{interaction_token}", true);
+ public static final Route EDIT_FOLLOWUP = new Route(PATCH, "webhooks/{application_id}/{interaction_token}/messages/{message_id}", true);
+ public static final Route DELETE_FOLLOWUP = new Route(DELETE, "webhooks/{application_id}/{interaction_token}/messages/{message_id}", true);
+ public static final Route GET_ORIGINAL = new Route(GET, "webhooks/{application_id}/{interaction_token}/messages/@original", true);
}
public static class Self
@@ -98,28 +83,11 @@ public static class Self
public static final Route LEAVE_GUILD = new Route(DELETE, "users/@me/guilds/{guild_id}");
public static final Route GET_PRIVATE_CHANNELS = new Route(GET, "users/@me/channels");
public static final Route CREATE_PRIVATE_CHANNEL = new Route(POST, "users/@me/channels");
-
- // Client only
- public static final Route USER_SETTINGS = new Route(GET, "users/@me/settings");
- public static final Route GET_CONNECTIONS = new Route(GET, "users/@me/connections");
- public static final Route FRIEND_SUGGESTIONS = new Route(GET, "friend-suggestions");
- public static final Route GET_RECENT_MENTIONS = new Route(GET, "users/@me/mentions");
}
public static class Users
{
public static final Route GET_USER = new Route(GET, "users/{user_id}");
- public static final Route GET_PROFILE = new Route(GET, "users/{user_id}/profile");
- public static final Route GET_NOTE = new Route(GET, "users/@me/notes/{user_id}");
- public static final Route SET_NOTE = new Route(PUT, "users/@me/notes/{user_id}");
- }
-
- public static class Relationships
- {
- public static final Route GET_RELATIONSHIPS = new Route(GET, "users/@me/relationships"); // Get Friends/Blocks/Incoming/Outgoing
- public static final Route GET_RELATIONSHIP = new Route(GET, "users/@me/relationships/{user_id}");
- public static final Route ADD_RELATIONSHIP = new Route(PUT, "users/@me/relationships/{user_id}"); // Add Friend/ Block
- public static final Route DELETE_RELATIONSHIP = new Route(DELETE, "users/@me/relationships/{user_id}"); // Delete Block/Unfriend/Ignore Request/Cancel Outgoing
}
public static class Guilds
@@ -171,12 +139,8 @@ public static class Guilds
public static final Route GET_WELCOME_SCREEN = new Route(GET, "guilds/{guild_id}/welcome-screen");
public static final Route MODIFY_WELCOME_SCREEN = new Route(PATCH, "guilds/{guild_id}/welcome-screen");
- //Client Only
public static final Route CREATE_GUILD = new Route(POST, "guilds");
public static final Route DELETE_GUILD = new Route(POST, "guilds/{guild_id}/delete");
- public static final Route ACK_GUILD = new Route(POST, "guilds/{guild_id}/ack");
-
- public static final Route MODIFY_NOTIFICATION_SETTINGS = new Route(PATCH, "users/@me/guilds/{guild_id}/settings");
}
public static class Emojis
@@ -212,7 +176,6 @@ public static class Webhooks
public static final Route MODIFY_WEBHOOK = new Route(PATCH, "webhooks/{webhook_id}");
public static final Route MODIFY_TOKEN_WEBHOOK = new Route(PATCH, "webhooks/{webhook_id}/{token}");
- // Separate
public static final Route EXECUTE_WEBHOOK = new Route(POST, "webhooks/{webhook_id}/{token}");
public static final Route EXECUTE_WEBHOOK_EDIT = new Route(PATCH, "webhooks/{webhook_id}/{token}/messages/{message_id}");
public static final Route EXECUTE_WEBHOOK_DELETE = new Route(DELETE, "webhooks/{webhook_id}/{token}/messages/{message_id}");
@@ -256,14 +219,6 @@ public static class Channels
public static final Route LIST_PUBLIC_ARCHIVED_THREADS = new Route(GET, "channels/{channel_id}/threads/archived/public");
public static final Route LIST_PRIVATE_ARCHIVED_THREADS = new Route(GET, "channels/{channel_id}/threads/archived/private");
public static final Route LIST_JOINED_PRIVATE_ARCHIVED_THREADS = new Route(GET, "channels/{channel_id}/users/@me/threads/archived/private");
-
- // Client Only
- public static final Route GET_RECIPIENTS = new Route(GET, "channels/{channel_id}/recipients");
- public static final Route GET_RECIPIENT = new Route(GET, "channels/{channel_id}/recipients/{user_id}");
- public static final Route ADD_RECIPIENT = new Route(PUT, "channels/{channel_id}/recipients/{user_id}");
- public static final Route REMOVE_RECIPIENT = new Route(DELETE, "channels/{channel_id}/recipients/{user_id}");
- public static final Route START_CALL = new Route(POST, "channels/{channel_id}/call/ring");
- public static final Route STOP_CALL = new Route(POST, "channels/{channel_id}/call/stop_ringing"); // aka deny or end call
}
public static class StageInstances
@@ -292,12 +247,8 @@ public static class Messages
public static final Route GET_MESSAGE_HISTORY = new Route(GET, "channels/{channel_id}/messages");
public static final Route CROSSPOST_MESSAGE = new Route(POST, "channels/{channel_id}/messages/{message_id}/crosspost");
- //Bot only
public static final Route GET_MESSAGE = new Route(GET, "channels/{channel_id}/messages/{message_id}");
public static final Route DELETE_MESSAGES = new Route(POST, "channels/{channel_id}/messages/bulk-delete");
-
- //Client only
- public static final Route ACK_MESSAGE = new Route(POST, "channels/{channel_id}/messages/{message_id}/ack");
}
public static class Invites
@@ -320,6 +271,38 @@ public static class Templates
public static final Route CREATE_GUILD_FROM_TEMPLATE = new Route(POST, "guilds/templates/{code}");
}
+ /**
+ * Create a route template for the given HTTP method.
+ *
+ *
Route syntax should include valid argument placeholders of the format: {@code '{' argument_name '}'}
+ *
The rate-limit handling in JDA relies on the correct names of major parameters:
+ *
+ * - {@code channel_id} for channel routes
+ * - {@code guild_id} for guild routes
+ * - {@code webhook_id} for webhook routes
+ * - {@code interaction_token} for interaction routes
+ *
+ *
+ * For example, to compose the route to create a message in a channel:
+ * {@code
+ * Route route = Route.custom(Method.POST, "channels/{channel_id}/messages");
+ * }
+ *
+ * To compile the route, use {@link #compile(String...)} with the positional arguments.
+ *
{@code
+ * Route.CompiledRoute compiled = route.compile(channelId);
+ * }
+ *
+ * @param method
+ * The HTTP method
+ * @param route
+ * The route template with valid argument placeholders
+ *
+ * @throws IllegalArgumentException
+ * If null is provided or the route is invalid (containing spaces or empty)
+ *
+ * @return The custom route template
+ */
@Nonnull
public static Route custom(@Nonnull Method method, @Nonnull String route)
{
@@ -329,100 +312,340 @@ public static Route custom(@Nonnull Method method, @Nonnull String route)
return new Route(method, route);
}
+ /**
+ * Create a route template for the with the {@link Method#DELETE DELETE} method.
+ *
+ * Route syntax should include valid argument placeholders of the format: {@code '{' argument_name '}'}
+ *
The rate-limit handling in JDA relies on the correct names of major parameters:
+ *
+ * - {@code channel_id} for channel routes
+ * - {@code guild_id} for guild routes
+ * - {@code webhook_id} for webhook routes
+ * - {@code interaction_token} for interaction routes
+ *
+ *
+ * For example, to compose the route to delete a message in a channel:
+ * {@code
+ * Route route = Route.custom(Method.DELETE, "channels/{channel_id}/messages/{message_id}");
+ * }
+ *
+ * To compile the route, use {@link #compile(String...)} with the positional arguments.
+ *
{@code
+ * Route.CompiledRoute compiled = route.compile(channelId, messageId);
+ * }
+ *
+ * @param route
+ * The route template with valid argument placeholders
+ *
+ * @throws IllegalArgumentException
+ * If null is provided or the route is invalid (containing spaces or empty)
+ *
+ * @return The custom route template
+ */
@Nonnull
public static Route delete(@Nonnull String route)
{
return custom(DELETE, route);
}
+ /**
+ * Create a route template for the with the {@link Method#POST POST} method.
+ *
+ * Route syntax should include valid argument placeholders of the format: {@code '{' argument_name '}'}
+ *
The rate-limit handling in JDA relies on the correct names of major parameters:
+ *
+ * - {@code channel_id} for channel routes
+ * - {@code guild_id} for guild routes
+ * - {@code webhook_id} for webhook routes
+ * - {@code interaction_token} for interaction routes
+ *
+ *
+ * For example, to compose the route to create a message in a channel:
+ * {@code
+ * Route route = Route.custom(Method.POST, "channels/{channel_id}/messages");
+ * }
+ *
+ * To compile the route, use {@link #compile(String...)} with the positional arguments.
+ *
{@code
+ * Route.CompiledRoute compiled = route.compile(channelId);
+ * }
+ *
+ * @param route
+ * The route template with valid argument placeholders
+ *
+ * @throws IllegalArgumentException
+ * If null is provided or the route is invalid (containing spaces or empty)
+ *
+ * @return The custom route template
+ */
@Nonnull
public static Route post(@Nonnull String route)
{
return custom(POST, route);
}
+ /**
+ * Create a route template for the with the {@link Method#PUT PUT} method.
+ *
+ * Route syntax should include valid argument placeholders of the format: {@code '{' argument_name '}'}
+ *
The rate-limit handling in JDA relies on the correct names of major parameters:
+ *
+ * - {@code channel_id} for channel routes
+ * - {@code guild_id} for guild routes
+ * - {@code webhook_id} for webhook routes
+ * - {@code interaction_token} for interaction routes
+ *
+ *
+ * For example, to compose the route to ban a user in a guild:
+ * {@code
+ * Route route = Route.custom(Method.PUT, "guilds/{guild_id}/bans/{user_id}");
+ * }
+ *
+ * To compile the route, use {@link #compile(String...)} with the positional arguments.
+ *
{@code
+ * Route.CompiledRoute compiled = route.compile(guildId, userId);
+ * }
+ *
+ * @param route
+ * The route template with valid argument placeholders
+ *
+ * @throws IllegalArgumentException
+ * If null is provided or the route is invalid (containing spaces or empty)
+ *
+ * @return The custom route template
+ */
@Nonnull
public static Route put(@Nonnull String route)
{
return custom(PUT, route);
}
+ /**
+ * Create a route template for the with the {@link Method#PATCH PATCH} method.
+ *
+ * Route syntax should include valid argument placeholders of the format: {@code '{' argument_name '}'}
+ *
The rate-limit handling in JDA relies on the correct names of major parameters:
+ *
+ * - {@code channel_id} for channel routes
+ * - {@code guild_id} for guild routes
+ * - {@code webhook_id} for webhook routes
+ * - {@code interaction_token} for interaction routes
+ *
+ *
+ * For example, to compose the route to edit a message in a channel:
+ * {@code
+ * Route route = Route.custom(Method.PATCH, "channels/{channel_id}/messages/{message_id}");
+ * }
+ *
+ * To compile the route, use {@link #compile(String...)} with the positional arguments.
+ *
{@code
+ * Route.CompiledRoute compiled = route.compile(channelId, messageId);
+ * }
+ *
+ * @param route
+ * The route template with valid argument placeholders
+ *
+ * @throws IllegalArgumentException
+ * If null is provided or the route is invalid (containing spaces or empty)
+ *
+ * @return The custom route template
+ */
@Nonnull
public static Route patch(@Nonnull String route)
{
return custom(PATCH, route);
}
+ /**
+ * Create a route template for the with the {@link Method#GET GET} method.
+ *
+ * Route syntax should include valid argument placeholders of the format: {@code '{' argument_name '}'}
+ *
The rate-limit handling in JDA relies on the correct names of major parameters:
+ *
+ * - {@code channel_id} for channel routes
+ * - {@code guild_id} for guild routes
+ * - {@code webhook_id} for webhook routes
+ * - {@code interaction_token} for interaction routes
+ *
+ *
+ * For example, to compose the route to get a message in a channel:
+ * {@code
+ * Route route = Route.custom(Method.GET, "channels/{channel_id}/messages/{message_id}");
+ * }
+ *
+ * To compile the route, use {@link #compile(String...)} with the positional arguments.
+ *
{@code
+ * Route.CompiledRoute compiled = route.compile(channelId, messageId);
+ * }
+ *
+ * @param route
+ * The route template with valid argument placeholders
+ *
+ * @throws IllegalArgumentException
+ * If null is provided or the route is invalid (containing spaces or empty)
+ *
+ * @return The custom route template
+ */
@Nonnull
public static Route get(@Nonnull String route)
{
return custom(GET, route);
}
- private static final String majorParameters = "guild_id:channel_id:webhook_id:interaction_token";
- private final String route;
+ /**
+ * The known major parameters used for rate-limits.
+ *
+ * Instead of {@code webhook_id + webhook_token}, we use {@code interaction_token} for interaction routes.
+ *
+ * @see Rate Limit Documentation
+ */
+ public static final List MAJOR_PARAMETER_NAMES = Helpers.listOf(
+ "guild_id", "channel_id", "webhook_id", "interaction_token"
+ );
+
private final Method method;
private final int paramCount;
+ private final String[] template;
+ private final boolean isInteraction;
- private Route(Method method, String route)
+ private Route(Method method, String route, boolean isInteraction)
{
this.method = method;
- this.route = route;
- this.paramCount = Helpers.countMatches(route, '{'); //All parameters start with {
+ this.template = Helpers.split(route, "/");
+ this.isInteraction = isInteraction;
+
+ // Validate route syntax
+ int paramCount = 0;
+ for (String element : this.template)
+ {
+ int opening = Helpers.countMatches(element, '{');
+ int closing = Helpers.countMatches(element, '}');
+ if (element.startsWith("{") && element.endsWith("}"))
+ {
+ // Ensure the brackets are only on the start and end
+ // Valid: {guild_id}
+ // Invalid: {guild_id}abc
+ // Invalid: {{guild_id}}
+ Checks.check(closing == 1 && opening == 1, "Route element has invalid syntax: '%s'", element);
+ paramCount += 1;
+ }
+ else if (opening > 0 || closing > 0)
+ {
+ // Handle potential stray brackets
+ // Invalid: guilds{/guild_id} -> ["guilds{", "guild_id}"]
+ throw new IllegalArgumentException("Route element has invalid syntax: '" + element + "'");
+ }
+ }
+ this.paramCount = paramCount;
+
+ }
- if (paramCount != Helpers.countMatches(route, '}'))
- throw new IllegalArgumentException("An argument does not have both {}'s for route: " + method + " " + route);
+ private Route(Method method, String route)
+ {
+ this(method, route, false);
}
+ /**
+ * Whether this route is a route related to interactions.
+ *
Interactions have some special handling, since they are exempt from global rate-limits and are limited to 15 minute uptime.
+ *
+ * @return True, if this route is for interactions
+ */
+ public boolean isInteractionBucket()
+ {
+ return isInteraction;
+ }
+
+ /**
+ * The {@link Method} of this route template.
+ *
Multiple routes with different HTTP methods can share a rate-limit.
+ *
+ * @return The HTTP method
+ */
+ @Nonnull
public Method getMethod()
{
return method;
}
+ /**
+ * The route template with argument placeholders.
+ *
+ * @return The route template
+ */
+ @Nonnull
public String getRoute()
{
- return route;
+ return String.join("/", template);
}
+ /**
+ * The number of parameters for this route, not including query parameters.
+ *
+ * @return The parameter count
+ */
public int getParamCount()
{
return paramCount;
}
- public CompiledRoute compile(String... params)
+ /**
+ * Compile the route with provided parameters.
+ *
The number of parameters must match the number of placeholders in the route template.
+ * The provided arguments are positional and will replace the placeholders of the template in order of appearance.
+ *
+ * Use {@link CompiledRoute#withQueryParams(String...)} to add query parameters to the route.
+ *
+ * @param params
+ * The parameters to compile the route with
+ *
+ * @throws IllegalArgumentException
+ * If the number of parameters does not match the number of placeholders, or null is provided
+ *
+ * @return The compiled route, ready to use for rate-limit handling
+ */
+ @Nonnull
+ public CompiledRoute compile(@Nonnull String... params)
{
- if (params.length != paramCount)
+ Checks.noneNull(params, "Arguments");
+ Checks.check(
+ params.length == paramCount,
+ "Error Compiling Route: [%s], incorrect amount of parameters provided. Expected: %d, Provided: %d",
+ this, paramCount, params.length
+ );
+
+ StringJoiner major = new StringJoiner(":").setEmptyValue("n/a");
+ StringJoiner compiledRoute = new StringJoiner("/");
+
+ int paramIndex = 0;
+ for (String element : template)
{
- throw new IllegalArgumentException("Error Compiling Route: [" + route + "], incorrect amount of parameters provided." +
- "Expected: " + paramCount + ", Provided: " + params.length);
- }
-
- //Compile the route for interfacing with discord.
- Set major = new HashSet<>();
- StringBuilder compiledRoute = new StringBuilder(route);
- for (int i = 0; i < paramCount; i++)
- {
- int paramStart = compiledRoute.indexOf("{");
- int paramEnd = compiledRoute.indexOf("}");
- String paramName = compiledRoute.substring(paramStart+1, paramEnd);
- if (majorParameters.contains(paramName))
+ if (element.charAt(0) == '{')
{
- if (params[i].length() > 30) // probably a long interaction_token, hash it to keep logs clean (not useful anyway)
- major.add(paramName + "=" + Integer.toUnsignedString(params[i].hashCode()));
- else
- major.add(paramName + "=" + params[i]);
+ String name = element.substring(1, element.length() - 1);
+ String value = params[paramIndex++];
+ if (MAJOR_PARAMETER_NAMES.contains(name))
+ {
+ if (value.length() > 30) // probably a long interaction_token, hash it to keep logs clean (not useful anyway)
+ major.add(name + "=" + Integer.toUnsignedString(value.hashCode()));
+ else
+ major.add(name + "=" + value);
+ }
+ compiledRoute.add(EncodingUtil.encodeUTF8(value));
+ }
+ else
+ {
+ compiledRoute.add(element);
}
-
- compiledRoute.replace(paramStart, paramEnd + 1, params[i]);
}
- return new CompiledRoute(this, compiledRoute.toString(), major.isEmpty() ? "n/a" : String.join(":", major));
+ return new CompiledRoute(this, compiledRoute.toString(), major.toString());
}
@Override
public int hashCode()
{
- return (route + method.toString()).hashCode();
+ return Objects.hash(method, Arrays.hashCode(template));
}
@Override
@@ -432,68 +655,150 @@ public boolean equals(Object o)
return false;
Route oRoute = (Route) o;
- return method.equals(oRoute.method) && route.equals(oRoute.route);
+ return method.equals(oRoute.method) && Arrays.equals(template, oRoute.template);
}
@Override
public String toString()
{
- return new EntityString(this)
- .setType(method)
- .addMetadata("route", route)
- .toString();
+ return method + "/" + getRoute();
}
+ /**
+ * A route compiled with arguments.
+ *
+ * @see Route#compile(String...)
+ */
public class CompiledRoute
{
private final Route baseRoute;
private final String major;
private final String compiledRoute;
- private final boolean hasQueryParams;
+ private final List query;
- private CompiledRoute(Route baseRoute, String compiledRoute, String major, boolean hasQueryParams)
+ private CompiledRoute(Route baseRoute, String compiledRoute, String major)
{
this.baseRoute = baseRoute;
this.compiledRoute = compiledRoute;
this.major = major;
- this.hasQueryParams = hasQueryParams;
+ this.query = null;
}
- private CompiledRoute(Route baseRoute, String compiledRoute, String major)
+ private CompiledRoute(CompiledRoute original, List query)
{
- this(baseRoute, compiledRoute, major, false);
+ this.baseRoute = original.baseRoute;
+ this.compiledRoute = original.compiledRoute;
+ this.major = original.major;
+ this.query = query;
}
+ /**
+ * Returns a copy of this CompiledRoute with the provided parameters added as query.
+ *
This will use percent-encoding
+ * for all provided values but not for the keys.
+ *
+ * Example Usage
+ *
{@code
+ * Route.CompiledRoute history = Route.GET_MESSAGE_HISTORY.compile(channelId);
+ *
+ * // returns a new route
+ * route = history.withQueryParams(
+ * "limit", 100
+ * );
+ * // adds another parameter ontop of limit
+ * route = route.withQueryParams(
+ * "after", messageId
+ * );
+ *
+ * // now the route has both limit and after, you can also do this in one call:
+ * route = history.withQueryParams(
+ * "limit", 100,
+ * "after", messageId
+ * );
+ * }
+ *
+ * @param params
+ * The parameters to add as query, alternating key and value (see example)
+ *
+ * @throws IllegalArgumentException
+ * If the number of arguments is not even or null is provided
+ *
+ * @return A copy of this CompiledRoute with the provided parameters added as query
+ */
@Nonnull
@CheckReturnValue
- public CompiledRoute withQueryParams(String... params)
+ public CompiledRoute withQueryParams(@Nonnull String... params)
{
- Checks.check(params.length >= 2, "params length must be at least 2");
- Checks.check(params.length % 2 == 0, "params length must be a multiple of 2");
+ Checks.notNull(params, "Params");
+ Checks.check(params.length >= 2, "Params length must be at least 2");
+ Checks.check((params.length & 1) == 0, "Params length must be a multiple of 2");
- StringBuilder newRoute = new StringBuilder(compiledRoute);
+ List newQuery;
+ if (query == null)
+ {
+ newQuery = new ArrayList<>(params.length / 2);
+ }
+ else
+ {
+ newQuery = new ArrayList<>(query.size() + params.length / 2);
+ newQuery.addAll(query);
+ }
- for (int i = 0; i < params.length; i++)
- newRoute.append(!hasQueryParams && i == 0 ? '?' : '&').append(params[i]).append('=').append(params[++i]);
+ // Assuming names don't need encoding
+ for (int i = 0; i < params.length; i += 2)
+ {
+ Checks.notEmpty(params[i], "Query key [" + i/2 + "]");
+ Checks.notNull(params[i + 1], "Query value [" + i/2 + "]");
+ newQuery.add(params[i] + '=' + EncodingUtil.encodeUTF8(params[i + 1]));
+ }
- return new CompiledRoute(baseRoute, newRoute.toString(), major, true);
+ return new CompiledRoute(this, newQuery);
}
+ /**
+ * The string of major parameters used by this route.
+ *
This is important for rate-limit handling.
+ *
+ * @return The string of major parameters used by this route
+ */
+ @Nonnull
public String getMajorParameters()
{
return major;
}
+ /**
+ * The compiled route string of the endpoint,
+ * including all arguments and query parameters.
+ *
+ * @return The compiled route string of the endpoint
+ */
+ @Nonnull
public String getCompiledRoute()
{
- return compiledRoute;
+ if (query == null)
+ return compiledRoute;
+ // Append query to url
+ return compiledRoute + '?' + String.join("&", query);
}
+ /**
+ * The route template with the original placeholders.
+ *
+ * @return The route template with the original placeholders
+ */
+ @Nonnull
public Route getBaseRoute()
{
return baseRoute;
}
+ /**
+ * The HTTP method.
+ *
+ * @return The HTTP method
+ */
+ @Nonnull
public Method getMethod()
{
return baseRoute.method;
diff --git a/src/main/java/net/dv8tion/jda/api/requests/SequentialRestRateLimiter.java b/src/main/java/net/dv8tion/jda/api/requests/SequentialRestRateLimiter.java
new file mode 100644
index 0000000000..8e7b4082af
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/requests/SequentialRestRateLimiter.java
@@ -0,0 +1,535 @@
+/*
+ * 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.requests;
+
+import net.dv8tion.jda.api.utils.MiscUtil;
+import net.dv8tion.jda.internal.utils.JDALogger;
+import okhttp3.Headers;
+import okhttp3.Response;
+import org.slf4j.Logger;
+
+import javax.annotation.Nonnull;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A bucket is determined via the Path+Method+Major in the following way:
+ *
+ * - Get Hash from Path+Method (we call this route)
+ * - Get bucket from Hash+Major (we call this bucketid)
+ *
+ *
+ * If no hash is known we default to the constant "uninit" hash. The hash is loaded from HTTP responses using the "X-RateLimit-Bucket" response header.
+ * This hash is per Method+Path and can be stored indefinitely once received.
+ * Some endpoints don't return a hash, this means that the endpoint is uninit and will be in queue with only the major parameter.
+ *
+ *
To explain this further, lets look at the example of message history. The endpoint to fetch message history is {@code GET/channels/{channel.id}/messages}.
+ * This endpoint does not have any rate limit (uninit) and will thus use the hash {@code uninit+GET/channels/{channel.id}/messages}.
+ * The bucket id for this will be {@code uninit+GET/channels/{channel.id}/messages:channel_id={channel.id}} where {@code {channel.id}} would be replaced with the respective id.
+ * This means you can fetch history concurrently for multiple channels, but it will be in sequence for the same channel.
+ *
+ *
If the endpoint is not uninit we will receive a hash on the first response.
+ * Once this happens every uninit bucket will start moving its queue to the correct bucket.
+ * This is done during the queue work iteration so many requests to one endpoint would be moved correctly.
+ *
+ *
For example, the first message sending:
+ *
{@code
+ * public void onReady(ReadyEvent event) {
+ * TextChannel channel = event.getJDA().getTextChannelById("123");
+ * for (int i = 1; i <= 100; i++) {
+ * channel.sendMessage("Message: " + i).queue();
+ * }
+ * }
+ * }
+ *
+ * This will send 100 messages on startup. At this point we don't yet know the hash for this route, so we put them all in {@code uninit+POST/channels/{channel.id}/messages:channel_id=123}.
+ * The bucket iterates the requests in sync and gets the first response. This response provides the hash for this route, and we create a bucket for it.
+ * Once the response is handled we continue with the next request in the uninit bucket and notice the new bucket. We then move all related requests to this bucket.
+ */
+public final class SequentialRestRateLimiter implements RestRateLimiter
+{
+ private static final Logger log = JDALogger.getLog(RestRateLimiter.class);
+ private static final String UNINIT_BUCKET = "uninit"; // we generate an uninit bucket for every major parameter configuration
+
+ private final CompletableFuture> shutdownHandle = new CompletableFuture<>();
+
+ private final Future> cleanupWorker;
+ private final RateLimitConfig config;
+
+ private boolean isStopped, isShutdown;
+
+ private final ReentrantLock lock = new ReentrantLock();
+ // Route -> Should we print warning for 429? AKA did we already hit it once before
+ private final Set hitRatelimit = new HashSet<>(5);
+ // Route -> Hash
+ private final Map hashes = new HashMap<>();
+ // Hash + Major Parameter -> Bucket
+ private final Map buckets = new HashMap<>();
+ // Bucket -> Rate-Limit Worker
+ private final Map> rateLimitQueue = new HashMap<>();
+
+ public SequentialRestRateLimiter(@Nonnull RateLimitConfig config)
+ {
+ this.config = config;
+ this.cleanupWorker = config.getPool().scheduleAtFixedRate(this::cleanup, 30, 30, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void enqueue(@Nonnull RestRateLimiter.Work task)
+ {
+ MiscUtil.locked(lock, () -> {
+ Bucket bucket = getBucket(task.getRoute());
+ bucket.enqueue(task);
+ runBucket(bucket);
+ });
+ }
+
+ @Override
+ public void stop(boolean shutdown, @Nonnull Runnable callback)
+ {
+ MiscUtil.locked(lock, () -> {
+ boolean doShutdown = shutdown;
+ if (!isStopped)
+ {
+ isStopped = true;
+ shutdownHandle.thenRun(callback);
+ if (!doShutdown)
+ {
+ int size = buckets.size();
+ int average = (int) Math.ceil(
+ buckets.values().stream()
+ .map(Bucket::getRequests)
+ .mapToInt(Collection::size)
+ .average().orElse(0)
+ );
+
+ if (size > 0 && average > 0)
+ log.info("Waiting for {} bucket(s) to finish. Average queue size of {} requests", size, average);
+ else if (size == 0)
+ doShutdown = true;
+ }
+ }
+ if (doShutdown && !isShutdown)
+ shutdown();
+ });
+ }
+
+ @Override
+ public boolean isStopped()
+ {
+ return isStopped;
+ }
+
+ @Override
+ public int cancelRequests()
+ {
+ return MiscUtil.locked(lock, () -> {
+ // Empty buckets will be removed by the cleanup worker, which also checks for rate limit parameters
+ int cancelled = (int) buckets.values()
+ .stream()
+ .map(Bucket::getRequests)
+ .flatMap(Collection::stream)
+ .filter(request -> !request.isPriority() && !request.isCancelled())
+ .peek(Work::cancel)
+ .count();
+
+ if (cancelled == 1)
+ log.warn("Cancelled 1 request!");
+ else if (cancelled > 1)
+ log.warn("Cancelled {} requests!", cancelled);
+ return cancelled;
+ });
+ }
+
+ private void shutdown()
+ {
+ isShutdown = true;
+ cleanupWorker.cancel(false);
+ cleanup();
+ shutdownHandle.complete(null);
+ }
+
+ private void cleanup()
+ {
+ // This will remove buckets that are no longer needed every 30 seconds to avoid memory leakage
+ // We will keep the hashes in memory since they are very limited (by the amount of possible routes)
+ MiscUtil.locked(lock, () -> {
+ int size = buckets.size();
+ Iterator> entries = buckets.entrySet().iterator();
+
+ while (entries.hasNext())
+ {
+ Map.Entry entry = entries.next();
+ Bucket bucket = entry.getValue();
+ if (isShutdown)
+ bucket.requests.forEach(Work::cancel); // Cancel all requests
+ bucket.requests.removeIf(Work::isSkipped); // Remove cancelled requests
+
+ // Check if the bucket is empty
+ if (bucket.isUninit() && bucket.requests.isEmpty())
+ entries.remove(); // remove uninit if requests are empty
+ // If the requests of the bucket are drained and the reset is expired the bucket has no valuable information
+ else if (bucket.requests.isEmpty() && bucket.reset <= getNow())
+ entries.remove();
+ // Remove empty buckets when the rate limiter is stopped
+ else if (bucket.requests.isEmpty() && isStopped)
+ entries.remove();
+ }
+ // Log how many buckets were removed
+ size -= buckets.size();
+ if (size > 0)
+ log.debug("Removed {} expired buckets", size);
+ });
+ }
+
+ private String getRouteHash(Route route)
+ {
+ return hashes.getOrDefault(route, UNINIT_BUCKET + "+" + route);
+ }
+
+ private Bucket getBucket(Route.CompiledRoute route)
+ {
+ return MiscUtil.locked(lock, () ->
+ {
+ // Retrieve the hash via the route
+ String hash = getRouteHash(route.getBaseRoute());
+ // Get or create a bucket for the hash + major parameters
+ String bucketId = hash + ":" + route.getMajorParameters();
+ return this.buckets.computeIfAbsent(bucketId, (id) ->
+ {
+ if (route.getBaseRoute().isInteractionBucket())
+ return new InteractionBucket(id);
+ else
+ return new ClassicBucket(id);
+ });
+ });
+ }
+
+ private void runBucket(Bucket bucket)
+ {
+ if (isShutdown)
+ return;
+ // Schedule a new bucket worker if no worker is running
+ MiscUtil.locked(lock, () ->
+ rateLimitQueue.computeIfAbsent(bucket,
+ (k) -> config.getPool().schedule(bucket, bucket.getRateLimit(), TimeUnit.MILLISECONDS)));
+ }
+
+ private long parseLong(String input)
+ {
+ return input == null ? 0L : Long.parseLong(input);
+ }
+
+ private long parseDouble(String input)
+ {
+ //The header value is using a double to represent milliseconds and seconds:
+ // 5.250 this is 5 seconds and 250 milliseconds (5250 milliseconds)
+ return input == null ? 0L : (long) (Double.parseDouble(input) * 1000);
+ }
+
+ private long getNow()
+ {
+ return System.currentTimeMillis();
+ }
+
+ private void updateBucket(Route.CompiledRoute route, Response response)
+ {
+ MiscUtil.locked(lock, () ->
+ {
+ try
+ {
+ Bucket bucket = getBucket(route);
+ Headers headers = response.headers();
+
+ boolean global = headers.get(GLOBAL_HEADER) != null;
+ boolean cloudflare = headers.get("via") == null;
+ String hash = headers.get(HASH_HEADER);
+ String scope = headers.get(SCOPE_HEADER);
+ long now = getNow();
+
+ // Create a new bucket for the hash if needed
+ Route baseRoute = route.getBaseRoute();
+ if (hash != null)
+ {
+ if (!this.hashes.containsKey(baseRoute))
+ {
+ this.hashes.put(baseRoute, hash);
+ log.debug("Caching bucket hash {} -> {}", baseRoute, hash);
+ }
+
+ bucket = getBucket(route);
+ }
+
+ if (response.code() == 429)
+ {
+ String retryAfterHeader = headers.get(RETRY_AFTER_HEADER);
+ long retryAfter = parseLong(retryAfterHeader) * 1000; // seconds precision
+ // Handle global rate limit if necessary
+ if (global)
+ {
+ config.getGlobalRateLimit().setClassic(now + retryAfter);
+ log.error("Encountered global rate limit! Retry-After: {} ms Scope: {}", retryAfter, scope);
+ }
+ // Handle cloudflare rate limits, this applies to all routes and uses seconds for retry-after
+ else if (cloudflare)
+ {
+ config.getGlobalRateLimit().setCloudflare(now + retryAfter);
+ log.error("Encountered cloudflare rate limit! Retry-After: {} s", retryAfter / 1000);
+ }
+ // Handle hard rate limit, pretty much just log that it happened
+ else
+ {
+ boolean firstHit = hitRatelimit.add(baseRoute) && retryAfter < 60000;
+ // Update the bucket to the new information
+ bucket.remaining = 0;
+ bucket.reset = getNow() + retryAfter;
+ // don't log warning if we hit the rate limit for the first time, likely due to initialization of the bucket
+ // unless its a long retry-after delay (more than a minute)
+ if (firstHit)
+ log.debug("Encountered 429 on route {} with bucket {} Retry-After: {} ms Scope: {}", baseRoute, bucket.bucketId, retryAfter, scope);
+ else
+ log.warn("Encountered 429 on route {} with bucket {} Retry-After: {} ms Scope: {}", baseRoute, bucket.bucketId, retryAfter, scope);
+ }
+ return bucket;
+ }
+
+ // If hash is null this means we didn't get enough information to update a bucket
+ if (hash == null)
+ return bucket;
+
+ // Update the bucket parameters with new information
+ String limitHeader = headers.get(LIMIT_HEADER);
+ String remainingHeader = headers.get(REMAINING_HEADER);
+ String resetAfterHeader = headers.get(RESET_AFTER_HEADER);
+ String resetHeader = headers.get(RESET_HEADER);
+
+// bucket.limit = (int) Math.max(1L, parseLong(limitHeader));
+ bucket.remaining = (int) parseLong(remainingHeader);
+ if (config.isRelative())
+ bucket.reset = now + parseDouble(resetAfterHeader);
+ else
+ bucket.reset = parseDouble(resetHeader);
+ log.trace("Updated bucket {} to ({}/{}, {})", bucket.bucketId, bucket.remaining, limitHeader, bucket.reset - now);
+ return bucket;
+ }
+ catch (Exception e)
+ {
+ Bucket bucket = getBucket(route);
+ log.error("Encountered Exception while updating a bucket. Route: {} Bucket: {} Code: {} Headers:\n{}",
+ route.getBaseRoute(), bucket, response.code(), response.headers(), e);
+ return bucket;
+ }
+ });
+ }
+
+ private abstract class Bucket implements Runnable
+ {
+ protected final String bucketId;
+ protected final Deque requests = new ConcurrentLinkedDeque<>();
+
+ protected long reset = 0;
+ protected int remaining = 1;
+
+ public Bucket(String bucketId)
+ {
+ this.bucketId = bucketId;
+ }
+
+ public boolean isUninit()
+ {
+ return bucketId.startsWith(UNINIT_BUCKET);
+ }
+
+ public void enqueue(Work request)
+ {
+ requests.addLast(request);
+ }
+
+ public void retry(Work request)
+ {
+ requests.addFirst(request);
+ }
+
+ public long getReset()
+ {
+ return reset;
+ }
+
+ public int getRemaining()
+ {
+ return remaining;
+ }
+
+ public abstract long getGlobalRateLimit(long now);
+
+ public long getRateLimit()
+ {
+ long now = getNow();
+
+ long global = getGlobalRateLimit(now);
+ // Global rate limit is more important to handle
+ if (global > 0)
+ return global;
+
+ // Check if the bucket reset time has expired
+ if (reset <= now)
+ {
+ // Update the remaining uses to the limit (we don't know better)
+ remaining = 1;
+ return 0L;
+ }
+
+ // If there are remaining requests we don't need to do anything, otherwise return backoff in milliseconds
+ return remaining < 1 ? reset - now : 0L;
+ }
+
+ protected boolean isGlobalRateLimit()
+ {
+ return getGlobalRateLimit(getNow()) > 0;
+ }
+
+ protected void backoff()
+ {
+ // Schedule backoff if requests are not done
+ MiscUtil.locked(lock, () -> {
+ rateLimitQueue.remove(this);
+ if (!requests.isEmpty())
+ runBucket(this);
+ else if (isStopped)
+ buckets.remove(bucketId);
+ if (isStopped && buckets.isEmpty())
+ shutdown();
+ });
+ }
+
+ public Queue getRequests()
+ {
+ return requests;
+ }
+
+ protected Boolean moveRequest(Work request)
+ {
+ return MiscUtil.locked(lock, () ->
+ {
+ // Attempt moving request to correct bucket if it has been created
+ Bucket bucket = getBucket(request.getRoute());
+ if (bucket != this)
+ {
+ bucket.enqueue(request);
+ runBucket(bucket);
+ return true;
+ }
+ return false;
+ });
+ }
+
+ protected boolean execute(Work request)
+ {
+ try
+ {
+ Response response = request.execute();
+ if (response != null)
+ updateBucket(request.getRoute(), response);
+ if (!request.isDone())
+ retry(request);
+ }
+ catch (Throwable ex)
+ {
+ log.error("Encountered exception trying to execute request", ex);
+ if (ex instanceof Error)
+ throw (Error) ex;
+ return true;
+ }
+ return false;
+ }
+
+ public void run()
+ {
+ log.trace("Bucket {} is running {} requests", bucketId, requests.size());
+ while (!requests.isEmpty())
+ {
+ long rateLimit = getRateLimit();
+ if (rateLimit > 0L)
+ {
+ // We need to backoff since we ran out of remaining uses or hit the global rate limit
+ Work request = requests.peekFirst(); // this *should* not be null
+ String baseRoute = request != null ? request.getRoute().getBaseRoute().toString() : "N/A";
+ if (!isGlobalRateLimit() && rateLimit >= 1000 * 60 * 30) // 30 minutes
+ log.warn("Encountered long {} minutes Rate-Limit on route {}", TimeUnit.MILLISECONDS.toMinutes(rateLimit), baseRoute);
+ log.debug("Backing off {} ms for bucket {} on route {}", rateLimit, bucketId, baseRoute);
+ break;
+ }
+
+ Work request = requests.removeFirst();
+ if (request.isSkipped())
+ continue;
+
+ // Check if a bucket has been discovered and initialized for this route
+ if (isUninit())
+ {
+ boolean shouldSkip = moveRequest(request);
+ if (shouldSkip) continue;
+ }
+
+ if (execute(request)) break;
+ }
+
+ backoff();
+ }
+
+ @Override
+ public String toString()
+ {
+ return bucketId;
+ }
+ }
+
+ private class ClassicBucket extends Bucket
+ {
+ public ClassicBucket(String bucketId)
+ {
+ super(bucketId);
+ }
+
+ @Override
+ public long getGlobalRateLimit(long now)
+ {
+ GlobalRateLimit holder = config.getGlobalRateLimit();
+ long global = Math.max(holder.getClassic(), holder.getCloudflare());
+ return global - now;
+ }
+ }
+
+ private class InteractionBucket extends Bucket
+ {
+ public InteractionBucket(@Nonnull String bucketId)
+ {
+ super(bucketId);
+ }
+
+ @Override
+ public long getGlobalRateLimit(long now)
+ {
+ // Only cloudflare bans apply to interactions
+ return config.getGlobalRateLimit().getCloudflare() - now;
+ }
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManager.java b/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManager.java
index 9d7fd5d33e..b5ad7c4bc6 100644
--- a/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManager.java
+++ b/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManager.java
@@ -23,6 +23,8 @@
import net.dv8tion.jda.api.entities.SelfUser;
import net.dv8tion.jda.api.exceptions.InvalidTokenException;
import net.dv8tion.jda.api.requests.GatewayIntent;
+import net.dv8tion.jda.api.requests.RestConfig;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.ChunkingFilter;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.SessionController;
@@ -31,7 +33,6 @@
import net.dv8tion.jda.internal.entities.SelfUserImpl;
import net.dv8tion.jda.internal.managers.PresenceImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.JDALogger;
import net.dv8tion.jda.internal.utils.UnlockHook;
@@ -146,6 +147,8 @@ public class DefaultShardManager implements ShardManager
*/
protected final ChunkingFilter chunkingFilter;
+ protected final IntFunction extends RestConfig> restConfigProvider;
+
public DefaultShardManager(@Nonnull String token)
{
this(token, null);
@@ -153,14 +156,14 @@ public DefaultShardManager(@Nonnull String token)
public DefaultShardManager(@Nonnull String token, @Nullable Collection shardIds)
{
- this(token, shardIds, null, null, null, null, null, null, null);
+ this(token, shardIds, null, null, null, null, null, null, null, null);
}
public DefaultShardManager(
@Nonnull String token, @Nullable Collection shardIds,
@Nullable ShardingConfig shardingConfig, @Nullable EventConfig eventConfig,
@Nullable PresenceProviderConfig presenceConfig, @Nullable ThreadingProviderConfig threadingConfig,
- @Nullable ShardingSessionConfig sessionConfig, @Nullable ShardingMetaConfig metaConfig,
+ @Nullable ShardingSessionConfig sessionConfig, @Nullable ShardingMetaConfig metaConfig, @Nullable IntFunction extends RestConfig> restConfigProvider,
@Nullable ChunkingFilter chunkingFilter)
{
this.token = token;
@@ -171,6 +174,7 @@ public DefaultShardManager(
this.presenceConfig = presenceConfig == null ? PresenceProviderConfig.getDefault() : presenceConfig;
this.metaConfig = metaConfig == null ? ShardingMetaConfig.getDefault() : metaConfig;
this.chunkingFilter = chunkingFilter == null ? ChunkingFilter.ALL : chunkingFilter;
+ this.restConfigProvider = restConfigProvider == null ? (i) -> new RestConfig() : restConfigProvider;
this.executor = createExecutor(this.threadingConfig.getThreadFactory());
this.shutdownHook = this.metaConfig.isUseShutdownHook() ? new Thread(this::shutdown, "JDA Shutdown Hook") : null;
@@ -512,7 +516,10 @@ protected JDAImpl buildInstance(final int shardId)
threadingConfig.setEventPool(eventPool, shutdownEventPool);
threadingConfig.setAudioPool(audioPool, shutdownAudioPool);
MetaConfig metaConfig = new MetaConfig(this.metaConfig.getMaxBufferSize(), this.metaConfig.getContextMap(shardId), this.metaConfig.getCacheFlags(), this.sessionConfig.getFlags());
- final JDAImpl jda = new JDAImpl(authConfig, sessionConfig, threadingConfig, metaConfig);
+ RestConfig restConfig = this.restConfigProvider.apply(shardId);
+ if (restConfig == null)
+ restConfig = new RestConfig();
+ final JDAImpl jda = new JDAImpl(authConfig, sessionConfig, threadingConfig, metaConfig, restConfig);
jda.setMemberCachePolicy(shardingConfig.getMemberCachePolicy());
threadingConfig.init(jda::getIdentifierString);
// We can only do member chunking with the GUILD_MEMBERS intent
diff --git a/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManagerBuilder.java b/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManagerBuilder.java
index f83f6517ef..e4e6bd435c 100644
--- a/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManagerBuilder.java
+++ b/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManagerBuilder.java
@@ -16,6 +16,8 @@
package net.dv8tion.jda.api.sharding;
import com.neovisionaries.ws.client.WebSocketFactory;
+import net.dv8tion.jda.annotations.ForRemoval;
+import net.dv8tion.jda.annotations.ReplaceWith;
import net.dv8tion.jda.api.GatewayEncoding;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.audio.factory.IAudioSendFactory;
@@ -26,6 +28,7 @@
import net.dv8tion.jda.api.hooks.VoiceDispatchInterceptor;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.RestConfig;
import net.dv8tion.jda.api.utils.ChunkingFilter;
import net.dv8tion.jda.api.utils.Compression;
import net.dv8tion.jda.api.utils.MemberCachePolicy;
@@ -84,6 +87,7 @@ public class DefaultShardManagerBuilder
protected ThreadPoolProvider extends ExecutorService> callbackPoolProvider = null;
protected ThreadPoolProvider extends ExecutorService> eventPoolProvider = null;
protected ThreadPoolProvider extends ScheduledExecutorService> audioPoolProvider = null;
+ protected IntFunction extends RestConfig> restConfigProvider = null;
protected Collection shards = null;
protected OkHttpClient.Builder httpClientBuilder = null;
protected OkHttpClient httpClient = null;
@@ -559,11 +563,53 @@ public DefaultShardManagerBuilder setEventPassthrough(boolean enable)
* @since 4.1.0
*/
@Nonnull
+ @Deprecated
+ @ForRemoval(deadline = "5.1.0")
+ @ReplaceWith("setRestConfig(new RestConfig().setRelativeRateLimit(enable))")
public DefaultShardManagerBuilder setRelativeRateLimit(boolean enable)
{
return setFlag(ConfigFlag.USE_RELATIVE_RATELIMIT, enable);
}
+ /**
+ * Custom {@link RestConfig} to use.
+ *
This can be used to customize how rate-limits are handled and configure a custom http proxy.
+ *
+ * @param provider
+ * The {@link RestConfig} provider to use
+ *
+ * @throws IllegalArgumentException
+ * If null is provided
+ *
+ * @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public DefaultShardManagerBuilder setRestConfigProvider(@Nonnull IntFunction extends RestConfig> provider)
+ {
+ Checks.notNull(provider, "RestConfig Provider");
+ this.restConfigProvider = provider;
+ return this;
+ }
+
+ /**
+ * Custom {@link RestConfig} to use.
+ *
This can be used to customize how rate-limits are handled and configure a custom http proxy.
+ *
+ * @param config
+ * The {@link RestConfig} to use
+ *
+ * @throws IllegalArgumentException
+ * If null is provided
+ *
+ * @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public DefaultShardManagerBuilder setRestConfig(@Nonnull RestConfig config)
+ {
+ Checks.notNull(config, "RestConfig");
+ return setRestConfigProvider(ignored -> config);
+ }
+
/**
* Enable specific cache flags.
*
This will not disable any currently set cache flags.
@@ -2196,7 +2242,7 @@ public ShardManager build(boolean login) throws IllegalArgumentException
final ThreadingProviderConfig threadingConfig = new ThreadingProviderConfig(rateLimitPoolProvider, gatewayPoolProvider, callbackPoolProvider, eventPoolProvider, audioPoolProvider, threadFactory);
final ShardingSessionConfig sessionConfig = new ShardingSessionConfig(sessionController, voiceDispatchInterceptor, httpClient, httpClientBuilder, wsFactory, audioSendFactory, flags, shardingFlags, maxReconnectDelay, largeThreshold);
final ShardingMetaConfig metaConfig = new ShardingMetaConfig(maxBufferSize, contextProvider, cacheFlags, flags, compression, encoding);
- final DefaultShardManager manager = new DefaultShardManager(this.token, this.shards, shardingConfig, eventConfig, presenceConfig, threadingConfig, sessionConfig, metaConfig, chunkingFilter);
+ final DefaultShardManager manager = new DefaultShardManager(this.token, this.shards, shardingConfig, eventConfig, presenceConfig, threadingConfig, sessionConfig, metaConfig, restConfigProvider, chunkingFilter);
if (login)
manager.login();
diff --git a/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java b/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java
index d534bf7a92..19464ab700 100644
--- a/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java
+++ b/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java
@@ -29,6 +29,7 @@
import net.dv8tion.jda.api.exceptions.InvalidTokenException;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.cache.CacheView;
import net.dv8tion.jda.api.utils.cache.ShardCacheView;
@@ -36,7 +37,6 @@
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import javax.annotation.CheckReturnValue;
diff --git a/src/main/java/net/dv8tion/jda/api/utils/FileProxy.java b/src/main/java/net/dv8tion/jda/api/utils/FileProxy.java
index bd7616ae38..a99cef43a7 100644
--- a/src/main/java/net/dv8tion/jda/api/utils/FileProxy.java
+++ b/src/main/java/net/dv8tion/jda/api/utils/FileProxy.java
@@ -16,8 +16,8 @@
package net.dv8tion.jda.api.utils;
import net.dv8tion.jda.api.exceptions.HttpException;
+import net.dv8tion.jda.api.requests.RestConfig;
import net.dv8tion.jda.internal.requests.FunctionalCallback;
-import net.dv8tion.jda.internal.requests.Requester;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.FutureUtil;
import net.dv8tion.jda.internal.utils.IOUtil;
@@ -135,7 +135,7 @@ protected Request getRequest(String url)
{
return new Request.Builder()
.url(url)
- .addHeader("user-agent", Requester.USER_AGENT)
+ .addHeader("user-agent", RestConfig.USER_AGENT)
.addHeader("accept-encoding", "gzip, deflate")
.build();
}
diff --git a/src/main/java/net/dv8tion/jda/api/utils/SessionController.java b/src/main/java/net/dv8tion/jda/api/utils/SessionController.java
index 582675319e..864925ac8b 100644
--- a/src/main/java/net/dv8tion/jda/api/utils/SessionController.java
+++ b/src/main/java/net/dv8tion/jda/api/utils/SessionController.java
@@ -16,7 +16,10 @@
package net.dv8tion.jda.api.utils;
+import net.dv8tion.jda.annotations.ForRemoval;
+import net.dv8tion.jda.annotations.ReplaceWith;
import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.requests.RestRateLimiter;
import javax.annotation.Nonnull;
@@ -126,21 +129,46 @@ default void setConcurrency(int level) {}
* Provides the cross-session global REST ratelimit it received through {@link #setGlobalRatelimit(long)}.
*
* @return The current global REST ratelimit or -1 if unset
+ *
+ * @deprecated Use {@link #getRateLimitHandle()} instead
*/
- long getGlobalRatelimit();
+ @Deprecated
+ @ForRemoval(deadline = "5.0.0")
+ @ReplaceWith("getRateLimitHandle().getClassic()")
+ default long getGlobalRatelimit()
+ {
+ return -1;
+ }
/**
* Called by the RateLimiter if the global rest ratelimit has changed.
*
* @param ratelimit
* The new global ratelimit
+ *
+ * @deprecated Use {@link #getRateLimitHandle()} instead
*/
- void setGlobalRatelimit(long ratelimit);
+ @Deprecated
+ @ForRemoval(deadline = "5.0.0")
+ @ReplaceWith("getRateLimitHandle().getClassic()")
+ default void setGlobalRatelimit(long ratelimit) {}
+
+ /**
+ * The store for global rate-limits of all types.
+ *
This can be used to share the global rate-limit information between shards on the same IP.
+ *
+ * @return The global rate-limiter
+ */
+ @Nonnull
+ default RestRateLimiter.GlobalRateLimit getRateLimitHandle()
+ {
+ return new GlobalRateLimitAdapter(this);
+ }
/**
* Discord's gateway URL, which is used to receive events.
*
- * Called by JDA when starting a new gateway session (Connecting, Reconnecting).
+ * Called by JDA when starting a new gateway session (Connecting, Reconnecting).
*
* @return The gateway endpoint
*/
@@ -274,4 +302,68 @@ interface SessionConnectNode
*/
void run(boolean isLast) throws InterruptedException;
}
+
+ /**
+ * Wrapper for {@link #getGlobalRatelimit()} and {@link #setGlobalRatelimit(long)}.
+ */
+ @Deprecated
+ @ForRemoval(deadline = "5.0.0")
+ @ReplaceWith("getRateLimitHandle()")
+ @SuppressWarnings("DeprecatedIsStillUsed")
+ class GlobalRateLimitAdapter implements RestRateLimiter.GlobalRateLimit
+ {
+ private final SessionController controller;
+
+ public GlobalRateLimitAdapter(@Nonnull SessionController controller)
+ {
+ SessionControllerAdapter.log.warn("Using outdated implementation of global rate-limit handling. It is recommended to use GlobalRateLimit interface instead!");
+ this.controller = controller;
+ }
+
+ /**
+ * Forwarding to {@link SessionController#getGlobalRatelimit()}
+ *
+ * @return The current global ratelimit
+ */
+ @Override
+ public long getClassic()
+ {
+ return controller.getGlobalRatelimit();
+ }
+
+ /**
+ * Forwarding to {@link SessionController#setGlobalRatelimit(long)}
+ *
+ * @param ratelimit
+ * The new global ratelimit
+ */
+ @Override
+ public void setClassic(long ratelimit)
+ {
+ controller.setGlobalRatelimit(ratelimit);
+ }
+
+ /**
+ * Forwarding to {@link SessionController#getGlobalRatelimit()}
+ *
+ * @return The current global ratelimit
+ */
+ @Override
+ public long getCloudflare()
+ {
+ return getClassic();
+ }
+
+ /**
+ * Forwarding to {@link SessionController#setGlobalRatelimit(long)}
+ *
+ * @param timestamp
+ * The new global ratelimit
+ */
+ @Override
+ public void setCloudflare(long timestamp)
+ {
+ setClassic(timestamp);
+ }
+ }
}
diff --git a/src/main/java/net/dv8tion/jda/api/utils/SessionControllerAdapter.java b/src/main/java/net/dv8tion/jda/api/utils/SessionControllerAdapter.java
index 46c64d86fb..41f4a5f0c6 100644
--- a/src/main/java/net/dv8tion/jda/api/utils/SessionControllerAdapter.java
+++ b/src/main/java/net/dv8tion/jda/api/utils/SessionControllerAdapter.java
@@ -23,9 +23,10 @@
import net.dv8tion.jda.api.exceptions.InvalidTokenException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.RestRateLimiter;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.JDALogger;
import org.slf4j.Logger;
@@ -33,21 +34,20 @@
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
public class SessionControllerAdapter implements SessionController
{
protected static final Logger log = JDALogger.getLog(SessionControllerAdapter.class);
protected final Object lock = new Object();
protected Queue connectQueue;
- protected AtomicLong globalRatelimit;
+ protected RestRateLimiter.GlobalRateLimit globalRatelimit;
protected Thread workerHandle;
protected long lastConnect = 0;
public SessionControllerAdapter()
{
connectQueue = new ConcurrentLinkedQueue<>();
- globalRatelimit = new AtomicLong(Long.MIN_VALUE);
+ globalRatelimit = RestRateLimiter.GlobalRateLimit.create();
}
@Override
@@ -64,16 +64,11 @@ public void removeSession(@Nonnull SessionConnectNode node)
connectQueue.remove(node);
}
+ @Nonnull
@Override
- public long getGlobalRatelimit()
- {
- return globalRatelimit.get();
- }
-
- @Override
- public void setGlobalRatelimit(long ratelimit)
+ public RestRateLimiter.GlobalRateLimit getRateLimitHandle()
{
- globalRatelimit.set(ratelimit);
+ return globalRatelimit;
}
@Nonnull
diff --git a/src/main/java/net/dv8tion/jda/api/utils/WidgetUtil.java b/src/main/java/net/dv8tion/jda/api/utils/WidgetUtil.java
index 8750570522..3c50a47115 100644
--- a/src/main/java/net/dv8tion/jda/api/utils/WidgetUtil.java
+++ b/src/main/java/net/dv8tion/jda/api/utils/WidgetUtil.java
@@ -15,20 +15,12 @@
*/
package net.dv8tion.jda.api.utils;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UncheckedIOException;
-import java.net.HttpURLConnection;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Widget;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
+import net.dv8tion.jda.api.requests.RestConfig;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.WidgetImpl;
-import net.dv8tion.jda.internal.requests.Requester;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.Helpers;
import net.dv8tion.jda.internal.utils.IOUtil;
@@ -36,6 +28,13 @@
import okhttp3.Request;
import okhttp3.Response;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.net.HttpURLConnection;
+
/**
* The WidgetUtil is a class for interacting with various facets of Discord's
* guild widgets
@@ -45,8 +44,8 @@
*/
public class WidgetUtil
{
- public static final String WIDGET_PNG = Requester.DISCORD_API_PREFIX + "guilds/%s/widget.png?style=%s";
- public static final String WIDGET_URL = Requester.DISCORD_API_PREFIX + "guilds/%s/widget.json";
+ public static final String WIDGET_PNG = RestConfig.DEFAULT_BASE_URL + "guilds/%s/widget.png?style=%s";
+ public static final String WIDGET_URL = RestConfig.DEFAULT_BASE_URL + "guilds/%s/widget.json";
public static final String WIDGET_HTML = "";
/**
@@ -205,7 +204,7 @@ public static Widget getWidget(long guildId) throws RateLimitedException
Request request = new Request.Builder()
.url(String.format(WIDGET_URL, guildId))
.method("GET", null)
- .header("user-agent", Requester.USER_AGENT)
+ .header("user-agent", RestConfig.USER_AGENT)
.header("accept-encoding", "gzip")
.build();
diff --git a/src/main/java/net/dv8tion/jda/internal/JDAImpl.java b/src/main/java/net/dv8tion/jda/internal/JDAImpl.java
index d42108ffe0..85594d5d79 100644
--- a/src/main/java/net/dv8tion/jda/internal/JDAImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/JDAImpl.java
@@ -47,10 +47,7 @@
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.managers.AudioManager;
import net.dv8tion.jda.api.managers.Presence;
-import net.dv8tion.jda.api.requests.GatewayIntent;
-import net.dv8tion.jda.api.requests.Request;
-import net.dv8tion.jda.api.requests.Response;
-import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.*;
import net.dv8tion.jda.api.requests.restaction.CacheRestAction;
import net.dv8tion.jda.api.requests.restaction.CommandCreateAction;
import net.dv8tion.jda.api.requests.restaction.CommandEditAction;
@@ -131,6 +128,7 @@ public class JDAImpl implements JDA
protected final ThreadingConfig threadConfig;
protected final SessionConfig sessionConfig;
protected final MetaConfig metaConfig;
+ protected final RestConfig restConfig;
public ShutdownReason shutdownReason = ShutdownReason.USER_SHUTDOWN; // indicates why shutdown happened in awaitStatus / awaitReady
protected WebSocketClient client;
@@ -155,21 +153,20 @@ public class JDAImpl implements JDA
public JDAImpl(AuthorizationConfig authConfig)
{
- this(authConfig, null, null, null);
+ this(authConfig, null, null, null, null);
}
public JDAImpl(
AuthorizationConfig authConfig, SessionConfig sessionConfig,
- ThreadingConfig threadConfig, MetaConfig metaConfig)
+ ThreadingConfig threadConfig, MetaConfig metaConfig, RestConfig restConfig)
{
this.authConfig = authConfig;
this.threadConfig = threadConfig == null ? ThreadingConfig.getDefault() : threadConfig;
this.sessionConfig = sessionConfig == null ? SessionConfig.getDefault() : sessionConfig;
this.metaConfig = metaConfig == null ? MetaConfig.getDefault() : metaConfig;
+ this.restConfig = restConfig == null ? new RestConfig() : restConfig;
this.shutdownHook = this.metaConfig.isUseShutdownHook() ? new Thread(this::shutdownNow, "JDA Shutdown Hook") : null;
this.presence = new PresenceImpl(this);
- this.requester = new Requester(this);
- this.requester.setRetryOnTimeout(this.sessionConfig.isRetryOnTimeout());
this.guildSetupController = new GuildSetupController(this);
this.audioController = new DirectAudioControllerImpl(this);
this.eventCache = new EventCache();
@@ -191,11 +188,6 @@ public boolean isEventPassthrough()
return sessionConfig.isEventPassthrough();
}
- public boolean isRelativeRateLimit()
- {
- return sessionConfig.isRelativeRateLimit();
- }
-
public boolean isCacheFlagSet(CacheFlag flag)
{
return metaConfig.getCacheFlags().contains(flag);
@@ -296,8 +288,19 @@ public int login(ShardInfo shardInfo, Compression compression, boolean validateT
public int login(String gatewayUrl, ShardInfo shardInfo, Compression compression, boolean validateToken, int intents, GatewayEncoding encoding)
{
this.shardInfo = shardInfo;
- threadConfig.init(this::getIdentifierString);
- requester.getRateLimiter().init();
+
+ // Delayed init for thread-pools so they can set the shard info as their name
+ this.threadConfig.init(this::getIdentifierString);
+ // Setup rest-module and rate-limiter subsystem
+ RestRateLimiter rateLimiter = this.restConfig.getRateLimiterFactory().apply(
+ new RestRateLimiter.RateLimitConfig(
+ this.threadConfig.getRateLimitPool(),
+ getSessionController().getRateLimitHandle(),
+ this.sessionConfig.isRelativeRateLimit() && this.restConfig.isRelativeRateLimit()
+ ));
+ this.requester = new Requester(this, this.authConfig, this.restConfig, rateLimiter);
+ this.requester.setRetryOnTimeout(this.sessionConfig.isRetryOnTimeout());
+
this.gatewayUrl = gatewayUrl == null ? getGateway() : gatewayUrl;
Checks.notNull(this.gatewayUrl, "Gateway URL");
@@ -832,7 +835,7 @@ public SelfUser getSelfUser()
@Override
public synchronized void shutdownNow()
{
- requester.shutdown(); // stop all requests
+ requester.stop(true, this::shutdownRequester); // stop all requests
shutdown();
threadConfig.shutdownNow();
}
@@ -867,8 +870,7 @@ public void shutdownInternals(ShutdownEvent event)
guildSetupController.close();
// stop accepting new requests
- if (requester.stop()) // returns true if no more requests will be executed
- shutdownRequester(); // in that case shutdown entirely
+ requester.stop(false, this::shutdownRequester);
threadConfig.shutdown();
if (shutdownHook != null)
@@ -889,7 +891,6 @@ public void shutdownInternals(ShutdownEvent event)
public void shutdownRequester()
{
// Stop all request processing
- requester.shutdown();
threadConfig.shutdownRequester();
// If the websocket has been shutdown too, we can fire the shutdown event
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/AbstractWebhookClient.java b/src/main/java/net/dv8tion/jda/internal/entities/AbstractWebhookClient.java
index f15e1dac33..456525d13d 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/AbstractWebhookClient.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/AbstractWebhookClient.java
@@ -22,6 +22,7 @@
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction;
import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction;
import net.dv8tion.jda.api.utils.AttachedFile;
@@ -29,7 +30,6 @@
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.WebhookMessageCreateActionImpl;
import net.dv8tion.jda.internal.requests.restaction.WebhookMessageEditActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
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 3c565a5376..ef18eea01d 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
@@ -49,6 +49,7 @@
import net.dv8tion.jda.api.managers.GuildWelcomeScreenManager;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.*;
import net.dv8tion.jda.api.requests.restaction.order.CategoryOrderAction;
import net.dv8tion.jda.api.requests.restaction.order.ChannelOrderAction;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/GuildVoiceStateImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/GuildVoiceStateImpl.java
index c838c3a170..2aa06c8165 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/GuildVoiceStateImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildVoiceStateImpl.java
@@ -26,10 +26,10 @@
import net.dv8tion.jda.api.entities.channel.unions.AudioChannelUnion;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.EntityString;
import net.dv8tion.jda.internal.utils.Helpers;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/InviteImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/InviteImpl.java
index 9501a44472..657026d081 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/InviteImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/InviteImpl.java
@@ -24,13 +24,13 @@
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.EntityString;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/PermissionOverrideImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/PermissionOverrideImpl.java
index 9954b14f62..c7a2c432aa 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/PermissionOverrideImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/PermissionOverrideImpl.java
@@ -22,10 +22,10 @@
import net.dv8tion.jda.api.entities.channel.attribute.IPermissionContainer;
import net.dv8tion.jda.api.entities.channel.unions.IPermissionContainerUnion;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.PermissionOverrideAction;
import net.dv8tion.jda.internal.JDAImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.requests.restaction.PermissionOverrideActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/ReceivedMessage.java b/src/main/java/net/dv8tion/jda/internal/entities/ReceivedMessage.java
index ffacef8177..cf5ffbd9b7 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/ReceivedMessage.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/ReceivedMessage.java
@@ -39,6 +39,7 @@
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.MessageEditAction;
import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction;
@@ -49,7 +50,6 @@
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.EntityString;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/RoleImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/RoleImpl.java
index 2db0c5c3b6..c7801becba 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/RoleImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/RoleImpl.java
@@ -28,6 +28,7 @@
import net.dv8tion.jda.api.exceptions.HierarchyException;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.RoleManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.RoleAction;
import net.dv8tion.jda.api.utils.cache.CacheFlag;
@@ -35,7 +36,6 @@
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IPermissionContainerMixin;
import net.dv8tion.jda.internal.managers.RoleManagerImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.EntityString;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/ScheduledEventImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/ScheduledEventImpl.java
index 16c635d154..c3c3516fdd 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/ScheduledEventImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/ScheduledEventImpl.java
@@ -16,14 +16,16 @@
package net.dv8tion.jda.internal.entities;
import net.dv8tion.jda.api.Permission;
-import net.dv8tion.jda.api.entities.*;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.ScheduledEvent;
+import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.ScheduledEventManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.pagination.ScheduledEventMembersPaginationAction;
import net.dv8tion.jda.internal.managers.ScheduledEventManagerImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.requests.restaction.pagination.ScheduledEventMembersPaginationActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/SelfUserImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/SelfUserImpl.java
index 15cdf127c5..45502a6772 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/SelfUserImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/SelfUserImpl.java
@@ -31,12 +31,6 @@ public class SelfUserImpl extends UserImpl implements SelfUser
private boolean mfaEnabled;
private long applicationId;
- //Client only
- private String email;
- private String phoneNumber;
- private boolean mobile;
- private boolean nitro;
-
public SelfUserImpl(long id, JDAImpl api)
{
super(id, api);
@@ -83,10 +77,7 @@ public boolean isMfaEnabled()
@Override
public long getAllowedFileSize()
{
- if (this.nitro) // by directly accessing the field we don't need to check the account type
- return Message.MAX_FILE_SIZE_NITRO;
- else
- return Message.MAX_FILE_SIZE;
+ return Message.MAX_FILE_SIZE;
}
@Nonnull
@@ -108,30 +99,6 @@ public SelfUserImpl setMfaEnabled(boolean enabled)
return this;
}
- public SelfUserImpl setEmail(String email)
- {
- this.email = email;
- return this;
- }
-
- public SelfUserImpl setPhoneNumber(String phoneNumber)
- {
- this.phoneNumber = phoneNumber;
- return this;
- }
-
- public SelfUserImpl setMobile(boolean mobile)
- {
- this.mobile = mobile;
- return this;
- }
-
- public SelfUserImpl setNitro(boolean nitro)
- {
- this.nitro = nitro;
- return this;
- }
-
public SelfUserImpl setApplicationId(long id)
{
this.applicationId = id;
@@ -148,10 +115,6 @@ public static SelfUserImpl copyOf(SelfUserImpl other, JDAImpl jda)
return selfUser
.setVerified(other.verified)
.setMfaEnabled(other.mfaEnabled)
- .setEmail(other.email)
- .setPhoneNumber(other.phoneNumber)
- .setMobile(other.mobile)
- .setNitro(other.nitro)
.setApplicationId(other.applicationId);
}
}
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/StageInstanceImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/StageInstanceImpl.java
index 22819dc17f..4b214ec4ec 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/StageInstanceImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/StageInstanceImpl.java
@@ -23,9 +23,9 @@
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.StageInstanceManager;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.internal.managers.StageInstanceManagerImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.EntityString;
import javax.annotation.Nonnull;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/UserImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/UserImpl.java
index b20269e261..18204d2302 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/UserImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/UserImpl.java
@@ -19,6 +19,7 @@
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.CacheRestAction;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.data.DataObject;
@@ -26,7 +27,6 @@
import net.dv8tion.jda.internal.entities.channel.concrete.PrivateChannelImpl;
import net.dv8tion.jda.internal.requests.DeferredRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.EntityString;
import net.dv8tion.jda.internal.utils.Helpers;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/WebhookImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/WebhookImpl.java
index 29f59bddca..22f74d504a 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/WebhookImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/WebhookImpl.java
@@ -24,10 +24,10 @@
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.WebhookManager;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.RestConfig;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.internal.managers.WebhookManagerImpl;
-import net.dv8tion.jda.internal.requests.Requester;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.requests.restaction.WebhookMessageCreateActionImpl;
import net.dv8tion.jda.internal.requests.restaction.WebhookMessageEditActionImpl;
@@ -139,7 +139,7 @@ public String getToken()
@Override
public String getUrl()
{
- return Requester.DISCORD_API_PREFIX + "webhooks/" + getId() + (getToken() == null ? "" : "/" + getToken());
+ return RestConfig.DEFAULT_BASE_URL + "webhooks/" + getId() + (getToken() == null ? "" : "/" + getToken());
}
@Override
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/NewsChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/NewsChannelImpl.java
index cb5d5b7508..c42b1d124c 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/NewsChannelImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/NewsChannelImpl.java
@@ -27,13 +27,13 @@
import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion;
import net.dv8tion.jda.api.managers.channel.concrete.NewsChannelManager;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.ChannelAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.GuildImpl;
import net.dv8tion.jda.internal.entities.channel.middleman.AbstractStandardGuildMessageChannelImpl;
import net.dv8tion.jda.internal.managers.channel.concrete.NewsChannelManagerImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import javax.annotation.Nonnull;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/PrivateChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/PrivateChannelImpl.java
index d23fe464ed..57fc210aa9 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/PrivateChannelImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/PrivateChannelImpl.java
@@ -21,12 +21,12 @@
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.entities.channel.AbstractChannelImpl;
import net.dv8tion.jda.internal.entities.channel.mixin.middleman.MessageChannelMixin;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/StageChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/StageChannelImpl.java
index 9e6054820a..8c2ba3c6ac 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/StageChannelImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/StageChannelImpl.java
@@ -29,6 +29,7 @@
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.channel.concrete.StageChannelManager;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.ChannelAction;
import net.dv8tion.jda.api.requests.restaction.StageInstanceAction;
import net.dv8tion.jda.api.utils.MiscUtil;
@@ -42,7 +43,6 @@
import net.dv8tion.jda.internal.entities.channel.mixin.middleman.GuildMessageChannelMixin;
import net.dv8tion.jda.internal.managers.channel.concrete.StageChannelManagerImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.StageInstanceActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java
index ddeaeb49bd..426594051f 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java
@@ -32,6 +32,7 @@
import net.dv8tion.jda.api.entities.channel.unions.IThreadContainerUnion;
import net.dv8tion.jda.api.managers.channel.concrete.ThreadChannelManager;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.CacheRestAction;
import net.dv8tion.jda.api.requests.restaction.pagination.ThreadMemberPaginationAction;
import net.dv8tion.jda.api.utils.TimeUtil;
@@ -44,7 +45,6 @@
import net.dv8tion.jda.internal.managers.channel.concrete.ThreadChannelManagerImpl;
import net.dv8tion.jda.internal.requests.DeferredRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.pagination.ThreadMemberPaginationActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.Helpers;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/ChannelMixin.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/ChannelMixin.java
index 8d1ee2cb85..7f24ef5e51 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/ChannelMixin.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/ChannelMixin.java
@@ -19,8 +19,8 @@
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.unions.ChannelUnion;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IInviteContainerMixin.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IInviteContainerMixin.java
index 6fe30ef0cc..55ead6fdd5 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IInviteContainerMixin.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IInviteContainerMixin.java
@@ -20,13 +20,13 @@
import net.dv8tion.jda.api.entities.Invite;
import net.dv8tion.jda.api.entities.channel.attribute.IInviteContainer;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.InviteAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.entities.EntityBuilder;
import net.dv8tion.jda.internal.entities.channel.mixin.middleman.GuildChannelMixin;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.InviteActionImpl;
import javax.annotation.Nonnull;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IThreadContainerMixin.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IThreadContainerMixin.java
index 4328c88d73..82924e6308 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IThreadContainerMixin.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IThreadContainerMixin.java
@@ -20,10 +20,10 @@
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
import net.dv8tion.jda.api.entities.channel.unions.IThreadContainerUnion;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction;
import net.dv8tion.jda.api.requests.restaction.pagination.ThreadChannelPaginationAction;
import net.dv8tion.jda.internal.entities.channel.mixin.middleman.GuildChannelMixin;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.ThreadChannelActionImpl;
import net.dv8tion.jda.internal.requests.restaction.pagination.ThreadChannelPaginationActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IWebhookContainerMixin.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IWebhookContainerMixin.java
index 24f04cd590..55b30d08fe 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IWebhookContainerMixin.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/attribute/IWebhookContainerMixin.java
@@ -21,6 +21,7 @@
import net.dv8tion.jda.api.entities.channel.attribute.IWebhookContainer;
import net.dv8tion.jda.api.entities.channel.unions.IWebhookContainerUnion;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.WebhookAction;
import net.dv8tion.jda.api.utils.data.DataArray;
@@ -28,7 +29,6 @@
import net.dv8tion.jda.internal.entities.EntityBuilder;
import net.dv8tion.jda.internal.entities.channel.mixin.middleman.GuildChannelMixin;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.requests.restaction.WebhookActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/GuildChannelMixin.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/GuildChannelMixin.java
index 4050b64811..c157e3a207 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/GuildChannelMixin.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/GuildChannelMixin.java
@@ -21,9 +21,9 @@
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.internal.entities.channel.mixin.ChannelMixin;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import javax.annotation.CheckReturnValue;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/GuildMessageChannelMixin.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/GuildMessageChannelMixin.java
index 8f7bc8132d..d9578d8eca 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/GuildMessageChannelMixin.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/GuildMessageChannelMixin.java
@@ -23,14 +23,13 @@
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.entities.sticker.StickerSnowflake;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.TimeUtil;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.MessageCreateActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
-import net.dv8tion.jda.internal.utils.EncodingUtil;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
@@ -70,15 +69,13 @@ default RestAction removeReactionById(@Nonnull String messageId, @Nonnull
if (!getJDA().getSelfUser().equals(user))
checkPermission(Permission.MESSAGE_MANAGE);
- final String encoded = EncodingUtil.encodeReaction(emoji.getAsReactionCode());
-
String targetUser;
if (user.equals(getJDA().getSelfUser()))
targetUser = "@me";
else
targetUser = user.getId();
- final Route.CompiledRoute route = Route.Messages.REMOVE_REACTION.compile(getId(), messageId, encoded, targetUser);
+ final Route.CompiledRoute route = Route.Messages.REMOVE_REACTION.compile(getId(), messageId, emoji.getAsReactionCode(), targetUser);
return new RestActionImpl<>(getJDA(), route);
}
@@ -103,8 +100,7 @@ default RestAction clearReactionsById(@Nonnull String messageId, @Nonnull
checkPermission(Permission.MESSAGE_MANAGE);
- String code = EncodingUtil.encodeReaction(emoji.getAsReactionCode());
- Route.CompiledRoute route = Route.Messages.CLEAR_EMOJI_REACTIONS.compile(getId(), messageId, code);
+ Route.CompiledRoute route = Route.Messages.CLEAR_EMOJI_REACTIONS.compile(getId(), messageId, emoji.getAsReactionCode());
return new RestActionImpl<>(getJDA(), route);
}
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/MessageChannelMixin.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/MessageChannelMixin.java
index 286b02abd2..8f33c5990d 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/MessageChannelMixin.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/mixin/middleman/MessageChannelMixin.java
@@ -28,6 +28,7 @@
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
import net.dv8tion.jda.api.requests.restaction.MessageEditAction;
@@ -40,7 +41,6 @@
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/emoji/RichCustomEmojiImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/emoji/RichCustomEmojiImpl.java
index 6f2b6ea7e4..31e9573d32 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/emoji/RichCustomEmojiImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/emoji/RichCustomEmojiImpl.java
@@ -27,6 +27,7 @@
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.CustomEmojiManager;
import net.dv8tion.jda.api.requests.ErrorResponse;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.CacheRestAction;
import net.dv8tion.jda.api.utils.data.DataObject;
@@ -35,7 +36,6 @@
import net.dv8tion.jda.internal.managers.CustomEmojiManagerImpl;
import net.dv8tion.jda.internal.requests.DeferredRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.utils.EntityString;
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/sticker/GuildStickerImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/sticker/GuildStickerImpl.java
index 838ac24c06..d2fc96eca4 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/sticker/GuildStickerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/sticker/GuildStickerImpl.java
@@ -24,6 +24,7 @@
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.GuildStickerManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.ErrorResponse;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.CacheRestAction;
@@ -32,7 +33,6 @@
import net.dv8tion.jda.internal.managers.GuildStickerManagerImpl;
import net.dv8tion.jda.internal.requests.DeferredRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.utils.EntityString;
import net.dv8tion.jda.internal.utils.Helpers;
diff --git a/src/main/java/net/dv8tion/jda/internal/interactions/InteractionHookImpl.java b/src/main/java/net/dv8tion/jda/internal/interactions/InteractionHookImpl.java
index 0b6ba9fe7d..30fe894f74 100644
--- a/src/main/java/net/dv8tion/jda/internal/interactions/InteractionHookImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/interactions/InteractionHookImpl.java
@@ -18,13 +18,15 @@
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.exceptions.InteractionExpiredException;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.entities.AbstractWebhookClient;
-import net.dv8tion.jda.internal.requests.Route;
+import net.dv8tion.jda.internal.entities.ReceivedMessage;
import net.dv8tion.jda.internal.requests.restaction.TriggerRestAction;
import net.dv8tion.jda.internal.requests.restaction.WebhookMessageCreateActionImpl;
import net.dv8tion.jda.internal.requests.restaction.WebhookMessageEditActionImpl;
@@ -135,8 +137,9 @@ public RestAction retrieveOriginal()
{
JDAImpl jda = (JDAImpl) getJDA();
Route.CompiledRoute route = Route.Interactions.GET_ORIGINAL.compile(jda.getSelfUser().getApplicationId(), interaction.getToken());
- return onReady(new TriggerRestAction<>(jda, route, (response, request) ->
- jda.getEntityBuilder().createMessageWithChannel(response.getObject(), getInteraction().getMessageChannel(), false)));
+ TriggerRestAction action = new TriggerRestAction<>(jda, route, (response, request) -> createMessage(jda, response.getObject()));
+ action.setCheck(this::checkExpired);
+ return onReady(action);
}
@Nonnull
@@ -145,8 +148,10 @@ public WebhookMessageCreateActionImpl sendRequest()
{
Route.CompiledRoute route = Route.Interactions.CREATE_FOLLOWUP.compile(getJDA().getSelfUser().getApplicationId(), interaction.getToken());
route = route.withQueryParams("wait", "true");
- Function transform = (json) -> ((JDAImpl) api).getEntityBuilder().createMessageWithChannel(json, getInteraction().getMessageChannel(), false);
- return onReady(new WebhookMessageCreateActionImpl<>(getJDA(), route, transform)).setEphemeral(ephemeral);
+ Function transform = (json) -> createMessage((JDAImpl) api, json);
+ WebhookMessageCreateActionImpl action = new WebhookMessageCreateActionImpl<>(getJDA(), route, transform).setEphemeral(ephemeral);
+ action.setCheck(this::checkExpired);
+ return onReady(action);
}
@Nonnull
@@ -157,8 +162,10 @@ public WebhookMessageEditActionImpl editRequest(String messageId)
Checks.isSnowflake(messageId);
Route.CompiledRoute route = Route.Interactions.EDIT_FOLLOWUP.compile(getJDA().getSelfUser().getApplicationId(), interaction.getToken(), messageId);
route = route.withQueryParams("wait", "true");
- Function transform = (json) -> ((JDAImpl) api).getEntityBuilder().createMessageWithChannel(json, getInteraction().getMessageChannel(), false);
- return onReady(new WebhookMessageEditActionImpl<>(getJDA(), route, transform));
+ Function transform = (json) -> createMessage((JDAImpl) api, json);
+ WebhookMessageEditActionImpl action = new WebhookMessageEditActionImpl<>(getJDA(), route, transform);
+ action.setCheck(this::checkExpired);
+ return onReady(action);
}
@Nonnull
@@ -168,6 +175,20 @@ public RestAction deleteMessageById(@Nonnull String messageId)
if (!"@original".equals(messageId))
Checks.isSnowflake(messageId);
Route.CompiledRoute route = Route.Interactions.DELETE_FOLLOWUP.compile(getJDA().getSelfUser().getApplicationId(), interaction.getToken(), messageId);
- return onReady(new TriggerRestAction<>(getJDA(), route));
+ TriggerRestAction action = new TriggerRestAction<>(getJDA(), route);
+ action.setCheck(this::checkExpired);
+ return onReady(action);
+ }
+
+ private ReceivedMessage createMessage(JDAImpl jda, DataObject json)
+ {
+ return jda.getEntityBuilder().createMessageWithChannel(json, getInteraction().getMessageChannel(), false);
+ }
+
+ private boolean checkExpired()
+ {
+ if (isExpired())
+ throw new InteractionExpiredException();
+ return true;
}
}
diff --git a/src/main/java/net/dv8tion/jda/internal/interactions/command/CommandImpl.java b/src/main/java/net/dv8tion/jda/internal/interactions/command/CommandImpl.java
index 0de2302905..1300583913 100644
--- a/src/main/java/net/dv8tion/jda/internal/interactions/command/CommandImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/interactions/command/CommandImpl.java
@@ -24,12 +24,12 @@
import net.dv8tion.jda.api.interactions.commands.localization.LocalizationMap;
import net.dv8tion.jda.api.interactions.commands.privileges.IntegrationPrivilege;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.CommandEditAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.CommandEditActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.EntityString;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/AccountManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/AccountManagerImpl.java
index cefb1f69bf..88595c1653 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/AccountManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/AccountManagerImpl.java
@@ -21,8 +21,8 @@
import net.dv8tion.jda.api.managers.AccountManager;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/CustomEmojiManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/CustomEmojiManagerImpl.java
index e53ae15f79..9228ed8e61 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/CustomEmojiManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/CustomEmojiManagerImpl.java
@@ -21,10 +21,10 @@
import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.CustomEmojiManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.emoji.RichCustomEmojiImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/GuildManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/GuildManagerImpl.java
index 177664d665..3089406239 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/GuildManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/GuildManagerImpl.java
@@ -23,8 +23,8 @@
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.GuildManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/GuildStickerManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/GuildStickerManagerImpl.java
index 1d332a0809..cd40866f2b 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/GuildStickerManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/GuildStickerManagerImpl.java
@@ -5,8 +5,8 @@
import net.dv8tion.jda.api.entities.sticker.StickerSnowflake;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.GuildStickerManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/GuildWelcomeScreenManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/GuildWelcomeScreenManagerImpl.java
index 63017ccbb4..2682071b02 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/GuildWelcomeScreenManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/GuildWelcomeScreenManagerImpl.java
@@ -21,9 +21,9 @@
import net.dv8tion.jda.api.entities.GuildWelcomeScreen;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.GuildWelcomeScreenManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/ManagerBase.java b/src/main/java/net/dv8tion/jda/internal/managers/ManagerBase.java
index 578bec2753..cb84543334 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/ManagerBase.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/ManagerBase.java
@@ -19,7 +19,7 @@
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
import net.dv8tion.jda.api.managers.Manager;
-import net.dv8tion.jda.internal.requests.Route;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/PermOverrideManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/PermOverrideManagerImpl.java
index ab92c11afb..60e4837162 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/PermOverrideManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/PermOverrideManagerImpl.java
@@ -22,9 +22,9 @@
import net.dv8tion.jda.api.entities.channel.attribute.IPermissionContainer;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.PermOverrideManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IPermissionContainerMixin;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/RoleManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/RoleManagerImpl.java
index adf8984b26..9da09421ba 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/RoleManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/RoleManagerImpl.java
@@ -24,8 +24,8 @@
import net.dv8tion.jda.api.exceptions.HierarchyException;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.RoleManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.PermissionUtil;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/ScheduledEventManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/ScheduledEventManagerImpl.java
index 5950f2364c..ccf2757e2e 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/ScheduledEventManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/ScheduledEventManagerImpl.java
@@ -16,14 +16,14 @@
package net.dv8tion.jda.internal.managers;
-import net.dv8tion.jda.api.entities.ScheduledEvent;
import net.dv8tion.jda.api.entities.Icon;
+import net.dv8tion.jda.api.entities.ScheduledEvent;
import net.dv8tion.jda.api.entities.channel.concrete.StageChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.managers.ScheduledEventManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.Helpers;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/StageInstanceManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/StageInstanceManagerImpl.java
index 5eeec4f961..8a5825a676 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/StageInstanceManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/StageInstanceManagerImpl.java
@@ -18,8 +18,8 @@
import net.dv8tion.jda.api.entities.StageInstance;
import net.dv8tion.jda.api.managers.StageInstanceManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/TemplateManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/TemplateManagerImpl.java
index dd324cdcdf..779ee9c0c2 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/TemplateManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/TemplateManagerImpl.java
@@ -22,8 +22,8 @@
import net.dv8tion.jda.api.entities.templates.Template;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.TemplateManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/WebhookManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/WebhookManagerImpl.java
index bb59b69867..353fa27eb7 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/WebhookManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/WebhookManagerImpl.java
@@ -24,8 +24,8 @@
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.WebhookManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/channel/ChannelManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/channel/ChannelManagerImpl.java
index 3d4f644824..af9cc36424 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/channel/ChannelManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/channel/ChannelManagerImpl.java
@@ -38,12 +38,12 @@
import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.managers.channel.ChannelManager;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IPermissionContainerMixin;
import net.dv8tion.jda.internal.entities.channel.mixin.middleman.GuildChannelMixin;
import net.dv8tion.jda.internal.managers.ManagerBase;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.requests.restaction.PermOverrideData;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.PermissionUtil;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/RateLimiter.java b/src/main/java/net/dv8tion/jda/internal/requests/RateLimiter.java
deleted file mode 100644
index ed49168058..0000000000
--- a/src/main/java/net/dv8tion/jda/internal/requests/RateLimiter.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.internal.requests;
-
-import net.dv8tion.jda.api.requests.Request;
-import net.dv8tion.jda.internal.utils.JDALogger;
-import org.slf4j.Logger;
-
-import java.util.Iterator;
-
-@SuppressWarnings("rawtypes")
-public abstract class RateLimiter
-{
- //Implementations of this class exist in the net.dv8tion.jda.api.requests.ratelimit package.
- protected static final Logger log = JDALogger.getLog(RateLimiter.class);
- protected final Requester requester;
- protected volatile boolean isShutdown = false, isStopped = false;
-
- protected RateLimiter(Requester requester)
- {
- this.requester = requester;
- }
-
- protected boolean isSkipped(Iterator it, Request request)
- {
- if (request.isSkipped())
- {
- cancel(it, request);
- return true;
- }
- return false;
- }
-
- private void cancel(Iterator it, Request request)
- {
- request.onCancelled();
- it.remove();
- }
-
- // -- Required Implementations --
- public abstract Long getRateLimit(Route.CompiledRoute route);
- protected abstract void queueRequest(Request request);
- protected abstract Long handleResponse(Route.CompiledRoute route, okhttp3.Response response);
-
-
- // --- Default Implementations --
-
- public boolean isRateLimited(Route.CompiledRoute route)
- {
- Long rateLimit = getRateLimit(route);
- return rateLimit != null && rateLimit > 0L;
- }
-
- public abstract int cancelRequests();
-
- public void init() {}
-
- // Return true if no more requests will be processed
- protected boolean stop()
- {
- isStopped = true;
- return true;
- }
-
- protected void shutdown()
- {
- isShutdown = true;
- stop();
- }
-}
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/Requester.java b/src/main/java/net/dv8tion/jda/internal/requests/Requester.java
index f13edaf641..c9c2fc7c46 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/Requester.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/Requester.java
@@ -17,12 +17,8 @@
package net.dv8tion.jda.internal.requests;
import net.dv8tion.jda.api.JDA;
-import net.dv8tion.jda.api.JDAInfo;
-import net.dv8tion.jda.api.requests.Request;
-import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.*;
import net.dv8tion.jda.internal.JDAImpl;
-import net.dv8tion.jda.internal.requests.ratelimit.BotRateLimiter;
-import net.dv8tion.jda.internal.utils.Helpers;
import net.dv8tion.jda.internal.utils.JDALogger;
import net.dv8tion.jda.internal.utils.config.AuthorizationConfig;
import okhttp3.Call;
@@ -33,23 +29,23 @@
import org.slf4j.Logger;
import org.slf4j.MDC;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import javax.net.ssl.SSLPeerUnverifiedException;
import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
-import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.RejectedExecutionException;
+import java.util.function.Consumer;
public class Requester
{
public static final Logger LOG = JDALogger.getLog(Requester.class);
- public static final String DISCORD_API_PREFIX = Helpers.format("https://discord.com/api/v%d/", JDAInfo.DISCORD_REST_VERSION);
- public static final String USER_AGENT = "DiscordBot (" + JDAInfo.GITHUB + ", " + JDAInfo.VERSION + ")";
@SuppressWarnings("deprecation")
public static final RequestBody EMPTY_BODY = RequestBody.create(null, new byte[0]);
public static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
@@ -59,7 +55,10 @@ public class Requester
protected final JDAImpl api;
protected final AuthorizationConfig authConfig;
- private final RateLimiter rateLimiter;
+ private final RestRateLimiter rateLimiter;
+ private final String baseUrl;
+ private final String userAgent;
+ private final Consumer super okhttp3.Request.Builder> customBuilder;
private final OkHttpClient httpClient;
@@ -69,19 +68,17 @@ public class Requester
private volatile boolean retryOnTimeout = false;
- public Requester(JDA api)
- {
- this(api, ((JDAImpl) api).getAuthorizationConfig());
- }
-
- public Requester(JDA api, AuthorizationConfig authConfig)
+ public Requester(JDA api, AuthorizationConfig authConfig, RestConfig config, RestRateLimiter rateLimiter)
{
if (authConfig == null)
throw new NullPointerException("Provided config was null!");
this.authConfig = authConfig;
this.api = (JDAImpl) api;
- this.rateLimiter = new BotRateLimiter(this);
+ this.rateLimiter = rateLimiter;
+ this.baseUrl = config.getBaseUrl();
+ this.userAgent = config.getUserAgent();
+ this.customBuilder = config.getCustomBuilder();
this.httpClient = this.api.getHttpClient();
}
@@ -106,13 +103,13 @@ public JDAImpl getJDA()
public void request(Request apiRequest)
{
- if (rateLimiter.isStopped)
+ if (rateLimiter.isStopped())
throw new RejectedExecutionException("The Requester has been stopped! No new requests can be requested!");
if (apiRequest.shouldQueue())
- rateLimiter.queueRequest(apiRequest);
+ rateLimiter.enqueue(new WorkTask(apiRequest));
else
- execute(apiRequest, true);
+ execute(new WorkTask(apiRequest), true);
}
private static boolean isRetry(Throwable e)
@@ -122,15 +119,15 @@ private static boolean isRetry(Throwable e)
|| e instanceof SSLPeerUnverifiedException; // SSL Certificate was wrong
}
- public Long execute(Request> apiRequest)
+ public okhttp3.Response execute(WorkTask task)
{
- return execute(apiRequest, false);
+ return execute(task, false);
}
/**
* Used to execute a Request. Processes request related to provided bucket.
*
- * @param apiRequest
+ * @param task
* The API request that needs to be sent
* @param handleOnRateLimit
* Whether to forward rate-limits, false if rate limit handling should take over
@@ -139,29 +136,35 @@ public Long execute(Request> apiRequest)
* the request can be made again. This could either be for the Per-Route ratelimit or the Global ratelimit.
*
Check if globalCooldown is {@code null} to determine if it was Per-Route or Global.
*/
- public Long execute(Request> apiRequest, boolean handleOnRateLimit)
+ public okhttp3.Response execute(WorkTask task, boolean handleOnRateLimit)
{
- return execute(apiRequest, false, handleOnRateLimit);
+ return execute(task, false, handleOnRateLimit);
}
- public Long execute(Request> apiRequest, boolean retried, boolean handleOnRatelimit)
+ public okhttp3.Response execute(WorkTask task, boolean retried, boolean handleOnRatelimit)
{
- Route.CompiledRoute route = apiRequest.getRoute();
- Long retryAfter = rateLimiter.getRateLimit(route);
- if (retryAfter != null && retryAfter > 0)
- {
- if (handleOnRatelimit)
- apiRequest.handleResponse(new Response(retryAfter, Collections.emptySet()));
- return retryAfter;
- }
+ Route.CompiledRoute route = task.getRoute();
okhttp3.Request.Builder builder = new okhttp3.Request.Builder();
- String url = DISCORD_API_PREFIX + route.getCompiledRoute();
+ String url = baseUrl + route.getCompiledRoute();
builder.url(url);
+ Request> apiRequest = task.request;
+
applyBody(apiRequest, builder);
- applyHeaders(apiRequest, builder, url.startsWith(DISCORD_API_PREFIX));
+ applyHeaders(apiRequest, builder);
+ if (customBuilder != null)
+ {
+ try
+ {
+ customBuilder.accept(builder);
+ }
+ catch (Exception e)
+ {
+ LOG.error("Custom request builder caused exception", e);
+ }
+ }
okhttp3.Request request = builder.build();
@@ -172,7 +175,8 @@ public Long execute(Request> apiRequest, boolean retried, boolean handleOnRate
okhttp3.Response lastResponse = null;
try
{
- LOG.trace("Executing request {} {}", apiRequest.getRoute().getMethod(), url);
+ LOG.trace("Executing request {} {}", task.getRoute().getMethod(), url);
+ int code = 0;
for (int attempt = 0; attempt < responses.length; attempt++)
{
if (apiRequest.isSkipped())
@@ -180,18 +184,19 @@ public Long execute(Request> apiRequest, boolean retried, boolean handleOnRate
Call call = httpClient.newCall(request);
lastResponse = call.execute();
+ code = lastResponse.code();
responses[attempt] = lastResponse;
String cfRay = lastResponse.header("CF-RAY");
if (cfRay != null)
rays.add(cfRay);
// Retry a few specific server errors that are related to server issues
- if (!shouldRetry(lastResponse.code()))
+ if (!shouldRetry(code))
break;
LOG.debug("Requesting {} -> {} returned status {}... retrying (attempt {})",
apiRequest.getRoute().getMethod(),
- url, lastResponse.code(), attempt + 1);
+ url, code, attempt + 1);
try
{
Thread.sleep(500 << attempt);
@@ -202,26 +207,32 @@ public Long execute(Request> apiRequest, boolean retried, boolean handleOnRate
}
}
- LOG.trace("Finished Request {} {} with code {}", route.getMethod(), lastResponse.request().url(), lastResponse.code());
+ LOG.trace("Finished Request {} {} with code {}", route.getMethod(), lastResponse.request().url(), code);
- if (shouldRetry(lastResponse.code()))
+ if (shouldRetry(code))
{
//Epic failure from other end. Attempted 4 times.
Response response = new Response(lastResponse, -1, rays);
apiRequest.handleResponse(response);
+ task.done = true;
return null;
}
- retryAfter = rateLimiter.handleResponse(route, lastResponse);
if (!rays.isEmpty())
LOG.debug("Received response with following cf-rays: {}", rays);
- if (retryAfter == null)
- apiRequest.handleResponse(new Response(lastResponse, -1, rays));
- else if (handleOnRatelimit)
+ if (handleOnRatelimit && code == 429)
+ {
+ long retryAfter = parseRetry(lastResponse);
+ task.done = true;
apiRequest.handleResponse(new Response(lastResponse, retryAfter, rays));
+ }
+ else if (code != 429)
+ {
+ task.handleResponse(lastResponse, rays);
+ }
- return retryAfter;
+ return lastResponse;
}
catch (UnknownHostException e)
{
@@ -232,7 +243,7 @@ else if (handleOnRatelimit)
catch (IOException e)
{
if (retryOnTimeout && !retried && isRetry(e))
- return execute(apiRequest, true, handleOnRatelimit);
+ return execute(task, true, handleOnRatelimit);
LOG.error("There was an I/O error while executing a REST request: {}", e.getMessage());
apiRequest.handleResponse(new Response(e, rays));
return null;
@@ -265,17 +276,13 @@ private void applyBody(Request> apiRequest, okhttp3.Request.Builder builder)
builder.method(method, body);
}
- private void applyHeaders(Request> apiRequest, okhttp3.Request.Builder builder, boolean authorized)
+ private void applyHeaders(Request> apiRequest, okhttp3.Request.Builder builder)
{
- builder.header("user-agent", USER_AGENT)
+ builder.header("user-agent", userAgent)
.header("accept-encoding", "gzip")
+ .header("authorization", authConfig.getToken())
.header("x-ratelimit-precision", "millisecond"); // still sending this in case of regressions
- //adding token to all requests to the discord api or cdn pages
- //we can check for startsWith(DISCORD_API_PREFIX) because the cdn endpoints don't need any kind of authorization
- if (authorized)
- builder.header("authorization", authConfig.getToken());
-
// Apply custom headers like X-Audit-Log-Reason
// If customHeaders is null this does nothing
if (apiRequest.getHeaders() != null)
@@ -290,7 +297,7 @@ public OkHttpClient getHttpClient()
return this.httpClient;
}
- public RateLimiter getRateLimiter()
+ public RestRateLimiter getRateLimiter()
{
return rateLimiter;
}
@@ -300,18 +307,87 @@ public void setRetryOnTimeout(boolean retryOnTimeout)
this.retryOnTimeout = retryOnTimeout;
}
- public boolean stop()
+ public void stop(boolean shutdown, Runnable callback)
{
- return rateLimiter.stop();
+ rateLimiter.stop(shutdown, callback);
}
- public void shutdown()
+ private static boolean shouldRetry(int code)
{
- rateLimiter.shutdown();
+ return code == 502 || code == 504 || code == 529;
}
- private static boolean shouldRetry(int code)
+ private long parseRetry(okhttp3.Response response)
{
- return code == 502 || code == 504 || code == 529;
+ String retryAfter = response.header(RestRateLimiter.RETRY_AFTER_HEADER, "0");
+ return (long) (Double.parseDouble(retryAfter) * 1000);
+ }
+
+ private class WorkTask implements RestRateLimiter.Work
+ {
+ private final Request> request;
+ private boolean done;
+
+ private WorkTask(Request> request)
+ {
+ this.request = request;
+ }
+
+ @Nonnull
+ @Override
+ public Route.CompiledRoute getRoute()
+ {
+ return request.getRoute();
+ }
+
+ @Nonnull
+ @Override
+ public JDA getJDA()
+ {
+ return request.getJDA();
+ }
+
+ @Nullable
+ @Override
+ public okhttp3.Response execute()
+ {
+ return Requester.this.execute(this);
+ }
+
+ @Override
+ public boolean isSkipped()
+ {
+ return request.isSkipped();
+ }
+
+ @Override
+ public boolean isDone()
+ {
+ return isSkipped() || done;
+ }
+
+ @Override
+ public boolean isPriority()
+ {
+ return request.isPriority();
+ }
+
+ @Override
+ public boolean isCancelled()
+ {
+ return request.isCancelled();
+ }
+
+ @Override
+ public void cancel()
+ {
+ request.cancel();
+ }
+
+ private void handleResponse(okhttp3.Response response, Set rays)
+ {
+ done = true;
+ request.handleResponse(new Response(response, -1, rays));
+ }
}
}
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/RestActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/RestActionImpl.java
index 749682168a..3184d67d1c 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/RestActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/RestActionImpl.java
@@ -19,10 +19,7 @@
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
-import net.dv8tion.jda.api.requests.Request;
-import net.dv8tion.jda.api.requests.Response;
-import net.dv8tion.jda.api.requests.RestAction;
-import net.dv8tion.jda.api.requests.RestFuture;
+import net.dv8tion.jda.api.requests.*;
import net.dv8tion.jda.api.utils.AttachedFile;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/ratelimit/BotRateLimiter.java b/src/main/java/net/dv8tion/jda/internal/requests/ratelimit/BotRateLimiter.java
deleted file mode 100644
index 1de1c42bbe..0000000000
--- a/src/main/java/net/dv8tion/jda/internal/requests/ratelimit/BotRateLimiter.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * 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.internal.requests.ratelimit;
-
-import net.dv8tion.jda.api.requests.Request;
-import net.dv8tion.jda.api.utils.MiscUtil;
-import net.dv8tion.jda.internal.requests.RateLimiter;
-import net.dv8tion.jda.internal.requests.Requester;
-import net.dv8tion.jda.internal.requests.Route;
-import okhttp3.Headers;
-import org.jetbrains.annotations.Contract;
-
-import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.ReentrantLock;
-
-/*
-
-** How does it work? **
-
-A bucket is determined via the Path+Method+Major in the following way:
-
- 1. Get Hash from Path+Method (we call this route)
- 2. Get bucket from Hash+Major (we call this bucketid)
-
-If no hash is known we default to the constant "unlimited" hash. The hash is loaded from HTTP responses using the "X-RateLimit-Bucket" response header.
-This hash is per Method+Path and can be stored indefinitely once received.
-Some endpoints don't return a hash, this means that the endpoint is **unlimited** and will be in queue with only the major parameter.
-
-To explain this further, lets look at the example of message history. The endpoint to fetch message history is "GET/channels/{channel.id}/messages".
-This endpoint does not have any rate limit (unlimited) and will thus use the hash "unlimited+GET/channels/{channel.id}/messages".
-The bucket id for this will be "unlimited+GET/channels/{channel.id}/messages:guild_id:{channel.id}:webhook_id" where "{channel.id}" would be replaced with the respective id.
-This means you can fetch history concurrently for multiple channels but it will be in sequence for the same channel.
-
-If the endpoint is not unlimited we will receive a hash on the first response.
-Once this happens every unlimited bucket will start moving its queue to the correct bucket.
-This is done during the queue work iteration so many requests to one endpoint would be moved correctly.
-
-For example, the first message sending:
-
- public void onReady(ReadyEvent event) {
- TextChannel channel = event.getJDA().getTextChannelById("123");
- for (int i = 1; i <= 100; i++) {
- channel.sendMessage("Message: " + i).queue();
- }
- }
-
-This will send 100 messages on startup. At this point we don't yet know the hash for this route so we put them all in "unlimited+POST/channels/{channel.id}/messages:guild_id:123:webhook_id".
-The bucket iterates the requests in sync and gets the first response. This response provides the hash for this route and we create a bucket for it.
-Once the response is handled we continue with the next request in the unlimited bucket and notice the new bucket. We then move all related requests to this bucket.
-
- */
-public class BotRateLimiter extends RateLimiter
-{
- private static final String RESET_AFTER_HEADER = "X-RateLimit-Reset-After";
- private static final String RESET_HEADER = "X-RateLimit-Reset";
- private static final String LIMIT_HEADER = "X-RateLimit-Limit";
- private static final String REMAINING_HEADER = "X-RateLimit-Remaining";
- private static final String GLOBAL_HEADER = "X-RateLimit-Global";
- private static final String HASH_HEADER = "X-RateLimit-Bucket";
- private static final String RETRY_AFTER_HEADER = "Retry-After";
- private static final String UNLIMITED_BUCKET = "unlimited"; // we generate an unlimited bucket for every major parameter configuration
-
- private final ReentrantLock bucketLock = new ReentrantLock();
- // Route -> Should we print warning for 429? AKA did we already hit it once before
- private final Set hitRatelimit = ConcurrentHashMap.newKeySet(5);
- // Route -> Hash
- private final Map hashes = new ConcurrentHashMap<>();
- // Hash + Major Parameter -> Bucket
- private final Map buckets = new ConcurrentHashMap<>();
- // Bucket -> Rate-Limit Worker
- private final Map> rateLimitQueue = new ConcurrentHashMap<>();
- private Future> cleanupWorker;
-
- public BotRateLimiter(Requester requester)
- {
- super(requester);
- }
-
- @Override
- public void init()
- {
- cleanupWorker = getScheduler().scheduleAtFixedRate(this::cleanup, 30, 30, TimeUnit.SECONDS);
- }
-
- private ScheduledExecutorService getScheduler()
- {
- return requester.getJDA().getRateLimitPool();
- }
-
- @Override
- public int cancelRequests()
- {
- return MiscUtil.locked(bucketLock, () -> {
- // Empty buckets will be removed by the cleanup worker, which also checks for rate limit parameters
- AtomicInteger count = new AtomicInteger(0);
- buckets.values()
- .stream()
- .map(Bucket::getRequests)
- .flatMap(Collection::stream)
- .filter(request -> !request.isPriority() && !request.isCancelled())
- .forEach(request -> {
- request.cancel();
- count.incrementAndGet();
- });
-
- int cancelled = count.get();
- if (cancelled == 1)
- RateLimiter.log.warn("Cancelled 1 request!");
- else if (cancelled > 1)
- RateLimiter.log.warn("Cancelled {} requests!", cancelled);
- return cancelled;
- });
- }
-
- private void cleanup()
- {
- // This will remove buckets that are no longer needed every 30 seconds to avoid memory leakage
- // We will keep the hashes in memory since they are very limited (by the amount of possible routes)
- MiscUtil.locked(bucketLock, () -> {
- int size = buckets.size();
- Iterator> entries = buckets.entrySet().iterator();
-
- while (entries.hasNext())
- {
- Map.Entry entry = entries.next();
- Bucket bucket = entry.getValue();
- if (isShutdown)
- {
- bucket.requests.forEach(Request::cancel);
- bucket.requests.clear();
- entries.remove();
- continue;
- }
- else
- {
- // Remove cancelled requests
- bucket.requests.removeIf(Request::isSkipped);
- }
-
- // Check if the bucket is empty
- if (bucket.isUnlimited() && bucket.requests.isEmpty())
- entries.remove(); // remove unlimited if requests are empty
- // If the requests of the bucket are drained and the reset is expired the bucket has no valuable information
- else if (bucket.requests.isEmpty() && bucket.reset <= getNow())
- entries.remove();
- // Remove empty buckets when the rate limiter is stopped
- else if (bucket.requests.isEmpty() && isStopped)
- entries.remove();
- }
- // Log how many buckets were removed
- size -= buckets.size();
- if (size > 0)
- log.debug("Removed {} expired buckets", size);
- });
- }
-
- private String getRouteHash(Route route)
- {
- return hashes.getOrDefault(route, UNLIMITED_BUCKET + "+" + route);
- }
-
- @Override
- protected boolean stop()
- {
- return MiscUtil.locked(bucketLock, () -> {
- if (!isStopped)
- {
- super.stop();
- if (cleanupWorker != null)
- cleanupWorker.cancel(false);
- cleanup();
- int size = buckets.size();
- if (!isShutdown && size > 0) // Tell user about active buckets so they don't get confused by the longer shutdown
- {
- int average = (int) Math.ceil(
- buckets.values().stream()
- .map(Bucket::getRequests)
- .mapToInt(Collection::size)
- .average().orElse(0)
- );
-
- log.info("Waiting for {} bucket(s) to finish. Average queue size of {} requests", size, average);
- }
- }
-
- // No more requests to process?
- return buckets.isEmpty();
- });
- }
-
- @Override
- public Long getRateLimit(Route.CompiledRoute route)
- {
- Bucket bucket = getBucket(route, false);
- return bucket == null ? 0L : bucket.getRateLimit();
- }
-
- @Override
- @SuppressWarnings("rawtypes")
- protected void queueRequest(Request request)
- {
- // Create bucket and enqueue request
- MiscUtil.locked(bucketLock, () -> {
- Bucket bucket = getBucket(request.getRoute(), true);
- bucket.enqueue(request);
- runBucket(bucket);
- });
- }
-
- @Override
- protected Long handleResponse(Route.CompiledRoute route, okhttp3.Response response)
- {
- return MiscUtil.locked(bucketLock, () -> {
- long rateLimit = updateBucket(route, response).getRateLimit();
- if (response.code() == 429)
- return rateLimit;
- else
- return null;
- });
- }
-
- private Bucket updateBucket(Route.CompiledRoute route, okhttp3.Response response)
- {
- return MiscUtil.locked(bucketLock, () -> {
- try
- {
- Bucket bucket = getBucket(route, true);
- Headers headers = response.headers();
-
- boolean global = headers.get(GLOBAL_HEADER) != null;
- boolean cloudflare = headers.get("via") == null;
- String hash = headers.get(HASH_HEADER);
- long now = getNow();
-
- // Create a new bucket for the hash if needed
- Route baseRoute = route.getBaseRoute();
- if (hash != null)
- {
- if (!this.hashes.containsKey(baseRoute))
- {
- this.hashes.put(baseRoute, hash);
- log.debug("Caching bucket hash {} -> {}", baseRoute, hash);
- }
-
- bucket = getBucket(route, true);
- }
-
- if (response.code() == 429)
- {
- String retryAfterHeader = headers.get(RETRY_AFTER_HEADER);
- long retryAfter = parseLong(retryAfterHeader) * 1000; // seconds precision
- // Handle global rate limit if necessary
- if (global)
- {
- requester.getJDA().getSessionController().setGlobalRatelimit(now + retryAfter);
- log.error("Encountered global rate limit! Retry-After: {} ms", retryAfter);
- }
- // Handle cloudflare rate limits, this applies to all routes and uses seconds for retry-after
- else if (cloudflare)
- {
- requester.getJDA().getSessionController().setGlobalRatelimit(now + retryAfter);
- log.error("Encountered cloudflare rate limit! Retry-After: {} s", retryAfter / 1000);
- }
- // Handle hard rate limit, pretty much just log that it happened
- else
- {
- boolean firstHit = hitRatelimit.add(baseRoute) && retryAfter < 60000;
- // Update the bucket to the new information
- bucket.remaining = 0;
- bucket.reset = getNow() + retryAfter;
- // don't log warning if we hit the rate limit for the first time, likely due to initialization of the bucket
- // unless its a long retry-after delay (more than a minute)
- if (firstHit)
- log.debug("Encountered 429 on route {} with bucket {} Retry-After: {} ms", baseRoute, bucket.bucketId, retryAfter);
- else
- log.warn("Encountered 429 on route {} with bucket {} Retry-After: {} ms", baseRoute, bucket.bucketId, retryAfter);
- }
- return bucket;
- }
-
- // If hash is null this means we didn't get enough information to update a bucket
- if (hash == null)
- return bucket;
-
- // Update the bucket parameters with new information
- String limitHeader = headers.get(LIMIT_HEADER);
- String remainingHeader = headers.get(REMAINING_HEADER);
- String resetAfterHeader = headers.get(RESET_AFTER_HEADER);
- String resetHeader = headers.get(RESET_HEADER);
-
- bucket.limit = (int) Math.max(1L, parseLong(limitHeader));
- bucket.remaining = (int) parseLong(remainingHeader);
- if (requester.getJDA().isRelativeRateLimit())
- bucket.reset = now + parseDouble(resetAfterHeader);
- else
- bucket.reset = parseDouble(resetHeader);
- log.trace("Updated bucket {} to ({}/{}, {})", bucket.bucketId, bucket.remaining, bucket.limit, bucket.reset - now);
- return bucket;
- }
- catch (Exception e)
- {
- Bucket bucket = getBucket(route, true);
- log.error("Encountered Exception while updating a bucket. Route: {} Bucket: {} Code: {} Headers:\n{}",
- route.getBaseRoute(), bucket, response.code(), response.headers(), e);
- return bucket;
- }
- });
- }
-
- @Contract("_,true->!null")
- private Bucket getBucket(Route.CompiledRoute route, boolean create)
- {
- return MiscUtil.locked(bucketLock, () ->
- {
- // Retrieve the hash via the route
- String hash = getRouteHash(route.getBaseRoute());
- // Get or create a bucket for the hash + major parameters
- String bucketId = hash + ":" + route.getMajorParameters();
- Bucket bucket = this.buckets.get(bucketId);
- if (bucket == null && create)
- this.buckets.put(bucketId, bucket = new Bucket(bucketId));
-
- return bucket;
- });
- }
-
- private void runBucket(Bucket bucket)
- {
- if (isShutdown)
- return;
- // Schedule a new bucket worker if no worker is running
- MiscUtil.locked(bucketLock, () ->
- rateLimitQueue.computeIfAbsent(bucket,
- (k) -> getScheduler().schedule(bucket, bucket.getRateLimit(), TimeUnit.MILLISECONDS)));
- }
-
- private long parseLong(String input)
- {
- return input == null ? 0L : Long.parseLong(input);
- }
-
- private long parseDouble(String input)
- {
- //The header value is using a double to represent milliseconds and seconds:
- // 5.250 this is 5 seconds and 250 milliseconds (5250 milliseconds)
- return input == null ? 0L : (long) (Double.parseDouble(input) * 1000);
- }
-
- public long getNow()
- {
- return System.currentTimeMillis();
- }
-
- @SuppressWarnings("rawtypes")
- private class Bucket implements IBucket, Runnable
- {
- private final String bucketId;
- private final Deque requests = new ConcurrentLinkedDeque<>();
-
- private long reset = 0;
- private int remaining = 1;
- private int limit = 1;
-
- public Bucket(String bucketId)
- {
- this.bucketId = bucketId;
- }
-
- public void enqueue(Request request)
- {
- requests.addLast(request);
- }
-
- public void retry(Request request)
- {
- requests.addFirst(request);
- }
-
- private boolean isGlobalRateLimit()
- {
- return requester.getJDA().getSessionController().getGlobalRatelimit() > getNow();
- }
-
- public long getRateLimit()
- {
- long now = getNow();
- long global = requester.getJDA().getSessionController().getGlobalRatelimit();
- // Global rate limit is more important to handle
- if (global > now)
- return global - now;
- // Check if the bucket reset time has expired
- if (reset <= now)
- {
- // Update the remaining uses to the limit (we don't know better)
- remaining = limit;
- return 0L;
- }
-
- // If there are remaining requests we don't need to do anything, otherwise return backoff in milliseconds
- return remaining < 1 ? reset - now : 0L;
- }
-
- public long getReset()
- {
- return reset;
- }
-
- public int getRemaining()
- {
- return remaining;
- }
-
- public int getLimit()
- {
- return limit;
- }
-
- private boolean isUnlimited()
- {
- return bucketId.startsWith("unlimited");
- }
-
- private void backoff()
- {
- // Schedule backoff if requests are not done
- MiscUtil.locked(bucketLock, () -> {
- rateLimitQueue.remove(this);
- if (!requests.isEmpty())
- runBucket(this);
- else if (isStopped)
- buckets.remove(bucketId);
- if (isStopped && buckets.isEmpty())
- requester.getJDA().shutdownRequester();
- });
- }
-
- @Override
- public void run()
- {
- log.trace("Bucket {} is running {} requests", bucketId, requests.size());
- while (!requests.isEmpty())
- {
- Long rateLimit = getRateLimit();
- if (rateLimit > 0L)
- {
- // We need to backoff since we ran out of remaining uses or hit the global rate limit
- Request request = requests.peekFirst(); // this *should* not be null
- String baseRoute = request != null ? request.getRoute().getBaseRoute().toString() : "N/A";
- if (!isGlobalRateLimit() && rateLimit >= 1000 * 60 * 30) // 30 minutes
- log.warn("Encountered long {} minutes Rate-Limit on route {}", TimeUnit.MILLISECONDS.toMinutes(rateLimit), baseRoute);
- log.debug("Backing off {} ms for bucket {} on route {}", rateLimit, bucketId, baseRoute);
- break;
- }
-
- Request request = requests.removeFirst();
- if (request.isSkipped())
- continue;
- if (isUnlimited())
- {
- boolean shouldSkip = MiscUtil.locked(bucketLock, () -> {
- // Attempt moving request to correct bucket if it has been created
- Bucket bucket = getBucket(request.getRoute(), true);
- if (bucket != this)
- {
- bucket.enqueue(request);
- runBucket(bucket);
- return true;
- }
- return false;
- });
- if (shouldSkip) continue;
- }
-
- try
- {
- rateLimit = requester.execute(request);
- if (rateLimit != null)
- retry(request); // this means we hit a hard rate limit (429) so the request needs to be retried
- }
- catch (Throwable ex)
- {
- log.error("Encountered exception trying to execute request", ex);
- if (ex instanceof Error)
- throw (Error) ex;
- break;
- }
- }
-
- backoff();
- }
-
- @Override
- public Queue getRequests()
- {
- return requests;
- }
-
- @Override
- public String toString()
- {
- return bucketId;
- }
- }
-}
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/ratelimit/IBucket.java b/src/main/java/net/dv8tion/jda/internal/requests/ratelimit/IBucket.java
deleted file mode 100644
index 33416c94f2..0000000000
--- a/src/main/java/net/dv8tion/jda/internal/requests/ratelimit/IBucket.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.internal.requests.ratelimit;
-
-import net.dv8tion.jda.api.requests.Request;
-
-import java.util.Queue;
-
-public interface IBucket
-{
- Queue getRequests();
-}
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/AuditableRestActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/AuditableRestActionImpl.java
index 283b78bd0d..160277f7e3 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/AuditableRestActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/AuditableRestActionImpl.java
@@ -20,10 +20,10 @@
import net.dv8tion.jda.api.audit.ThreadLocalReason;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.EncodingUtil;
import okhttp3.RequestBody;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java
index edbd5eb5bc..b135e1df78 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java
@@ -38,11 +38,11 @@
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.ChannelAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.EntityBuilder;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.PermissionUtil;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandCreateActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandCreateActionImpl.java
index c505a2b6c7..ba7873b7f5 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandCreateActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandCreateActionImpl.java
@@ -26,13 +26,13 @@
import net.dv8tion.jda.api.interactions.commands.localization.LocalizationMap;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.CommandCreateAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import net.dv8tion.jda.internal.interactions.command.CommandImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import okhttp3.RequestBody;
import org.jetbrains.annotations.NotNull;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandEditActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandEditActionImpl.java
index 01cbc8148f..646c1ce18c 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandEditActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandEditActionImpl.java
@@ -26,12 +26,12 @@
import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.CommandEditAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import net.dv8tion.jda.internal.interactions.command.CommandImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandListUpdateActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandListUpdateActionImpl.java
index be8de5633a..de31faa8ee 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandListUpdateActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/CommandListUpdateActionImpl.java
@@ -22,12 +22,12 @@
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.internal.entities.GuildImpl;
import net.dv8tion.jda.internal.interactions.command.CommandImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ForumPostActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ForumPostActionImpl.java
index 79f34ef327..07285efa32 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ForumPostActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ForumPostActionImpl.java
@@ -28,13 +28,13 @@
import net.dv8tion.jda.api.entities.channel.forums.ForumTagSnowflake;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.ForumPostAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.internal.entities.EntityBuilder;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.message.MessageCreateBuilderMixin;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/GuildActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/GuildActionImpl.java
index b5974d1f84..5c6cd26dba 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/GuildActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/GuildActionImpl.java
@@ -20,11 +20,11 @@
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Icon;
import net.dv8tion.jda.api.entities.channel.ChannelType;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.GuildAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/InviteActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/InviteActionImpl.java
index 02d520fe4a..8d1ef016c0 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/InviteActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/InviteActionImpl.java
@@ -20,9 +20,9 @@
import net.dv8tion.jda.api.entities.Invite;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.InviteAction;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/MemberActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/MemberActionImpl.java
index 9975b014e6..70e136722c 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/MemberActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/MemberActionImpl.java
@@ -20,10 +20,10 @@
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.MemberAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.Helpers;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/MessageCreateActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/MessageCreateActionImpl.java
index a7f24d9311..395366df56 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/MessageCreateActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/MessageCreateActionImpl.java
@@ -23,12 +23,12 @@
import net.dv8tion.jda.api.entities.sticker.StickerSnowflake;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.message.MessageCreateBuilderMixin;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/MessageEditActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/MessageEditActionImpl.java
index 479ae49176..0f12988db3 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/MessageEditActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/MessageEditActionImpl.java
@@ -20,11 +20,11 @@
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.MessageEditAction;
import net.dv8tion.jda.api.utils.messages.MessageEditBuilder;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.message.MessageEditBuilderMixin;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/PermissionOverrideActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/PermissionOverrideActionImpl.java
index f0adbb5e58..91f885b0d6 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/PermissionOverrideActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/PermissionOverrideActionImpl.java
@@ -26,11 +26,11 @@
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.PermissionOverrideAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.PermissionOverrideImpl;
import net.dv8tion.jda.internal.entities.channel.mixin.attribute.IPermissionContainerMixin;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.PermissionUtil;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/RoleActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/RoleActionImpl.java
index 991dfd9db0..298e299678 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/RoleActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/RoleActionImpl.java
@@ -23,10 +23,10 @@
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.RoleAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.GuildImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ScheduledEventActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ScheduledEventActionImpl.java
index ed14cd24c0..72983b540c 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ScheduledEventActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ScheduledEventActionImpl.java
@@ -17,17 +17,17 @@
package net.dv8tion.jda.internal.requests.restaction;
import net.dv8tion.jda.api.entities.Guild;
-import net.dv8tion.jda.api.entities.ScheduledEvent;
import net.dv8tion.jda.api.entities.Icon;
+import net.dv8tion.jda.api.entities.ScheduledEvent;
import net.dv8tion.jda.api.entities.channel.concrete.StageChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.ScheduledEventAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.GuildImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.Helpers;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/StageInstanceActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/StageInstanceActionImpl.java
index 22859611b4..a380334a33 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/StageInstanceActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/StageInstanceActionImpl.java
@@ -20,11 +20,11 @@
import net.dv8tion.jda.api.entities.channel.concrete.StageChannel;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.StageInstanceAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.GuildImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ThreadChannelActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ThreadChannelActionImpl.java
index a977717527..ee7fbb4b01 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ThreadChannelActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ThreadChannelActionImpl.java
@@ -23,9 +23,9 @@
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/TriggerRestAction.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/TriggerRestAction.java
index 2607b8777c..56aae6ece2 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/TriggerRestAction.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/TriggerRestAction.java
@@ -20,10 +20,10 @@
import net.dv8tion.jda.api.exceptions.ContextException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import okhttp3.RequestBody;
import javax.annotation.Nonnull;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookActionImpl.java
index f1c550ade1..5b9b8c1274 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookActionImpl.java
@@ -23,9 +23,9 @@
import net.dv8tion.jda.api.entities.channel.unions.IWebhookContainerUnion;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.WebhookAction;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookMessageCreateActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookMessageCreateActionImpl.java
index 045d1a7415..b9e7133c32 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookMessageCreateActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookMessageCreateActionImpl.java
@@ -20,12 +20,12 @@
import net.dv8tion.jda.api.entities.Message.MessageFlag;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction;
import net.dv8tion.jda.api.utils.FileUpload;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.message.MessageCreateBuilderMixin;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookMessageEditActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookMessageEditActionImpl.java
index d31663babe..9fed1c3f28 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookMessageEditActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/WebhookMessageEditActionImpl.java
@@ -19,11 +19,11 @@
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.api.utils.messages.MessageEditBuilder;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.message.MessageEditBuilderMixin;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/interactions/InteractionCallbackImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/interactions/InteractionCallbackImpl.java
index 43735989f5..f89f4ed9b4 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/interactions/InteractionCallbackImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/interactions/InteractionCallbackImpl.java
@@ -19,10 +19,10 @@
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.interactions.InteractionCallbackAction;
import net.dv8tion.jda.internal.interactions.InteractionImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import javax.annotation.Nonnull;
import java.util.concurrent.CompletableFuture;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/ChannelOrderActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/ChannelOrderActionImpl.java
index bf653ea930..b538459fe9 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/ChannelOrderActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/ChannelOrderActionImpl.java
@@ -27,10 +27,10 @@
import net.dv8tion.jda.api.entities.channel.concrete.Category;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.order.ChannelOrderAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/OrderActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/OrderActionImpl.java
index bd7ba0d13a..5d18719623 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/OrderActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/OrderActionImpl.java
@@ -17,9 +17,9 @@
package net.dv8tion.jda.internal.requests.restaction.order;
import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.order.OrderAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import javax.annotation.Nonnull;
@@ -45,7 +45,7 @@ public abstract class OrderActionImpl>
* JDA instance which is associated with the entities contained
* in the order list
* @param route
- * The {@link net.dv8tion.jda.internal.requests.Route.CompiledRoute CompiledRoute}
+ * The {@link net.dv8tion.jda.api.requests.Route.CompiledRoute CompiledRoute}
* which is provided to the {@link RestActionImpl#RestActionImpl(JDA, Route.CompiledRoute, okhttp3.RequestBody) RestAction Constructor}
*/
public OrderActionImpl(JDA api, Route.CompiledRoute route)
@@ -62,7 +62,7 @@ public OrderActionImpl(JDA api, Route.CompiledRoute route)
* @param ascendingOrder
* Whether or not the order of items should be ascending
* @param route
- * The {@link net.dv8tion.jda.internal.requests.Route.CompiledRoute CompiledRoute}
+ * The {@link net.dv8tion.jda.api.requests.Route.CompiledRoute CompiledRoute}
* which is provided to the {@link RestActionImpl#RestActionImpl(JDA, Route.CompiledRoute, okhttp3.RequestBody) RestAction Constructor}
*/
public OrderActionImpl(JDA api, boolean ascendingOrder, Route.CompiledRoute route)
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/RoleOrderActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/RoleOrderActionImpl.java
index ec95961ba6..d2ecf1afcf 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/RoleOrderActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/order/RoleOrderActionImpl.java
@@ -21,10 +21,10 @@
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.order.RoleOrderAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.RequestBody;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/AuditLogPaginationActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/AuditLogPaginationActionImpl.java
index 88786a4795..a48fa65d3d 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/AuditLogPaginationActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/AuditLogPaginationActionImpl.java
@@ -27,12 +27,12 @@
import net.dv8tion.jda.api.exceptions.ParsingException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.pagination.AuditLogPaginationAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.EntityBuilder;
import net.dv8tion.jda.internal.entities.GuildImpl;
-import net.dv8tion.jda.internal.requests.Route;
import javax.annotation.Nonnull;
import java.util.ArrayList;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/BanPaginationActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/BanPaginationActionImpl.java
index 1b3ebd1235..6fd0ea797a 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/BanPaginationActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/BanPaginationActionImpl.java
@@ -18,12 +18,12 @@
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.pagination.BanPaginationAction;
import net.dv8tion.jda.api.requests.restaction.pagination.PaginationAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.EntityBuilder;
-import net.dv8tion.jda.internal.requests.Route;
import javax.annotation.Nonnull;
import java.util.ArrayList;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/MessagePaginationActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/MessagePaginationActionImpl.java
index 9818b5515b..0d3b75ee2b 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/MessagePaginationActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/MessagePaginationActionImpl.java
@@ -26,10 +26,10 @@
import net.dv8tion.jda.api.exceptions.ParsingException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.pagination.MessagePaginationAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.internal.entities.EntityBuilder;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import javax.annotation.Nonnull;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/PaginationActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/PaginationActionImpl.java
index 198bf0937f..1fe4c158df 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/PaginationActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/PaginationActionImpl.java
@@ -17,10 +17,10 @@
package net.dv8tion.jda.internal.requests.restaction.pagination;
import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.pagination.PaginationAction;
import net.dv8tion.jda.api.utils.Procedure;
import net.dv8tion.jda.internal.requests.RestActionImpl;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;
import javax.annotation.Nonnull;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ReactionPaginationActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ReactionPaginationActionImpl.java
index acd14e3873..1849d568f1 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ReactionPaginationActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ReactionPaginationActionImpl.java
@@ -20,15 +20,13 @@
import net.dv8tion.jda.api.entities.MessageReaction;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
-import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.exceptions.ParsingException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.pagination.ReactionPaginationAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.internal.entities.EntityBuilder;
-import net.dv8tion.jda.internal.requests.Route;
-import net.dv8tion.jda.internal.utils.EncodingUtil;
import javax.annotation.Nonnull;
import java.util.EnumSet;
@@ -70,8 +68,7 @@ public ReactionPaginationActionImpl(MessageChannel channel, String messageId, St
protected static String getCode(MessageReaction reaction)
{
- Emoji emoji = reaction.getEmoji();
- return EncodingUtil.encodeUTF8(emoji.getAsReactionCode());
+ return reaction.getEmoji().getAsReactionCode();
}
@Nonnull
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ScheduledEventMembersPaginationActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ScheduledEventMembersPaginationActionImpl.java
index 003f12d907..63036613d5 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ScheduledEventMembersPaginationActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ScheduledEventMembersPaginationActionImpl.java
@@ -17,17 +17,17 @@
package net.dv8tion.jda.internal.requests.restaction.pagination;
import net.dv8tion.jda.api.entities.Guild;
-import net.dv8tion.jda.api.entities.ScheduledEvent;
import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.ScheduledEvent;
import net.dv8tion.jda.api.exceptions.ParsingException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.pagination.ScheduledEventMembersPaginationAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.EntityBuilder;
import net.dv8tion.jda.internal.entities.GuildImpl;
-import net.dv8tion.jda.internal.requests.Route;
import javax.annotation.Nonnull;
import java.util.ArrayList;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ThreadChannelPaginationActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ThreadChannelPaginationActionImpl.java
index fb4861f3bb..8a66407a37 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ThreadChannelPaginationActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ThreadChannelPaginationActionImpl.java
@@ -8,11 +8,11 @@
import net.dv8tion.jda.api.exceptions.ParsingException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.pagination.ThreadChannelPaginationAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.EntityBuilder;
-import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Helpers;
import javax.annotation.Nonnull;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ThreadMemberPaginationActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ThreadMemberPaginationActionImpl.java
index 179497814e..b8f84440c3 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ThreadMemberPaginationActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/pagination/ThreadMemberPaginationActionImpl.java
@@ -21,12 +21,12 @@
import net.dv8tion.jda.api.exceptions.ParsingException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.pagination.ThreadMemberPaginationAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.EntityBuilder;
import net.dv8tion.jda.internal.entities.channel.concrete.ThreadChannelImpl;
-import net.dv8tion.jda.internal.requests.Route;
import javax.annotation.Nonnull;
import java.util.ArrayList;
diff --git a/src/main/java/net/dv8tion/jda/internal/utils/Checks.java b/src/main/java/net/dv8tion/jda/internal/utils/Checks.java
index fd9270c6ec..4ce2bee3d8 100644
--- a/src/main/java/net/dv8tion/jda/internal/utils/Checks.java
+++ b/src/main/java/net/dv8tion/jda/internal/utils/Checks.java
@@ -38,7 +38,7 @@
public class Checks
{
public static final Pattern ALPHANUMERIC_WITH_DASH = Pattern.compile("[\\w-]+", Pattern.UNICODE_CHARACTER_CLASS);
- public static final Pattern ALPHANUMERIC = Pattern.compile("[\\w]+", Pattern.UNICODE_CHARACTER_CLASS);
+ public static final Pattern ALPHANUMERIC = Pattern.compile("\\w+", Pattern.UNICODE_CHARACTER_CLASS);
public static final Pattern LOWERCASE_ASCII_ALPHANUMERIC = Pattern.compile("[a-z0-9_]+");
@Contract("null -> fail")
diff --git a/src/main/java/net/dv8tion/jda/internal/utils/Helpers.java b/src/main/java/net/dv8tion/jda/internal/utils/Helpers.java
index aa331b482a..05b7f0f5b3 100644
--- a/src/main/java/net/dv8tion/jda/internal/utils/Helpers.java
+++ b/src/main/java/net/dv8tion/jda/internal/utils/Helpers.java
@@ -213,6 +213,26 @@ public static int codePointLength(final CharSequence string)
return (int) string.codePoints().count();
}
+ public static String[] split(String input, String match)
+ {
+ List out = new ArrayList<>();
+ int i = 0;
+ while (i < input.length())
+ {
+ int j = input.indexOf(match, i);
+ if (j == -1)
+ {
+ out.add(input.substring(i));
+ break;
+ }
+
+ out.add(input.substring(i, j));
+ i = j + match.length();
+ }
+
+ return out.toArray(new String[0]);
+ }
+
// ## CollectionUtils ##
public static boolean deepEquals(Collection> first, Collection> second)
@@ -251,6 +271,12 @@ public static Set setOf(T... elements)
return set;
}
+ @SafeVarargs
+ public static List listOf(T... elements)
+ {
+ return Collections.unmodifiableList(Arrays.asList(elements));
+ }
+
public static TLongObjectMap convertToMap(ToLongFunction getId, DataArray array)
{
TLongObjectMap map = new TLongObjectHashMap<>();