diff --git a/zander-hub/pom.xml b/zander-hub/pom.xml index 30d959b..fba0a52 100644 --- a/zander-hub/pom.xml +++ b/zander-hub/pom.xml @@ -11,15 +11,6 @@ zander-hub 1.0 - - - - src/main/resources - true - - - - papermc diff --git a/zander-velocity/src/main/java/org/modularsoft/zander/velocity/events/UserChatEvent.java b/zander-velocity/src/main/java/org/modularsoft/zander/velocity/events/UserChatEvent.java index ebcc06c..2359822 100644 --- a/zander-velocity/src/main/java/org/modularsoft/zander/velocity/events/UserChatEvent.java +++ b/zander-velocity/src/main/java/org/modularsoft/zander/velocity/events/UserChatEvent.java @@ -8,28 +8,17 @@ import io.github.ModularEnigma.Request; import io.github.ModularEnigma.Response; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.modularsoft.zander.velocity.ZanderVelocityMain; import org.modularsoft.zander.velocity.model.Filter; import org.modularsoft.zander.velocity.model.discord.DiscordChat; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; -import java.util.Optional; - public class UserChatEvent { - private final MiniMessage miniMessage = MiniMessage.miniMessage(); - @Subscribe public void UserChatEvent(PlayerChatEvent event) { Player player = event.getPlayer(); String rawMessage = event.getMessage(); - Component originalMessage = Component.text(rawMessage); String BaseAPIURL = ZanderVelocityMain.getConfig().getString(Route.from("BaseAPIURL")); String APIKey = ZanderVelocityMain.getConfig().getString(Route.from("APIKey")); @@ -79,15 +68,6 @@ public void UserChatEvent(PlayerChatEvent event) { Response discordChatReqRes = discordChatReq.execute(); ZanderVelocityMain.getLogger().info("Response (" + discordChatReqRes.getStatusCode() + "): " + discordChatReqRes.getBody()); } - - Component formattedMessage = formatChatMessage(player, originalMessage); - player.getCurrentServer() - .map(serverConnection -> serverConnection.getServer()) - .ifPresentOrElse( - server -> server.getPlayersConnected().forEach(target -> target.sendMessage(formattedMessage)), - () -> player.sendMessage(formattedMessage) - ); - event.setResult(PlayerChatEvent.ChatResult.denied()); } catch (Exception e) { Component builder = Component.text("The chat filter could not be reached at this time, there maybe an issue with the API.").color(NamedTextColor.YELLOW); player.sendMessage(builder); @@ -95,166 +75,4 @@ public void UserChatEvent(PlayerChatEvent event) { ZanderVelocityMain.getLogger().error("Chat filter error for player {}", player.getUsername(), e); } } - - private Component formatChatMessage(Player player, Component originalMessage) { - LuckPermsMeta metaData = resolveLuckPermsMeta(player).orElse(null); - Component rankPrefix = buildRankPrefix(metaData); - - return Component.text() - .append(rankPrefix) - .append(Component.space()) - .append(Component.text(player.getUsername())) - .append(Component.text(": ")) - .append(originalMessage) - .build(); - } - - private Component buildRankPrefix(LuckPermsMeta metaData) { - String prefix = metaData != null ? metaData.prefix : null; - String rankNameMeta = getMetaValue(metaData, "displayname"); - String rankDescriptionMeta = getMetaValue(metaData, "rank_description"); - - String rankName = (rankNameMeta != null && !rankNameMeta.isBlank()) - ? rankNameMeta - : (prefix != null && !prefix.isBlank() ? prefix : "Member"); - String rankDescription = (rankDescriptionMeta != null && !rankDescriptionMeta.isBlank()) - ? rankDescriptionMeta - : "No description set for this rank."; - - rankName = stripLegacy(rankName); - rankDescription = stripLegacy(rankDescription); - - Component prefixComponent = buildPrefixComponent(prefix, rankName); - Component hoverText = Component.text() - .append(prefixComponent) - .append(Component.space()) - .append(Component.text(rankName).color(NamedTextColor.GOLD)) - .append(Component.newline()) - .append(Component.text(rankDescription).color(NamedTextColor.GRAY)) - .build(); - - return prefixComponent.hoverEvent(HoverEvent.showText(hoverText)); - } - - private String getMetaValue(LuckPermsMeta metaData, String baseKey) { - if (metaData == null) { - return null; - } - String scopedKey = null; - if (metaData.primaryGroup != null && !metaData.primaryGroup.isBlank()) { - scopedKey = baseKey + "." + metaData.primaryGroup; - } - if (scopedKey != null) { - String scopedValue = metaData.metaValues.get(scopedKey); - if (scopedValue != null && !scopedValue.isBlank()) { - return scopedValue; - } - } - String directValue = metaData.metaValues.get(baseKey); - if (directValue != null && !directValue.isBlank()) { - return directValue; - } - return null; - } - - private Component buildPrefixComponent(String prefix, String rankName) { - if (prefix != null && !prefix.isBlank()) { - return LegacyComponentSerializer.legacyAmpersand().deserialize(prefix); - } - - String miniMessagePrefix = "[" - + escapeMiniMessageContent(rankName) - + "]"; - return miniMessage.deserialize(miniMessagePrefix); - } - - private String escapeMiniMessageContent(String input) { - return input.replace("<", "\\<").replace(">", "\\>"); - } - - private String stripLegacy(String input) { - return input.replaceAll("ยง.", "").replaceAll("&.", ""); - } - - private Optional resolveLuckPermsMeta(Player player) { - try { - Class providerClass = Class.forName("net.luckperms.api.LuckPermsProvider"); - Object luckPerms = providerClass.getMethod("get").invoke(null); - Object playerAdapter = luckPerms.getClass().getMethod("getPlayerAdapter", Class.class) - .invoke(luckPerms, Player.class); - Object user = playerAdapter.getClass().getMethod("getUser", Player.class).invoke(playerAdapter, player); - Object metaData = playerAdapter.getClass().getMethod("getMetaData", Player.class).invoke(playerAdapter, player); - - String prefix = null; - String primaryGroup = null; - Map> metaMap = Map.of(); - - if (user != null) { - Method primaryGroupMethod = user.getClass().getMethod("getPrimaryGroup"); - Object primaryGroupResult = primaryGroupMethod.invoke(user); - if (primaryGroupResult instanceof String) { - primaryGroup = (String) primaryGroupResult; - } - } - - if (metaData != null) { - Method prefixMethod = metaData.getClass().getMethod("getPrefix"); - Object prefixResult = prefixMethod.invoke(metaData); - if (prefixResult instanceof String) { - prefix = (String) prefixResult; - } - Method metaMethod = metaData.getClass().getMethod("getMeta"); - Object metaResult = metaMethod.invoke(metaData); - if (metaResult instanceof Map rawMeta) { - metaMap = castMetaMap(rawMeta); - } - } - - return Optional.of(new LuckPermsMeta(prefix, primaryGroup, flattenMeta(metaMap))); - } catch (ReflectiveOperationException | LinkageError ignored) { - return Optional.empty(); - } - } - - private Map flattenMeta(Map> metaMap) { - Map flattened = new java.util.HashMap<>(); - for (Map.Entry> entry : metaMap.entrySet()) { - List values = entry.getValue(); - if (values == null) { - continue; - } - for (String value : values) { - if (value != null && !value.isBlank()) { - flattened.put(entry.getKey(), value); - break; - } - } - } - return flattened; - } - - @SuppressWarnings("unchecked") - private Map> castMetaMap(Map rawMeta) { - Map> casted = new java.util.HashMap<>(); - for (Map.Entry entry : rawMeta.entrySet()) { - Object key = entry.getKey(); - Object value = entry.getValue(); - if (key instanceof String && value instanceof List listValue) { - casted.put((String) key, (List) listValue); - } - } - return casted; - } - - private static class LuckPermsMeta { - private final String prefix; - private final String primaryGroup; - private final Map metaValues; - - private LuckPermsMeta(String prefix, String primaryGroup, Map metaValues) { - this.prefix = prefix; - this.primaryGroup = primaryGroup; - this.metaValues = metaValues; - } - } } diff --git a/zander-velocity/src/main/java/org/modularsoft/zander/velocity/events/session/UserOnLogin.java b/zander-velocity/src/main/java/org/modularsoft/zander/velocity/events/session/UserOnLogin.java index cb70099..e65ec1b 100644 --- a/zander-velocity/src/main/java/org/modularsoft/zander/velocity/events/session/UserOnLogin.java +++ b/zander-velocity/src/main/java/org/modularsoft/zander/velocity/events/session/UserOnLogin.java @@ -20,9 +20,6 @@ public void UserLoginEvent (PostLoginEvent event) { ZanderVelocityMain.getPrivateMessageService().updateNameCache(player.getUniqueId(), player.getUsername()); ZanderVelocityMain.getProxy().getScheduler().buildTask(ZanderVelocityMain.getInstance(), () -> { - String BaseAPIURL = ZanderVelocityMain.getConfig().getString(Route.from("BaseAPIURL")); - String APIKey = ZanderVelocityMain.getConfig().getString(Route.from("APIKey")); - try { // // Send User Creation API POST for new user diff --git a/zander-velocity/src/main/java/org/modularsoft/zander/velocity/util/api/Heartbeat.java b/zander-velocity/src/main/java/org/modularsoft/zander/velocity/util/api/Heartbeat.java index 263f10c..bd4e975 100644 --- a/zander-velocity/src/main/java/org/modularsoft/zander/velocity/util/api/Heartbeat.java +++ b/zander-velocity/src/main/java/org/modularsoft/zander/velocity/util/api/Heartbeat.java @@ -8,49 +8,60 @@ import net.kyori.adventure.text.format.NamedTextColor; import org.modularsoft.zander.velocity.ZanderVelocityMain; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; public class Heartbeat { + private static final int MAX_CONSECUTIVE_FAILURES = 3; + private static final int REQUEST_TIMEOUT_SECONDS = 10; + public static void startHeartbeatTask() { String BaseAPIURL = ZanderVelocityMain.getConfig().getString(Route.from("BaseAPIURL")); String APIKey = ZanderVelocityMain.getConfig().getString(Route.from("APIKey")); + AtomicInteger consecutiveFailures = new AtomicInteger(0); + ZanderVelocityMain.getProxy().getScheduler().buildTask(ZanderVelocityMain.getInstance(), () -> { try { - // Your existing code here - // GET request to link to rules. Request req = Request.builder() .setURL(BaseAPIURL + "/heartbeat") .setMethod(Request.Method.GET) .addHeader("x-access-token", APIKey) .build(); - Response res = req.execute(); + Response res = CompletableFuture.supplyAsync(req::execute) + .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + String json = res.getBody(); Boolean heartbeat = JsonPath.read(json, "$.success"); - System.out.println("API Heartbeat Success"); - - // Check if the heartbeat is not successful - if (!heartbeat) { - // Kick all players - ZanderVelocityMain.getProxy().getAllPlayers().forEach(player -> { - Component message = Component.text("API Heartbeat Failed, the server is temporarily offline.") - .color(NamedTextColor.RED); - player.disconnect(message); - }); + + if (heartbeat) { + consecutiveFailures.set(0); + System.out.println("API Heartbeat Success"); + } else { + int failures = consecutiveFailures.incrementAndGet(); + ZanderVelocityMain.getLogger().warn("API Heartbeat returned failure ({}/{})", failures, MAX_CONSECUTIVE_FAILURES); + if (failures >= MAX_CONSECUTIVE_FAILURES) { + kickAllPlayers(); + } } } catch (Exception e) { - // Handle exceptions here - e.printStackTrace(); - ZanderVelocityMain.getLogger().error("API Heartbeat Failed, kicking all players until back online."); - - // Kick all players - ZanderVelocityMain.getProxy().getAllPlayers().forEach(player -> { - Component message = Component.text("API Heartbeat Failed, the server is temporarily offline.") - .color(NamedTextColor.RED); - player.disconnect(message); - }); + int failures = consecutiveFailures.incrementAndGet(); + ZanderVelocityMain.getLogger().error("API Heartbeat error ({}/{}): {}", failures, MAX_CONSECUTIVE_FAILURES, e.getMessage()); + if (failures >= MAX_CONSECUTIVE_FAILURES) { + ZanderVelocityMain.getLogger().error("API Heartbeat failed {} consecutive times, kicking all players.", MAX_CONSECUTIVE_FAILURES); + kickAllPlayers(); + } } }).repeat(60, TimeUnit.SECONDS).schedule(); } -} \ No newline at end of file + + private static void kickAllPlayers() { + ZanderVelocityMain.getProxy().getAllPlayers().forEach(player -> { + Component message = Component.text("API Heartbeat Failed, the server is temporarily offline.") + .color(NamedTextColor.RED); + player.disconnect(message); + }); + } +} diff --git a/zander-waterfall/src/main/java/org/modularsoft/zander/waterfall/util/api/Heartbeat.java b/zander-waterfall/src/main/java/org/modularsoft/zander/waterfall/util/api/Heartbeat.java index e31dbba..49121cc 100644 --- a/zander-waterfall/src/main/java/org/modularsoft/zander/waterfall/util/api/Heartbeat.java +++ b/zander-waterfall/src/main/java/org/modularsoft/zander/waterfall/util/api/Heartbeat.java @@ -6,49 +6,59 @@ import net.md_5.bungee.api.ProxyServer; import org.modularsoft.zander.waterfall.ConfigurationManager; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; public class Heartbeat { + private static final int MAX_CONSECUTIVE_FAILURES = 3; + private static final int REQUEST_TIMEOUT_SECONDS = 10; + public static void startHeartbeatTask() { - // Create a ScheduledExecutorService with a single thread ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - // Schedule the task to run every 60 seconds + AtomicInteger consecutiveFailures = new AtomicInteger(0); + scheduler.scheduleAtFixedRate(() -> { try { - // Your existing code here - // GET request to link to rules. Request req = Request.builder() .setURL(ConfigurationManager.getConfig().get("BaseAPIURL") + "/heartbeat") .setMethod(Request.Method.GET) .addHeader("x-access-token", String.valueOf(ConfigurationManager.getConfig().get("APIKey"))) .build(); - Response res = req.execute(); + Response res = CompletableFuture.supplyAsync(req::execute) + .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + String json = res.getBody(); Boolean heartbeat = JsonPath.read(json, "$.success"); - System.out.println("API Heartbeat Success"); - - // Check if the heartbeat is not successful - if (!heartbeat) { - // Kick all players - ProxyServer.getInstance().getPlayers().forEach(player -> { - player.disconnect("API Heartbeat Failed, the server is temporarily offline."); - }); + + if (heartbeat) { + consecutiveFailures.set(0); + System.out.println("API Heartbeat Success"); + } else { + int failures = consecutiveFailures.incrementAndGet(); + System.out.println("API Heartbeat returned failure (" + failures + "/" + MAX_CONSECUTIVE_FAILURES + ")"); + if (failures >= MAX_CONSECUTIVE_FAILURES) { + kickAllPlayers(); + } } } catch (Exception e) { - // Handle exceptions here - e.printStackTrace(); - - System.out.println("API Heartbeat Failed, kicking all players until back online."); - - // Kick all players - ProxyServer.getInstance().getPlayers().forEach(player -> { - player.disconnect("API Heartbeat Failed, the server is temporarily offline."); - }); + int failures = consecutiveFailures.incrementAndGet(); + System.out.println("API Heartbeat error (" + failures + "/" + MAX_CONSECUTIVE_FAILURES + "): " + e.getMessage()); + if (failures >= MAX_CONSECUTIVE_FAILURES) { + System.out.println("API Heartbeat failed " + MAX_CONSECUTIVE_FAILURES + " consecutive times, kicking all players."); + kickAllPlayers(); + } } }, 0, 60, TimeUnit.SECONDS); } + + private static void kickAllPlayers() { + ProxyServer.getInstance().getPlayers().forEach(player -> { + player.disconnect("API Heartbeat Failed, the server is temporarily offline."); + }); + } }