diff --git a/kls_database.db b/kls_database.db index f26bd3f..cba6bda 100644 Binary files a/kls_database.db and b/kls_database.db differ diff --git a/paper/src/main/java/online/mineroo/paper/ProxyNetworkService.java b/paper/src/main/java/online/mineroo/paper/ProxyNetworkService.java index 0d07429..d65725a 100644 --- a/paper/src/main/java/online/mineroo/paper/ProxyNetworkService.java +++ b/paper/src/main/java/online/mineroo/paper/ProxyNetworkService.java @@ -90,7 +90,7 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa if (f != null) { f.complete(new NetworkResponse(504, "Proxy request timed out (Proxy didn't respond or no player online)")); } - }, 100L); // 5 seconds timeout + }, 200L); // 10 seconds timeout } catch (Exception e) { future.completeExceptionally(e); diff --git a/paper/src/main/java/online/mineroo/paper/listeners/PlayerBindListener.java b/paper/src/main/java/online/mineroo/paper/listeners/PlayerBindListener.java index 00e5f6c..ae4406b 100644 --- a/paper/src/main/java/online/mineroo/paper/listeners/PlayerBindListener.java +++ b/paper/src/main/java/online/mineroo/paper/listeners/PlayerBindListener.java @@ -1,22 +1,18 @@ package online.mineroo.paper.listeners; -import com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent; -import io.papermc.paper.connection.PlayerConfigurationConnection; import io.papermc.paper.connection.PlayerGameConnection; import io.papermc.paper.dialog.Dialog; import io.papermc.paper.dialog.DialogResponseView; -import io.papermc.paper.event.connection.configuration.AsyncPlayerConnectionConfigureEvent; import io.papermc.paper.event.player.PlayerCustomClickEvent; import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import net.kyori.adventure.audience.Audience; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -26,9 +22,18 @@ import online.mineroo.common.BindRequest.PlayerBindResponse; import online.mineroo.common.BindRequest.PlayerBindStatusEnum; import online.mineroo.paper.Config; import online.mineroo.paper.MinerooCore; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +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.event.player.PlayerQuitEvent; import org.jspecify.annotations.NullMarked; @NullMarked @@ -38,190 +43,211 @@ public class PlayerBindListener implements Listener { private final Map> awaitingResponse = new ConcurrentHashMap<>(); + // Set to store UUIDs of players who are currently being checked or binding + private final Set restrictedPlayers = ConcurrentHashMap.newKeySet(); + public PlayerBindListener(MinerooCore plugin) { this.plugin = plugin; } @EventHandler - public void onPlayerConfigure(AsyncPlayerConnectionConfigureEvent event) { + public void onPlayerJoin(PlayerJoinEvent event) { Config config = this.plugin.getConfigObject(); - if (!config.getPlayer().getPlayerBind().isRequired()) { return; } - UUID uuid = event.getConnection().getProfile().getId(); - if (uuid == null) - return; + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); - try { - var response = this.plugin.getBindRequest().checkPlayerBindStatus(uuid, null).join(); + // Lock player immediately upon join + restrictedPlayers.add(uuid); - PlayerBindStatusEnum status = response.getStatus(); - - plugin.getSLF4JLogger().info(status.toString()); - if (status == PlayerBindStatusEnum.BOUND) { - return; - } else if (status == PlayerBindStatusEnum.NOT_BOUND) { - Dialog dialog = RegistryAccess.registryAccess() - .getRegistry(RegistryKey.DIALOG) - .get(Key.key("mineroo:bind_user")); - if (dialog == null) { - return; - } - - PlayerConfigurationConnection connection = event.getConnection(); - - UUID uniqueId = connection.getProfile().getId(); - - if (uniqueId == null) { - return; - } - - CompletableFuture dialogResponse = new CompletableFuture<>(); - dialogResponse.completeOnTimeout(null, 1, TimeUnit.MINUTES); - awaitingResponse.put(uniqueId, dialogResponse); - - Audience audience = connection.getAudience(); - audience.showDialog(dialog); - - PlayerBindResponse bindResponse = dialogResponse.join(); - if (bindResponse == null) { - audience.closeDialog(); - connection.disconnect( - Component.text("Operation timed out, please try again.", NamedTextColor.RED) - ); - awaitingResponse.remove(uniqueId); - return; - } - - if (bindResponse.getStatus() == PlayerBindEnum.BOUND) { - return; - } else if (bindResponse.getStatus() == PlayerBindEnum.PENDING) { - audience.closeDialog(); - String msg = "Binding request submitted!\nPlease go to Mineroo.Online to confirm the " - + "binding. After confirmation, please re-enter the server."; - connection.disconnect(Component.text(msg, NamedTextColor.GREEN)); - } else { - audience.closeDialog(); - - String statusStr = (bindResponse.getStatus() != null) - ? bindResponse.getStatus().toString() - : "UNKNOWN_STATUS"; - String msg = bindResponse.getMessage(); - if (msg == null) - msg = "Bind failed."; - - String kickMessage = statusStr + " : " + msg; - - connection.disconnect(Component.text(kickMessage, NamedTextColor.RED)); - } - awaitingResponse.remove(uniqueId); - - } else { - event.getConnection().disconnect( - Component.text("Internal server error, please try again later.", NamedTextColor.RED) - ); + // 延迟 2 秒 (40 ticks) 再发送检查请求,给予 Proxy 通道建立缓冲时间,防止发包过快导致丢失 (504) + Bukkit.getScheduler().runTaskLater(plugin, () -> { + if (!player.isOnline()) { + restrictedPlayers.remove(uuid); // 玩家中途退出,清理缓存 return; } - } catch (Exception e) { - this.plugin.getLogger().severe("Failed to check bind status for " + uuid); - e.printStackTrace(); - event.getConnection().disconnect( - Component.text("Internal server error, please try again later.", NamedTextColor.RED) - ); - } - } + // 异步检查,避免阻塞主线程 + this.plugin.getBindRequest() + .checkPlayerBindStatus(uuid, null) + .thenAccept(response -> { + // 回到主线程处理 UI 和 踢出逻辑 + Bukkit.getScheduler().runTask(plugin, () -> { + if (!player.isOnline()) + return; // 玩家可能在检查期间退出了 - @EventHandler - void onHandleDialog(PlayerCustomClickEvent event) { - // Main handlr only use for PlayerConfigureationConnection, - // Inside Block use for handle command bind request. - if (event.getCommonConnection() instanceof PlayerGameConnection playerConn) { - // handle command bind - Player player = playerConn.getPlayer(); - UUID playerUuid = player.getUniqueId(); - this.PlayerBindProcess(event, playerUuid, response -> { - if (response.getStatus() == PlayerBindEnum.PENDING) { - player.sendMessage(this.plugin.getMessageManager().get("info.bind.player.pending")); - } else if (response.getStatus() == PlayerBindEnum.BOUND) { - player.sendMessage(this.plugin.getMessageManager().get("info.bind.player.success")); - } else { - String statusText = - (response.getStatus() != null) ? response.getStatus().toString() : "UNKNOWN_STATUS"; - String messageText = - (response.getMessage() != null) ? response.getMessage() : "Bind Failed"; - String fullMessage = "[ " + statusText + " ]: " + messageText; - player.sendMessage(Component.text(fullMessage, NamedTextColor.RED)); - } - }); - } else if (event.getCommonConnection() - instanceof PlayerConfigurationConnection configurationConnection) { - // handle required configuration event bind - UUID playerUuid = configurationConnection.getProfile().getId(); - this.PlayerBindProcess(event, playerUuid, response -> { - setConnectionJoinResult(playerUuid, response); - }); - } - } - - private void PlayerBindProcess( - PlayerCustomClickEvent event, UUID playerUuid, Consumer callback - ) { - if (playerUuid == null) { - return; - } - - Key key = event.getIdentifier(); - - if (key.equals(Key.key("mineroo:bind_user/confirm"))) { - DialogResponseView view = event.getDialogResponseView(); - if (view == null) { - return; - } - - String userEmail = view.getText("user_email"); - String playerName = "Unknown"; - - if (event.getCommonConnection() instanceof PlayerConfigurationConnection conn) { - playerName = conn.getProfile().getName(); - } else if (event.getCommonConnection() instanceof PlayerGameConnection conn) { - playerName = conn.getPlayer().getName(); - } - - BindRequest bindRequest = plugin.getBindRequest(); - - bindRequest.PlayerBindRequest(userEmail, playerUuid, playerName) - .thenAccept(response -> { callback.accept(response); }) - .exceptionally(ex -> { - callback.accept(BindRequest.createPlayerBindErrorResponse( - "Network request failed: " + ex.getMessage() - )); + handleBindStatus(player, response.getStatus(), response.getMessage()); + }); + }) + .exceptionally(e -> { + plugin.getLogger().severe("Failed to check bind status for " + uuid); + e.printStackTrace(); + Bukkit.getScheduler().runTask(plugin, () -> { + player.kick( + Component.text("Internal server error, please try again later.", NamedTextColor.RED) + ); + }); return null; }); + }, 40L); + } - } else if (key.equals(Key.key("mineroo:bind_user/cancel"))) { - callback.accept(BindRequest.createPlayerBindErrorResponse( - this.plugin.getMessageManager().getString("info.bind.player.canceled") + private void handleBindStatus(Player player, PlayerBindStatusEnum status, String message) { + if (status == PlayerBindStatusEnum.BOUND) { + // Player is bound, release restriction + restrictedPlayers.remove(player.getUniqueId()); + return; + } else if (status == PlayerBindStatusEnum.NOT_BOUND) { + Dialog dialog = RegistryAccess.registryAccess() + .getRegistry(RegistryKey.DIALOG) + .get(Key.key("mineroo:bind_user")); + if (dialog == null) { + plugin.getLogger().severe("Dialog 'mineroo:bind_user' not found!"); + return; + } + + UUID uniqueId = player.getUniqueId(); + CompletableFuture dialogResponse = new CompletableFuture<>(); + dialogResponse.completeOnTimeout(null, 2, TimeUnit.MINUTES); + awaitingResponse.put(uniqueId, dialogResponse); + + // 使用 player 直接显示 Dialog + player.showDialog(dialog); + + // 异步等待 Dialog 结果,不阻塞主线程 + dialogResponse.thenAccept(bindResponse -> { + Bukkit.getScheduler().runTask(plugin, () -> { + awaitingResponse.remove(uniqueId); + if (bindResponse == null) { + player.closeDialog(); + player.kick( + Component.text("Operation timed out, please try again.", NamedTextColor.RED) + ); + return; + } + + if (bindResponse.getStatus() == PlayerBindEnum.BOUND) { + // 极其罕见的情况:请求瞬间已绑定(通常不会发生,除非已有自动绑定逻辑) + restrictedPlayers.remove(uniqueId); + player.sendMessage(this.plugin.getMessageManager().get("info.bind.player.success")); + } else if (bindResponse.getStatus() == PlayerBindEnum.PENDING) { + player.closeDialog(); + // 提交成功,踢出玩家,让其去网站确认 + player.kick(this.plugin.getMessageManager().get("info.bind.player.pending")); + } else { + player.closeDialog(); + String statusStr = (bindResponse.getStatus() != null) + ? bindResponse.getStatus().toString() + : "UNKNOWN"; + String msg = + bindResponse.getMessage() != null ? bindResponse.getMessage() : "Bind failed"; + player.kick(Component.text(statusStr + " : " + msg, NamedTextColor.RED)); + } + }); + }); + + } else { + player.kick(Component.text( + "Bind Check Error: " + (message != null ? message : status.toString()), NamedTextColor.RED )); } } @EventHandler - void onConnectionClose(PlayerConnectionCloseEvent event) { - awaitingResponse.remove(event.getPlayerUniqueId()); + void onHandleDialog(PlayerCustomClickEvent event) { + Player player = null; + if (event.getCommonConnection() instanceof PlayerGameConnection conn) { + player = conn.getPlayer(); + } + + if (player == null) + return; + UUID playerUuid = player.getUniqueId(); + + Key key = event.getIdentifier(); + + if (key.equals(Key.key("mineroo:bind_user/confirm"))) { + DialogResponseView view = event.getDialogResponseView(); + if (view == null) + return; + + String userEmail = view.getText("user_email"); + String playerName = player.getName(); + + final UUID targetUuid = playerUuid; + + plugin.getBindRequest() + .PlayerBindRequest(userEmail, targetUuid, playerName) + .thenAccept(response -> setConnectionJoinResult(targetUuid, response)) + .exceptionally(ex -> { + setConnectionJoinResult( + targetUuid, + BindRequest.createPlayerBindErrorResponse("Network Error: " + ex.getMessage()) + ); + return null; + }); + } else if (key.equals(Key.key("mineroo:bind_user/cancel"))) { + setConnectionJoinResult( + playerUuid, BindRequest.createPlayerBindErrorResponse("Canceled by user") + ); + } } - /** + @EventHandler + void onPlayerQuit(PlayerQuitEvent event) { + UUID uuid = event.getPlayer().getUniqueId(); + awaitingResponse.remove(uuid); + restrictedPlayers.remove(uuid); + } - * Simple utility method for setting a connection's dialog response result. + // --- Restriction Event Handlers --- - */ + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerMove(PlayerMoveEvent event) { + if (restrictedPlayers.contains(event.getPlayer().getUniqueId())) { + if (event.getFrom().getX() != event.getTo().getX() + || event.getFrom().getY() != event.getTo().getY() + || event.getFrom().getZ() != event.getTo().getZ()) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerDropItem(PlayerDropItemEvent event) { + if (restrictedPlayers.contains(event.getPlayer().getUniqueId())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerInteract(PlayerInteractEvent event) { + if (restrictedPlayers.contains(event.getPlayer().getUniqueId())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInventoryOpen(InventoryOpenEvent event) { + if (event.getPlayer() instanceof Player player + && restrictedPlayers.contains(player.getUniqueId())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInventoryClick(InventoryClickEvent event) { + if (event.getWhoClicked() instanceof Player player + && restrictedPlayers.contains(player.getUniqueId())) { + event.setCancelled(true); + } + } private void setConnectionJoinResult(UUID uniqueId, PlayerBindResponse value) { CompletableFuture future = awaitingResponse.get(uniqueId); - if (future != null) { future.complete(value); } diff --git a/velocity/src/main/java/online/mineroo/velocity/listeners/ChannelListener.java b/velocity/src/main/java/online/mineroo/velocity/listeners/ChannelListener.java index 87b5f3c..4149bed 100644 --- a/velocity/src/main/java/online/mineroo/velocity/listeners/ChannelListener.java +++ b/velocity/src/main/java/online/mineroo/velocity/listeners/ChannelListener.java @@ -68,6 +68,9 @@ public class ChannelListener { String jsonPayload = in.readUTF(); ProxyNetworkRequest req = gson.fromJson(jsonPayload, ProxyNetworkRequest.class); reqId = req.getRequestId(); + + long startTime = System.currentTimeMillis(); + plugin.getLogger().info("[Debug] Received API Request: " + req.getEndpoint() + " (ID: " + reqId + ")"); CompletableFuture future; @@ -89,34 +92,37 @@ public class ChannelListener { future = httpService.postData(req.getEndpoint(), bodyJson); } else { - - // replace placeholders + // ... (GET logic) + // ... + java.util.Map finalParams = new java.util.HashMap<>(); if (req.getParams() != null) { for (java.util.Map.Entry 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()); - + + plugin.getLogger().info("Proxy GET [ " + req.getEndpoint() + " ] params: " + finalParams); future = httpService.getData(req.getEndpoint(), finalParams); } // 3. Handle the result and send back the response String finalReqId = reqId; future.thenAccept(response -> { + long duration = System.currentTimeMillis() - startTime; + plugin.getLogger().info("[Debug] API Response received for " + finalReqId + " in " + duration + "ms. Status: " + response.getStatusCode()); + // 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); + long duration = System.currentTimeMillis() - startTime; + plugin.getLogger().error("[Debug] API Request Failed for " + finalReqId + " after " + duration + "ms", e); + sendResponse(backendServer, finalReqId, false, "Proxy Error: " + e.getMessage()); return null; });