diff --git a/src/main/java/net/dv8tion/jda/api/JDA.java b/src/main/java/net/dv8tion/jda/api/JDA.java index 522a5c2e06..869d5f8d4c 100644 --- a/src/main/java/net/dv8tion/jda/api/JDA.java +++ b/src/main/java/net/dv8tion/jda/api/JDA.java @@ -33,6 +33,7 @@ import net.dv8tion.jda.api.managers.Presence; 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.sharding.ShardManager; import net.dv8tion.jda.api.utils.MiscUtil; @@ -42,7 +43,6 @@ import net.dv8tion.jda.internal.interactions.CommandDataImpl; 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 net.dv8tion.jda.internal.utils.EntityString; import net.dv8tion.jda.internal.utils.Helpers; diff --git a/src/main/java/net/dv8tion/jda/api/JDABuilder.java b/src/main/java/net/dv8tion/jda/api/JDABuilder.java index 6aae036256..e613ea170a 100644 --- a/src/main/java/net/dv8tion/jda/api/JDABuilder.java +++ b/src/main/java/net/dv8tion/jda/api/JDABuilder.java @@ -16,15 +16,18 @@ package net.dv8tion.jda.api; import com.neovisionaries.ws.client.WebSocketFactory; +import net.dv8tion.jda.annotations.ForRemoval; +import net.dv8tion.jda.annotations.ReplaceWith; import net.dv8tion.jda.api.audio.factory.IAudioSendFactory; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.events.Event; -import net.dv8tion.jda.api.exceptions.InvalidTokenException; import net.dv8tion.jda.api.events.session.ReadyEvent; +import net.dv8tion.jda.api.exceptions.InvalidTokenException; import net.dv8tion.jda.api.hooks.IEventManager; 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.*; import net.dv8tion.jda.api.utils.cache.CacheFlag; import net.dv8tion.jda.internal.JDAImpl; @@ -93,6 +96,7 @@ public class JDABuilder protected ChunkingFilter chunkingFilter = ChunkingFilter.ALL; protected MemberCachePolicy memberCachePolicy = MemberCachePolicy.ALL; protected GatewayEncoding encoding = GatewayEncoding.JSON; + protected RestConfig restConfig = new RestConfig(); private JDABuilder(@Nullable String token, int intents) { @@ -557,15 +561,36 @@ public JDABuilder setEventPassthrough(boolean enable) * True, if the relative {@code X-RateLimit-Reset-After} header should be used. * * @return The JDABuilder instance. Useful for chaining. - * - * @since 4.1.0 */ @Nonnull + @Deprecated + @ForRemoval(deadline = "5.1.0") + @ReplaceWith("setRestConfig(new RestConfig().setRelativeRateLimit(enable))") public JDABuilder setRelativeRateLimit(boolean enable) { return setFlag(ConfigFlag.USE_RELATIVE_RATELIMIT, enable); } + /** + * Custom {@link RestConfig} to use for this JDA instance. + *
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 JDABuilder instance. Useful for chaining. + */ + @Nonnull + public JDABuilder setRestConfig(@Nonnull RestConfig config) + { + Checks.notNull(config, "RestConfig"); + this.restConfig = config; + return this; + } + /** * Enable specific cache flags. *
This will not disable any currently set cache flags. @@ -1778,7 +1803,7 @@ public JDA build() SessionConfig sessionConfig = new SessionConfig(controller, httpClient, wsFactory, voiceDispatchInterceptor, flags, maxReconnectDelay, largeThreshold); MetaConfig metaConfig = new MetaConfig(maxBufferSize, contextMap, cacheFlags, flags); - JDAImpl jda = new JDAImpl(authConfig, sessionConfig, threadingConfig, metaConfig); + JDAImpl jda = new JDAImpl(authConfig, sessionConfig, threadingConfig, metaConfig, restConfig); jda.setMemberCachePolicy(memberCachePolicy); // We can only do member chunking with the GUILD_MEMBERS intent if ((intents & GatewayIntent.GUILD_MEMBERS.getRawValue()) == 0) diff --git a/src/main/java/net/dv8tion/jda/api/entities/Message.java b/src/main/java/net/dv8tion/jda/api/entities/Message.java index 127eef6e38..b8a2fa7f13 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Message.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Message.java @@ -42,6 +42,7 @@ import net.dv8tion.jda.api.interactions.components.LayoutComponent; import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.requests.RestConfig; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; import net.dv8tion.jda.api.requests.restaction.MessageEditAction; @@ -57,7 +58,6 @@ import net.dv8tion.jda.internal.JDAImpl; import net.dv8tion.jda.internal.entities.ReceivedMessage; 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.IOUtil; import okhttp3.MultipartBody; @@ -2608,7 +2608,7 @@ protected Request getRequest() { return new Request.Builder() .url(getUrl()) - .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/entities/MessageHistory.java b/src/main/java/net/dv8tion/jda/api/entities/MessageHistory.java index 1e2f0b3634..d6ada27629 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/MessageHistory.java +++ b/src/main/java/net/dv8tion/jda/api/entities/MessageHistory.java @@ -25,6 +25,7 @@ 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.utils.MiscUtil; import net.dv8tion.jda.api.utils.TimeUtil; import net.dv8tion.jda.api.utils.data.DataArray; @@ -32,7 +33,6 @@ import net.dv8tion.jda.internal.JDAImpl; 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.JDALogger; import org.apache.commons.collections4.map.ListOrderedMap; diff --git a/src/main/java/net/dv8tion/jda/api/entities/MessageReaction.java b/src/main/java/net/dv8tion/jda/api/entities/MessageReaction.java index 8b980e954c..820e2389f7 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/MessageReaction.java +++ b/src/main/java/net/dv8tion/jda/api/entities/MessageReaction.java @@ -30,12 +30,11 @@ import net.dv8tion.jda.api.exceptions.InsufficientPermissionException; import net.dv8tion.jda.api.exceptions.PermissionException; import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.requests.Route; import net.dv8tion.jda.api.requests.restaction.pagination.ReactionPaginationAction; import net.dv8tion.jda.internal.requests.RestActionImpl; -import net.dv8tion.jda.internal.requests.Route; import net.dv8tion.jda.internal.requests.restaction.pagination.ReactionPaginationActionImpl; import net.dv8tion.jda.internal.utils.Checks; -import net.dv8tion.jda.internal.utils.EncodingUtil; import net.dv8tion.jda.internal.utils.EntityString; import javax.annotation.CheckReturnValue; @@ -340,7 +339,7 @@ public RestAction removeReaction(@Nonnull User user) throw new InsufficientPermissionException(guildChannel, Permission.MESSAGE_MANAGE); } - String code = EncodingUtil.encodeReaction(emoji.getAsReactionCode()); + String code = emoji.getAsReactionCode(); String target = self ? "@me" : user.getId(); Route.CompiledRoute route = Route.Messages.REMOVE_REACTION.compile(channel.getId(), getMessageId(), code, target); return new RestActionImpl<>(getJDA(), route); diff --git a/src/main/java/net/dv8tion/jda/api/entities/MessageReference.java b/src/main/java/net/dv8tion/jda/api/entities/MessageReference.java index 0544175ae5..2dfe6b8b08 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/MessageReference.java +++ b/src/main/java/net/dv8tion/jda/api/entities/MessageReference.java @@ -23,10 +23,10 @@ import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion; 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.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.Nonnull; diff --git a/src/main/java/net/dv8tion/jda/api/entities/Webhook.java b/src/main/java/net/dv8tion/jda/api/entities/Webhook.java index 8752b4f073..85021c624e 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Webhook.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Webhook.java @@ -22,9 +22,9 @@ import net.dv8tion.jda.api.entities.channel.unions.IWebhookContainerUnion; import net.dv8tion.jda.api.managers.WebhookManager; 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.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/api/entities/channel/concrete/NewsChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/NewsChannel.java index 00c5817fc4..be99a8696a 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/NewsChannel.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/NewsChannel.java @@ -23,9 +23,9 @@ import net.dv8tion.jda.api.exceptions.MissingAccessException; 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.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/entities/channel/middleman/MessageChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/MessageChannel.java index da2e90cb94..1175733e3b 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/MessageChannel.java +++ b/src/main/java/net/dv8tion/jda/api/entities/channel/middleman/MessageChannel.java @@ -31,6 +31,7 @@ import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; 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; @@ -46,14 +47,12 @@ import net.dv8tion.jda.internal.JDAImpl; 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.requests.restaction.AuditableRestActionImpl; import net.dv8tion.jda.internal.requests.restaction.MessageCreateActionImpl; import net.dv8tion.jda.internal.requests.restaction.MessageEditActionImpl; import net.dv8tion.jda.internal.requests.restaction.pagination.MessagePaginationActionImpl; import net.dv8tion.jda.internal.requests.restaction.pagination.ReactionPaginationActionImpl; import net.dv8tion.jda.internal.utils.Checks; -import net.dv8tion.jda.internal.utils.EncodingUtil; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; @@ -1672,8 +1671,7 @@ default RestAction addReactionById(@Nonnull String messageId, @Nonnull Emo Checks.isSnowflake(messageId, "Message ID"); Checks.notNull(emoji, "Emoji"); - String encoded = EncodingUtil.encodeReaction(emoji.getAsReactionCode()); - Route.CompiledRoute route = Route.Messages.ADD_REACTION.compile(getId(), messageId, encoded, "@me"); + Route.CompiledRoute route = Route.Messages.ADD_REACTION.compile(getId(), messageId, emoji.getAsReactionCode(), "@me"); return new RestActionImpl<>(getJDA(), route); } @@ -1782,8 +1780,7 @@ default RestAction 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 customBuilder; + private Function 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 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 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 getRateLimiterFactory() + { + return rateLimiter; + } + + /** + * The custom request interceptor. + * + * @return The custom interceptor, or null if none is configured + */ + @Nullable + public Consumer 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: + *
    + *
  1. Get Hash from Path+Method (we call this route)
  2. + *
  3. Get bucket from Hash+Major (we call this bucketid)
  4. + *
+ * + *

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 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 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 callbackPoolProvider = null; protected ThreadPoolProvider eventPoolProvider = null; protected ThreadPoolProvider audioPoolProvider = null; + protected IntFunction 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 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 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<>();