diff --git a/src/main/java/net/viper/status/modules/globalchat/GlobalChatModule.java b/src/main/java/net/viper/status/modules/globalchat/GlobalChatModule.java index d65f465..86094d5 100644 --- a/src/main/java/net/viper/status/modules/globalchat/GlobalChatModule.java +++ b/src/main/java/net/viper/status/modules/globalchat/GlobalChatModule.java @@ -1,9 +1,5 @@ package net.viper.status.modules.globalchat; -import net.luckperms.api.LuckPerms; -import net.luckperms.api.LuckPermsProvider; -import net.luckperms.api.model.user.User; -import net.luckperms.api.cacheddata.CachedMetaData; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.ComponentBuilder; @@ -18,11 +14,13 @@ import net.md_5.bungee.api.event.ServerSwitchEvent; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.chat.ComponentSerializer; // Wichtig für JSON +import net.md_5.bungee.chat.ComponentSerializer; import net.md_5.bungee.event.EventHandler; import net.viper.status.module.Module; import java.io.*; +import java.lang.Class; // WICHTIG für Reflection +import java.lang.reflect.Method; // WICHTIG für Reflection import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.text.SimpleDateFormat; @@ -33,30 +31,34 @@ import java.util.regex.Pattern; /** * GlobalChatModule - Integriert Global Chat, Filter, Logs und Support in die StatusAPI. * Nutzt die zentrale verify.properties für Chat- und Server-Einstellungen. - * Sendet Nachrichten via Plugin Channel an Backend-Server, um Signatur-Probleme zu vermeiden. + * Ermöglicht manuelle Ränge über UUID-Overrides. + * Nutzt REFLECTION für LuckPerms (Optional). */ public class GlobalChatModule implements Module, Listener { private static final String CHANNEL_CONTROL = "global:control"; - private static final String CHANNEL_CHAT = "global:chat"; // NEU: Kanal für Chat-Relay + private static final String CHANNEL_CHAT = "global:chat"; private Plugin plugin; private List badWords = new ArrayList<>(); private File logFolder; private boolean chatMuted = false; - private boolean isChatEnabled = true; // Status ob der Chat aktiv ist + private boolean isChatEnabled = true; private final Map playerIsOp = new ConcurrentHashMap<>(); private final Map lastSupportContact = new ConcurrentHashMap<>(); private final Set suppressJoinQuit = ConcurrentHashMap.newKeySet(); - private final Set chatLockPlayers = ConcurrentHashMap.newKeySet(); // Toggle Chat List + private final Set chatLockPlayers = ConcurrentHashMap.newKeySet(); private List welcomeMessages = new ArrayList<>(); private Map serverDisplayNames = new HashMap<>(); - // NEU: Map für gruppen-spezifische Formate aus der verify.properties + // Map für gruppen-spezifische Formate aus der verify.properties private Map groupFormats = new HashMap<>(); + + // NEU: Map für manuelle Ränge aus der verify.properties (Override) + private final Map manualRanks = new ConcurrentHashMap<>(); @Override public String getName() { @@ -83,7 +85,6 @@ public class GlobalChatModule implements Module, Listener { } plugin.getProxy().getPluginManager().registerListener(plugin, this); - loadFilter(); loadWelcomeMessages(); @@ -99,7 +100,7 @@ public class GlobalChatModule implements Module, Listener { plugin.getProxy().getPluginManager().registerCommand(plugin, new InfoCommand()); plugin.getProxy().getPluginManager().registerCommand(plugin, new ChatToggleCommand()); - plugin.getLogger().info("§aGlobalChatModule aktiviert (Relay-Mode aktiv)!"); + plugin.getLogger().info("§aGlobalChatModule aktiviert (Mit Manuellen Rang-Overrides)!"); } @Override @@ -148,26 +149,41 @@ public class GlobalChatModule implements Module, Listener { isChatEnabled = Boolean.parseBoolean(props.getProperty("chat.enabled", "true")); serverDisplayNames.clear(); - // NEU: Gruppen-Formate laden groupFormats.clear(); + manualRanks.clear(); // NEU for (String key : props.stringPropertyNames()) { - if (key.startsWith("server.")) { + // 1. Manuelle Overrides laden (Höchste Priorität!) + if (key.startsWith("override.")) { + String uuidStr = key.substring("override.".length()); + try { + UUID uuid = UUID.fromString(uuidStr); + String groupName = props.getProperty(key); + manualRanks.put(uuid, groupName); + plugin.getLogger().info("Manueller Override geladen: " + uuidStr + " -> " + groupName); + } catch (IllegalArgumentException ex) { + plugin.getLogger().warning("Ungültige UUID in override: " + uuidStr); + } + } + // 2. Server Aliase + else if (key.startsWith("server.")) { String[] parts = key.split("\\."); if (parts.length == 2) { String serverName = parts[1]; String displayName = props.getProperty(key); serverDisplayNames.put(serverName, displayName); } - } else if (key.startsWith("groupformat.")) { - // Format: groupformat.Owner = &c[Owner] || &b || &d + } + // 3. Group Formate + else if (key.startsWith("groupformat.")) { String groupName = key.substring("groupformat.".length()); String format = props.getProperty(key); - groupFormats.put(groupName, format); + groupFormats.put(groupName.toLowerCase(), format); } } plugin.getLogger().info("§eGeladene Server-Displaynames: " + serverDisplayNames.size() + " (Chat aktiv: " + isChatEnabled + ")"); plugin.getLogger().info("§eGeladene Chat-Formate: " + groupFormats.size()); + plugin.getLogger().info("§eGeladene Manuelle Overrides: " + manualRanks.size()); } catch (IOException e) { e.printStackTrace(); } @@ -220,12 +236,10 @@ public class GlobalChatModule implements Module, Listener { private void sendRandomWelcomeMessage(ProxiedPlayer player) { if (welcomeMessages.isEmpty()) return; - Random rand = new Random(); String message = welcomeMessages.get(rand.nextInt(welcomeMessages.size())); message = message.replace("%player%", player.getName()); message = ChatColor.translateAlternateColorCodes('&', message); - player.sendMessage(new TextComponent(message)); } @@ -239,75 +253,61 @@ public class GlobalChatModule implements Module, Listener { ProxiedPlayer player = (ProxiedPlayer) e.getSender(); - // ==================== CHAT LOCK / TOGGLE CHECK ==================== + // CHAT LOCK / TOGGLE CHECK if (chatLockPlayers.contains(player.getUniqueId())) { - return; // Nachricht geht direkt zum Server (z.B. für Lands/Quests) + return; } - // ======================================================================== String originalMsg = e.getMessage(); if (suppressJoinQuit.contains(player.getUniqueId()) && - (originalMsg.contains("joined the Game") || originalMsg.contains("left the Game"))) { + (originalMsg.contains("joined§ Game") || originalMsg.contains("left§ Game"))) { plugin.getLogger().info("Unterdrücke Join-/Quit-Nachricht für " + player.getName()); e.setCancelled(true); return; } - // Globaler Mute if (chatMuted && !player.hasPermission("globalchat.bypass")) { player.sendMessage(new TextComponent("§cDer globale Chat ist derzeit deaktiviert!")); e.setCancelled(true); return; } - // Badword-Zensur String censoredMsg = originalMsg; for (String bad : badWords) { if (bad == null || bad.trim().isEmpty()) continue; censoredMsg = censoredMsg.replaceAll("(?i)" + Pattern.quote(bad), repeat("*", bad.length())); } - // Event canceln (Wichtig, damit nicht doppelt angezeigt wird) e.setCancelled(true); String serverName = player.getServer().getInfo().getName(); String serverDisplay = getDisplayName(serverName); - // Index 0: Prefix (Rang), Index 1: Suffix, Index 2: Spielername Farbe, Index 3: Chat Farbe String[] ps = getPrefixSuffix(player); String prefix = ps[0]; String playerColor = ps[2]; String chatColor = ps[3]; String displayTag = ""; - if (!prefix.isEmpty()) { - displayTag = prefix; - } else if (!ps[1].isEmpty()) { - displayTag = ps[1]; - } + if (!prefix.isEmpty()) displayTag = prefix; + else if (!ps[1].isEmpty()) displayTag = ps[1]; if (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " "; StringBuilder out = new StringBuilder(); - out.append("§7[").append(serverDisplay).append("§r§7] "); // FIX: Reset vor der Klammer + out.append("§7[").append(serverDisplay).append("§r§7] "); if (!displayTag.isEmpty()) out.append(displayTag); - - // Spielername mit eigener Farbe out.append(playerColor).append(player.getName()); - - out.append("§f: ").append(chatColor).append(censoredMsg); // Chatnachricht mit eigener Farbe + out.append("§f: ").append(chatColor).append(censoredMsg); String chatOut = out.toString(); - // NEU: Nachricht erstellen und an alle Server senden TextComponent chatComponent = new TextComponent(chatOut); String jsonMessage = ComponentSerializer.toString(chatComponent); for (ServerInfo server : plugin.getProxy().getServers().values()) { - // Wir senden nur an Server, die Spieler haben (Performance) if (server.getPlayers().isEmpty()) continue; - try { server.sendData(CHANNEL_CHAT, jsonMessage.getBytes(StandardCharsets.UTF_8)); } catch (Exception ex) { @@ -315,7 +315,6 @@ public class GlobalChatModule implements Module, Listener { } } - // Loggen (lokal auf Bungee) String logEntry = "[" + serverName + "] " + (displayTag.isEmpty() ? "" : stripColor(displayTag) + " ") + player.getName() + @@ -341,14 +340,12 @@ public class GlobalChatModule implements Module, Listener { try { sendSuppressJoinQuit(from, player.getUniqueId()); - plugin.getLogger().info("Sent suppress quit message for " + player.getName() + " to server " + from.getName()); } catch (Throwable ex) { plugin.getLogger().warning("Fehler beim Senden der Quit-Unterdrückung: " + ex.getMessage()); } try { sendSuppressJoinQuit(target, player.getUniqueId()); - plugin.getLogger().info("Sent suppress join message for " + player.getName() + " to server " + target.getName()); } catch (Throwable ex) { plugin.getLogger().warning("Fehler beim Senden der Join-Unterdrückung: " + ex.getMessage()); } @@ -370,11 +367,9 @@ public class GlobalChatModule implements Module, Listener { String fromName = from.getName(); String fromDisplay = getDisplayName(fromName); - String toName = to.getName(); String toDisplay = getDisplayName(toName); - // Hier nutzen wir nur das Prefix (Index 0), nicht die Chatfarbe oder Spielerfarbe String[] ps = getPrefixSuffix(player); String prefix = ps[0]; String suffix = ps[1]; @@ -386,18 +381,14 @@ public class GlobalChatModule implements Module, Listener { if (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " "; StringBuilder msg = new StringBuilder(); - msg.append("§7[").append(toDisplay).append("§r§7] "); // FIX: Reset vor der Klammer + msg.append("§7[").append(toDisplay).append("§r§7] "); if (!displayTag.isEmpty()) msg.append(displayTag); - - // Switch Nachricht: Spielername weiß oder Standard msg.append(player.getName()); - msg.append(" §7hat den Server gewechselt: §e") .append(fromDisplay).append(" §7→ §e").append(toDisplay).append("§7."); String finalMsg = msg.toString(); - // NEU: Switch Nachricht auch über Relay senden TextComponent switchComponent = new TextComponent(finalMsg); String jsonMessage = ComponentSerializer.toString(switchComponent); @@ -435,79 +426,54 @@ public class GlobalChatModule implements Module, Listener { } // =========================== - // Prefix/Suffix via LuckPerms & Properties (ROBUST + CHAT COLOR + PLAYER COLOR) + // Prefix/Suffix (Logic: Manual > LP > Perm) // =========================== private String[] getPrefixSuffix(ProxiedPlayer player) { String prefix = ""; String suffix = ""; - String playerColor = "§f"; // Standard Weiß für Spielername - String chatColor = "§f"; // Standard Weiß für Chat - - // Standard Gruppe falls nichts gefunden wird + String playerColor = "§f"; + String chatColor = "§f"; String groupName = "Spieler"; - // 1. Versuch: Gruppe von LuckPerms holen - try { - LuckPerms lp = LuckPermsProvider.get(); - if (lp != null) { - User user = lp.getUserManager().getUser(player.getUniqueId()); - if (user == null) { - try { - // User synchron laden - user = lp.getUserManager().loadUser(player.getUniqueId()).join(); - } catch (Exception ignored) {} - } - if (user != null) { - String lpGroup = user.getPrimaryGroup(); - if (lpGroup != null && !lpGroup.isEmpty() && !lpGroup.equalsIgnoreCase("default")) { - groupName = lpGroup; - } - } + // 0. NEU: HÖCHSTE PRIO - Manueller Override aus verify.properties + if (manualRanks.containsKey(player.getUniqueId())) { + groupName = manualRanks.get(player.getUniqueId()); + // plugin.getLogger().info("Nutze manuellen Override für " + player.getName() + ": " + groupName); + } + // 1. LuckPerms via Reflection (Bungee) + else { + String lpGroup = getLuckPermsGroup(player); + if (lpGroup != null && !lpGroup.isEmpty() && !lpGroup.equalsIgnoreCase("default")) { + groupName = lpGroup; } - } catch (Throwable ignored) {} + } - // 2. FALLBACK: Permission-Check (Funktioniert auch OHNE LuckPerms Datenbank) + // 2. FALLBACK: Permission-Check if (groupName == null || groupName.equalsIgnoreCase("default") || groupName.equalsIgnoreCase("Spieler")) { if (player.hasPermission("group.owner")) groupName = "Owner"; else if (player.hasPermission("group.admin")) groupName = "Admin"; else if (player.hasPermission("group.developer")) groupName = "Developer"; else if (player.hasPermission("group.premium")) groupName = "Premium"; - else if (player.hasPermission("group.spieler")) groupName = "Spieler"; else groupName = "Spieler"; } - // 3. Farben aus verify.properties anwenden (Höchste Priorität) - if (groupName != null && groupFormats.containsKey(groupName)) { - String rawFormat = groupFormats.get(groupName); + // 3. Farben aus verify.properties anwenden + if (groupName != null && groupFormats.containsKey(groupName.toLowerCase())) { + String rawFormat = groupFormats.get(groupName.toLowerCase()); - // NEU: Check auf " || " für Spielername Farbe UND Chat Farbe if (rawFormat.contains(" || ")) { String[] parts = rawFormat.split(" \\|\\| "); - // Syntax: Prefix || PlayerColor || ChatColor - - // Teil 1: Prefix (Rang) prefix = ChatColor.translateAlternateColorCodes('&', parts[0]); - - // Teil 2: Spieler Name Farbe playerColor = ChatColor.translateAlternateColorCodes('&', parts[1]); - - // Teil 3: Chat Farbe chatColor = ChatColor.translateAlternateColorCodes('&', parts[2]); - suffix = ""; - } - // ALTER FALLBACK (Kompatibilität): Check auf ": " - else if (rawFormat.contains(": ")) { + } else if (rawFormat.contains(": ")) { String[] parts = rawFormat.split(": ", 2); - // Teil 1: Prefix prefix = ChatColor.translateAlternateColorCodes('&', parts[0]); - // Teil 2: Chat Farbe chatColor = ChatColor.translateAlternateColorCodes('&', parts[1]); - // Spielername bleibt Standard Weiß playerColor = "§f"; suffix = ""; } else { - // Kein Separator gefunden -> Nur Prefix prefix = ChatColor.translateAlternateColorCodes('&', rawFormat); suffix = ""; playerColor = "§f"; @@ -517,21 +483,29 @@ public class GlobalChatModule implements Module, Listener { return new String[]{prefix, suffix, playerColor, chatColor}; } - // 4. Wenn nichts in Properties gefunden wurde, Fallback auf LuckPerms Meta + // 4. LuckPerms Meta Fallback (Nur falls kein Config-Format da war) try { - LuckPerms lp = LuckPermsProvider.get(); - if (lp != null) { - User user = lp.getUserManager().getUser(player.getUniqueId()); - if (user == null) { - try { user = lp.getUserManager().loadUser(player.getUniqueId()).join(); } catch (Exception ignored) {} - } + Class providerClass = Class.forName("net.luckperms.api.LuckPermsProvider"); + Method getMethod = providerClass.getMethod("get"); + Object api = getMethod.invoke(null); + if (api != null) { + Method getUserManagerMethod = api.getClass().getMethod("getUserManager"); + Object userManager = getUserManagerMethod.invoke(api); + Method loadUserMethod = userManager.getClass().getMethod("loadUser", UUID.class); + Object userFuture = loadUserMethod.invoke(userManager, player.getUniqueId()); + Method joinMethod = userFuture.getClass().getMethod("join"); + Object user = joinMethod.invoke(userFuture); if (user != null) { - CachedMetaData meta = user.getCachedData().getMetaData(); + Class metaClass = Class.forName("net.luckperms.api.cacheddata.CachedMetaData"); + Method getMetaDataMethod = user.getClass().getMethod("getCachedData"); + Object meta = getMetaDataMethod.invoke(user); if (meta != null) { - String p = meta.getPrefix(); - String s = meta.getSuffix(); - if (p != null) prefix = ChatColor.translateAlternateColorCodes('&', p); - if (s != null) suffix = ChatColor.translateAlternateColorCodes('&', s); + Method getPrefixMethod = metaClass.getMethod("getPrefix"); + Method getSuffixMethod = metaClass.getMethod("getSuffix"); + Object p = getPrefixMethod.invoke(meta); + Object s = getSuffixMethod.invoke(meta); + if (p != null) prefix = ChatColor.translateAlternateColorCodes('&', p.toString()); + if (s != null) suffix = ChatColor.translateAlternateColorCodes('&', s.toString()); } } } @@ -543,14 +517,40 @@ public class GlobalChatModule implements Module, Listener { return new String[]{prefix, suffix, playerColor, chatColor}; } + // HELPER: LuckPerms Group via Reflection + private String getLuckPermsGroup(ProxiedPlayer player) { + try { + if (plugin.getProxy().getPluginManager().getPlugin("LuckPerms") == null) return null; + + Class providerClass = Class.forName("net.luckperms.api.LuckPermsProvider"); + Method getMethod = providerClass.getMethod("get"); + Object api = getMethod.invoke(null); + + if (api != null) { + Method getUserManagerMethod = api.getClass().getMethod("getUserManager"); + Object userManager = getUserManagerMethod.invoke(api); + Method loadUserMethod = userManager.getClass().getMethod("loadUser", UUID.class); + Object userFuture = loadUserMethod.invoke(userManager, player.getUniqueId()); + Method joinMethod = userFuture.getClass().getMethod("join"); + Object user = joinMethod.invoke(userFuture); + if (user != null) { + Method getPrimaryGroupMethod = user.getClass().getMethod("getPrimaryGroup"); + return (String) getPrimaryGroupMethod.invoke(user); + } + } + } catch (ClassNotFoundException e) { + return null; + } catch (Exception e) { + plugin.getLogger().warning("Fehler beim Auslesen von LuckPerms (Reflection): " + e.getMessage()); + } + return null; + } + private String stripColor(String s) { if (s == null) return ""; return ChatColor.stripColor(s); } - // =========================== - // Filter einlesen - // =========================== private void loadFilter() { String fileName = "filter.yml"; File file = new File(plugin.getDataFolder(), fileName); @@ -588,9 +588,6 @@ public class GlobalChatModule implements Module, Listener { } } - // =========================== - // Logs aufräumen / schreiben - // =========================== private void cleanupOldLogs() { File[] files = logFolder.listFiles(); if (files == null) return; @@ -618,9 +615,6 @@ public class GlobalChatModule implements Module, Listener { } } - // =========================== - // Staff-Check - // =========================== private boolean isStaff(ProxiedPlayer p) { if (p == null) return false; Boolean reportedOp = playerIsOp.get(p.getUniqueId()); @@ -685,7 +679,6 @@ public class GlobalChatModule implements Module, Listener { String serverRaw = player.getServer().getInfo().getName(); String serverDisplay = getDisplayName(serverRaw); - // Support-Nachricht geht nur ans Team, nicht über den Global Chat Relay TextComponent supportMsg = new TextComponent("§7[Support] §b" + player.getName() + " §7vom Server §e" + serverDisplay + " §7: §f" + msg); supportMsg.setHoverEvent(new HoverEvent(Action.SHOW_TEXT, new ComponentBuilder("Klicke, um /reply " + player.getName() + " zu schreiben").create())); supportMsg.setClickEvent(new net.md_5.bungee.api.chat.ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.SUGGEST_COMMAND, "/reply " + player.getName() + " "));