add mojang api rate limit error

This commit is contained in:
Lee 2024-04-10 09:30:35 +01:00
parent 330c3efc78
commit 2e58d9c925
7 changed files with 58 additions and 27 deletions

@ -1,6 +1,8 @@
package cc.fascinated.common; package cc.fascinated.common;
import cc.fascinated.exception.impl.RateLimitException;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
@ -33,6 +35,9 @@ public class WebRequest {
if (profile.getStatusCode().isError()) { if (profile.getStatusCode().isError()) {
return null; return null;
} }
if (profile.getStatusCode().isSameCodeAs(HttpStatus.TOO_MANY_REQUESTS)) {
throw new RateLimitException("Rate limit reached");
}
return profile.getBody(); return profile.getBody();
} catch (HttpClientErrorException ex) { } catch (HttpClientErrorException ex) {
return null; return null;

@ -18,7 +18,7 @@ public class HomeController {
@RequestMapping(value = "/") @RequestMapping(value = "/")
public String home(Model model) { public String home(Model model) {
model.addAttribute("player_example_url", Config.INSTANCE.getWebPublicUrl() + "/player/" + exampleUuid); model.addAttribute("player_example_url", Config.INSTANCE.getWebPublicUrl() + "/player/" + exampleUuid);
model.addAttribute("java_server_example_url", Config.INSTANCE.getWebPublicUrl() + "/java/play.hypixel.net"); model.addAttribute("java_server_example_url", Config.INSTANCE.getWebPublicUrl() + "/server/java/play.hypixel.net");
return "index"; return "index";
} }
} }

@ -0,0 +1,9 @@
package cc.fascinated.exception.impl;
public class MojangAPIRateLimitException extends RateLimitException {
public MojangAPIRateLimitException() {
super("Mojang API rate limit exceeded. Please try again later.");
}
}

@ -0,0 +1,12 @@
package cc.fascinated.exception.impl;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public class RateLimitException extends RuntimeException {
public RateLimitException(String message) {
super(message);
}
}

@ -24,7 +24,7 @@ public final class CachedMinecraftServer implements Serializable {
* The cached server. * The cached server.
*/ */
@NonNull @NonNull
private final MinecraftServer value; private final MinecraftServer server;
/** /**
* The unix timestamp of when this * The unix timestamp of when this

@ -3,27 +3,23 @@ package cc.fascinated.service;
import cc.fascinated.common.PlayerUtils; import cc.fascinated.common.PlayerUtils;
import cc.fascinated.common.Tuple; import cc.fascinated.common.Tuple;
import cc.fascinated.common.UUIDUtils; import cc.fascinated.common.UUIDUtils;
import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.exception.impl.MojangAPIRateLimitException;
import cc.fascinated.exception.impl.RateLimitException;
import cc.fascinated.exception.impl.ResourceNotFoundException; import cc.fascinated.exception.impl.ResourceNotFoundException;
import cc.fascinated.model.cache.CachedPlayer; import cc.fascinated.model.cache.CachedPlayer;
import cc.fascinated.model.cache.CachedPlayerName; import cc.fascinated.model.cache.CachedPlayerName;
import cc.fascinated.model.mojang.MojangProfile; import cc.fascinated.model.mojang.MojangProfile;
import cc.fascinated.model.mojang.MojangUsernameToUuid; import cc.fascinated.model.mojang.MojangUsernameToUuid;
import cc.fascinated.model.player.Cape; import cc.fascinated.model.player.Cape;
import cc.fascinated.model.player.Player;
import cc.fascinated.model.player.Skin; import cc.fascinated.model.player.Skin;
import cc.fascinated.repository.PlayerCacheRepository; import cc.fascinated.repository.PlayerCacheRepository;
import cc.fascinated.repository.PlayerNameCacheRepository; import cc.fascinated.repository.PlayerNameCacheRepository;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service @Log4j2 @Service @Log4j2
public class PlayerService { public class PlayerService {
@ -47,6 +43,7 @@ public class PlayerService {
* @return the player * @return the player
*/ */
public CachedPlayer getPlayer(String id) { public CachedPlayer getPlayer(String id) {
id = id.toUpperCase(); // Convert the id to uppercase to prevent case sensitivity
UUID uuid = PlayerUtils.getUuidFromString(id); UUID uuid = PlayerUtils.getUuidFromString(id);
if (uuid == null) { // If the id is not a valid uuid, get the uuid from the username if (uuid == null) { // If the id is not a valid uuid, get the uuid from the username
uuid = usernameToUuid(id); uuid = usernameToUuid(id);
@ -57,19 +54,23 @@ public class PlayerService {
return cachedPlayer.get(); return cachedPlayer.get();
} }
MojangProfile mojangProfile = mojangAPIService.getProfile(uuid.toString()); try {
MojangProfile mojangProfile = mojangAPIService.getProfile(uuid.toString()); // Get the player profile from Mojang
Tuple<Skin, Cape> skinAndCape = mojangProfile.getSkinAndCape(); Tuple<Skin, Cape> skinAndCape = mojangProfile.getSkinAndCape();
CachedPlayer player = new CachedPlayer( CachedPlayer player = new CachedPlayer(
uuid, uuid, // Player UUID
mojangProfile.getName(), mojangProfile.getName(), // Player Name
skinAndCape.getLeft(), // Skin skinAndCape.getLeft(), // Skin
skinAndCape.getRight(), // Cape skinAndCape.getRight(), // Cape
System.currentTimeMillis() System.currentTimeMillis() // Cache time
); );
playerCacheRepository.save(player); playerCacheRepository.save(player);
player.setCached(-1); // Indicate that the player is not cached player.setCached(-1); // Indicate that the player is not cached
return player; return player;
} catch (RateLimitException exception) {
throw new MojangAPIRateLimitException();
}
} }
/** /**
@ -83,6 +84,7 @@ public class PlayerService {
if (cachedPlayerName.isPresent()) { if (cachedPlayerName.isPresent()) {
return cachedPlayerName.get().getUniqueId(); return cachedPlayerName.get().getUniqueId();
} }
try {
MojangUsernameToUuid mojangUsernameToUuid = mojangAPIService.getUuidFromUsername(username); MojangUsernameToUuid mojangUsernameToUuid = mojangAPIService.getUuidFromUsername(username);
if (mojangUsernameToUuid == null) { if (mojangUsernameToUuid == null) {
throw new ResourceNotFoundException("Player with username '%s' not found".formatted(username)); throw new ResourceNotFoundException("Player with username '%s' not found".formatted(username));
@ -90,5 +92,8 @@ public class PlayerService {
UUID uuid = UUIDUtils.addDashes(mojangUsernameToUuid.getId()); UUID uuid = UUIDUtils.addDashes(mojangUsernameToUuid.getId());
playerNameCacheRepository.save(new CachedPlayerName(username, uuid)); playerNameCacheRepository.save(new CachedPlayerName(username, uuid));
return uuid; return uuid;
} catch (RateLimitException exception) {
throw new MojangAPIRateLimitException();
}
} }
} }

@ -73,7 +73,7 @@ public class ServerService {
public byte[] getServerFavicon(String hostname, int port) { public byte[] getServerFavicon(String hostname, int port) {
String icon = null; // The server base64 icon String icon = null; // The server base64 icon
try { try {
JavaMinecraftServer.Favicon favicon = ((JavaMinecraftServer) getServer(MinecraftServer.Platform.JAVA.name(), hostname, port).getValue()).getFavicon(); JavaMinecraftServer.Favicon favicon = ((JavaMinecraftServer) getServer(MinecraftServer.Platform.JAVA.name(), hostname, port).getServer()).getFavicon();
if (favicon != null) { // Use the server's favicon if (favicon != null) { // Use the server's favicon
icon = favicon.getBase64(); icon = favicon.getBase64();
icon = icon.substring(icon.indexOf(",") + 1); // Remove the data type from the server icon icon = icon.substring(icon.indexOf(",") + 1); // Remove the data type from the server icon