From 5f3d9c0ba5f403f506de02e2e48fad6583bb0430 Mon Sep 17 00:00:00 2001 From: Connor Linfoot Date: Wed, 14 Jun 2023 23:59:18 +0100 Subject: [PATCH 1/2] Implement a RateLimit object to contain the rate limit headers in replies --- .../main/java/net/hypixel/api/HypixelAPI.java | 12 ++++- .../hypixel/api/http/HypixelHttpResponse.java | 11 +++++ .../java/net/hypixel/api/http/RateLimit.java | 44 +++++++++++++++++++ .../net/hypixel/api/reply/BoostersReply.java | 2 +- .../net/hypixel/api/reply/CountsReply.java | 2 +- .../net/hypixel/api/reply/FindGuildReply.java | 2 +- .../net/hypixel/api/reply/GuildReply.java | 14 +++--- .../java/net/hypixel/api/reply/KeyReply.java | 2 +- .../hypixel/api/reply/LeaderboardsReply.java | 2 +- .../net/hypixel/api/reply/PlayerReply.java | 2 +- .../api/reply/PunishmentStatsReply.java | 2 +- .../hypixel/api/reply/RateLimitedReply.java | 22 ++++++++++ .../hypixel/api/reply/RecentGamesReply.java | 2 +- .../net/hypixel/api/reply/StatusReply.java | 2 +- .../api/reply/skyblock/SkyBlockNewsReply.java | 4 +- .../reply/skyblock/SkyBlockProfileReply.java | 4 +- .../reply/skyblock/SkyBlockProfilesReply.java | 4 +- .../bingo/SkyBlockBingoDataReply.java | 4 +- .../hypixel/api/example/GetPlayerExample.java | 6 +++ .../hypixel/api/apache/ApacheHttpClient.java | 16 ++++++- .../api/reactor/ReactorHttpClient.java | 34 +++++++------- .../api/unirest/UnirestHttpClient.java | 24 +++++++++- 22 files changed, 171 insertions(+), 46 deletions(-) create mode 100644 hypixel-api-core/src/main/java/net/hypixel/api/http/RateLimit.java create mode 100644 hypixel-api-core/src/main/java/net/hypixel/api/reply/RateLimitedReply.java diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/HypixelAPI.java b/hypixel-api-core/src/main/java/net/hypixel/api/HypixelAPI.java index 781c4ead..98fd3dbb 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/HypixelAPI.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/HypixelAPI.java @@ -154,6 +154,10 @@ public CompletableFuture getGuildById(String id) { ); } + /** + * @deprecated Endpoint is deprecated and will be removed on 14th August 2023. + */ + @Deprecated public CompletableFuture getKey() { return get(KeyReply.class, "key"); } @@ -304,7 +308,13 @@ private CompletableFuture get(Class clazz, Strin if (clazz == ResourceReply.class) { return checkReply((R) new ResourceReply(Utilities.GSON.fromJson(response.getBody(), JsonObject.class))); } - return checkReply(Utilities.GSON.fromJson(response.getBody(), clazz)); + + R reply = Utilities.GSON.fromJson(response.getBody(), clazz); + if (reply instanceof RateLimitedReply) { + ((RateLimitedReply) reply).setRateLimit(response.getRateLimit()); + } + + return checkReply(reply); }); } diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/http/HypixelHttpResponse.java b/hypixel-api-core/src/main/java/net/hypixel/api/http/HypixelHttpResponse.java index dc198540..c089ca7d 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/http/HypixelHttpResponse.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/http/HypixelHttpResponse.java @@ -4,10 +4,17 @@ public class HypixelHttpResponse { private final int statusCode; private final String body; + private final RateLimit rateLimit; + @Deprecated public HypixelHttpResponse(int statusCode, String body) { + this(statusCode, body, null); + } + + public HypixelHttpResponse(int statusCode, String body, RateLimit rateLimit) { this.statusCode = statusCode; this.body = body; + this.rateLimit = rateLimit; } public int getStatusCode() { @@ -17,4 +24,8 @@ public int getStatusCode() { public String getBody() { return body; } + + public RateLimit getRateLimit() { + return rateLimit; + } } diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/http/RateLimit.java b/hypixel-api-core/src/main/java/net/hypixel/api/http/RateLimit.java new file mode 100644 index 00000000..2e2dbb28 --- /dev/null +++ b/hypixel-api-core/src/main/java/net/hypixel/api/http/RateLimit.java @@ -0,0 +1,44 @@ +package net.hypixel.api.http; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class RateLimit { + private final int limit; + private final int remaining; + private final int reset; + private final Date resetAt; + + public RateLimit(int limit, int remaining, int reset) { + this.limit = limit; + this.remaining = remaining; + this.reset = reset; + this.resetAt = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(reset)); + } + + public int getLimit() { + return limit; + } + + public int getRemaining() { + return remaining; + } + + public int getReset() { + return reset; + } + + public Date getResetAt() { + return resetAt; + } + + @Override + public String toString() { + return "RateLimit{" + + "limit=" + limit + + ", remaining=" + remaining + + ", reset=" + reset + + ", resetAt=" + resetAt + + '}'; + } +} diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/BoostersReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/BoostersReply.java index e9c24c14..1ba05d32 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/BoostersReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/BoostersReply.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.UUID; -public class BoostersReply extends AbstractReply { +public class BoostersReply extends RateLimitedReply { private List boosters; private BoosterState boosterState; diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/CountsReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/CountsReply.java index 31c7a7cd..863a1314 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/CountsReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/CountsReply.java @@ -2,7 +2,7 @@ import java.util.Map; -public class CountsReply extends AbstractReply { +public class CountsReply extends RateLimitedReply { private Map games; private int playerCount; diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/FindGuildReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/FindGuildReply.java index 475a9dc5..998d78c3 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/FindGuildReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/FindGuildReply.java @@ -1,6 +1,6 @@ package net.hypixel.api.reply; -public class FindGuildReply extends AbstractReply { +public class FindGuildReply extends RateLimitedReply { private String guild; /** diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/GuildReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/GuildReply.java index e23cfbc4..5a38fe48 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/GuildReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/GuildReply.java @@ -1,22 +1,18 @@ package net.hypixel.api.reply; import com.google.gson.annotations.SerializedName; -import java.time.LocalDate; -import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; import net.hypixel.api.data.type.GameType; import net.hypixel.api.data.type.GuildAchievement; import net.hypixel.api.reply.PlayerReply.Player; import net.hypixel.api.util.Banner; +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.util.*; + // Suppressed because most fields are assigned by Gson via reflection. @SuppressWarnings({"unused", "RedundantSuppression", "MismatchedQueryAndUpdateOfCollection"}) -public class GuildReply extends AbstractReply { +public class GuildReply extends RateLimitedReply { private Guild guild; diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/KeyReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/KeyReply.java index 26654ab8..97a0c116 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/KeyReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/KeyReply.java @@ -4,7 +4,7 @@ import java.util.UUID; -public class KeyReply extends AbstractReply { +public class KeyReply extends RateLimitedReply { private Key record; public Key getRecord() { diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/LeaderboardsReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/LeaderboardsReply.java index 74e7d45e..51896dc4 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/LeaderboardsReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/LeaderboardsReply.java @@ -6,7 +6,7 @@ import java.util.Map; import java.util.UUID; -public class LeaderboardsReply extends AbstractReply { +public class LeaderboardsReply extends RateLimitedReply { private Map> leaderboards; public Map> getLeaderboards() { diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/PlayerReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/PlayerReply.java index 93c1ea89..a6029939 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/PlayerReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/PlayerReply.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.UUID; -public class PlayerReply extends AbstractReply { +public class PlayerReply extends RateLimitedReply { // Suppressed because this field is dynamically assigned by Gson using reflection. @SuppressWarnings({"unused", "RedundantSuppression"}) diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/PunishmentStatsReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/PunishmentStatsReply.java index cbe7567e..b5c67cb8 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/PunishmentStatsReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/PunishmentStatsReply.java @@ -2,7 +2,7 @@ import com.google.gson.annotations.SerializedName; -public class PunishmentStatsReply extends AbstractReply { +public class PunishmentStatsReply extends RateLimitedReply { @SerializedName("staff_rollingDaily") private int staffRollingDaily; @SerializedName("staff_total") diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/RateLimitedReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/RateLimitedReply.java new file mode 100644 index 00000000..c7ccdfd9 --- /dev/null +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/RateLimitedReply.java @@ -0,0 +1,22 @@ +package net.hypixel.api.reply; + +import net.hypixel.api.http.RateLimit; + +public abstract class RateLimitedReply extends AbstractReply { + private RateLimit rateLimit; + + public RateLimit getRateLimit() { + return rateLimit; + } + + public void setRateLimit(RateLimit rateLimit) { + this.rateLimit = rateLimit; + } + + @Override + public String toString() { + return "RateLimitedReply{" + + "rateLimit=" + rateLimit + + "} " + super.toString(); + } +} diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/RecentGamesReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/RecentGamesReply.java index b9a52162..e551e76c 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/RecentGamesReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/RecentGamesReply.java @@ -5,7 +5,7 @@ import java.time.ZonedDateTime; import java.util.List; -public class RecentGamesReply extends AbstractReply { +public class RecentGamesReply extends RateLimitedReply { private List games; diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/StatusReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/StatusReply.java index 5aaee3f7..93578e51 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/StatusReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/StatusReply.java @@ -3,7 +3,7 @@ import com.google.gson.annotations.SerializedName; import net.hypixel.api.data.type.ServerType; -public class StatusReply extends AbstractReply { +public class StatusReply extends RateLimitedReply { /** * {@link StatusReply.Session} instance of player diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockNewsReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockNewsReply.java index d1942496..bb80acb0 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockNewsReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockNewsReply.java @@ -2,9 +2,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import net.hypixel.api.reply.AbstractReply; +import net.hypixel.api.reply.RateLimitedReply; -public class SkyBlockNewsReply extends AbstractReply { +public class SkyBlockNewsReply extends RateLimitedReply { private JsonElement items; public JsonArray getItems() { diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockProfileReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockProfileReply.java index b38491fe..0dbb46db 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockProfileReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockProfileReply.java @@ -2,9 +2,9 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import net.hypixel.api.reply.AbstractReply; +import net.hypixel.api.reply.RateLimitedReply; -public class SkyBlockProfileReply extends AbstractReply { +public class SkyBlockProfileReply extends RateLimitedReply { private JsonElement profile; public JsonObject getProfile() { diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockProfilesReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockProfilesReply.java index 0e5b1923..68fdb6a7 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockProfilesReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/SkyBlockProfilesReply.java @@ -2,9 +2,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import net.hypixel.api.reply.AbstractReply; +import net.hypixel.api.reply.RateLimitedReply; -public class SkyBlockProfilesReply extends AbstractReply { +public class SkyBlockProfilesReply extends RateLimitedReply { private JsonElement profiles; public JsonArray getProfiles() { diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/bingo/SkyBlockBingoDataReply.java b/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/bingo/SkyBlockBingoDataReply.java index 983c18a2..eb13e180 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/bingo/SkyBlockBingoDataReply.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/reply/skyblock/bingo/SkyBlockBingoDataReply.java @@ -1,10 +1,10 @@ package net.hypixel.api.reply.skyblock.bingo; -import net.hypixel.api.reply.AbstractReply; +import net.hypixel.api.reply.RateLimitedReply; import java.util.List; -public class SkyBlockBingoDataReply extends AbstractReply { +public class SkyBlockBingoDataReply extends RateLimitedReply { private List events; public List getEvents() { diff --git a/hypixel-api-example/src/main/java/net/hypixel/api/example/GetPlayerExample.java b/hypixel-api-example/src/main/java/net/hypixel/api/example/GetPlayerExample.java index 3d5bd018..a7fa7304 100644 --- a/hypixel-api-example/src/main/java/net/hypixel/api/example/GetPlayerExample.java +++ b/hypixel-api-example/src/main/java/net/hypixel/api/example/GetPlayerExample.java @@ -121,5 +121,11 @@ public static void main(String[] args) { * `.getRaw()` method. */ System.out.println("Raw JSON ------> " + player.getRaw()); + + /* + * RateLimit object is available to any reply from a request to an authenticated endpoint and returns context + * to the rate limit of the used API key. + */ + System.out.println("Rate Limit ----> " + apiReply.getRateLimit()); } } diff --git a/hypixel-api-transport-apache/src/main/java/net/hypixel/api/apache/ApacheHttpClient.java b/hypixel-api-transport-apache/src/main/java/net/hypixel/api/apache/ApacheHttpClient.java index c4600bb8..09b87b71 100644 --- a/hypixel-api-transport-apache/src/main/java/net/hypixel/api/apache/ApacheHttpClient.java +++ b/hypixel-api-transport-apache/src/main/java/net/hypixel/api/apache/ApacheHttpClient.java @@ -2,6 +2,7 @@ import net.hypixel.api.http.HypixelHttpClient; import net.hypixel.api.http.HypixelHttpResponse; +import net.hypixel.api.http.RateLimit; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; @@ -31,7 +32,7 @@ public CompletableFuture makeRequest(String url) { return CompletableFuture.supplyAsync(() -> { try { HttpResponse response = this.httpClient.execute(new HttpGet(url)); - return new HypixelHttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), "UTF-8")); + return new HypixelHttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), "UTF-8"), null); } catch (IOException e) { throw new RuntimeException(e); } @@ -45,13 +46,24 @@ public CompletableFuture makeAuthenticatedRequest(String ur request.addHeader("API-Key", this.apiKey.toString()); try { HttpResponse response = this.httpClient.execute(request); - return new HypixelHttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), "UTF-8")); + return new HypixelHttpResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity(), "UTF-8"), createRateLimitResponse(response)); } catch (IOException e) { throw new RuntimeException(e); } }, this.executorService); } + private RateLimit createRateLimitResponse(HttpResponse response) { + if (response.getStatusLine().getStatusCode() != 200) { + return null; + } + + int limit = Integer.parseInt(response.getFirstHeader("RateLimit-Limit").getValue()); + int remaining = Integer.parseInt(response.getFirstHeader("RateLimit-Remaining").getValue()); + int reset = Integer.parseInt(response.getFirstHeader("RateLimit-Reset").getValue()); + return new RateLimit(limit, remaining, reset); + } + @Override public void shutdown() { this.executorService.shutdown(); diff --git a/hypixel-api-transport-reactor/src/main/java/net/hypixel/api/reactor/ReactorHttpClient.java b/hypixel-api-transport-reactor/src/main/java/net/hypixel/api/reactor/ReactorHttpClient.java index c258abb3..40dbee9f 100644 --- a/hypixel-api-transport-reactor/src/main/java/net/hypixel/api/reactor/ReactorHttpClient.java +++ b/hypixel-api-transport-reactor/src/main/java/net/hypixel/api/reactor/ReactorHttpClient.java @@ -3,6 +3,7 @@ import io.netty.handler.codec.http.HttpResponseStatus; import net.hypixel.api.http.HypixelHttpClient; import net.hypixel.api.http.HypixelHttpResponse; +import net.hypixel.api.http.RateLimit; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -10,7 +11,7 @@ import reactor.core.scheduler.Schedulers; import reactor.netty.http.client.HttpClient; import reactor.netty.http.client.HttpClientResponse; -import reactor.util.function.Tuple2; +import reactor.util.function.Tuple3; import java.time.Duration; import java.util.UUID; @@ -102,8 +103,8 @@ public CompletableFuture makeAuthenticatedRequest(String ur return toHypixelResponseFuture(makeRequest(url, true)); } - private static CompletableFuture toHypixelResponseFuture(Mono> result) { - return result.map(tuple -> new HypixelHttpResponse(tuple.getT2(), tuple.getT1())) + private static CompletableFuture toHypixelResponseFuture(Mono> result) { + return result.map(tuple -> new HypixelHttpResponse(tuple.getT2(), tuple.getT1(), tuple.getT3())) .toFuture(); } @@ -120,7 +121,7 @@ public void shutdown() { * @param path full url * @param isAuthenticated whether to enable authentication or not */ - public Mono> makeRequest(String path, boolean isAuthenticated) { + public Mono> makeRequest(String path, boolean isAuthenticated) { return Mono.create(sink -> { RequestCallback callback = new RequestCallback(path, sink, isAuthenticated, this); @@ -186,18 +187,20 @@ private ResponseHandlingResult handleResponse(HttpClientResponse response, Reque // execute this last to prevent a possible exception from messing up our clock synchronization this.blockingQueue.put(requestCallback); - return new ResponseHandlingResult(false, response.status().code()); + return new ResponseHandlingResult(false, response.status().code(), null); } - if (this.firstRequestReturned.compareAndSet(false, true)) { - int timeRemaining = Math.max(1, response.responseHeaders().getInt("ratelimit-reset", 10)); - int requestsRemaining = response.responseHeaders().getInt("ratelimit-remaining", 110); + int limit = Math.max(1, response.responseHeaders().getInt("ratelimit-limit", 10)); + int timeRemaining = Math.max(1, response.responseHeaders().getInt("ratelimit-reset", 10)); + int requestsRemaining = response.responseHeaders().getInt("ratelimit-remaining", 110); + RateLimit rateLimit = new RateLimit(limit, requestsRemaining, timeRemaining); + if (this.firstRequestReturned.compareAndSet(false, true)) { this.setActionsLeftThisMinute(requestsRemaining); - resetForFirstRequest(timeRemaining); } - return new ResponseHandlingResult(true, response.status().code()); + + return new ResponseHandlingResult(true, response.status().code(), rateLimit); } /** @@ -219,13 +222,13 @@ private void resetForFirstRequest(int timeRemaining) { */ private static class RequestCallback { private final String url; - private final MonoSink> requestResultSink; + private final MonoSink> requestResultSink; private final ReactorHttpClient requestRateLimiter; private final boolean isAuthenticated; private final ReentrantLock lock = new ReentrantLock(); private boolean isCanceled = false; - private RequestCallback(String url, MonoSink> requestResultSink, boolean isAuthenticated, ReactorHttpClient requestRateLimiter) { + private RequestCallback(String url, MonoSink> requestResultSink, boolean isAuthenticated, ReactorHttpClient requestRateLimiter) { this.url = url; this.requestResultSink = requestResultSink; this.requestRateLimiter = requestRateLimiter; @@ -263,9 +266,8 @@ private void sendRequest() { .responseSingle((response, body) -> { try { ResponseHandlingResult result = requestRateLimiter.handleResponse(response, this); - if (result.allowToPass) { - return body.asString().zipWith(Mono.just(result.statusCode)); + return Mono.zip(body.asString(), Mono.just(result.statusCode), Mono.just(result.rateLimit)); } return Mono.empty(); } catch (InterruptedException e) { @@ -282,10 +284,12 @@ private void sendRequest() { private static class ResponseHandlingResult { public final boolean allowToPass; public final int statusCode; + public final RateLimit rateLimit; - public ResponseHandlingResult(boolean allowToPass, int statusCode) { + public ResponseHandlingResult(boolean allowToPass, int statusCode, RateLimit rateLimit) { this.allowToPass = allowToPass; this.statusCode = statusCode; + this.rateLimit = rateLimit; } } } diff --git a/hypixel-api-transport-unirest/src/main/java/net/hypixel/api/unirest/UnirestHttpClient.java b/hypixel-api-transport-unirest/src/main/java/net/hypixel/api/unirest/UnirestHttpClient.java index 48690211..614fce0a 100644 --- a/hypixel-api-transport-unirest/src/main/java/net/hypixel/api/unirest/UnirestHttpClient.java +++ b/hypixel-api-transport-unirest/src/main/java/net/hypixel/api/unirest/UnirestHttpClient.java @@ -1,8 +1,10 @@ package net.hypixel.api.unirest; +import kong.unirest.HttpResponse; import kong.unirest.Unirest; import net.hypixel.api.http.HypixelHttpClient; import net.hypixel.api.http.HypixelHttpResponse; +import net.hypixel.api.http.RateLimit; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -17,12 +19,30 @@ public UnirestHttpClient(UUID apiKey) { @Override public CompletableFuture makeRequest(String url) { - return Unirest.get(url).header("User-Agent", DEFAULT_USER_AGENT).asStringAsync().thenApply(res -> new HypixelHttpResponse(res.getStatus(), res.getBody())); + return Unirest.get(url) + .header("User-Agent", DEFAULT_USER_AGENT) + .asStringAsync() + .thenApply(res -> new HypixelHttpResponse(res.getStatus(), res.getBody(), null)); } @Override public CompletableFuture makeAuthenticatedRequest(String url) { - return Unirest.get(url).header("User-Agent", DEFAULT_USER_AGENT).header("API-Key", this.apiKey.toString()).asStringAsync().thenApply(res -> new HypixelHttpResponse(res.getStatus(), res.getBody())); + return Unirest.get(url) + .header("User-Agent", DEFAULT_USER_AGENT) + .header("API-Key", this.apiKey.toString()) + .asStringAsync() + .thenApply(res -> new HypixelHttpResponse(res.getStatus(), res.getBody(), createRateLimitResponse(res))); + } + + private RateLimit createRateLimitResponse(HttpResponse response) { + if (response.getStatus() != 200) { + return null; + } + + int limit = Integer.parseInt(response.getHeaders().getFirst("RateLimit-Limit")); + int remaining = Integer.parseInt(response.getHeaders().getFirst("RateLimit-Remaining")); + int reset = Integer.parseInt(response.getHeaders().getFirst("RateLimit-Reset")); + return new RateLimit(limit, remaining, reset); } @Override From b631b2ca7271d25d1bbb0df9cddebdd6f22c3cb5 Mon Sep 17 00:00:00 2001 From: Connor Linfoot Date: Thu, 15 Jun 2023 00:09:42 +0100 Subject: [PATCH 2/2] Add some docs in RateLimit --- .../main/java/net/hypixel/api/http/RateLimit.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hypixel-api-core/src/main/java/net/hypixel/api/http/RateLimit.java b/hypixel-api-core/src/main/java/net/hypixel/api/http/RateLimit.java index 2e2dbb28..3fbef1d1 100644 --- a/hypixel-api-core/src/main/java/net/hypixel/api/http/RateLimit.java +++ b/hypixel-api-core/src/main/java/net/hypixel/api/http/RateLimit.java @@ -16,18 +16,31 @@ public RateLimit(int limit, int remaining, int reset) { this.resetAt = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(reset)); } + /** + * @return the total limit allowed for the used API key per interval + */ public int getLimit() { return limit; } + /** + * @return the remaining amount of requests for the used API key during this interval + */ public int getRemaining() { return remaining; } + /** + * @return the time in seconds until the limit interval resets + */ public int getReset() { return reset; } + /** + * @return the date at which time the limit interval resets, this date won't be accurate to the millisecond due to + * the only context being in seconds + */ public Date getResetAt() { return resetAt; }