feat: use clang-format
This commit is contained in:
9
.clang-format
Normal file
9
.clang-format
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
Language: Java
|
||||||
|
BasedOnStyle: Google
|
||||||
|
|
||||||
|
AlignAfterOpenBracket: BlockIndent
|
||||||
|
|
||||||
|
ColumnLimit: 100
|
||||||
|
BinPackArguments: false
|
||||||
|
AllowAllArgumentsOnNextLine: true
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
package online.mineroo.common;
|
package online.mineroo.common;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,14 +48,14 @@ 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(String hostname, int port) {
|
public CompletableFuture<Boolean> checkBindStatus(String hostname, String 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";
|
||||||
|
|
||||||
Map<String, String> params = new HashMap<>();
|
Map<String, String> params = new HashMap<>();
|
||||||
|
|
||||||
params.put("address", hostname);
|
params.put("address", hostname);
|
||||||
params.put("port", String.valueOf(port));
|
params.put("port", port);
|
||||||
|
|
||||||
return networkService.getData(path, params)
|
return networkService.getData(path, params)
|
||||||
.thenApply(response -> {
|
.thenApply(response -> {
|
||||||
@@ -86,12 +91,26 @@ 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, int port) {
|
public CompletableFuture<Boolean> initialMotdVerifyRequest(String hostname, String port) {
|
||||||
String path = "/server/motd-verify";
|
String path = "/server/motd-verify";
|
||||||
|
|
||||||
|
Integer num_port = -1;
|
||||||
|
if (!"$port".equals(port)) {
|
||||||
|
try {
|
||||||
|
num_port = Integer.valueOf(port);
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
num_port = 25565;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JsonObject json = new JsonObject();
|
JsonObject json = new JsonObject();
|
||||||
json.addProperty("server_address", hostname);
|
json.addProperty("server_address", hostname);
|
||||||
|
|
||||||
|
if (num_port == -1) {
|
||||||
json.addProperty("server_port", port);
|
json.addProperty("server_port", port);
|
||||||
|
} else {
|
||||||
|
json.addProperty("server_port", num_port);
|
||||||
|
}
|
||||||
|
|
||||||
return networkService.postData(path, json)
|
return networkService.postData(path, json)
|
||||||
.thenApply(response -> {
|
.thenApply(response -> {
|
||||||
@@ -149,7 +168,6 @@ public class BindRequest {
|
|||||||
|
|
||||||
return networkService.postData(path, json)
|
return networkService.postData(path, json)
|
||||||
.thenApply(response -> {
|
.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
|
||||||
@@ -162,6 +180,152 @@ public class BindRequest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PlayerBindResponse implements Serializable {
|
||||||
|
|
||||||
|
@SerializedName("status")
|
||||||
|
private PlayerBindStatus status;
|
||||||
|
|
||||||
|
@SerializedName("message")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@SerializedName("cached")
|
||||||
|
private boolean cached;
|
||||||
|
|
||||||
|
@SerializedName("timestamp")
|
||||||
|
private long timestamp;
|
||||||
|
|
||||||
|
@SerializedName("info")
|
||||||
|
private PlayerBindInfo info;
|
||||||
|
|
||||||
|
// --- Getters ---
|
||||||
|
|
||||||
|
public PlayerBindStatus getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCached() {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerBindInfo getInfo() {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(PlayerBindStatus status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum PlayerBindStatus {
|
||||||
|
@SerializedName("notBound")
|
||||||
|
NOT_BOUND,
|
||||||
|
|
||||||
|
@SerializedName("bound")
|
||||||
|
BOUND,
|
||||||
|
|
||||||
|
@SerializedName("conflictUser")
|
||||||
|
CONFLICT_USER,
|
||||||
|
|
||||||
|
@SerializedName("conflictPlayer")
|
||||||
|
CONFLICT_PLAYER,
|
||||||
|
|
||||||
|
@SerializedName("error")
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PlayerBindInfo implements Serializable {
|
||||||
|
@SerializedName("bound_uuid")
|
||||||
|
private String boundUuid;
|
||||||
|
|
||||||
|
@SerializedName("minecraft_uuid")
|
||||||
|
private String minecraftUuid;
|
||||||
|
|
||||||
|
@SerializedName("bound_user_id")
|
||||||
|
private Integer boundUserId;
|
||||||
|
|
||||||
|
@SerializedName("user_id")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
// --- Getters ---
|
||||||
|
|
||||||
|
public String getBoundUuid() {
|
||||||
|
return boundUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMinecraftUuid() {
|
||||||
|
return minecraftUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getBoundUserId() {
|
||||||
|
return boundUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEffectiveUuid() {
|
||||||
|
return minecraftUuid != null ? minecraftUuid : boundUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getEffectiveUserId() {
|
||||||
|
return userId != null ? userId : boundUserId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerBindResponse createErrorResponse(String errorMessage) {
|
||||||
|
PlayerBindResponse errorRes = new PlayerBindResponse();
|
||||||
|
errorRes.setStatus(PlayerBindStatus.ERROR);
|
||||||
|
errorRes.setMessage(errorMessage);
|
||||||
|
return errorRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<PlayerBindResponse> checkPlayerBindStatus(UUID player_uuid, String user_email) {
|
||||||
|
String path = "/server/user-bind-status";
|
||||||
|
|
||||||
|
Map<String, String> params = new HashMap<>();
|
||||||
|
|
||||||
|
params.put("user_email", user_email);
|
||||||
|
params.put("player_uuid", player_uuid.toString());
|
||||||
|
|
||||||
|
return networkService.getData(path, params)
|
||||||
|
.thenApply(response -> {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
String responseBody = response.getBody();
|
||||||
|
|
||||||
|
if (response.getStatusCode() != 200) {
|
||||||
|
try {
|
||||||
|
return gson.fromJson(responseBody, PlayerBindResponse.class);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return createErrorResponse("HTTP Error: " + response.getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return gson.fromJson(responseBody, PlayerBindResponse.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Mineroo Bind: Failed to parse JSON", e);
|
||||||
|
return createErrorResponse("JSON Parse Error");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exceptionally(e -> {
|
||||||
|
logger.error("Mineroo Bind: Network exception", e);
|
||||||
|
return createErrorResponse("Network Error: " + e.getMessage());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the MOTD token received from the API.
|
* Gets the MOTD token received from the API.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package online.mineroo.common;
|
|||||||
|
|
||||||
public class ProtocolConstants {
|
public class ProtocolConstants {
|
||||||
// Protocol channel name
|
// Protocol channel name
|
||||||
public static String PROTOCOL_CHANNEL = "mineroo:plugin:api";
|
public static String PROTOCOL_CHANNEL = "mineroo:net";
|
||||||
|
|
||||||
// Sub-channel/event names
|
// Sub-channel/event names
|
||||||
public static String API_REQUEST = "API_REQUEST";
|
public static String API_REQUEST = "OAPI_REQUEST";
|
||||||
|
public static String API_RESPONSE = "OAPI_RESPONSE";
|
||||||
public static String BIND_MOTD_TOKEN = "SET_MOTD_TOKEN";
|
public static String BIND_MOTD_TOKEN = "SET_MOTD_TOKEN";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class Config {
|
|||||||
this.description = config.getString("server.description", "A minecraft server");
|
this.description = config.getString("server.description", "A minecraft server");
|
||||||
this.bind = new ServerBindSection(
|
this.bind = new ServerBindSection(
|
||||||
config.getString("server.bind.address", ""),
|
config.getString("server.bind.address", ""),
|
||||||
config.getInt("server.bind.port", 0),
|
config.getObject("server.bind.port", Integer.class, null),
|
||||||
config.getString("server.bind.token", "get token from `mineroo.online/resources/servers` page!"));
|
config.getString("server.bind.token", "get token from `mineroo.online/resources/servers` page!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,10 +85,10 @@ public class Config {
|
|||||||
|
|
||||||
public static class ServerBindSection {
|
public static class ServerBindSection {
|
||||||
private final String address;
|
private final String address;
|
||||||
private final int port;
|
private final Integer port;
|
||||||
private final String bindToken;
|
private final String bindToken;
|
||||||
|
|
||||||
public ServerBindSection(String address, int port, String bindToken) {
|
public ServerBindSection(String address, Integer port, String bindToken) {
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.bindToken = bindToken;
|
this.bindToken = bindToken;
|
||||||
@@ -98,7 +98,7 @@ public class Config {
|
|||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPort() {
|
public Integer getPort() {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
package online.mineroo.paper;
|
package online.mineroo.paper;
|
||||||
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
|
||||||
|
|
||||||
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
||||||
|
import online.mineroo.common.BindRequest;
|
||||||
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.listeners.BindListener;
|
||||||
|
import online.mineroo.paper.listeners.PlayerBindListener;
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
public class MinerooCore extends JavaPlugin implements Listener {
|
public class MinerooCore extends JavaPlugin implements Listener {
|
||||||
|
|
||||||
private MessageManager messageManager;
|
private MessageManager messageManager;
|
||||||
private NetworkServiceInterface networkService;
|
private NetworkServiceInterface networkService;
|
||||||
|
private BindRequest bindRequest;
|
||||||
private Config config;
|
private Config config;
|
||||||
|
|
||||||
|
private BindListener bindListener;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
|
|
||||||
|
this.bindListener = new BindListener();
|
||||||
|
getServer().getPluginManager().registerEvents(bindListener, this);
|
||||||
|
|
||||||
reloadAll();
|
reloadAll();
|
||||||
|
|
||||||
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
|
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
|
||||||
commands.registrar().register(new MainCommand(this).build());
|
commands.registrar().register(new MainCommand(this).build());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getServer().getMessenger().registerOutgoingPluginChannel(this, ProtocolConstants.PROTOCOL_CHANNEL);
|
getServer().getPluginManager().registerEvents(new PlayerBindListener(this), this);
|
||||||
|
|
||||||
messageManager = new MessageManager();
|
messageManager = new MessageManager();
|
||||||
|
|
||||||
@@ -40,19 +40,7 @@ public class MinerooCore extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {}
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
|
||||||
Player player = event.getPlayer();
|
|
||||||
|
|
||||||
PlayerBindDialog dialog = new PlayerBindDialog();
|
|
||||||
|
|
||||||
player.showDialog(dialog.getDialog(messageManager));
|
|
||||||
|
|
||||||
player.sendMessage(messageManager.get("message.test", "player", player.getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reloadAll() {
|
public void reloadAll() {
|
||||||
reloadConfig();
|
reloadConfig();
|
||||||
@@ -66,10 +54,11 @@ public class MinerooCore extends JavaPlugin implements Listener {
|
|||||||
String token = config.getServer().getServerBind().getBindToken();
|
String token = config.getServer().getServerBind().getBindToken();
|
||||||
String hostname = config.getTest().getApiHostname();
|
String hostname = config.getTest().getApiHostname();
|
||||||
this.networkService = new HttpNetworkService(hostname, "mserver", token);
|
this.networkService = new HttpNetworkService(hostname, "mserver", token);
|
||||||
getLogger().info("Using direct HTTP network service.");
|
getLogger().info("Using direct HTTP network [ " + hostname + " ]");
|
||||||
getLogger().info("API: " + hostname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindRequest = new BindRequest(this.getSLF4JLogger(), this.getNetworkService());
|
||||||
|
|
||||||
if (this.messageManager == null) {
|
if (this.messageManager == null) {
|
||||||
this.messageManager = new MessageManager();
|
this.messageManager = new MessageManager();
|
||||||
} else {
|
} else {
|
||||||
@@ -88,4 +77,12 @@ public class MinerooCore extends JavaPlugin implements Listener {
|
|||||||
public MessageManager getMessageManager() {
|
public MessageManager getMessageManager() {
|
||||||
return messageManager;
|
return messageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BindListener getBindListener() {
|
||||||
|
return this.bindListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BindRequest getBindRequest() {
|
||||||
|
return bindRequest;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package online.mineroo.paper;
|
||||||
|
|
||||||
|
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
|
||||||
|
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
|
||||||
|
import io.papermc.paper.registry.data.dialog.ActionButton;
|
||||||
|
import io.papermc.paper.registry.data.dialog.DialogBase;
|
||||||
|
import io.papermc.paper.registry.data.dialog.body.DialogBody;
|
||||||
|
import io.papermc.paper.registry.data.dialog.input.DialogInput;
|
||||||
|
import io.papermc.paper.registry.data.dialog.type.DialogType;
|
||||||
|
import io.papermc.paper.registry.event.RegistryEvents;
|
||||||
|
import io.papermc.paper.registry.keys.DialogKeys;
|
||||||
|
import java.util.List;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
import online.mineroo.common.MessageManager;
|
||||||
|
|
||||||
|
public class MinerooCoreBootstrap implements PluginBootstrap {
|
||||||
|
@Override
|
||||||
|
public void bootstrap(BootstrapContext context) {
|
||||||
|
MessageManager messageManager = new MessageManager();
|
||||||
|
|
||||||
|
ActionButton confirmButton =
|
||||||
|
ActionButton.create(messageManager.get("dialog.bind.player.confirm"), null, 100, null);
|
||||||
|
|
||||||
|
ActionButton cancelButton =
|
||||||
|
ActionButton.create(messageManager.get("dialog.bind.player.cancel"), null, 100, null);
|
||||||
|
|
||||||
|
DialogInput usernameInput =
|
||||||
|
DialogInput.text("username", messageManager.get("dialog.bind.player.email")).build();
|
||||||
|
|
||||||
|
DialogBase dialogBase =
|
||||||
|
DialogBase.builder(messageManager.get("dialog.bind.player.title"))
|
||||||
|
.body(
|
||||||
|
List.of(DialogBody.plainMessage(messageManager.get("dialog.bind.player.content")))
|
||||||
|
)
|
||||||
|
.inputs(List.of(usernameInput))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
DialogType confirmationType = DialogType.confirmation(confirmButton, cancelButton);
|
||||||
|
|
||||||
|
context.getLifecycleManager().registerEventHandler(RegistryEvents.DIALOG.compose().newHandler(
|
||||||
|
event
|
||||||
|
-> event.registry().register(
|
||||||
|
DialogKeys.create(Key.key("papermc:bind_mineroo_user")),
|
||||||
|
builder -> builder.base(dialogBase).type(confirmationType)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,33 +5,44 @@ import com.google.common.io.ByteArrayDataOutput;
|
|||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import online.mineroo.common.ProxyNetworkRequest;
|
import online.mineroo.common.NetworkResponse;
|
||||||
import online.mineroo.common.NetworkServiceInterface;
|
import online.mineroo.common.NetworkServiceInterface;
|
||||||
import online.mineroo.common.ProtocolConstants;
|
import online.mineroo.common.ProtocolConstants;
|
||||||
import online.mineroo.common.NetworkResponse;
|
import online.mineroo.common.ProxyNetworkRequest;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
public class ProxyNetworkService implements NetworkServiceInterface, PluginMessageListener {
|
public class ProxyNetworkService implements NetworkServiceInterface, PluginMessageListener, Listener {
|
||||||
|
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
private final String CHANNEL = "mineroo:net";
|
private final String CHANNEL = ProtocolConstants.PROTOCOL_CHANNEL;
|
||||||
|
|
||||||
// Store pending requests <RequestId, Future>
|
// Stores sent requests <RequestId, Future>
|
||||||
private final Map<String, CompletableFuture<NetworkResponse>> pendingRequests = new ConcurrentHashMap<>();
|
private final Map<String, CompletableFuture<NetworkResponse>> pendingRequests = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// Buffer queue: stores raw packets that cannot be sent when no player is online
|
||||||
|
private final Queue<byte[]> messageQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
public ProxyNetworkService(JavaPlugin plugin) {
|
public ProxyNetworkService(JavaPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
// Register channels
|
// Register channels
|
||||||
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CHANNEL);
|
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CHANNEL);
|
||||||
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, CHANNEL, this);
|
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, CHANNEL, this);
|
||||||
|
// Register event (used to listen for player join to flush queue)
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -48,57 +59,110 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa
|
|||||||
JsonObject body) {
|
JsonObject body) {
|
||||||
CompletableFuture<NetworkResponse> future = new CompletableFuture<>();
|
CompletableFuture<NetworkResponse> future = new CompletableFuture<>();
|
||||||
|
|
||||||
// 1. Check if there is any player online (Plugin Message must be sent via a
|
try {
|
||||||
// player)
|
// 1. Prepare request data
|
||||||
Player player = com.google.common.collect.Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
|
ProxyNetworkRequest req = new ProxyNetworkRequest(method, endpoint, params, body);
|
||||||
if (player == null) {
|
String jsonPayload = gson.toJson(req);
|
||||||
future.completeExceptionally(new IllegalStateException("No player online to proxy request to Velocity"));
|
|
||||||
|
// Safety check: Plugin Message single packet cannot be too large (conservative
|
||||||
|
// limit 30KB)
|
||||||
|
byte[] payloadBytes = jsonPayload.getBytes(StandardCharsets.UTF_8);
|
||||||
|
if (payloadBytes.length > 30000) {
|
||||||
|
future.completeExceptionally(
|
||||||
|
new IllegalArgumentException("Request body too large (>30KB). Protocol limit exceeded."));
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Prepare the packet
|
// 2. Register Future
|
||||||
ProxyNetworkRequest req = new ProxyNetworkRequest(method, endpoint, params, body);
|
|
||||||
pendingRequests.put(req.getRequestId(), future);
|
pendingRequests.put(req.getRequestId(), future);
|
||||||
|
|
||||||
|
// 3. Build packet
|
||||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||||
out.writeUTF(ProtocolConstants.API_REQUEST); // Sub-channel
|
out.writeUTF(ProtocolConstants.API_REQUEST);
|
||||||
out.writeUTF(gson.toJson(req)); // Payload
|
out.writeUTF(jsonPayload); // Length checked above, writeUTF is safe here
|
||||||
|
|
||||||
// 3. Send
|
// 4. Try to send or queue
|
||||||
player.sendPluginMessage(plugin, CHANNEL, out.toByteArray());
|
trySendOrQueue(out.toByteArray());
|
||||||
|
|
||||||
// 4. Set timeout (prevent memory leak if Velocity does not respond)
|
// 5. Set timeout (prevent Future from hanging forever)
|
||||||
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> {
|
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> {
|
||||||
CompletableFuture<NetworkResponse> f = pendingRequests.remove(req.getRequestId());
|
CompletableFuture<NetworkResponse> f = pendingRequests.remove(req.getRequestId());
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
f.complete(new NetworkResponse(504, "Proxy request timed out"));
|
f.complete(new NetworkResponse(504, "Proxy request timed out (Proxy didn't respond or no player online)"));
|
||||||
}
|
}
|
||||||
}, 100L); // 5 seconds timeout
|
}, 100L); // 5 seconds timeout
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
future.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core logic: send if there is a player online, otherwise queue
|
||||||
|
*/
|
||||||
|
private void trySendOrQueue(byte[] message) {
|
||||||
|
Player player = com.google.common.collect.Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
|
||||||
|
|
||||||
|
if (player != null) {
|
||||||
|
player.sendPluginMessage(plugin, CHANNEL, message);
|
||||||
|
} else {
|
||||||
|
// No player online, add to queue
|
||||||
|
// plugin.getLogger().info("[Network] Request queued (No player online)");
|
||||||
|
messageQueue.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only flush queued requests when a player joins
|
||||||
|
*/
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
if (messageQueue.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
plugin.getLogger().info("[Network] Player joined. Flushing " + messageQueue.size() + " queued requests...");
|
||||||
|
|
||||||
|
// Delay 1 second to ensure player connection is stable
|
||||||
|
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
// Double check: prevent player from leaving immediately after joining
|
||||||
|
if (player == null || !player.isOnline())
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (!messageQueue.isEmpty()) {
|
||||||
|
byte[] msg = messageQueue.poll();
|
||||||
|
if (msg != null) {
|
||||||
|
player.sendPluginMessage(plugin, CHANNEL, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 20L);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
|
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
|
||||||
if (!channel.equals(CHANNEL))
|
if (!channel.equals(CHANNEL))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
ByteArrayDataInput in = ByteStreams.newDataInput(message);
|
ByteArrayDataInput in = ByteStreams.newDataInput(message);
|
||||||
String subChannel = in.readUTF();
|
String subChannel = in.readUTF();
|
||||||
|
|
||||||
if (subChannel.equals("API_RESP")) {
|
if (subChannel.equals(ProtocolConstants.API_RESPONSE)) {
|
||||||
String reqId = in.readUTF();
|
String reqId = in.readUTF();
|
||||||
boolean success = in.readBoolean();
|
boolean success = in.readBoolean();
|
||||||
String data = in.readUTF(); // If success: body, if failure: error message
|
String data = in.readUTF();
|
||||||
|
|
||||||
CompletableFuture<NetworkResponse> future = pendingRequests.remove(reqId);
|
CompletableFuture<NetworkResponse> future = pendingRequests.remove(reqId);
|
||||||
if (future != null) {
|
if (future != null) {
|
||||||
if (success) {
|
// If Proxy did not provide a specific status code, map simply here
|
||||||
future.complete(new NetworkResponse(200, data));
|
int status = success ? 200 : 500;
|
||||||
} else {
|
future.complete(new NetworkResponse(status, data));
|
||||||
future.complete(new NetworkResponse(500, data));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[Network] Failed to parse plugin message: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import io.papermc.paper.command.brigadier.CommandSourceStack;
|
|||||||
import io.papermc.paper.command.brigadier.Commands;
|
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 online.mineroo.paper.ProxyNetworkService;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
public class BindCommand {
|
public class BindCommand {
|
||||||
@@ -46,6 +48,14 @@ public class BindCommand {
|
|||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyMotdToken(String motdToken) {
|
||||||
|
if (plugin.getNetworkService() instanceof ProxyNetworkService) {
|
||||||
|
sendMotdTokenToVelocity(motdToken);
|
||||||
|
} else {
|
||||||
|
plugin.getBindListener().setVerificationToken(motdToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendMotdTokenToVelocity(String motdToken) {
|
private void sendMotdTokenToVelocity(String motdToken) {
|
||||||
// Send via any online player (Plugin Messaging must be attached to a player)
|
// 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);
|
org.bukkit.entity.Player player = org.bukkit.Bukkit.getOnlinePlayers().stream().findFirst().orElse(null);
|
||||||
@@ -61,12 +71,15 @@ public class BindCommand {
|
|||||||
// Run the binding process asynchronously to avoid blocking the main thread
|
// Run the binding process asynchronously to avoid blocking the main thread
|
||||||
org.bukkit.Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
org.bukkit.Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
Logger logger = plugin.getSLF4JLogger();
|
Logger logger = plugin.getSLF4JLogger();
|
||||||
BindRequest bindRequest = new BindRequest(
|
BindRequest bindRequest = plugin.getBindRequest();
|
||||||
logger,
|
|
||||||
plugin.getNetworkService());
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
String address = plugin.getConfigObject().getServer().getServerBind().getAddress();
|
String address = plugin.getConfigObject().getServer().getServerBind().getAddress();
|
||||||
int port = plugin.getConfigObject().getServer().getServerBind().getPort();
|
String port = String.valueOf(plugin.getConfigObject().getServer().getServerBind().getPort());
|
||||||
|
if (plugin.getNetworkService() instanceof ProxyNetworkService) {
|
||||||
|
address = "$hostname";
|
||||||
|
port = "$port";
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Check binding status
|
// 1. Check binding status
|
||||||
if (bindRequest.checkBindStatus(address, port).join()) {
|
if (bindRequest.checkBindStatus(address, port).join()) {
|
||||||
@@ -80,7 +93,7 @@ public class BindCommand {
|
|||||||
if (motdOk) {
|
if (motdOk) {
|
||||||
String motdToken = bindRequest.getMotdToken();
|
String motdToken = bindRequest.getMotdToken();
|
||||||
// Send MOTD token to Velocity
|
// Send MOTD token to Velocity
|
||||||
sendMotdTokenToVelocity(motdToken);
|
applyMotdToken(motdToken);
|
||||||
context.getSource().getSender().sendMessage(plugin.getMessageManager().get("command.bind.server.wait"));
|
context.getSource().getSender().sendMessage(plugin.getMessageManager().get("command.bind.server.wait"));
|
||||||
try {
|
try {
|
||||||
Thread.sleep(2 * 60 * 1000 + 5000); // 2m 5s
|
Thread.sleep(2 * 60 * 1000 + 5000); // 2m 5s
|
||||||
@@ -124,7 +137,7 @@ public class BindCommand {
|
|||||||
plugin.getMessageManager().get("command.bind.server.failed"));
|
plugin.getMessageManager().get("command.bind.server.failed"));
|
||||||
} finally {
|
} finally {
|
||||||
// After binding is complete, you can send a message to clear the MOTD token
|
// After binding is complete, you can send a message to clear the MOTD token
|
||||||
sendMotdTokenToVelocity(""); // Clear MOTD
|
applyMotdToken(""); // Clear MOTD
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Command.SINGLE_SUCCESS;
|
return Command.SINGLE_SUCCESS;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package online.mineroo.paper.listeners;
|
||||||
|
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.server.ServerListPingEvent;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class BindListener implements Listener {
|
||||||
|
|
||||||
|
private final AtomicReference<String> verificationToken = new AtomicReference<>(null);
|
||||||
|
|
||||||
|
public void setVerificationToken(String token) {
|
||||||
|
if (token == null || token.isEmpty()) {
|
||||||
|
this.verificationToken.set(null);
|
||||||
|
} else {
|
||||||
|
this.verificationToken.set(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
public void onServerListPing(ServerListPingEvent event) {
|
||||||
|
String token = verificationToken.get();
|
||||||
|
|
||||||
|
if (token == null || token.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.motd(Component.text(token));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package online.mineroo.paper.listeners;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
import online.mineroo.common.BindRequest.PlayerBindStatus;
|
||||||
|
import online.mineroo.common.MessageManager;
|
||||||
|
import online.mineroo.paper.Config;
|
||||||
|
import online.mineroo.paper.MinerooCore;
|
||||||
|
import online.mineroo.paper.utils.PlayerBindDialog;
|
||||||
|
|
||||||
|
@NullMarked
|
||||||
|
public class PlayerBindListener implements Listener {
|
||||||
|
|
||||||
|
private final MinerooCore plugin;
|
||||||
|
|
||||||
|
private Set<UUID> pendingBindPlayers = new HashSet<>();
|
||||||
|
|
||||||
|
public PlayerBindListener(MinerooCore plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
|
||||||
|
MessageManager messageManager = this.plugin.getMessageManager();
|
||||||
|
Config config = this.plugin.getConfigObject();
|
||||||
|
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
boolean isForceBind = config.getPlayer().getPlayerBind().isRequired();
|
||||||
|
|
||||||
|
if (isForceBind) {
|
||||||
|
lockPlayer(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.plugin.getBindRequest().checkPlayerBindStatus(player.getUniqueId(), null)
|
||||||
|
.thenAccept(response -> {
|
||||||
|
|
||||||
|
// 4. 切回主线程 (Bukkit API 必须在主线程调用)
|
||||||
|
// 这一步非常重要,否则报错 "Asynchronous player tracker update"
|
||||||
|
this.plugin.getServer().getScheduler().runTask(this.plugin, () -> {
|
||||||
|
|
||||||
|
// 检查玩家是否还在同一个服务器 (防止请求期间玩家退出了)
|
||||||
|
if (!player.isOnline())
|
||||||
|
return;
|
||||||
|
|
||||||
|
PlayerBindStatus status = response.getStatus();
|
||||||
|
|
||||||
|
if (status == PlayerBindStatus.BOUND) {
|
||||||
|
if (isForceBind) {
|
||||||
|
unlockPlayer(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (status == PlayerBindStatus.ERROR) {
|
||||||
|
this.plugin.getLogger()
|
||||||
|
.warning("Bind check failed for " + player.getName() + ": " + response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerBindDialog dialog = new PlayerBindDialog();
|
||||||
|
player.showDialog(dialog.getDialog(messageManager));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
if (!pendingBindPlayers.contains(player.getUniqueId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location from = event.getFrom();
|
||||||
|
Location to = event.getTo();
|
||||||
|
|
||||||
|
if (from.getX() == to.getX() && from.getY() == to.getY() && from.getZ() == to.getZ()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location newLocation = from.clone();
|
||||||
|
newLocation.setYaw(to.getYaw());
|
||||||
|
newLocation.setPitch(to.getPitch());
|
||||||
|
event.setTo(newLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInteract(PlayerInteractEvent event) {
|
||||||
|
if (isLocked(event.getPlayer()))
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onDrop(PlayerDropItemEvent event) {
|
||||||
|
if (isLocked(event.getPlayer()))
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
if (event.getWhoClicked() instanceof Player player && isLocked(player)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onDamage(EntityDamageEvent event) {
|
||||||
|
if (event.getEntity() instanceof Player player && isLocked(player)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onBlockBreak(BlockBreakEvent event) {
|
||||||
|
if (isLocked(event.getPlayer()))
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onBlockPlace(BlockPlaceEvent event) {
|
||||||
|
if (isLocked(event.getPlayer()))
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onChat(io.papermc.paper.event.player.AsyncChatEvent event) {
|
||||||
|
if (isLocked(event.getPlayer()))
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLocked(Player player) {
|
||||||
|
return pendingBindPlayers.contains(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lockPlayer(Player player) {
|
||||||
|
pendingBindPlayers.add(player.getUniqueId());
|
||||||
|
|
||||||
|
player.setWalkSpeed(0f);
|
||||||
|
player.setFlySpeed(0f);
|
||||||
|
|
||||||
|
// player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP,
|
||||||
|
// Integer.MAX_VALUE, 200, false, false));
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, Integer.MAX_VALUE, 1, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockPlayer(Player player) {
|
||||||
|
if (pendingBindPlayers.remove(player.getUniqueId())) {
|
||||||
|
player.setWalkSpeed(0.2f);
|
||||||
|
player.setFlySpeed(0.1f);
|
||||||
|
// player.removePotionEffect(PotionEffectType.JUMP);
|
||||||
|
player.removePotionEffect(PotionEffectType.BLINDNESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package online.mineroo.paper.listeners;
|
|
||||||
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.jspecify.annotations.NullMarked;
|
|
||||||
|
|
||||||
@NullMarked
|
|
||||||
public class PlayerJoinListener implements Listener {
|
|
||||||
}
|
|
||||||
@@ -5,14 +5,15 @@ package online.mineroo.velocity;
|
|||||||
* Handles plugin initialization, configuration loading, and provides access to core components.
|
* Handles plugin initialization, configuration loading, and provides access to core components.
|
||||||
*/
|
*/
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.velocitypowered.api.command.CommandManager;
|
|
||||||
import com.velocitypowered.api.event.Subscribe;
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||||
import com.velocitypowered.api.plugin.Plugin;
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
|
|
||||||
import online.mineroo.common.MessageManager;
|
import online.mineroo.common.MessageManager;
|
||||||
|
import online.mineroo.common.ProtocolConstants;
|
||||||
import online.mineroo.velocity.listeners.BindListener;
|
import online.mineroo.velocity.listeners.BindListener;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -27,7 +28,7 @@ import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
|||||||
|
|
||||||
@Plugin(id = "mineroo-velocity", name = "Mineroo Velocity", version = "0.1.0-SNAPSHOT", url = "https://mineroo.online", description = "Mineroo main plugin", authors = {
|
@Plugin(id = "mineroo-velocity", name = "Mineroo Velocity", version = "0.1.0-SNAPSHOT", url = "https://mineroo.online", description = "Mineroo main plugin", authors = {
|
||||||
"YuKun Liu" })
|
"YuKun Liu" })
|
||||||
public class MinerooCore {
|
public class MinerooVelocity {
|
||||||
|
|
||||||
// Reference to the Velocity ProxyServer instance
|
// Reference to the Velocity ProxyServer instance
|
||||||
private final ProxyServer server;
|
private final ProxyServer server;
|
||||||
@@ -53,7 +54,7 @@ public class MinerooCore {
|
|||||||
* @param dataDirectory Directory for plugin data
|
* @param dataDirectory Directory for plugin data
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public MinerooCore(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
|
public MinerooVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.dataDirectory = dataDirectory;
|
this.dataDirectory = dataDirectory;
|
||||||
@@ -61,16 +62,19 @@ public class MinerooCore {
|
|||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onProxyInitialization(ProxyInitializeEvent event) {
|
public void onProxyInitialization(ProxyInitializeEvent event) {
|
||||||
|
|
||||||
// Register event listeners
|
// Register event listeners
|
||||||
this.bindListener = new BindListener();
|
this.bindListener = new BindListener();
|
||||||
server.getEventManager().register(this, bindListener);
|
server.getEventManager().register(this, bindListener);
|
||||||
|
|
||||||
// Register ChannelListener to handle cross-platform MOTD token messages
|
|
||||||
server.getEventManager().register(this, new online.mineroo.velocity.listeners.ChannelListener(this));
|
|
||||||
|
|
||||||
// Load configuration
|
// Load configuration
|
||||||
reloadConfig();
|
reloadConfig();
|
||||||
|
|
||||||
|
server.getChannelRegistrar().register(MinecraftChannelIdentifier.from(ProtocolConstants.PROTOCOL_CHANNEL));
|
||||||
|
|
||||||
|
// Register ChannelListener to handle cross-platform MOTD token messages
|
||||||
|
server.getEventManager().register(this, new online.mineroo.velocity.listeners.ChannelListener(this));
|
||||||
|
|
||||||
// Initialize message manager
|
// Initialize message manager
|
||||||
this.messageManager = new MessageManager();
|
this.messageManager = new MessageManager();
|
||||||
}
|
}
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
// 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<String> 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));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -23,7 +23,7 @@ public class BindListener {
|
|||||||
public void onPing(ProxyPingEvent event) {
|
public void onPing(ProxyPingEvent event) {
|
||||||
String token = verificationToken.get();
|
String token = verificationToken.get();
|
||||||
|
|
||||||
if (token == null) {
|
if (token == null || token.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,208 @@
|
|||||||
package online.mineroo.velocity.listeners;
|
package online.mineroo.velocity.listeners;
|
||||||
|
|
||||||
import com.google.common.io.ByteArrayDataInput;
|
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.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
import com.velocitypowered.api.event.Subscribe;
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
import com.velocitypowered.api.proxy.ServerConnection;
|
import com.velocitypowered.api.proxy.ServerConnection;
|
||||||
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
|
import online.mineroo.common.*;
|
||||||
|
import online.mineroo.velocity.MinerooVelocity;
|
||||||
|
|
||||||
import online.mineroo.common.ProtocolConstants;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import online.mineroo.velocity.MinerooCore;
|
|
||||||
|
|
||||||
public class ChannelListener {
|
public class ChannelListener {
|
||||||
private final MinerooCore plugin;
|
|
||||||
|
|
||||||
public ChannelListener(MinerooCore plugin) {
|
private final MinerooVelocity plugin;
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
|
private final HttpNetworkService httpService;
|
||||||
|
|
||||||
|
public ChannelListener(MinerooVelocity plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
||||||
|
String apiUrl = "https://oapi.mineroo.online";
|
||||||
|
|
||||||
|
this.httpService = new HttpNetworkService(apiUrl, "mserver", plugin.getConfig().getBind().getBindToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onPluginMessage(PluginMessageEvent event) {
|
public void onPluginMessage(PluginMessageEvent event) {
|
||||||
|
// Only handle messages from the specified protocol channel
|
||||||
if (!event.getIdentifier().getId().equals(ProtocolConstants.PROTOCOL_CHANNEL))
|
if (!event.getIdentifier().getId().equals(ProtocolConstants.PROTOCOL_CHANNEL))
|
||||||
return;
|
return;
|
||||||
|
// Only handle messages from a backend server
|
||||||
if (!(event.getSource() instanceof ServerConnection))
|
if (!(event.getSource() instanceof ServerConnection))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
ServerConnection backendServer = (ServerConnection) event.getSource();
|
||||||
ByteArrayDataInput in = event.dataAsDataStream();
|
ByteArrayDataInput in = event.dataAsDataStream();
|
||||||
|
|
||||||
|
try {
|
||||||
String subChannel = in.readUTF();
|
String subChannel = in.readUTF();
|
||||||
|
|
||||||
|
if (subChannel.equals(ProtocolConstants.API_REQUEST)) {
|
||||||
|
handleApiRequest(in, backendServer);
|
||||||
|
}
|
||||||
|
|
||||||
if (subChannel.equals(ProtocolConstants.BIND_MOTD_TOKEN)) {
|
if (subChannel.equals(ProtocolConstants.BIND_MOTD_TOKEN)) {
|
||||||
String token = in.readUTF();
|
String token = in.readUTF();
|
||||||
plugin.getBindListener().setVerificationToken(token);
|
plugin.getBindListener().setVerificationToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().error("Failed to handle plugin message", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleApiRequest(ByteArrayDataInput in, ServerConnection backendServer) {
|
||||||
|
String reqId = "unknown";
|
||||||
|
try {
|
||||||
|
// 1. Parse the request
|
||||||
|
String jsonPayload = in.readUTF();
|
||||||
|
ProxyNetworkRequest req = gson.fromJson(jsonPayload, ProxyNetworkRequest.class);
|
||||||
|
reqId = req.getRequestId();
|
||||||
|
|
||||||
|
CompletableFuture<NetworkResponse> future;
|
||||||
|
|
||||||
|
// 2. Delegate to HttpNetworkService for processing
|
||||||
|
if ("POST".equalsIgnoreCase(req.getMethod())) {
|
||||||
|
JsonObject bodyJson = null;
|
||||||
|
try {
|
||||||
|
JsonObject originalJson = gson.fromJson(req.getJsonBody(), JsonObject.class);
|
||||||
|
if (originalJson == null)
|
||||||
|
originalJson = new JsonObject();
|
||||||
|
|
||||||
|
bodyJson = resolvePlacehodersInJson(originalJson).getAsJsonObject();
|
||||||
|
|
||||||
|
plugin.getLogger().info("Proxy POST [ " + req.getEndpoint() + " ]\n\t" + bodyJson.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().error("JSON parsing error", e);
|
||||||
|
bodyJson = new JsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
future = httpService.postData(req.getEndpoint(), bodyJson);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// replace placeholders
|
||||||
|
java.util.Map<String, String> finalParams = new java.util.HashMap<>();
|
||||||
|
|
||||||
|
if (req.getParams() != null) {
|
||||||
|
for (java.util.Map.Entry<String, String> entry : req.getParams().entrySet()) {
|
||||||
|
String originalKey = entry.getKey();
|
||||||
|
String originalValue = entry.getValue();
|
||||||
|
|
||||||
|
String replacedValue = resolvePlaceholders(originalValue);
|
||||||
|
|
||||||
|
finalParams.put(originalKey, replacedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getLogger().info("Proxy GET [ " + req.getEndpoint() + " ]\n\t" + finalParams.toString());
|
||||||
|
|
||||||
|
future = httpService.getData(req.getEndpoint(), finalParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Handle the result and send back the response
|
||||||
|
String finalReqId = reqId;
|
||||||
|
future.thenAccept(response -> {
|
||||||
|
// Check if HTTP status code is 2xx
|
||||||
|
boolean success = response.getStatusCode() >= 200 && response.getStatusCode() < 300;
|
||||||
|
sendResponse(backendServer, finalReqId, success, response.getBody());
|
||||||
|
}).exceptionally(e -> {
|
||||||
|
plugin.getLogger().error("API Proxy Request Failed", e);
|
||||||
|
sendResponse(backendServer, finalReqId, false, "Proxy Error: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().error("Error parsing API request", e);
|
||||||
|
sendResponse(backendServer, reqId, false, "Protocol Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendResponse(ServerConnection server, String reqId, boolean success, String data) {
|
||||||
|
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||||
|
out.writeUTF(ProtocolConstants.API_RESPONSE);
|
||||||
|
out.writeUTF(reqId);
|
||||||
|
out.writeBoolean(success);
|
||||||
|
out.writeUTF(data != null ? data : "");
|
||||||
|
server.sendPluginMessage(MinecraftChannelIdentifier.from(ProtocolConstants.PROTOCOL_CHANNEL), out.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonElement resolvePlacehodersInJson(JsonElement element) {
|
||||||
|
if (element.isJsonPrimitive()) {
|
||||||
|
JsonPrimitive primitive = element.getAsJsonPrimitive();
|
||||||
|
if (primitive.isString()) {
|
||||||
|
String original = primitive.getAsString();
|
||||||
|
|
||||||
|
if ("$port".equals(original)) {
|
||||||
|
Integer port = null;
|
||||||
|
if (plugin.getConfig().getBind() != null) {
|
||||||
|
port = plugin.getConfig().getBind().getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port == null || port <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JsonPrimitive(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("$timestamp".equals(original)) {
|
||||||
|
return new JsonPrimitive(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
String replaced = resolvePlaceholders(original);
|
||||||
|
return new JsonPrimitive(replaced);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.isJsonObject()) {
|
||||||
|
JsonObject obj = element.getAsJsonObject();
|
||||||
|
JsonObject newObj = new JsonObject();
|
||||||
|
for (java.util.Map.Entry<String, JsonElement> entry : obj.entrySet()) {
|
||||||
|
JsonElement processedValue = resolvePlacehodersInJson(entry.getValue());
|
||||||
|
|
||||||
|
if (processedValue != null) {
|
||||||
|
newObj.add(entry.getKey(), processedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.isJsonArray()) {
|
||||||
|
JsonArray array = element.getAsJsonArray();
|
||||||
|
JsonArray newArray = new JsonArray();
|
||||||
|
for (JsonElement item : array) {
|
||||||
|
JsonElement processedItem = resolvePlacehodersInJson(item);
|
||||||
|
|
||||||
|
if (processedItem != null) {
|
||||||
|
newArray.add(processedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolvePlaceholders(String input) {
|
||||||
|
if (input == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String hostname = plugin.getConfig().getBind().getAddress();
|
||||||
|
String port = String.valueOf(plugin.getConfig().getBind().getPort());
|
||||||
|
|
||||||
|
return input
|
||||||
|
.replace("$hostname", hostname != null ? hostname : "unknown")
|
||||||
|
.replace("$port", port)
|
||||||
|
.replace("$timestamp", String.valueOf(System.currentTimeMillis()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user