diff --git a/pom.xml b/pom.xml
index a54eec2..b8ef856 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,12 @@
1.18.32
provided
+
+ com.google.code.gson
+ gson
+ 2.10.1
+ compile
+
diff --git a/src/main/java/me/braydon/mc/RESTfulMC.java b/src/main/java/me/braydon/mc/RESTfulMC.java
index 2a62f95..9e61dbf 100644
--- a/src/main/java/me/braydon/mc/RESTfulMC.java
+++ b/src/main/java/me/braydon/mc/RESTfulMC.java
@@ -1,5 +1,7 @@
package me.braydon.mc;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -16,9 +18,14 @@ import java.util.Objects;
*/
@SpringBootApplication
@Slf4j(topic = "RESTfulMC")
-public final class RESTfulMC {
+public class RESTfulMC {
+ public static final Gson GSON = new GsonBuilder()
+ .serializeNulls()
+ .create();
+
@SneakyThrows
public static void main(@NonNull String[] args) {
+ // Handle loading of our configuration file
File config = new File("application.yml");
if (!config.exists()) { // Saving the default config if it doesn't exist locally
Files.copy(Objects.requireNonNull(RESTfulMC.class.getResourceAsStream("/application.yml")), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
@@ -28,6 +35,8 @@ public final class RESTfulMC {
return;
}
log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config
+
+ // Start the app
SpringApplication.run(RESTfulMC.class, args);
}
}
\ No newline at end of file
diff --git a/src/main/java/me/braydon/mc/service/MojangAPI.java b/src/main/java/me/braydon/mc/service/MojangAPI.java
new file mode 100644
index 0000000..7a5120d
--- /dev/null
+++ b/src/main/java/me/braydon/mc/service/MojangAPI.java
@@ -0,0 +1,13 @@
+package me.braydon.mc.service;
+
+/**
+ * Endpoints for the Mojang API.
+ *
+ * @author Braydon
+ */
+public class MojangAPI {
+ private static final String SESSION_SERVER_ENDPOINT = "https://sessionserver.mojang.com";
+ private static final String API_ENDPOINT = "https://api.mojang.com";
+ protected static final String UUID_TO_PROFILE = SESSION_SERVER_ENDPOINT + "/session/minecraft/profile/%s";
+ protected static final String USERNAME_TO_UUID = API_ENDPOINT + "/users/profiles/minecraft/%s";
+}
\ No newline at end of file
diff --git a/src/main/java/me/braydon/mc/service/MojangService.java b/src/main/java/me/braydon/mc/service/MojangService.java
new file mode 100644
index 0000000..fb76975
--- /dev/null
+++ b/src/main/java/me/braydon/mc/service/MojangService.java
@@ -0,0 +1,93 @@
+package me.braydon.mc.service;
+
+import lombok.NonNull;
+import lombok.extern.log4j.Log4j2;
+import me.braydon.mc.common.UUIDUtils;
+import me.braydon.mc.common.web.JsonWebException;
+import me.braydon.mc.common.web.JsonWebRequest;
+import me.braydon.mc.exception.impl.BadRequestException;
+import me.braydon.mc.exception.impl.ResourceNotFoundException;
+import me.braydon.mc.model.Player;
+import me.braydon.mc.model.token.MojangProfileToken;
+import me.braydon.mc.model.token.MojangUsernameToUUIDToken;
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Service;
+
+import java.util.UUID;
+
+/**
+ * A service for interacting with the Mojang API.
+ *
+ * @author Braydon
+ */
+@Service
+@Log4j2(topic = "Mojang Service")
+public final class MojangService {
+ /**
+ * Get a player by their username or UUID.
+ *
+ * @param query the query to search for the player by
+ * @return the player
+ * @throws BadRequestException if the UUID is malformed
+ * @throws ResourceNotFoundException if the player is not found
+ */
+ @NonNull
+ public Player getPlayer(@NonNull String query) throws BadRequestException, ResourceNotFoundException {
+ log.info("Requesting player with query: {}", query);
+
+ UUID uuid; // The player UUID to lookup
+ boolean fullLength = query.length() == 36; // Was a UUID provided?
+ if (query.length() == 32 || fullLength) { // Parse the query as a UUID
+ try {
+ uuid = fullLength ? UUID.fromString(query) : UUIDUtils.addDashes(query);
+ log.info("Parsed {}UUID: {} -> {}", fullLength ? "" : "trimmed ", query, uuid);
+ } catch (IllegalArgumentException ex) {
+ throw new BadRequestException("Malformed UUID provided: %s".formatted(query));
+ }
+ } else { // The query is a username, request from Mojang
+ uuid = usernameToUUID(query);
+ log.info("Found UUID for username {}: {}", query, uuid);
+ }
+
+ // Send a request to Mojang requesting
+ // the player profile by their UUID
+ try {
+ log.info("Retrieving player profile for UUID: {}", uuid);
+ MojangProfileToken token = JsonWebRequest.makeRequest(
+ MojangAPI.UUID_TO_PROFILE.formatted(uuid), HttpMethod.GET
+ ).execute(MojangProfileToken.class);
+
+ // Return our player model representing the requested player
+ return new Player(uuid, token.getName(), token.getProfileActions());
+ } catch (JsonWebException ex) {
+ // No profile found, return null
+ if (ex.getStatusCode() == 400) {
+ throw new ResourceNotFoundException("Player not found with query: %s".formatted(query));
+ }
+ throw ex;
+ }
+ }
+
+ /**
+ * Get the UUID of a player by their username.
+ *
+ * @param username the player's username
+ * @return the player's UUID
+ * @throws ResourceNotFoundException if the player isn't found
+ */
+ @NonNull
+ private UUID usernameToUUID(@NonNull String username) throws ResourceNotFoundException {
+ // Make a request to Mojang requesting the UUID
+ try {
+ MojangUsernameToUUIDToken token = JsonWebRequest.makeRequest(
+ MojangAPI.USERNAME_TO_UUID.formatted(username), HttpMethod.GET
+ ).execute(MojangUsernameToUUIDToken.class);
+ return UUIDUtils.addDashes(token.getId()); // Return the UUID with dashes
+ } catch (JsonWebException ex) {
+ if (ex.getStatusCode() == 404) {
+ throw new ResourceNotFoundException("Player not found with username: %s".formatted(username));
+ }
+ throw ex;
+ }
+ }
+}
\ No newline at end of file