diff --git a/src/main/java/net/minestom/server/utils/mojang/MojangUtils.java b/src/main/java/net/minestom/server/utils/mojang/MojangUtils.java index f99a65f2c..2bf3e9f16 100644 --- a/src/main/java/net/minestom/server/utils/mojang/MojangUtils.java +++ b/src/main/java/net/minestom/server/utils/mojang/MojangUtils.java @@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.util.UUID; import java.util.concurrent.TimeUnit; /** @@ -24,36 +25,107 @@ public final class MojangUtils { .softValues() .build(); + /** + * Gets a player's UUID from their username + * @param username The players username + * @return The {@link UUID} + * @throws IOException with text detailing the exception + */ + @Blocking + public static @NotNull UUID getUUID(String username) throws IOException { + // Thanks stackoverflow: https://stackoverflow.com/a/19399768/13247146 + return UUID.fromString( + retrieve(String.format(FROM_USERNAME_URL, username)).get("id") + .getAsString() + .replaceFirst( + "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", + "$1-$2-$3-$4-$5" + ) + ); + } + + /** + * Gets a player's username from their UUID + * @param playerUUID The {@link UUID} of the player + * @return The player's username + * @throws IOException with text detailing the exception + */ + @Blocking + public static @NotNull String getUsername(UUID playerUUID) throws IOException { + return retrieve(String.format(FROM_UUID_URL, playerUUID)).get("name").getAsString(); + } + + /** + * Gets a {@link JsonObject} with the response from the mojang API + * @param uuid The UUID as a {@link UUID} + * @return The {@link JsonObject} or {@code null} if the mojang API is down or the UUID is invalid + */ + @Blocking + public static @Nullable JsonObject fromUuid(@NotNull UUID uuid) { + return fromUuid(uuid.toString()); + } + + /** + * Gets a {@link JsonObject} with the response from the mojang API + * @param uuid The UUID as a {@link String} + * @return The {@link JsonObject} or {@code null} if the mojang API is down or the UUID is invalid + */ @Blocking public static @Nullable JsonObject fromUuid(@NotNull String uuid) { - return retrieve(String.format(FROM_UUID_URL, uuid)); + try { + return retrieve(String.format(FROM_UUID_URL, uuid)); + } catch (IOException e) { + return null; + } } + /** + * Gets a {@link JsonObject} with the response from the mojang API + * @param username The username as a {@link String} + * @return The {@link JsonObject} or {@code null} if the mojang API is down or the username is invalid + */ @Blocking public static @Nullable JsonObject fromUsername(@NotNull String username) { - return retrieve(String.format(FROM_USERNAME_URL, username)); + try { + return retrieve(String.format(FROM_USERNAME_URL, username)); + } catch (IOException e) { + return null; + } } - private static @Nullable JsonObject retrieve(@NotNull String url) { - return URL_CACHE.get(url, s -> { - try { - // Retrieve from the rate-limited Mojang API - final String response = URLUtils.getText(url); - // If our response is "", that means the url did not get a proper object from the url - // So the username or UUID was invalid, and therefore we return null - if (response.isEmpty()) { - return null; - } + /** + * Gets the JsonObject from a URL, expects a mojang player URL so the errors might not make sense if it is not + * @param url The url to retrieve + * @return The {@link JsonObject} of the result + * @throws IOException with the text detailing the exception + */ + private static @NotNull JsonObject retrieve(@NotNull String url) throws IOException { + @Nullable final var cacheResult = URL_CACHE.getIfPresent(url); - JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); - if (jsonObject.has("errorMessage")) { - return null; - } - return jsonObject; - } catch (IOException e) { - MinecraftServer.getExceptionManager().handleException(e); - throw new RuntimeException(e); - } - }); + if (cacheResult != null) { + return cacheResult; + } + + final String response; + try { + // Retrieve from the rate-limited Mojang API + response = URLUtils.getText(url); + } catch (IOException e) { + MinecraftServer.getExceptionManager().handleException(e); + throw new RuntimeException(e); + } + + // If our response is "", that means the url did not get a proper object from the url + // So the username or UUID was invalid, and therefore we return null + if (response.isEmpty()) { + throw new IOException("The Mojang API is down"); + } + + JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); + if (jsonObject.has("errorMessage")) { + throw new IOException(jsonObject.get("errorMessage").getAsString()); + } + URL_CACHE.put(url, jsonObject); + return jsonObject; } } diff --git a/src/test/java/net/minestom/server/utils/TestMojangUtils.java b/src/test/java/net/minestom/server/utils/TestMojangUtils.java index eee508bc3..3a910dff4 100644 --- a/src/test/java/net/minestom/server/utils/TestMojangUtils.java +++ b/src/test/java/net/minestom/server/utils/TestMojangUtils.java @@ -3,9 +3,13 @@ package net.minestom.server.utils; import net.minestom.server.utils.mojang.MojangUtils; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.util.UUID; + import static org.junit.jupiter.api.Assertions.*; public class TestMojangUtils { + private final UUID JEB_UUID = UUID.fromString("853c80ef-3c37-49fd-aa49-938b674adae6"); @Test public void testValidNameWorks() { var result = MojangUtils.fromUsername("jeb_"); @@ -21,7 +25,7 @@ public class TestMojangUtils { @Test public void testValidUuidWorks() { - var result = MojangUtils.fromUuid("853c80ef3c3749fdaa49938b674adae6"); + var result = MojangUtils.fromUuid(JEB_UUID.toString()); assertNotNull(result); assertEquals("jeb_", result.get("name").getAsString()); assertEquals("853c80ef3c3749fdaa49938b674adae6", result.get("id").getAsString()); @@ -38,4 +42,27 @@ public class TestMojangUtils { var result = MojangUtils.fromUuid("00000000-0000-0000-0000-000000000000"); assertNull(result); } + + @Test + public void testValidUUIDWorks() { + var result = MojangUtils.fromUuid(JEB_UUID); + assertNotNull(result); + assertEquals("jeb_", result.get("name").getAsString()); + assertEquals("853c80ef3c3749fdaa49938b674adae6", result.get("id").getAsString()); + } + + @Test + public void testGetValidNameWorks() throws IOException { + assertEquals(JEB_UUID, MojangUtils.getUUID("jeb_")); + } + + @Test + public void testGetValidUUIDWorks() throws IOException { + assertEquals("jeb_", MojangUtils.getUsername(JEB_UUID)); + } + + @Test + public void testGetInvalidNameThrows() { + assertThrows(IOException.class, () -> MojangUtils.getUUID("a")); // Too short + } }