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

@@ -1,6 +1,7 @@
package online.mineroo.velocity.commands;
import java.util.List;
import java.util.concurrent.CompletionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -12,9 +13,11 @@ import com.velocitypowered.api.command.SimpleCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import online.mineroo.common.MessageManager;
import online.mineroo.common.NetworkServiceInterface;
import online.mineroo.velocity.Config;
import online.mineroo.velocity.MinerooCore;
import online.mineroo.velocity.utils.BindRequest;
import online.mineroo.common.BindRequest;
import online.mineroo.common.HttpNetworkService;
public class MainCommand implements SimpleCommand {
@@ -94,43 +97,64 @@ public class MainCommand implements SimpleCommand {
Logger logger = plugin.getLogger();
// Run in a separate worker thread to allow blocking (Thread.sleep)
plugin.getServer().getScheduler().buildTask(plugin, () -> {
BindRequest request = new BindRequest(logger, bind_token);
// 1. Initialize the Network Service locally (Velocity connects directly)
NetworkServiceInterface networkService = new HttpNetworkService(
"https://oapi.mineroo.online",
"mbind",
bind_token);
BindRequest request = new BindRequest(logger, networkService);
try {
// Status check before initial bind
if (request.checkBindStatus()) {
// 2. Check Status
// We use .join() to block the thread until the Future completes.
if (request.checkBindStatus().join()) {
source.sendMessage(msg.get("command.bind.server.already-bound"));
return;
}
boolean inital_bind = request.initalMotdVerifyRequest(bind_address, bind_port);
// 3. Initial Verification
boolean inital_bind = request.initialMotdVerifyRequest(bind_address, bind_port).join();
if (inital_bind) {
String motdToken = request.getMotdToken();
plugin.getBindListener().setVerificationToken(motdToken);
source.sendMessage(msg.get("command.bind.server.wait"));
Thread.sleep(2 * 60 * 1000 + 5000);
// 4. Wait for external refresh (Blocking this worker thread is intended)
try {
Thread.sleep(2 * 60 * 1000 + 5000); // 2m 5s
} catch (InterruptedException e) {
logger.error("Bind thread interrupted", e);
return;
}
// Final Bind
// 5. Final Bind & Retry Loop
boolean success = false;
int maxRetries = 3;
for (int i = 1; i <= maxRetries; i++) {
if (request.finalizeServerBinding(bind_name, bind_description)) {
// Check final status synchronously using join()
if (request.finalizeServerBinding(bind_name, bind_description).join()) {
success = true;
break;
}
if (i < maxRetries) {
source.sendMessage(msg.get("command.bind.server.retry",
"current", String.valueOf(i),
"max", String.valueOf(maxRetries)));
Thread.sleep(10000);
try {
Thread.sleep(10000); // Wait 10s before retry
} catch (InterruptedException e) {
break;
}
}
}
@@ -145,14 +169,16 @@ public class MainCommand implements SimpleCommand {
Component.text("Initial handshake failed. Check your token or network.", NamedTextColor.RED));
}
} catch (CompletionException e) {
// Unpack the wrapper exception from join() to see the real network error
logger.error("Mineroo Bind Network Error: " + e.getCause().getMessage());
} catch (Exception e) {
logger.error("Mineroo Bind: " + e.toString());
logger.error("Mineroo Bind Error: " + e.toString());
} finally {
plugin.getBindListener().clearVerificationToken();
}
}).schedule();
}
public void sendHelp(CommandSource source) {

View File

@@ -1,205 +0,0 @@
package online.mineroo.velocity.utils;
import java.io.IOException;
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.util.Base64;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
/**
* BindRequest handles server binding and verification with the Mineroo API.
* <p>
* This class provides methods to:
* <ul>
* <li>Check if the server is bound using the bind token</li>
* <li>Initiate a MOTD verification request</li>
* <li>Finalize the server binding with additional server details</li>
* </ul>
* It manages authentication, HTTP requests, and response parsing for the binding process.
*/
public class BindRequest {
/**
* The base endpoint for Mineroo API requests.
*/
private static final String endpoint = "https://oapi.mineroo.online";
/**
* Logger instance for logging errors and information.
*/
private final Logger logger;
/**
* HTTP client used for sending requests to the Mineroo API.
*/
private final HttpClient httpClient;
/**
* The bind token used for authenticating API requests.
*/
private final String bindToken;
/**
* The MOTD token received from the API after verification.
*/
private String motdToken = "";
/**
* The session token received from the API after verification.
*/
private String sessionToken = "";
/**
* Constructs a BindRequest instance.
*
* @param logger Logger for logging errors and information
* @param bindToken The bind token for API authentication
*/
public BindRequest(Logger logger, String bindToken) {
this.logger = logger;
this.bindToken = bindToken;
this.httpClient = HttpClient.newHttpClient();
}
/**
* Checks the bind status from the oapi.mineroo.online API.
*
* @return true if status is 'bound', false otherwise
*/
/**
* Checks the bind status from the oapi.mineroo.online API.
*
* @return true if status is 'bound', false otherwise
*/
public boolean checkBindStatus() {
String uri = endpoint + "/server/server-bind-status";
String basicAuth = "mbind:" + bindToken;
String encodedAuth = Base64.getEncoder().encodeToString(basicAuth.getBytes(StandardCharsets.UTF_8));
try {
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
.header("Authorization", "Basic " + encodedAuth)
.GET().build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
JsonObject responseData = JsonParser.parseString(response.body()).getAsJsonObject();
boolean bound = responseData.has("bound") && responseData.get("bound").getAsBoolean();
if (bound) {
return true;
} else {
logger.error("Mineroo Bind: Server not bound. 'bound' field is false.");
}
} else {
logger.error("Mineroo Bind: Failed to check bind status. Response: " + response.body());
}
} catch (Exception e) {
logger.error("Mineroo Bind: Exception while checking bind status", e);
}
return false;
}
/**
* Initiates a MOTD verification request to the Mineroo API.
*
* @param hostname The server hostname
* @param port The server port
* @return true if both motd_token and session_token are received, false otherwise
* @throws IOException, InterruptedException, JsonParseException on request or parsing failure
*/
public boolean initalMotdVerifyRequest(String hostname, Integer port)
throws IOException, InterruptedException, JsonParseException {
// generate uri
String uri = endpoint + "/server/motd-verify";
// build jsonBody
JsonObject json = new JsonObject();
json.addProperty("server_address", hostname);
json.addProperty("server_port", port);
String basicAuth = "mbind:" + bindToken;
String encodedAuth = Base64.getEncoder().encodeToString(basicAuth.getBytes(StandardCharsets.UTF_8));
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
.header("Content-Type", "application/json")
.header("Authorization", "Basic " + encodedAuth)
.POST(HttpRequest.BodyPublishers.ofString(json.toString())).build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
String body = response.body();
JsonObject responseData = JsonParser.parseString(body).getAsJsonObject();
if (responseData.has("motd_token")) {
motdToken = responseData.get("motd_token").getAsString();
} else {
logger.error("Mineroo Bind: MOTD Token not received.");
return false;
}
if (responseData.has("session_token")) {
sessionToken = responseData.get("session_token").getAsString();
} else {
logger.error("Mineroo Bind: Session Token not received.");
return false;
}
return true;
} else {
logger.error("Mineroo Bind: " + response.body().toString());
}
return false;
}
/**
* Finalizes the server binding by sending server details to the Mineroo API.
*
* @param name The server name (nullable)
* @param description The server description (nullable)
* @return true if the binding is successful (HTTP 200), false otherwise
* @throws IOException, InterruptedException, JsonParseException on request or parsing failure
*/
public boolean finalizeServerBinding(@Nullable String name, @Nullable String description)
throws IOException, InterruptedException, JsonParseException {
String uri = endpoint + "/server/server-bind";
JsonObject json = new JsonObject();
json.addProperty("session_token", sessionToken);
json.addProperty("name", name);
json.addProperty("description", description);
String basicAuth = "mbind:" + bindToken;
String encodedAuth = Base64.getEncoder().encodeToString(basicAuth.getBytes(StandardCharsets.UTF_8));
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
.header("Content-Type", "application/json")
.header("Authorization", "Basic " + encodedAuth)
.POST(HttpRequest.BodyPublishers.ofString(json.toString())).build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return response.statusCode() == 200;
}
/**
* Gets the MOTD token received from the API.
*
* @return the motdToken string
*/
public String getMotdToken() {
return motdToken;
}
}