add rate limit handler
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s
This commit is contained in:
parent
6b11a608bd
commit
49e223a8b9
90
src/main/java/cc/fascinated/common/Request.java
Normal file
90
src/main/java/cc/fascinated/common/Request.java
Normal file
@ -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.
|
* 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.
|
* The base multiplier for stars.
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package cc.fascinated.services;
|
package cc.fascinated.services;
|
||||||
|
|
||||||
|
import cc.fascinated.common.Request;
|
||||||
import cc.fascinated.exception.impl.BadRequestException;
|
import cc.fascinated.exception.impl.BadRequestException;
|
||||||
import cc.fascinated.model.token.ScoreSaberAccountToken;
|
import cc.fascinated.model.token.ScoreSaberAccountToken;
|
||||||
import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
|
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()));
|
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()))
|
HttpResponse<ScoreSaberAccountToken> response = Request.get(GET_PLAYER_ENDPOINT.formatted(user.getSteamId()), ScoreSaberAccountToken.class);
|
||||||
.asObject(ScoreSaberAccountToken.class);
|
|
||||||
if (response.getParsingError().isPresent()) { // Failed to parse the response
|
if (response.getParsingError().isPresent()) { // Failed to parse the response
|
||||||
throw new BadRequestException("Failed to parse ScoreSaber account for '%s'".formatted(user.getUsername()));
|
throw new BadRequestException("Failed to parse ScoreSaber account for '%s'".formatted(user.getUsername()));
|
||||||
}
|
}
|
||||||
@ -69,8 +69,7 @@ public class ScoreSaberService {
|
|||||||
return leaderboardOptional.get();
|
return leaderboardOptional.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse<ScoreSaberLeaderboardToken> response = Unirest.get(GET_LEADERBOARD_ENDPOINT.formatted(leaderboardId))
|
HttpResponse<ScoreSaberLeaderboardToken> response = Request.get(GET_LEADERBOARD_ENDPOINT.formatted(leaderboardId), ScoreSaberLeaderboardToken.class);
|
||||||
.asObject(ScoreSaberLeaderboardToken.class);
|
|
||||||
if (response.getParsingError().isPresent()) { // Failed to parse the response
|
if (response.getParsingError().isPresent()) { // Failed to parse the response
|
||||||
throw new BadRequestException("Failed to parse ScoreSaber leaderboard for '%s'".formatted(leaderboardId));
|
throw new BadRequestException("Failed to parse ScoreSaber leaderboard for '%s'".formatted(leaderboardId));
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user