diff --git a/src/main/java/cc.fascinated/common/WebRequest.java b/src/main/java/cc.fascinated/common/WebRequest.java index dc5ef00..f62f440 100644 --- a/src/main/java/cc.fascinated/common/WebRequest.java +++ b/src/main/java/cc.fascinated/common/WebRequest.java @@ -1,6 +1,8 @@ package cc.fascinated.common; +import cc.fascinated.exception.impl.RateLimitException; import lombok.experimental.UtilityClass; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.HttpClientErrorException; @@ -33,6 +35,9 @@ public class WebRequest { if (profile.getStatusCode().isError()) { return null; } + if (profile.getStatusCode().isSameCodeAs(HttpStatus.TOO_MANY_REQUESTS)) { + throw new RateLimitException("Rate limit reached"); + } return profile.getBody(); } catch (HttpClientErrorException ex) { return null; diff --git a/src/main/java/cc.fascinated/controller/HomeController.java b/src/main/java/cc.fascinated/controller/HomeController.java index df10912..9563256 100644 --- a/src/main/java/cc.fascinated/controller/HomeController.java +++ b/src/main/java/cc.fascinated/controller/HomeController.java @@ -18,7 +18,7 @@ public class HomeController { @RequestMapping(value = "/") public String home(Model model) { 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"; } } diff --git a/src/main/java/cc.fascinated/exception/impl/MojangAPIRateLimitException.java b/src/main/java/cc.fascinated/exception/impl/MojangAPIRateLimitException.java new file mode 100644 index 0000000..6c2535a --- /dev/null +++ b/src/main/java/cc.fascinated/exception/impl/MojangAPIRateLimitException.java @@ -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."); + } +} diff --git a/src/main/java/cc.fascinated/exception/impl/RateLimitException.java b/src/main/java/cc.fascinated/exception/impl/RateLimitException.java new file mode 100644 index 0000000..4403181 --- /dev/null +++ b/src/main/java/cc.fascinated/exception/impl/RateLimitException.java @@ -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); + } +} diff --git a/src/main/java/cc.fascinated/model/cache/CachedMinecraftServer.java b/src/main/java/cc.fascinated/model/cache/CachedMinecraftServer.java index 1b4572e..943ef03 100644 --- a/src/main/java/cc.fascinated/model/cache/CachedMinecraftServer.java +++ b/src/main/java/cc.fascinated/model/cache/CachedMinecraftServer.java @@ -24,7 +24,7 @@ public final class CachedMinecraftServer implements Serializable { * The cached server. */ @NonNull - private final MinecraftServer value; + private final MinecraftServer server; /** * The unix timestamp of when this diff --git a/src/main/java/cc.fascinated/service/PlayerService.java b/src/main/java/cc.fascinated/service/PlayerService.java index d50b6aa..3771163 100644 --- a/src/main/java/cc.fascinated/service/PlayerService.java +++ b/src/main/java/cc.fascinated/service/PlayerService.java @@ -3,27 +3,23 @@ package cc.fascinated.service; import cc.fascinated.common.PlayerUtils; import cc.fascinated.common.Tuple; 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.model.cache.CachedPlayer; import cc.fascinated.model.cache.CachedPlayerName; import cc.fascinated.model.mojang.MojangProfile; import cc.fascinated.model.mojang.MojangUsernameToUuid; import cc.fascinated.model.player.Cape; -import cc.fascinated.model.player.Player; import cc.fascinated.model.player.Skin; import cc.fascinated.repository.PlayerCacheRepository; import cc.fascinated.repository.PlayerNameCacheRepository; 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.stereotype.Service; -import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.TimeUnit; @Service @Log4j2 public class PlayerService { @@ -47,6 +43,7 @@ public class PlayerService { * @return the player */ public CachedPlayer getPlayer(String id) { + id = id.toUpperCase(); // Convert the id to uppercase to prevent case sensitivity UUID uuid = PlayerUtils.getUuidFromString(id); if (uuid == null) { // If the id is not a valid uuid, get the uuid from the username uuid = usernameToUuid(id); @@ -57,19 +54,23 @@ public class PlayerService { return cachedPlayer.get(); } - MojangProfile mojangProfile = mojangAPIService.getProfile(uuid.toString()); - Tuple skinAndCape = mojangProfile.getSkinAndCape(); - CachedPlayer player = new CachedPlayer( - uuid, - mojangProfile.getName(), - skinAndCape.getLeft(), // Skin - skinAndCape.getRight(), // Cape - System.currentTimeMillis() - ); + try { + MojangProfile mojangProfile = mojangAPIService.getProfile(uuid.toString()); // Get the player profile from Mojang + Tuple skinAndCape = mojangProfile.getSkinAndCape(); + CachedPlayer player = new CachedPlayer( + uuid, // Player UUID + mojangProfile.getName(), // Player Name + skinAndCape.getLeft(), // Skin + skinAndCape.getRight(), // Cape + System.currentTimeMillis() // Cache time + ); - playerCacheRepository.save(player); - player.setCached(-1); // Indicate that the player is not cached - return player; + playerCacheRepository.save(player); + player.setCached(-1); // Indicate that the player is not cached + return player; + } catch (RateLimitException exception) { + throw new MojangAPIRateLimitException(); + } } /** @@ -83,12 +84,16 @@ public class PlayerService { if (cachedPlayerName.isPresent()) { return cachedPlayerName.get().getUniqueId(); } - MojangUsernameToUuid mojangUsernameToUuid = mojangAPIService.getUuidFromUsername(username); - if (mojangUsernameToUuid == null) { - throw new ResourceNotFoundException("Player with username '%s' not found".formatted(username)); + try { + MojangUsernameToUuid mojangUsernameToUuid = mojangAPIService.getUuidFromUsername(username); + if (mojangUsernameToUuid == null) { + throw new ResourceNotFoundException("Player with username '%s' not found".formatted(username)); + } + UUID uuid = UUIDUtils.addDashes(mojangUsernameToUuid.getId()); + playerNameCacheRepository.save(new CachedPlayerName(username, uuid)); + return uuid; + } catch (RateLimitException exception) { + throw new MojangAPIRateLimitException(); } - UUID uuid = UUIDUtils.addDashes(mojangUsernameToUuid.getId()); - playerNameCacheRepository.save(new CachedPlayerName(username, uuid)); - return uuid; } } diff --git a/src/main/java/cc.fascinated/service/ServerService.java b/src/main/java/cc.fascinated/service/ServerService.java index 478cbdf..6641df1 100644 --- a/src/main/java/cc.fascinated/service/ServerService.java +++ b/src/main/java/cc.fascinated/service/ServerService.java @@ -73,7 +73,7 @@ public class ServerService { public byte[] getServerFavicon(String hostname, int port) { String icon = null; // The server base64 icon 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 icon = favicon.getBase64(); icon = icon.substring(icon.indexOf(",") + 1); // Remove the data type from the server icon