feat: code

This commit is contained in:
2025-12-12 06:13:27 -08:00
parent 4169d51317
commit 1a31570eea
8 changed files with 297 additions and 217 deletions

View File

@@ -11,6 +11,7 @@ dependencies {
implementation("net.kyori:adventure-api:4.25.0")
implementation("net.kyori:adventure-text-minimessage:4.25.0")
implementation("org.slf4j:slf4j-api:2.0.17")
}
java {

View File

@@ -0,0 +1,153 @@
package online.mineroo.common;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import online.mineroo.common.NetworkServiceInterface;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.util.concurrent.CompletableFuture;
/**
* BindRequest handles server binding and verification with the Mineroo API.
* <p>
* This class operates asynchronously and relies on the injected
* {@link NetworkService}
* to handle the actual HTTP transport and authentication.
*/
public class BindRequest {
private final Logger logger;
private final NetworkServiceInterface networkService;
// State cache
private String motdToken = "";
private String sessionToken = "";
/**
* Constructs a BindRequest instance.
*
* @param logger Logger for logging errors and information
* @param networkService The network service instance (Local or Proxy) that
* handles transport and auth
*/
public BindRequest(Logger logger, NetworkServiceInterface networkService) {
this.logger = logger;
this.networkService = networkService;
}
/**
* Checks the bind status asynchronously.
*
* @return A future that completes with true if status is 'bound', false
* otherwise
*/
public CompletableFuture<Boolean> checkBindStatus() {
// NetworkService is already configured with BaseURL, only need the path here
String path = "/server/server-bind-status";
return networkService.getData(path, null)
.thenApply(responseBody -> {
try {
JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject();
boolean bound = responseData.has("bound") && responseData.get("bound").getAsBoolean();
if (!bound) {
logger.warn("Mineroo Bind: Server not bound. 'bound' field is false.");
}
return bound;
} catch (Exception e) {
logger.error("Mineroo Bind: Failed to parse bind status response", e);
return false;
}
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Network error while checking status", e);
return false;
});
}
/**
* Initiates a MOTD verification request asynchronously.
*
* @param hostname The server hostname
* @param port The server port
* @return A future that completes with true if tokens are received
*/
public CompletableFuture<Boolean> initialMotdVerifyRequest(String hostname, Integer port) {
String path = "/server/motd-verify";
JsonObject json = new JsonObject();
json.addProperty("server_address", hostname);
json.addProperty("server_port", port);
return networkService.postData(path, json)
.thenApply(responseBody -> {
try {
JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject();
if (responseData.has("motd_token")) {
this.motdToken = responseData.get("motd_token").getAsString();
} else {
logger.error("Mineroo Bind: MOTD Token not received.");
return false;
}
if (responseData.has("session_token")) {
this.sessionToken = responseData.get("session_token").getAsString();
} else {
logger.error("Mineroo Bind: Session Token not received.");
return false;
}
return true;
} catch (Exception e) {
logger.error("Mineroo Bind: Failed to parse MOTD verify response: " + responseBody, e);
return false;
}
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Network error during MOTD verify", e);
return false;
});
}
/**
* Finalizes the server binding asynchronously.
*
* @param name The server name (nullable)
* @param description The server description (nullable)
* @return A future that completes with true if the binding is successful
*/
public CompletableFuture<Boolean> finalizeServerBinding(@Nullable String name, @Nullable String description) {
String path = "/server/server-bind";
JsonObject json = new JsonObject();
json.addProperty("session_token", this.sessionToken);
json.addProperty("name", name);
json.addProperty("description", description);
return networkService.postData(path, json)
.thenApply(responseBody -> {
// Assume as long as the request returns successfully and no exception is
// thrown, it is considered successful (HTTP 200)
// If the API returns a specific {"success": false}, you need to parse the JSON
// and check here
return true;
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Failed to finalize binding", e);
return false;
});
}
/**
* Gets the MOTD token received from the API.
*
* @return the motdToken string
*/
public String getMotdToken() {
return motdToken;
}
}

View File

@@ -0,0 +1,93 @@
package online.mineroo.common;
import com.google.gson.JsonObject;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class HttpNetworkService implements NetworkServiceInterface {
private final HttpClient client;
private final String baseUrl;
private final String authHeaderValue; // Precomputed Authorization header value
/**
* @param baseUrl Base URL of the API (e.g., "https://oapi.mineroo.online")
* @param username Username for Basic Auth (usually "mbind" in your case)
* @param password Password for Basic Auth (the token)
*/
public HttpNetworkService(String baseUrl, String username, String password) {
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
// It is recommended to configure connectTimeout
this.client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
// Pre-generate Basic Auth string
String auth = username + ":" + password;
this.authHeaderValue = "Basic " + Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
}
@Override
public CompletableFuture<String> getData(String endpoint, Map<String, String> params) {
return CompletableFuture.supplyAsync(() -> {
try {
// 1. Build URL and query parameters
StringBuilder urlBuilder = new StringBuilder(baseUrl + endpoint);
if (params != null && !params.isEmpty()) {
urlBuilder.append("?");
params.forEach((k, v) -> urlBuilder.append(k).append("=").append(v).append("&"));
// Simple trailing '&' removal (usually harmless, but cleaner)
urlBuilder.setLength(urlBuilder.length() - 1);
}
// 2. Build the request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(urlBuilder.toString()))
.header("Authorization", authHeaderValue) // Inject Auth
.header("User-Agent", "Mineroo-Plugin/1.0") // Recommended to add User-Agent
.GET()
.build();
// 3. Send the request
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// You can do a simple status code check here, or leave it to the upper layer
// To keep the interface pure, we just return the body
return response.body();
} catch (Exception e) {
// Wrap checked exceptions as runtime exceptions, Future will catch them
throw new RuntimeException("HTTP GET failed: " + endpoint, e);
}
});
}
@Override
public CompletableFuture<String> postData(String endpoint, JsonObject body) {
return CompletableFuture.supplyAsync(() -> {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + endpoint))
.header("Authorization", authHeaderValue) // Inject Auth
.header("Content-Type", "application/json")
.header("User-Agent", "Mineroo-Plugin/1.0")
.POST(HttpRequest.BodyPublishers.ofString(body.toString(), StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
} catch (Exception e) {
throw new RuntimeException("HTTP POST failed: " + endpoint, e);
}
});
}
}

View File

@@ -0,0 +1,12 @@
package online.mineroo.common;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import com.google.gson.JsonObject;
public interface NetworkServiceInterface {
CompletableFuture<String> getData(String endpoint, Map<String, String> params);
CompletableFuture<String> postData(String endpoint, JsonObject body);
}