feat: code

This commit is contained in:
2025-12-14 21:59:22 -08:00
parent 4e1faa3edc
commit 5d0ed54cba
13 changed files with 413 additions and 234 deletions

View File

@@ -5,6 +5,8 @@ import com.google.gson.JsonParser;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
@@ -41,18 +43,29 @@ public class BindRequest {
* @return A future that completes with true if status is 'bound', false * @return A future that completes with true if status is 'bound', false
* otherwise * otherwise
*/ */
public CompletableFuture<Boolean> checkBindStatus() { public CompletableFuture<Boolean> checkBindStatus(String hostname, int port) {
// NetworkService is already configured with BaseURL, only need the path here // NetworkService is already configured with BaseURL, only need the path here
String path = "/server/server-bind-status"; String path = "/server/server-bind-status";
return networkService.getData(path, null) Map<String, String> params = new HashMap<>();
.thenApply(responseBody -> {
params.put("address", hostname);
params.put("port", String.valueOf(port));
return networkService.getData(path, params)
.thenApply(response -> {
if (response.getStatusCode() != 200) {
return false;
}
String responseBody = response.getBody();
try { try {
JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject(); JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject();
boolean bound = responseData.has("bound") && responseData.get("bound").getAsBoolean(); boolean bound = responseData.has("bound") && responseData.get("bound").getAsBoolean();
if (!bound) { if (bound) {
logger.warn("Mineroo Bind: Server not bound. 'bound' field is false."); logger.warn("Mineroo Bind: Server already bound.");
} }
return bound; return bound;
} catch (Exception e) { } catch (Exception e) {
@@ -73,7 +86,7 @@ public class BindRequest {
* @param port The server port * @param port The server port
* @return A future that completes with true if tokens are received * @return A future that completes with true if tokens are received
*/ */
public CompletableFuture<Boolean> initialMotdVerifyRequest(String hostname, Integer port) { public CompletableFuture<Boolean> initialMotdVerifyRequest(String hostname, int port) {
String path = "/server/motd-verify"; String path = "/server/motd-verify";
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
@@ -81,7 +94,14 @@ public class BindRequest {
json.addProperty("server_port", port); json.addProperty("server_port", port);
return networkService.postData(path, json) return networkService.postData(path, json)
.thenApply(responseBody -> { .thenApply(response -> {
String responseBody = response.getBody();
if (response.getStatusCode() != 200) {
logger.error("Mineroo Bind: Api fetch failed.");
return false;
}
try { try {
JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject(); JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject();
@@ -128,7 +148,8 @@ public class BindRequest {
json.addProperty("description", description); json.addProperty("description", description);
return networkService.postData(path, json) return networkService.postData(path, json)
.thenApply(responseBody -> { .thenApply(response -> {
String responseBody = response.getBody();
// Assume as long as the request returns successfully and no exception is // Assume as long as the request returns successfully and no exception is
// thrown, it is considered successful (HTTP 200) // thrown, it is considered successful (HTTP 200)
// If the API returns a specific {"success": false}, you need to parse the JSON // If the API returns a specific {"success": false}, you need to parse the JSON

View File

@@ -19,7 +19,7 @@ public class HttpNetworkService implements NetworkServiceInterface {
/** /**
* @param baseUrl Base URL of the API (e.g., "https://oapi.mineroo.online") * @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 username Username for Basic Auth (usually "mserver" in your case)
* @param password Password for Basic Auth (the token) * @param password Password for Basic Auth (the token)
*/ */
public HttpNetworkService(String baseUrl, String username, String password) { public HttpNetworkService(String baseUrl, String username, String password) {
@@ -36,7 +36,7 @@ public class HttpNetworkService implements NetworkServiceInterface {
} }
@Override @Override
public CompletableFuture<String> getData(String endpoint, Map<String, String> params) { public CompletableFuture<NetworkResponse> getData(String endpoint, Map<String, String> params) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try { try {
// 1. Build URL and query parameters // 1. Build URL and query parameters
@@ -61,7 +61,8 @@ public class HttpNetworkService implements NetworkServiceInterface {
// You can do a simple status code check here, or leave it to the upper layer // 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 // To keep the interface pure, we just return the body
return response.body(); NetworkResponse resp = new NetworkResponse(response.statusCode(), response.body());
return resp;
} catch (Exception e) { } catch (Exception e) {
// Wrap checked exceptions as runtime exceptions, Future will catch them // Wrap checked exceptions as runtime exceptions, Future will catch them
@@ -71,7 +72,7 @@ public class HttpNetworkService implements NetworkServiceInterface {
} }
@Override @Override
public CompletableFuture<String> postData(String endpoint, JsonObject body) { public CompletableFuture<NetworkResponse> postData(String endpoint, JsonObject body) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try { try {
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
@@ -83,7 +84,9 @@ public class HttpNetworkService implements NetworkServiceInterface {
.build(); .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
NetworkResponse resp = new NetworkResponse(response.statusCode(), response.body());
return resp;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("HTTP POST failed: " + endpoint, e); throw new RuntimeException("HTTP POST failed: " + endpoint, e);

View File

@@ -0,0 +1,19 @@
package online.mineroo.common;
public class NetworkResponse {
private final int statusCode;
private final String body;
public NetworkResponse(int statusCode, String body) {
this.statusCode = statusCode;
this.body = body;
}
public int getStatusCode() {
return statusCode;
}
public String getBody() {
return body;
}
}

View File

@@ -6,7 +6,7 @@ import java.util.concurrent.CompletableFuture;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
public interface NetworkServiceInterface { public interface NetworkServiceInterface {
CompletableFuture<String> getData(String endpoint, Map<String, String> params); CompletableFuture<NetworkResponse> getData(String endpoint, Map<String, String> params);
CompletableFuture<String> postData(String endpoint, JsonObject body); CompletableFuture<NetworkResponse> postData(String endpoint, JsonObject body);
} }

View File

@@ -4,6 +4,7 @@ public class ProtocolConstants {
// Protocol channel name // Protocol channel name
public static String PROTOCOL_CHANNEL = "mineroo:plugin:api"; public static String PROTOCOL_CHANNEL = "mineroo:plugin:api";
// Event names // Sub-channel/event names
public static String BIND_USER_REQUEST = "user-bind-request"; public static String API_REQUEST = "API_REQUEST";
public static String BIND_MOTD_TOKEN = "SET_MOTD_TOKEN";
} }

View File

@@ -29,6 +29,10 @@ tasks {
minecraftVersion("1.21.10") minecraftVersion("1.21.10")
} }
shadowJar {
archiveFileName.set("mineroo-paper.jar")
}
withType<Jar> { withType<Jar> {
from("src/main/resources") from("src/main/resources")
} }

View File

@@ -7,13 +7,19 @@ public class Config {
private final ServerSection server; private final ServerSection server;
private final PlayersSection player; private final PlayersSection player;
private final ProxySection proxy; private final ProxySection proxy;
private final TestSection test;
public Config(FileConfiguration config) { public Config(FileConfiguration config) {
this.test = new TestSection(config);
this.proxy = new ProxySection(config); this.proxy = new ProxySection(config);
this.server = new ServerSection(config); this.server = new ServerSection(config);
this.player = new PlayersSection(config); this.player = new PlayersSection(config);
} }
public TestSection getTest() {
return test;
}
public ProxySection getProxy() { public ProxySection getProxy() {
return proxy; return proxy;
} }
@@ -26,6 +32,18 @@ public class Config {
return player; return player;
} }
public static class TestSection {
private final String apiHostname;
public TestSection(FileConfiguration config) {
this.apiHostname = config.getString("test.api_hostname", "https://oapi.mineroo.online");
}
public String getApiHostname() {
return apiHostname;
}
}
public static class ProxySection { public static class ProxySection {
private final boolean useVelocity; private final boolean useVelocity;

View File

@@ -6,6 +6,7 @@ import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import online.mineroo.common.HttpNetworkService; import online.mineroo.common.HttpNetworkService;
import online.mineroo.common.MessageManager; import online.mineroo.common.MessageManager;
import online.mineroo.common.NetworkServiceInterface; import online.mineroo.common.NetworkServiceInterface;
import online.mineroo.common.ProtocolConstants;
import online.mineroo.paper.commands.MainCommand; import online.mineroo.paper.commands.MainCommand;
import online.mineroo.paper.utils.PlayerBindDialog; import online.mineroo.paper.utils.PlayerBindDialog;
@@ -31,6 +32,8 @@ public class MinerooCore extends JavaPlugin implements Listener {
commands.registrar().register(new MainCommand(this).build()); commands.registrar().register(new MainCommand(this).build());
}); });
this.getServer().getMessenger().registerOutgoingPluginChannel(this, ProtocolConstants.PROTOCOL_CHANNEL);
messageManager = new MessageManager(); messageManager = new MessageManager();
getServer().getPluginManager().registerEvents(this, this); getServer().getPluginManager().registerEvents(this, this);
@@ -61,8 +64,10 @@ public class MinerooCore extends JavaPlugin implements Listener {
getLogger().info("Using Velocity proxy network service."); getLogger().info("Using Velocity proxy network service.");
} else { } else {
String token = config.getServer().getServerBind().getBindToken(); String token = config.getServer().getServerBind().getBindToken();
this.networkService = new HttpNetworkService("https://oapi.mineroo.online", "mbind", token); String hostname = config.getTest().getApiHostname();
this.networkService = new HttpNetworkService(hostname, "mserver", token);
getLogger().info("Using direct HTTP network service."); getLogger().info("Using direct HTTP network service.");
getLogger().info("API: " + hostname);
} }
if (this.messageManager == null) { if (this.messageManager == null) {

View File

@@ -7,6 +7,8 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import online.mineroo.common.ProxyNetworkRequest; import online.mineroo.common.ProxyNetworkRequest;
import online.mineroo.common.NetworkServiceInterface; import online.mineroo.common.NetworkServiceInterface;
import online.mineroo.common.ProtocolConstants;
import online.mineroo.common.NetworkResponse;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@@ -23,7 +25,7 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa
private final String CHANNEL = "mineroo:net"; private final String CHANNEL = "mineroo:net";
// Store pending requests <RequestId, Future> // Store pending requests <RequestId, Future>
private final Map<String, CompletableFuture<String>> pendingRequests = new ConcurrentHashMap<>(); private final Map<String, CompletableFuture<NetworkResponse>> pendingRequests = new ConcurrentHashMap<>();
public ProxyNetworkService(JavaPlugin plugin) { public ProxyNetworkService(JavaPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
@@ -33,18 +35,18 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa
} }
@Override @Override
public CompletableFuture<String> getData(String endpoint, Map<String, String> params) { public CompletableFuture<NetworkResponse> getData(String endpoint, Map<String, String> params) {
return sendRequest("GET", endpoint, params, null); return sendRequest("GET", endpoint, params, null);
} }
@Override @Override
public CompletableFuture<String> postData(String endpoint, JsonObject body) { public CompletableFuture<NetworkResponse> postData(String endpoint, JsonObject body) {
return sendRequest("POST", endpoint, null, body); return sendRequest("POST", endpoint, null, body);
} }
private CompletableFuture<String> sendRequest(String method, String endpoint, Map<String, String> params, private CompletableFuture<NetworkResponse> sendRequest(String method, String endpoint, Map<String, String> params,
JsonObject body) { JsonObject body) {
CompletableFuture<String> future = new CompletableFuture<>(); CompletableFuture<NetworkResponse> future = new CompletableFuture<>();
// 1. Check if there is any player online (Plugin Message must be sent via a // 1. Check if there is any player online (Plugin Message must be sent via a
// player) // player)
@@ -59,7 +61,7 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa
pendingRequests.put(req.getRequestId(), future); pendingRequests.put(req.getRequestId(), future);
ByteArrayDataOutput out = ByteStreams.newDataOutput(); ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("API_REQ"); // Sub-channel out.writeUTF(ProtocolConstants.API_REQUEST); // Sub-channel
out.writeUTF(gson.toJson(req)); // Payload out.writeUTF(gson.toJson(req)); // Payload
// 3. Send // 3. Send
@@ -67,9 +69,9 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa
// 4. Set timeout (prevent memory leak if Velocity does not respond) // 4. Set timeout (prevent memory leak if Velocity does not respond)
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> { plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> {
CompletableFuture<String> f = pendingRequests.remove(req.getRequestId()); CompletableFuture<NetworkResponse> f = pendingRequests.remove(req.getRequestId());
if (f != null) { if (f != null) {
f.completeExceptionally(new java.util.concurrent.TimeoutException("Proxy request timed out")); f.complete(new NetworkResponse(504, "Proxy request timed out"));
} }
}, 100L); // 5 seconds timeout }, 100L); // 5 seconds timeout
@@ -89,12 +91,12 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa
boolean success = in.readBoolean(); boolean success = in.readBoolean();
String data = in.readUTF(); // If success: body, if failure: error message String data = in.readUTF(); // If success: body, if failure: error message
CompletableFuture<String> future = pendingRequests.remove(reqId); CompletableFuture<NetworkResponse> future = pendingRequests.remove(reqId);
if (future != null) { if (future != null) {
if (success) { if (success) {
future.complete(data); future.complete(new NetworkResponse(200, data));
} else { } else {
future.completeExceptionally(new RuntimeException(data)); future.complete(new NetworkResponse(500, data));
} }
} }
} }

View File

@@ -11,7 +11,6 @@ import io.papermc.paper.command.brigadier.Commands;
import online.mineroo.common.BindRequest; import online.mineroo.common.BindRequest;
import online.mineroo.paper.MinerooCore; import online.mineroo.paper.MinerooCore;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BindCommand { public class BindCommand {
@@ -22,26 +21,112 @@ public class BindCommand {
} }
public LiteralCommandNode<CommandSourceStack> build() { public LiteralCommandNode<CommandSourceStack> build() {
LiteralArgumentBuilder<CommandSourceStack> bind = Commands.literal("bind"); LiteralArgumentBuilder<CommandSourceStack> bind = Commands.literal("bind")
.executes(context -> {
CommandSourceStack sender = context.getSource();
if (!sender.getSender().hasPermission("mineroo.admin.bind.server")) {
return bindPlayer(context);
}
return 0;
});
bind.then( bind.then(
Commands.literal("server") Commands.literal("server")
.requires(sender -> sender.getSender().hasPermission("mineroo.admin.bind.server"))) .requires(sender -> sender.getSender().hasPermission("mineroo.admin.bind.server"))
.executes(this::bindServer); .executes(this::bindServer));
bind.then(Commands.literal("player")); bind.then(
Commands.literal("player").requires(sender -> sender.getSender().hasPermission("mineroo.admin.bind.server"))
.executes(this::bindPlayer));
return bind.build(); return bind.build();
} }
private int bindPlayer(CommandContext<CommandSourceStack> context) {
return Command.SINGLE_SUCCESS;
}
private void sendMotdTokenToVelocity(String motdToken) {
// Send via any online player (Plugin Messaging must be attached to a player)
org.bukkit.entity.Player player = org.bukkit.Bukkit.getOnlinePlayers().stream().findFirst().orElse(null);
if (player == null)
return;
com.google.common.io.ByteArrayDataOutput out = com.google.common.io.ByteStreams.newDataOutput();
out.writeUTF(online.mineroo.common.ProtocolConstants.BIND_MOTD_TOKEN);
out.writeUTF(motdToken);
player.sendPluginMessage(plugin, online.mineroo.common.ProtocolConstants.PROTOCOL_CHANNEL, out.toByteArray());
}
private int bindServer(CommandContext<CommandSourceStack> context) throws CommandSyntaxException { private int bindServer(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
// Use slf4j logger for BindRequest to keep log style consistent across // Run the binding process asynchronously to avoid blocking the main thread
// platforms org.bukkit.Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
Logger logger = LoggerFactory.getLogger("MinerooPaper"); Logger logger = plugin.getSLF4JLogger();
BindRequest bindRequest = new BindRequest( BindRequest bindRequest = new BindRequest(
logger, logger,
plugin.getNetworkService()); plugin.getNetworkService());
try {
String address = plugin.getConfigObject().getServer().getServerBind().getAddress();
int port = plugin.getConfigObject().getServer().getServerBind().getPort();
// 1. Check binding status
if (bindRequest.checkBindStatus(address, port).join()) {
plugin.getMessageManager().get("command.bind.server.already-bound");
context.getSource().getSender()
.sendMessage(plugin.getMessageManager().get("command.bind.server.already-bound"));
return;
}
// 2. Initiate MOTD verification
boolean motdOk = bindRequest.initialMotdVerifyRequest(address, port).join();
if (motdOk) {
String motdToken = bindRequest.getMotdToken();
// Send MOTD token to Velocity
sendMotdTokenToVelocity(motdToken);
context.getSource().getSender().sendMessage(plugin.getMessageManager().get("command.bind.server.wait"));
try {
Thread.sleep(2 * 60 * 1000 + 5000); // 2m 5s
} catch (InterruptedException e) {
logger.error("Bind thread interrupted", e);
return;
}
// 3. Final Bind & Retry
boolean success = false;
int maxRetries = 3;
for (int i = 1; i <= maxRetries; i++) {
if (bindRequest.finalizeServerBinding(
plugin.getConfigObject().getServer().getServerName(),
plugin.getConfigObject().getServer().getDescription()).join()) {
success = true;
break;
}
if (i < maxRetries) {
context.getSource().getSender().sendMessage(
plugin.getMessageManager().get("command.bind.server.retry", "current", String.valueOf(i), "max",
String.valueOf(maxRetries)));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
break;
}
}
}
if (success) {
context.getSource().getSender().sendMessage(plugin.getMessageManager().get("command.bind.server.success"));
} else {
context.getSource().getSender().sendMessage(plugin.getMessageManager().get("command.bind.server.failed"));
}
} else {
context.getSource().getSender()
.sendMessage(plugin.getMessageManager().get("command.bind.server.inital.failed"));
}
} catch (Exception ex) {
logger.error("Bind process error", ex);
context.getSource().getSender().sendMessage(
plugin.getMessageManager().get("command.bind.server.failed"));
} finally {
// After binding is complete, you can send a message to clear the MOTD token
sendMotdTokenToVelocity(""); // Clear MOTD
}
});
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
} }
} }

View File

@@ -50,7 +50,7 @@ public class MainCommand {
messageManager.reload(); messageManager.reload();
// Send feedback // Send feedback
Component msg = messageManager.get("message.reload.success"); Component msg = messageManager.get("command.reload.success");
context.getSource().getSender().sendMessage(msg); context.getSource().getSender().sendMessage(msg);
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;

View File

@@ -1,9 +1,20 @@
# Bind Dialog
dialog.bind.player.title = <green>绑定您的 Mineroo.Online 账户</green> dialog.bind.player.title = <green>绑定您的 Mineroo.Online 账户</green>
dialog.bind.player.content = <aqua>绑定 Mineroo Online 账号以解锁更多实用功能</aqua> dialog.bind.player.content = <aqua>绑定 Mineroo Online 账号以解锁更多实用功能</aqua>
dialog.bind.player.email = <aqua>Mineroo 账户邮箱</aqua> dialog.bind.player.email = <aqua>Mineroo 账户邮箱</aqua>
dialog.bind.player.confirm = <green>确认</green> dialog.bind.player.confirm = <green>确认</green>
dialog.bind.player.cancel = <red>取消</red> dialog.bind.player.cancel = <red>取消</red>
dialog.bind.player.success = 请前往 Mineroo 个人中心完成验证。
message.bind.player.success = 请前往 Mineroo 个人中心完成验证。 # Bind Command
command.bind.server.already-bound=<yellow>当前服务器已在 Mineroo.Online 处于绑定状态。</yellow>
command.bind.server.start=<gray>开始服务器绑定流程,请稍等...</gray>
command.bind.server.inital.failed=<red>初次握手失败。检查您的令牌或网络。</red>
command.bind.server.wait=<aqua>正在验证服务器所有权... 数据同步可能需要 2 分钟,请稍候。</aqua>
command.bind.server.success=<green>绑定成功!<green>
command.bind.server.failed=<red>绑定失败。</red>
command.bind.server.retry=<yellow>验证未通过10 秒后重试 (<current>/<max>) ...</yellow>
message.reload.success = <green>插件配置文件重载成功!</green> # Reload Command
command.reload.success=<green>配置文件重载成功!耗时 <time>ms</green>
command.reload.failed=<red>配置文件重载失败,请查看控制台日志。</red>

View File

@@ -1,190 +1,200 @@
package online.mineroo.velocity.commands; // package online.mineroo.velocity.commands;
//
import java.util.List; // import java.util.List;
import java.util.concurrent.CompletionException; // import java.util.Optional;
import java.util.stream.Collectors; // import java.util.concurrent.CompletionException;
import java.util.stream.Stream; // import java.util.stream.Collectors;
// import java.util.stream.Stream;
import org.slf4j.Logger; //
// import org.slf4j.Logger;
import com.velocitypowered.api.command.CommandSource; //
import com.velocitypowered.api.command.SimpleCommand; // import com.velocitypowered.api.command.CommandSource;
// import com.velocitypowered.api.command.SimpleCommand;
import net.kyori.adventure.text.Component; //
import net.kyori.adventure.text.format.NamedTextColor; // import net.kyori.adventure.text.Component;
import online.mineroo.common.MessageManager; // import net.kyori.adventure.text.format.NamedTextColor;
import online.mineroo.common.NetworkServiceInterface; // import online.mineroo.common.MessageManager;
import online.mineroo.velocity.Config; // import online.mineroo.common.NetworkServiceInterface;
import online.mineroo.velocity.MinerooCore; // import online.mineroo.velocity.Config;
import online.mineroo.common.BindRequest; // import online.mineroo.velocity.MinerooCore;
import online.mineroo.common.HttpNetworkService; // import online.mineroo.common.BindRequest;
// import online.mineroo.common.HttpNetworkService;
public class MainCommand implements SimpleCommand { //
// public class MainCommand implements SimpleCommand {
private final MinerooCore plugin; //
// private final MinerooCore plugin;
public MainCommand(MinerooCore plugin) { //
this.plugin = plugin; // public MainCommand(MinerooCore plugin) {
} // this.plugin = plugin;
// }
@Override //
public void execute(Invocation invocation) { // @Override
String[] args = invocation.arguments(); // public void execute(Invocation invocation) {
if (args.length < 1) { // String[] args = invocation.arguments();
sendHelp(invocation.source()); // if (args.length < 1) {
return; // sendHelp(invocation.source());
} // return;
// }
String subCommand = args[0]; //
// String subCommand = args[0];
if (subCommand.equals("bind")) { //
bindServer(invocation.source()); // if (subCommand.equals("bind")) {
} // bindServer(invocation.source());
// }
if (subCommand.equalsIgnoreCase("reload")) { //
reload(invocation.source()); // if (subCommand.equalsIgnoreCase("reload")) {
} // reload(invocation.source());
// }
} //
// }
@Override //
public List<String> suggest(Invocation invocation) { // @Override
String[] args = invocation.arguments(); // public List<String> suggest(Invocation invocation) {
// String[] args = invocation.arguments();
if (args.length == 0) { //
return List.of("bind", "reload"); // if (args.length == 0) {
} // return List.of("bind", "reload");
// }
if (args.length == 1) { //
String input = args[0].toLowerCase(); // if (args.length == 1) {
return Stream.of("bind", "reload") // String input = args[0].toLowerCase();
.filter(cmd -> cmd.startsWith(input)) // return Stream.of("bind", "reload")
.collect(Collectors.toList()); // .filter(cmd -> cmd.startsWith(input))
} // .collect(Collectors.toList());
// }
return List.of(); //
} // return List.of();
// }
public void reload(CommandSource source) { //
MessageManager msg = plugin.getMessageManager(); // public void reload(CommandSource source) {
// MessageManager msg = plugin.getMessageManager();
long start = System.currentTimeMillis(); //
boolean success = plugin.reloadConfig(); // long start = System.currentTimeMillis();
// boolean success = plugin.reloadConfig();
if (success) { //
long time = System.currentTimeMillis() - start; // if (success) {
source.sendMessage(msg.get("command.reload.success", "time", String.valueOf(time))); // long time = System.currentTimeMillis() - start;
} else { // source.sendMessage(msg.get("command.reload.success", "time",
// String.valueOf(time)));
source.sendMessage(msg.get("command.reload.failed")); // } else {
} //
return; // source.sendMessage(msg.get("command.reload.failed"));
} // }
// return;
public void bindServer(CommandSource source) { // }
//
Config config = plugin.getConfig(); // public void bindServer(CommandSource source) {
MessageManager msg = plugin.getMessageManager(); //
// Config config = plugin.getConfig();
String bind_name = config.getServerName(); // MessageManager msg = plugin.getMessageManager();
String bind_description = config.getDescription(); //
// String bind_name = config.getServerName();
String bind_token = config.getBind().getBindToken(); // String bind_description = config.getDescription();
String bind_address = config.getBind().getAddress(); //
Integer bind_port = config.getBind().getPort(); // String bind_token = config.getBind().getBindToken();
// String bind_address = config.getBind().getAddress();
source.sendMessage(msg.get("command.bind.server.start")); //
// int bind_port =
Logger logger = plugin.getLogger(); // Optional.ofNullable(config.getBind().getPort()).orElse(25565);
//
// Run in a separate worker thread to allow blocking (Thread.sleep) // source.sendMessage(msg.get("command.bind.server.start"));
plugin.getServer().getScheduler().buildTask(plugin, () -> { //
// Logger logger = plugin.getLogger();
// 1. Initialize the Network Service locally (Velocity connects directly) //
NetworkServiceInterface networkService = new HttpNetworkService( // // Run in a separate worker thread to allow blocking (Thread.sleep)
"https://oapi.mineroo.online", // plugin.getServer().getScheduler().buildTask(plugin, () -> {
"mbind", //
bind_token); // // 1. Initialize the Network Service locally (Velocity connects directly)
// NetworkServiceInterface networkService = new HttpNetworkService(
BindRequest request = new BindRequest(logger, networkService); // "https://oapi.mineroo.online",
// "mserver",
try { // bind_token);
//
// 2. Check Status // BindRequest request = new BindRequest(logger, networkService);
// We use .join() to block the thread until the Future completes. //
if (request.checkBindStatus().join()) { // try {
source.sendMessage(msg.get("command.bind.server.already-bound")); //
return; // // 2. Check Status
} // // We use .join() to block the thread until the Future completes.
// if (request.checkBindStatus(bind_address, bind_port).join()) {
// 3. Initial Verification // source.sendMessage(msg.get("command.bind.server.already-bound"));
boolean inital_bind = request.initialMotdVerifyRequest(bind_address, bind_port).join(); // return;
// }
if (inital_bind) { //
String motdToken = request.getMotdToken(); // // 3. Initial Verification
plugin.getBindListener().setVerificationToken(motdToken); // boolean inital_bind = request.initialMotdVerifyRequest(bind_address,
// bind_port).join();
source.sendMessage(msg.get("command.bind.server.wait")); //
// if (inital_bind) {
// 4. Wait for external refresh (Blocking this worker thread is intended) // String motdToken = request.getMotdToken();
try { // plugin.getBindListener().setVerificationToken(motdToken);
Thread.sleep(2 * 60 * 1000 + 5000); // 2m 5s //
} catch (InterruptedException e) { // source.sendMessage(msg.get("command.bind.server.wait"));
logger.error("Bind thread interrupted", e); //
return; // // 4. Wait for external refresh (Blocking this worker thread is intended)
} // try {
// Thread.sleep(2 * 60 * 1000 + 5000); // 2m 5s
// 5. Final Bind & Retry Loop // } catch (InterruptedException e) {
boolean success = false; // logger.error("Bind thread interrupted", e);
int maxRetries = 3; // return;
// }
for (int i = 1; i <= maxRetries; i++) { //
// Check final status synchronously using join() // // 5. Final Bind & Retry Loop
if (request.finalizeServerBinding(bind_name, bind_description).join()) { // boolean success = false;
success = true; // int maxRetries = 3;
break; //
} // for (int i = 1; i <= maxRetries; i++) {
// // Check final status synchronously using join()
if (i < maxRetries) { // if (request.finalizeServerBinding(bind_name, bind_description).join()) {
source.sendMessage(msg.get("command.bind.server.retry", // success = true;
"current", String.valueOf(i), // break;
"max", String.valueOf(maxRetries))); // }
//
try { // if (i < maxRetries) {
Thread.sleep(10000); // Wait 10s before retry // source.sendMessage(msg.get("command.bind.server.retry",
} catch (InterruptedException e) { // "current", String.valueOf(i),
break; // "max", String.valueOf(maxRetries)));
} //
} // try {
} // Thread.sleep(10000); // Wait 10s before retry
// } catch (InterruptedException e) {
if (success) { // break;
source.sendMessage(msg.get("command.bind.server.success")); // }
} else { // }
source.sendMessage(msg.get("command.bind.server.failed")); // }
} //
// if (success) {
} else { // source.sendMessage(msg.get("command.bind.server.success"));
source.sendMessage( // } else {
Component.text("Initial handshake failed. Check your token or network.", NamedTextColor.RED)); // source.sendMessage(msg.get("command.bind.server.failed"));
} // }
//
} catch (CompletionException e) { // } else {
// Unpack the wrapper exception from join() to see the real network error // source.sendMessage(
logger.error("Mineroo Bind Network Error: " + e.getCause().getMessage()); // Component.text("Initial handshake failed. Check your token or network.",
} catch (Exception e) { // NamedTextColor.RED));
logger.error("Mineroo Bind Error: " + e.toString()); // }
} finally { //
plugin.getBindListener().clearVerificationToken(); // } catch (CompletionException e) {
} // // Unpack the wrapper exception from join() to see the real network error
// logger.error("Mineroo Bind Network Error: " + e.getCause().getMessage());
}).schedule(); // } catch (Exception e) {
} // logger.error("Mineroo Bind Error: " + e.toString());
// } finally {
public void sendHelp(CommandSource source) { // plugin.getBindListener().clearVerificationToken();
source.sendMessage(Component.text("=== # @Mineroo # ===", NamedTextColor.AQUA)); // }
source.sendMessage(Component.text(" /mineroo bind - Start to bind server", NamedTextColor.YELLOW)); //
source.sendMessage(Component.text(" /mineroo reload - Reload config", NamedTextColor.YELLOW)); // }).schedule();
source.sendMessage(Component.text("=== mineroo.online ===", NamedTextColor.AQUA)); // }
} //
} // public void sendHelp(CommandSource source) {
// source.sendMessage(Component.text("=== # @Mineroo # ===",
// NamedTextColor.AQUA));
// source.sendMessage(Component.text(" /mineroo bind - Start to bind server",
// NamedTextColor.YELLOW));
// source.sendMessage(Component.text(" /mineroo reload - Reload config",
// NamedTextColor.YELLOW));
// source.sendMessage(Component.text("=== mineroo.online ===",
// NamedTextColor.AQUA));
// }
// }