feat: code
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
Reference in New Issue
Block a user