add rate limit handler
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s

This commit is contained in:
Lee 2024-07-31 23:24:37 +01:00
parent 6b11a608bd
commit 49e223a8b9
3 changed files with 94 additions and 5 deletions

@ -0,0 +1,90 @@
package cc.fascinated.common;
import kong.unirest.core.Headers;
import kong.unirest.core.HttpResponse;
import kong.unirest.core.Unirest;
import lombok.extern.log4j.Log4j2;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@Log4j2(topic = "Request")
public class Request {
/**
* The rate limit headers.
*/
private static final List<String> rateLimitHeaders = List.of(
"X-RateLimit-Remaining",
"RateLimit-Remaining"
);
/**
* The rate limit reset headers.
*/
private static final List<String> rateLimitResetHeaders = List.of(
"X-RateLimit-Reset",
"RateLimit-Reset"
);
/**
* Sends a GET request to a URL.
*
* @param url the URL to send the request to
* @param clazz the class to parse the response to
* @param <T> the type of the response
* @return the response
*/
public static <T> HttpResponse<T> get(String url, Class<T> clazz) {
HttpResponse<T> response = Unirest.get(url).asObject(clazz);
int rateLimitRemaining = getRateLimitRemaining(response);
if (rateLimitRemaining == 0) {
long rateLimitReset = getRateLimitReset(response);
long timeLeft = rateLimitReset - System.currentTimeMillis();
try {
Thread.sleep(timeLeft);
} catch (InterruptedException e) {
log.error("Failed to sleep for rate limit reset", e);
}
response = Unirest.get(url).asObject(clazz);
}
return response;
}
/**
* Gets the rate limit remaining.
*
* @param response the response to get the rate limit remaining from
* @return the rate limit remaining
*/
public static int getRateLimitRemaining(HttpResponse<?> response) {
Headers headers = response.getHeaders();
for (String rateLimitHeader : rateLimitHeaders) {
if (headers.containsKey(rateLimitHeader)) {
return Integer.parseInt(headers.getFirst(rateLimitHeader));
}
}
return -1;
}
/**
* Gets the rate limit reset absolute time.
*
* @param response the response to get the rate limit reset time from
* @return the rate limit reset time
*/
public static long getRateLimitReset(HttpResponse<?> response) {
Headers headers = response.getHeaders();
for (String rateLimitResetHeader : rateLimitResetHeaders) {
if (headers.containsKey(rateLimitResetHeader)) {
long reset = Long.parseLong(headers.getFirst(rateLimitResetHeader));
if (reset < 86400) {// Assume it's in seconds left
return System.currentTimeMillis() + reset * 1000;
}
return reset * 1000; // Assume it's in seconds
}
}
return -1;
}
}

@ -30,7 +30,7 @@ public class ScoreSaberPlatform extends Platform {
/**
* Delay in ms for requests per minute.
*/
private static final long UPDATE_DELAY = 1000L / 250L; // 250 requests per minute
private static final long UPDATE_DELAY = 1000L / 150L; // 150 requests per minute
/**
* The base multiplier for stars.

@ -1,5 +1,6 @@
package cc.fascinated.services;
import cc.fascinated.common.Request;
import cc.fascinated.exception.impl.BadRequestException;
import cc.fascinated.model.token.ScoreSaberAccountToken;
import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
@ -45,8 +46,7 @@ public class ScoreSaberService {
throw new BadRequestException("%s does not have a linked ScoreSaber account".formatted(user.getUsername()));
}
HttpResponse<ScoreSaberAccountToken> response = Unirest.get(GET_PLAYER_ENDPOINT.formatted(user.getSteamId()))
.asObject(ScoreSaberAccountToken.class);
HttpResponse<ScoreSaberAccountToken> response = Request.get(GET_PLAYER_ENDPOINT.formatted(user.getSteamId()), ScoreSaberAccountToken.class);
if (response.getParsingError().isPresent()) { // Failed to parse the response
throw new BadRequestException("Failed to parse ScoreSaber account for '%s'".formatted(user.getUsername()));
}
@ -69,8 +69,7 @@ public class ScoreSaberService {
return leaderboardOptional.get();
}
HttpResponse<ScoreSaberLeaderboardToken> response = Unirest.get(GET_LEADERBOARD_ENDPOINT.formatted(leaderboardId))
.asObject(ScoreSaberLeaderboardToken.class);
HttpResponse<ScoreSaberLeaderboardToken> response = Request.get(GET_LEADERBOARD_ENDPOINT.formatted(leaderboardId), ScoreSaberLeaderboardToken.class);
if (response.getParsingError().isPresent()) { // Failed to parse the response
throw new BadRequestException("Failed to parse ScoreSaber leaderboard for '%s'".formatted(leaderboardId));
}