feat: code
This commit is contained in:
@@ -29,6 +29,10 @@ tasks {
|
||||
minecraftVersion("1.21.10")
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveFileName.set("mineroo-paper.jar")
|
||||
}
|
||||
|
||||
withType<Jar> {
|
||||
from("src/main/resources")
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 <RequestId, Future>
|
||||
private final Map<String, CompletableFuture<String>> pendingRequests = new ConcurrentHashMap<>();
|
||||
private final Map<String, CompletableFuture<NetworkResponse>> pendingRequests = new ConcurrentHashMap<>();
|
||||
|
||||
public ProxyNetworkService(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -33,18 +35,18 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> postData(String endpoint, JsonObject body) {
|
||||
public CompletableFuture<NetworkResponse> postData(String endpoint, JsonObject 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) {
|
||||
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
|
||||
// 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<String> f = pendingRequests.remove(req.getRequestId());
|
||||
CompletableFuture<NetworkResponse> 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<String> future = pendingRequests.remove(reqId);
|
||||
CompletableFuture<NetworkResponse> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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(
|
||||
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<CommandSourceStack> 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<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 {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
# Bind Dialog
|
||||
dialog.bind.player.title = <green>绑定您的 Mineroo.Online 账户</green>
|
||||
dialog.bind.player.content = <aqua>绑定 Mineroo Online 账号以解锁更多实用功能</aqua>
|
||||
dialog.bind.player.email = <aqua>Mineroo 账户邮箱</aqua>
|
||||
dialog.bind.player.confirm = <green>确认</green>
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user