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.");
+ });
+ }
}