diff --git a/common/src/main/java/online/mineroo/common/BindRequest.java b/common/src/main/java/online/mineroo/common/BindRequest.java index b31585a..cd3c5cc 100644 --- a/common/src/main/java/online/mineroo/common/BindRequest.java +++ b/common/src/main/java/online/mineroo/common/BindRequest.java @@ -5,6 +5,8 @@ import com.google.gson.JsonParser; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; /** @@ -41,18 +43,29 @@ public class BindRequest { * @return A future that completes with true if status is 'bound', false * otherwise */ - public CompletableFuture checkBindStatus() { + public CompletableFuture checkBindStatus(String hostname, int port) { // NetworkService is already configured with BaseURL, only need the path here String path = "/server/server-bind-status"; - return networkService.getData(path, null) - .thenApply(responseBody -> { + Map params = new HashMap<>(); + + 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 { 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."); + if (bound) { + logger.warn("Mineroo Bind: Server already bound."); } return bound; } catch (Exception e) { @@ -73,7 +86,7 @@ public class BindRequest { * @param port The server port * @return A future that completes with true if tokens are received */ - public CompletableFuture initialMotdVerifyRequest(String hostname, Integer port) { + public CompletableFuture initialMotdVerifyRequest(String hostname, int port) { String path = "/server/motd-verify"; JsonObject json = new JsonObject(); @@ -81,7 +94,14 @@ public class BindRequest { json.addProperty("server_port", port); 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 { JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject(); @@ -128,7 +148,8 @@ public class BindRequest { json.addProperty("description", description); 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 // thrown, it is considered successful (HTTP 200) // If the API returns a specific {"success": false}, you need to parse the JSON diff --git a/common/src/main/java/online/mineroo/common/HttpNetworkService.java b/common/src/main/java/online/mineroo/common/HttpNetworkService.java index 930ba18..76a6fef 100644 --- a/common/src/main/java/online/mineroo/common/HttpNetworkService.java +++ b/common/src/main/java/online/mineroo/common/HttpNetworkService.java @@ -19,7 +19,7 @@ public class HttpNetworkService implements NetworkServiceInterface { /** * @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) */ public HttpNetworkService(String baseUrl, String username, String password) { @@ -36,7 +36,7 @@ public class HttpNetworkService implements NetworkServiceInterface { } @Override - public CompletableFuture getData(String endpoint, Map params) { + public CompletableFuture getData(String endpoint, Map params) { return CompletableFuture.supplyAsync(() -> { try { // 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 // 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) { // Wrap checked exceptions as runtime exceptions, Future will catch them @@ -71,7 +72,7 @@ public class HttpNetworkService implements NetworkServiceInterface { } @Override - public CompletableFuture postData(String endpoint, JsonObject body) { + public CompletableFuture postData(String endpoint, JsonObject body) { return CompletableFuture.supplyAsync(() -> { try { HttpRequest request = HttpRequest.newBuilder() @@ -83,7 +84,9 @@ public class HttpNetworkService implements NetworkServiceInterface { .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - return response.body(); + + NetworkResponse resp = new NetworkResponse(response.statusCode(), response.body()); + return resp; } catch (Exception e) { throw new RuntimeException("HTTP POST failed: " + endpoint, e); diff --git a/common/src/main/java/online/mineroo/common/NetworkResponse.java b/common/src/main/java/online/mineroo/common/NetworkResponse.java new file mode 100644 index 0000000..23d1f1b --- /dev/null +++ b/common/src/main/java/online/mineroo/common/NetworkResponse.java @@ -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; + } +} diff --git a/common/src/main/java/online/mineroo/common/NetworkServiceInterface.java b/common/src/main/java/online/mineroo/common/NetworkServiceInterface.java index 9823824..e40d287 100644 --- a/common/src/main/java/online/mineroo/common/NetworkServiceInterface.java +++ b/common/src/main/java/online/mineroo/common/NetworkServiceInterface.java @@ -6,7 +6,7 @@ import java.util.concurrent.CompletableFuture; import com.google.gson.JsonObject; public interface NetworkServiceInterface { - CompletableFuture getData(String endpoint, Map params); + CompletableFuture getData(String endpoint, Map params); - CompletableFuture postData(String endpoint, JsonObject body); + CompletableFuture postData(String endpoint, JsonObject body); } diff --git a/common/src/main/java/online/mineroo/common/ProtocolConstants.java b/common/src/main/java/online/mineroo/common/ProtocolConstants.java index 7a60f23..246f163 100644 --- a/common/src/main/java/online/mineroo/common/ProtocolConstants.java +++ b/common/src/main/java/online/mineroo/common/ProtocolConstants.java @@ -4,6 +4,7 @@ public class ProtocolConstants { // Protocol channel name public static String PROTOCOL_CHANNEL = "mineroo:plugin:api"; - // Event names - public static String BIND_USER_REQUEST = "user-bind-request"; + // Sub-channel/event names + public static String API_REQUEST = "API_REQUEST"; + public static String BIND_MOTD_TOKEN = "SET_MOTD_TOKEN"; } diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index 3f9c0fc..bcaf195 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -29,6 +29,10 @@ tasks { minecraftVersion("1.21.10") } + shadowJar { + archiveFileName.set("mineroo-paper.jar") + } + withType { from("src/main/resources") } diff --git a/paper/src/main/java/online/mineroo/paper/Config.java b/paper/src/main/java/online/mineroo/paper/Config.java index 98fd0d3..e468165 100644 --- a/paper/src/main/java/online/mineroo/paper/Config.java +++ b/paper/src/main/java/online/mineroo/paper/Config.java @@ -7,13 +7,19 @@ public class Config { private final ServerSection server; private final PlayersSection player; private final ProxySection proxy; + private final TestSection test; public Config(FileConfiguration config) { + this.test = new TestSection(config); this.proxy = new ProxySection(config); this.server = new ServerSection(config); this.player = new PlayersSection(config); } + public TestSection getTest() { + return test; + } + public ProxySection getProxy() { return proxy; } @@ -26,6 +32,18 @@ public class Config { 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 { private final boolean useVelocity; diff --git a/paper/src/main/java/online/mineroo/paper/MinerooCore.java b/paper/src/main/java/online/mineroo/paper/MinerooCore.java index 28e0ca1..c7d7057 100644 --- a/paper/src/main/java/online/mineroo/paper/MinerooCore.java +++ b/paper/src/main/java/online/mineroo/paper/MinerooCore.java @@ -6,6 +6,7 @@ import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import online.mineroo.common.HttpNetworkService; import online.mineroo.common.MessageManager; import online.mineroo.common.NetworkServiceInterface; +import online.mineroo.common.ProtocolConstants; import online.mineroo.paper.commands.MainCommand; import online.mineroo.paper.utils.PlayerBindDialog; @@ -31,6 +32,8 @@ public class MinerooCore extends JavaPlugin implements Listener { commands.registrar().register(new MainCommand(this).build()); }); + this.getServer().getMessenger().registerOutgoingPluginChannel(this, ProtocolConstants.PROTOCOL_CHANNEL); + messageManager = new MessageManager(); getServer().getPluginManager().registerEvents(this, this); @@ -61,8 +64,10 @@ public class MinerooCore extends JavaPlugin implements Listener { getLogger().info("Using Velocity proxy network service."); } else { 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("API: " + hostname); } if (this.messageManager == null) { diff --git a/paper/src/main/java/online/mineroo/paper/ProxyNetworkService.java b/paper/src/main/java/online/mineroo/paper/ProxyNetworkService.java index 792424b..0975e0b 100644 --- a/paper/src/main/java/online/mineroo/paper/ProxyNetworkService.java +++ b/paper/src/main/java/online/mineroo/paper/ProxyNetworkService.java @@ -7,6 +7,8 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import online.mineroo.common.ProxyNetworkRequest; import online.mineroo.common.NetworkServiceInterface; +import online.mineroo.common.ProtocolConstants; +import online.mineroo.common.NetworkResponse; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @@ -23,7 +25,7 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa private final String CHANNEL = "mineroo:net"; // Store pending requests - private final Map> pendingRequests = new ConcurrentHashMap<>(); + private final Map> pendingRequests = new ConcurrentHashMap<>(); public ProxyNetworkService(JavaPlugin plugin) { this.plugin = plugin; @@ -33,18 +35,18 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa } @Override - public CompletableFuture getData(String endpoint, Map params) { + public CompletableFuture getData(String endpoint, Map params) { return sendRequest("GET", endpoint, params, null); } @Override - public CompletableFuture postData(String endpoint, JsonObject body) { + public CompletableFuture postData(String endpoint, JsonObject body) { return sendRequest("POST", endpoint, null, body); } - private CompletableFuture sendRequest(String method, String endpoint, Map params, + private CompletableFuture sendRequest(String method, String endpoint, Map params, JsonObject body) { - CompletableFuture future = new CompletableFuture<>(); + CompletableFuture future = new CompletableFuture<>(); // 1. Check if there is any player online (Plugin Message must be sent via a // player) @@ -59,7 +61,7 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa pendingRequests.put(req.getRequestId(), future); ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("API_REQ"); // Sub-channel + out.writeUTF(ProtocolConstants.API_REQUEST); // Sub-channel out.writeUTF(gson.toJson(req)); // Payload // 3. Send @@ -67,9 +69,9 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa // 4. Set timeout (prevent memory leak if Velocity does not respond) plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> { - CompletableFuture f = pendingRequests.remove(req.getRequestId()); + CompletableFuture f = pendingRequests.remove(req.getRequestId()); 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 @@ -89,12 +91,12 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa boolean success = in.readBoolean(); String data = in.readUTF(); // If success: body, if failure: error message - CompletableFuture future = pendingRequests.remove(reqId); + CompletableFuture future = pendingRequests.remove(reqId); if (future != null) { if (success) { - future.complete(data); + future.complete(new NetworkResponse(200, data)); } else { - future.completeExceptionally(new RuntimeException(data)); + future.complete(new NetworkResponse(500, data)); } } } diff --git a/paper/src/main/java/online/mineroo/paper/commands/BindCommand.java b/paper/src/main/java/online/mineroo/paper/commands/BindCommand.java index c163086..bbb09f0 100644 --- a/paper/src/main/java/online/mineroo/paper/commands/BindCommand.java +++ b/paper/src/main/java/online/mineroo/paper/commands/BindCommand.java @@ -11,7 +11,6 @@ import io.papermc.paper.command.brigadier.Commands; import online.mineroo.common.BindRequest; import online.mineroo.paper.MinerooCore; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class BindCommand { @@ -22,26 +21,112 @@ public class BindCommand { } public LiteralCommandNode build() { - LiteralArgumentBuilder bind = Commands.literal("bind"); + LiteralArgumentBuilder 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( Commands.literal("server") - .requires(sender -> sender.getSender().hasPermission("mineroo.admin.bind.server"))) - .executes(this::bindServer); + .requires(sender -> sender.getSender().hasPermission("mineroo.admin.bind.server")) + .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(); } - private int bindServer(CommandContext context) throws CommandSyntaxException { - // Use slf4j logger for BindRequest to keep log style consistent across - // platforms - Logger logger = LoggerFactory.getLogger("MinerooPaper"); - BindRequest bindRequest = new BindRequest( - logger, - plugin.getNetworkService()); + private int bindPlayer(CommandContext 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 context) throws CommandSyntaxException { + // Run the binding process asynchronously to avoid blocking the main thread + org.bukkit.Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + Logger logger = plugin.getSLF4JLogger(); + BindRequest bindRequest = new BindRequest( + logger, + 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; } } diff --git a/paper/src/main/java/online/mineroo/paper/commands/MainCommand.java b/paper/src/main/java/online/mineroo/paper/commands/MainCommand.java index 22af9b8..a987bf6 100644 --- a/paper/src/main/java/online/mineroo/paper/commands/MainCommand.java +++ b/paper/src/main/java/online/mineroo/paper/commands/MainCommand.java @@ -50,7 +50,7 @@ public class MainCommand { messageManager.reload(); // Send feedback - Component msg = messageManager.get("message.reload.success"); + Component msg = messageManager.get("command.reload.success"); context.getSource().getSender().sendMessage(msg); return Command.SINGLE_SUCCESS; diff --git a/paper/src/main/resources/i18n/messages.properties b/paper/src/main/resources/i18n/messages.properties index e280455..07c6ca0 100644 --- a/paper/src/main/resources/i18n/messages.properties +++ b/paper/src/main/resources/i18n/messages.properties @@ -1,9 +1,20 @@ +# Bind Dialog dialog.bind.player.title = 绑定您的 Mineroo.Online 账户 dialog.bind.player.content = 绑定 Mineroo Online 账号以解锁更多实用功能 dialog.bind.player.email = Mineroo 账户邮箱 dialog.bind.player.confirm = 确认 dialog.bind.player.cancel = 取消 +dialog.bind.player.success = 请前往 Mineroo 个人中心完成验证。 -message.bind.player.success = 请前往 Mineroo 个人中心完成验证。 +# Bind Command +command.bind.server.already-bound=当前服务器已在 Mineroo.Online 处于绑定状态。 +command.bind.server.start=开始服务器绑定流程,请稍等... +command.bind.server.inital.failed=初次握手失败。检查您的令牌或网络。 +command.bind.server.wait=正在验证服务器所有权... 数据同步可能需要 2 分钟,请稍候。 +command.bind.server.success=绑定成功! +command.bind.server.failed=绑定失败。 +command.bind.server.retry=验证未通过,10 秒后重试 (/) ... -message.reload.success = 插件配置文件重载成功! +# Reload Command +command.reload.success=配置文件重载成功!耗时 +command.reload.failed=配置文件重载失败,请查看控制台日志。 diff --git a/velocity/src/main/java/online/mineroo/velocity/commands/MainCommand.java b/velocity/src/main/java/online/mineroo/velocity/commands/MainCommand.java index bbf1e1c..e69fb44 100644 --- a/velocity/src/main/java/online/mineroo/velocity/commands/MainCommand.java +++ b/velocity/src/main/java/online/mineroo/velocity/commands/MainCommand.java @@ -1,190 +1,200 @@ -package online.mineroo.velocity.commands; - -import java.util.List; -import java.util.concurrent.CompletionException; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.slf4j.Logger; - -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 online.mineroo.common.MessageManager; -import online.mineroo.common.NetworkServiceInterface; -import online.mineroo.velocity.Config; -import online.mineroo.velocity.MinerooCore; -import online.mineroo.common.BindRequest; -import online.mineroo.common.HttpNetworkService; - -public class MainCommand implements SimpleCommand { - - private final MinerooCore plugin; - - public MainCommand(MinerooCore plugin) { - this.plugin = plugin; - } - - @Override - public void execute(Invocation invocation) { - String[] args = invocation.arguments(); - if (args.length < 1) { - sendHelp(invocation.source()); - return; - } - - String subCommand = args[0]; - - if (subCommand.equals("bind")) { - bindServer(invocation.source()); - } - - if (subCommand.equalsIgnoreCase("reload")) { - reload(invocation.source()); - } - - } - - @Override - public List suggest(Invocation invocation) { - String[] args = invocation.arguments(); - - if (args.length == 0) { - return List.of("bind", "reload"); - } - - if (args.length == 1) { - String input = args[0].toLowerCase(); - return Stream.of("bind", "reload") - .filter(cmd -> cmd.startsWith(input)) - .collect(Collectors.toList()); - } - - return List.of(); - } - - public void reload(CommandSource source) { - MessageManager msg = plugin.getMessageManager(); - - long start = System.currentTimeMillis(); - boolean success = plugin.reloadConfig(); - - if (success) { - long time = System.currentTimeMillis() - start; - source.sendMessage(msg.get("command.reload.success", "time", String.valueOf(time))); - } else { - - source.sendMessage(msg.get("command.reload.failed")); - } - return; - } - - public void bindServer(CommandSource source) { - - Config config = plugin.getConfig(); - MessageManager msg = plugin.getMessageManager(); - - String bind_name = config.getServerName(); - String bind_description = config.getDescription(); - - String bind_token = config.getBind().getBindToken(); - String bind_address = config.getBind().getAddress(); - Integer bind_port = config.getBind().getPort(); - - source.sendMessage(msg.get("command.bind.server.start")); - - Logger logger = plugin.getLogger(); - - // Run in a separate worker thread to allow blocking (Thread.sleep) - plugin.getServer().getScheduler().buildTask(plugin, () -> { - - // 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 { - - // 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; - } - - // 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")); - - // 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; - } - - // 5. Final Bind & Retry Loop - boolean success = false; - int maxRetries = 3; - - for (int i = 1; i <= maxRetries; i++) { - // 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))); - - try { - Thread.sleep(10000); // Wait 10s before retry - } catch (InterruptedException e) { - break; - } - } - } - - if (success) { - source.sendMessage(msg.get("command.bind.server.success")); - } else { - source.sendMessage(msg.get("command.bind.server.failed")); - } - - } else { - source.sendMessage( - 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 Error: " + e.toString()); - } finally { - plugin.getBindListener().clearVerificationToken(); - } - - }).schedule(); - } - - 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)); - } -} +// package online.mineroo.velocity.commands; +// +// import java.util.List; +// import java.util.Optional; +// import java.util.concurrent.CompletionException; +// import java.util.stream.Collectors; +// import java.util.stream.Stream; +// +// import org.slf4j.Logger; +// +// 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 online.mineroo.common.MessageManager; +// import online.mineroo.common.NetworkServiceInterface; +// import online.mineroo.velocity.Config; +// import online.mineroo.velocity.MinerooCore; +// import online.mineroo.common.BindRequest; +// import online.mineroo.common.HttpNetworkService; +// +// public class MainCommand implements SimpleCommand { +// +// private final MinerooCore plugin; +// +// public MainCommand(MinerooCore plugin) { +// this.plugin = plugin; +// } +// +// @Override +// public void execute(Invocation invocation) { +// String[] args = invocation.arguments(); +// if (args.length < 1) { +// sendHelp(invocation.source()); +// return; +// } +// +// String subCommand = args[0]; +// +// if (subCommand.equals("bind")) { +// bindServer(invocation.source()); +// } +// +// if (subCommand.equalsIgnoreCase("reload")) { +// reload(invocation.source()); +// } +// +// } +// +// @Override +// public List suggest(Invocation invocation) { +// String[] args = invocation.arguments(); +// +// if (args.length == 0) { +// return List.of("bind", "reload"); +// } +// +// if (args.length == 1) { +// String input = args[0].toLowerCase(); +// return Stream.of("bind", "reload") +// .filter(cmd -> cmd.startsWith(input)) +// .collect(Collectors.toList()); +// } +// +// return List.of(); +// } +// +// public void reload(CommandSource source) { +// MessageManager msg = plugin.getMessageManager(); +// +// long start = System.currentTimeMillis(); +// boolean success = plugin.reloadConfig(); +// +// if (success) { +// long time = System.currentTimeMillis() - start; +// source.sendMessage(msg.get("command.reload.success", "time", +// String.valueOf(time))); +// } else { +// +// source.sendMessage(msg.get("command.reload.failed")); +// } +// return; +// } +// +// public void bindServer(CommandSource source) { +// +// Config config = plugin.getConfig(); +// MessageManager msg = plugin.getMessageManager(); +// +// String bind_name = config.getServerName(); +// String bind_description = config.getDescription(); +// +// String bind_token = config.getBind().getBindToken(); +// String bind_address = config.getBind().getAddress(); +// +// int bind_port = +// Optional.ofNullable(config.getBind().getPort()).orElse(25565); +// +// source.sendMessage(msg.get("command.bind.server.start")); +// +// Logger logger = plugin.getLogger(); +// +// // Run in a separate worker thread to allow blocking (Thread.sleep) +// plugin.getServer().getScheduler().buildTask(plugin, () -> { +// +// // 1. Initialize the Network Service locally (Velocity connects directly) +// NetworkServiceInterface networkService = new HttpNetworkService( +// "https://oapi.mineroo.online", +// "mserver", +// bind_token); +// +// BindRequest request = new BindRequest(logger, networkService); +// +// try { +// +// // 2. Check Status +// // We use .join() to block the thread until the Future completes. +// if (request.checkBindStatus(bind_address, bind_port).join()) { +// source.sendMessage(msg.get("command.bind.server.already-bound")); +// return; +// } +// +// // 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")); +// +// // 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; +// } +// +// // 5. Final Bind & Retry Loop +// boolean success = false; +// int maxRetries = 3; +// +// for (int i = 1; i <= maxRetries; i++) { +// // 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))); +// +// try { +// Thread.sleep(10000); // Wait 10s before retry +// } catch (InterruptedException e) { +// break; +// } +// } +// } +// +// if (success) { +// source.sendMessage(msg.get("command.bind.server.success")); +// } else { +// source.sendMessage(msg.get("command.bind.server.failed")); +// } +// +// } else { +// source.sendMessage( +// 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 Error: " + e.toString()); +// } finally { +// plugin.getBindListener().clearVerificationToken(); +// } +// +// }).schedule(); +// } +// +// 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)); +// } +// }