feat: code

This commit is contained in:
2025-12-13 23:42:41 -08:00
parent 1a31570eea
commit 4e1faa3edc
11 changed files with 349 additions and 11 deletions

View File

@@ -2,7 +2,6 @@ package online.mineroo.common;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import online.mineroo.common.NetworkServiceInterface;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

View File

@@ -0,0 +1,41 @@
package online.mineroo.common;
import com.google.gson.JsonObject;
import java.util.Map;
import java.util.UUID;
public class ProxyNetworkRequest {
private final String requestId;
private final String method; // "GET" or "POST"
private final String endpoint;
private final Map<String, String> params;
private final String jsonBody;
public ProxyNetworkRequest(String method, String endpoint, Map<String, String> params, JsonObject body) {
this.requestId = UUID.randomUUID().toString();
this.method = method;
this.endpoint = endpoint;
this.params = params;
this.jsonBody = (body != null) ? body.toString() : null;
}
public String getRequestId() {
return requestId;
}
public String getMethod() {
return method;
}
public String getEndpoint() {
return endpoint;
}
public Map<String, String> getParams() {
return params;
}
public String getJsonBody() {
return jsonBody;
}
}

View File

@@ -5,21 +5,39 @@ import org.bukkit.configuration.file.FileConfiguration;
public class Config {
private final ServerSection server;
private final PlayerSection player;
private final PlayersSection player;
private final ProxySection proxy;
public Config(FileConfiguration config) {
this.proxy = new ProxySection(config);
this.server = new ServerSection(config);
this.player = new PlayerSection(config);
this.player = new PlayersSection(config);
}
public ProxySection getProxy() {
return proxy;
}
public ServerSection getServer() {
return server;
}
public PlayerSection getPlayer() {
public PlayersSection getPlayer() {
return player;
}
public static class ProxySection {
private final boolean useVelocity;
public ProxySection(FileConfiguration config) {
this.useVelocity = config.getBoolean("proxy.use_velocity", false);
}
public boolean isUseVelocity() {
return useVelocity;
}
}
public static class ServerSection {
private final String serverName;
private final String description;
@@ -71,13 +89,13 @@ public class Config {
}
}
public static class PlayerSection {
public static class PlayersSection {
private final PlayerBindSection bind;
public PlayerSection(FileConfiguration config) {
public PlayersSection(FileConfiguration config) {
this.bind = new PlayerBindSection(
config.getBoolean("player.bind.required", false),
config.getBoolean("player.bind.share_player_info", true));
config.getBoolean("players.bind.required", false),
config.getBoolean("players.bind.share_player_info", true));
}
public PlayerBindSection getPlayerBind() {

View File

@@ -2,7 +2,11 @@ package online.mineroo.paper;
import org.bukkit.plugin.java.JavaPlugin;
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.paper.commands.MainCommand;
import online.mineroo.paper.utils.PlayerBindDialog;
import org.bukkit.entity.Player;
@@ -13,12 +17,20 @@ import org.bukkit.event.player.PlayerJoinEvent;
public class MinerooCore extends JavaPlugin implements Listener {
private MessageManager messageManager;
private NetworkServiceInterface networkService;
private Config config;
@Override
public void onEnable() {
saveDefaultConfig();
reloadAll();
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
commands.registrar().register(new MainCommand(this).build());
});
messageManager = new MessageManager();
getServer().getPluginManager().registerEvents(this, this);
@@ -38,4 +50,37 @@ public class MinerooCore extends JavaPlugin implements Listener {
player.sendMessage(messageManager.get("message.test", "player", player.getName()));
}
public void reloadAll() {
reloadConfig();
this.config = new Config(getConfig());
boolean useVelocity = this.config.getProxy().isUseVelocity();
if (useVelocity) {
this.networkService = new ProxyNetworkService(this);
getLogger().info("Using Velocity proxy network service.");
} else {
String token = config.getServer().getServerBind().getBindToken();
this.networkService = new HttpNetworkService("https://oapi.mineroo.online", "mbind", token);
getLogger().info("Using direct HTTP network service.");
}
if (this.messageManager == null) {
this.messageManager = new MessageManager();
} else {
this.messageManager.reload();
}
}
public Config getConfigObject() {
return config;
}
public NetworkServiceInterface getNetworkService() {
return networkService;
}
public MessageManager getMessageManager() {
return messageManager;
}
}

View File

@@ -0,0 +1,102 @@
package online.mineroo.paper;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import online.mineroo.common.ProxyNetworkRequest;
import online.mineroo.common.NetworkServiceInterface;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
public class ProxyNetworkService implements NetworkServiceInterface, PluginMessageListener {
private final JavaPlugin plugin;
private final Gson gson = new Gson();
private final String CHANNEL = "mineroo:net";
// Store pending requests <RequestId, Future>
private final Map<String, CompletableFuture<String>> pendingRequests = new ConcurrentHashMap<>();
public ProxyNetworkService(JavaPlugin plugin) {
this.plugin = plugin;
// Register channels
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CHANNEL);
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, CHANNEL, this);
}
@Override
public CompletableFuture<String> getData(String endpoint, Map<String, String> params) {
return sendRequest("GET", endpoint, params, null);
}
@Override
public CompletableFuture<String> postData(String endpoint, JsonObject body) {
return sendRequest("POST", endpoint, null, body);
}
private CompletableFuture<String> sendRequest(String method, String endpoint, Map<String, String> params,
JsonObject body) {
CompletableFuture<String> future = new CompletableFuture<>();
// 1. Check if there is any player online (Plugin Message must be sent via a
// player)
Player player = com.google.common.collect.Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
if (player == null) {
future.completeExceptionally(new IllegalStateException("No player online to proxy request to Velocity"));
return future;
}
// 2. Prepare the packet
ProxyNetworkRequest req = new ProxyNetworkRequest(method, endpoint, params, body);
pendingRequests.put(req.getRequestId(), future);
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("API_REQ"); // Sub-channel
out.writeUTF(gson.toJson(req)); // Payload
// 3. Send
player.sendPluginMessage(plugin, CHANNEL, out.toByteArray());
// 4. Set timeout (prevent memory leak if Velocity does not respond)
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> {
CompletableFuture<String> f = pendingRequests.remove(req.getRequestId());
if (f != null) {
f.completeExceptionally(new java.util.concurrent.TimeoutException("Proxy request timed out"));
}
}, 100L); // 5 seconds timeout
return future;
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals(CHANNEL))
return;
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subChannel = in.readUTF();
if (subChannel.equals("API_RESP")) {
String reqId = in.readUTF();
boolean success = in.readBoolean();
String data = in.readUTF(); // If success: body, if failure: error message
CompletableFuture<String> future = pendingRequests.remove(reqId);
if (future != null) {
if (success) {
future.complete(data);
} else {
future.completeExceptionally(new RuntimeException(data));
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
package online.mineroo.paper.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import io.papermc.paper.command.brigadier.CommandSourceStack;
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 {
private MinerooCore plugin;
public BindCommand(MinerooCore plugin) {
this.plugin = plugin;
}
public LiteralCommandNode<CommandSourceStack> build() {
LiteralArgumentBuilder<CommandSourceStack> bind = Commands.literal("bind");
bind.then(
Commands.literal("server")
.requires(sender -> sender.getSender().hasPermission("mineroo.admin.bind.server")))
.executes(this::bindServer);
bind.then(Commands.literal("player"));
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());
return Command.SINGLE_SUCCESS;
}
}

View File

@@ -0,0 +1,59 @@
package online.mineroo.paper.commands;
import org.bukkit.entity.Player;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.Commands;
import online.mineroo.paper.MinerooCore;
import online.mineroo.common.MessageManager;
import net.kyori.adventure.text.Component;
public class MainCommand {
private final MinerooCore plugin;
public MainCommand(MinerooCore plugin) {
this.plugin = plugin;
}
public LiteralCommandNode<CommandSourceStack> build() {
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("mineroo");
root.then(
Commands.literal("reload")
.requires(sender -> sender.getSender().hasPermission("mineroo.admin.reload"))
.executes(this::executeReload));
root.then(new BindCommand(plugin).build());
return root.build();
}
private int executeReload(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
// Reload plugin config and message manager
plugin.reloadConfig();
plugin.reloadAll();
if (context.getSource().getExecutor() instanceof Player player) {
player.updateCommands();
}
MessageManager messageManager = plugin.getMessageManager();
messageManager.reload();
// Send feedback
Component msg = messageManager.get("message.reload.success");
context.getSource().getSender().sendMessage(msg);
return Command.SINGLE_SUCCESS;
}
}

View File

@@ -1,15 +1,19 @@
proxy:
use_velocity: false
server:
# Only for bind commands
# Only for `bind server` commands
serverName: ""
description: "A minecraft server"
# Only for bind commands
# Only for `bind server` commands
bind:
address: ""
port: 0
token: "get token from `mineroo.online/resources/servers` page!"
player:
players:
# for player bind
bind:
# Only bound player can enter this server
# > server will kick all mineroo unknown player

View File

@@ -5,3 +5,5 @@ dialog.bind.player.confirm = <green>确认</green>
dialog.bind.player.cancel = <red>取消</red>
message.bind.player.success = 请前往 Mineroo 个人中心完成验证。
message.reload.success = <green>插件配置文件重载成功!</green>

View File

@@ -1,6 +1,27 @@
# yaml-language-server: $schema=https://json.schemastore.org/paper-plugin.json
name: MinerooCore
main: online.mineroo.paper.MinerooCore
version: 1.0.0
author: YuKun Liu
website: https://mineroo.online
description: Mineroo Base Plugin
api-version: "1.21.10"
permissions:
mineroo.admin.reload:
description: "Reload plugin config files."
default: op
mineroo.admin.bind.server:
description: Bind the server instance
default: op
mineroo.user.bind.player:
description: Bind user account
default: "true"
mineroo.admin:
description: Grant all administrative permissions
default: op
children:
mineroo.admin.reload: true
mineroo.admin.bind.server: true