diff --git a/src/main/java/de/viper/globalchat/GlobalChat.java b/src/main/java/de/viper/globalchat/GlobalChat.java index 67f255a..daac6d9 100644 --- a/src/main/java/de/viper/globalchat/GlobalChat.java +++ b/src/main/java/de/viper/globalchat/GlobalChat.java @@ -1,673 +1,803 @@ -package de.viper.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; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.chat.HoverEvent.Action; -import net.md_5.bungee.api.config.ServerInfo; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.event.ChatEvent; -import net.md_5.bungee.api.event.ServerConnectEvent; -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; -import net.md_5.bungee.config.Configuration; -import net.md_5.bungee.config.ConfigurationProvider; -import net.md_5.bungee.config.YamlConfiguration; -import net.md_5.bungee.event.EventHandler; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.*; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; - -/** - * GlobalChat - Bungee Plugin - * - Filter (filter.yml) - * - Logs (plugins/GlobalChat/logs/YYYY-MM-DD.log) - * - /support, /reply, /info, /togglechat - * - Prefix/Suffix via LuckPerms (Proxy-side) - * - Server Aliase & Farben (via bungee.yml) - * - ServerSwitch announcement - * - Relay Mode: Sendet JSON an Spigot Server, um Secure-Chat Probleme zu vermeiden. - */ -public class GlobalChat extends Plugin implements Listener { - - private static final String CHANNEL_CONTROL = "global:control"; - private static final String CHANNEL_CHAT = "global:chat"; - - private List badWords = new ArrayList<>(); - private File logFolder; - private boolean chatMuted = false; - - // NEU: Config Handling für BungeeCord (Server Aliase) - private File configFile; - private Configuration config; - private Map serverAliases = new HashMap<>(); - - // Optional: falls Spigot-Server OP-Info per PluginMessage sendet - private final Map playerIsOp = new ConcurrentHashMap<>(); - // Letzte Support-Kontakte (staff UUID -> target UUID) - private final Map lastSupportContact = new ConcurrentHashMap<>(); - // Tracking für unterdrückte Join-/Quit-Nachrichten - private final Set suppressJoinQuit = ConcurrentHashMap.newKeySet(); - - // NEU: Liste für Spieler, die Toggle-Chat aktiviert haben - private final Set chatLockPlayers = ConcurrentHashMap.newKeySet(); - - // =========================== - // Neu: Welcome-Nachrichten - // =========================== - private List welcomeMessages = new ArrayList<>(); - - private void loadWelcomeMessages() { - File file = new File(getDataFolder(), "welcome.yml"); - if (!file.exists()) { - try { - getDataFolder().mkdirs(); - file.createNewFile(); - try (PrintWriter out = new PrintWriter(file)) { - out.println("welcome-messages:"); - out.println(" - \"&aWillkommen, %player%! Viel Spaß auf unserem Server!\""); - out.println(" - \"&aHey %player%, schön dich hier zu sehen! Los geht's!\""); - out.println(" - \"&a%player%, dein Abenteuer beginnt jetzt! Viel Spaß!\""); - out.println(" - \"&aWillkommen an Bord, %player%! Entdecke den Server!\""); - out.println(" - \"&a%player%, herzlich willkommen! Lass uns loslegen!\""); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - try { - List lines = Files.readAllLines(file.toPath()); - welcomeMessages.clear(); - for (String line : lines) { - line = line.trim(); - if (line.startsWith("-")) welcomeMessages.add(line.substring(1).trim()); - } - getLogger().info("§eGeladene Welcome-Nachrichten: " + welcomeMessages.size()); - } catch (IOException e) { - e.printStackTrace(); - } - } - - 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)); - } - - @Override - public void onEnable() { - // Plugin channels registrieren - try { - getProxy().registerChannel(CHANNEL_CONTROL); - getProxy().registerChannel(CHANNEL_CHAT); - } catch (Throwable ignored) { - getLogger().warning("Konnte Channels nicht registrieren."); - } - - // Konfiguration initialisieren - if (!getDataFolder().exists()) getDataFolder().mkdirs(); - configFile = new File(getDataFolder(), "bungee.yml"); - - // Standard-Konfiguration kopieren, falls nicht vorhanden - if (!configFile.exists()) { - try (InputStream in = getResourceAsStream("bungee.yml")) { - Files.copy(in, configFile.toPath()); - } catch (IOException e) { - e.printStackTrace(); - } - } - loadConfigData(); - - getProxy().getPluginManager().registerListener(this, this); - - loadFilter(); - loadWelcomeMessages(); - loadServerAliases(); // NEU - - logFolder = new File(getDataFolder(), "logs"); - if (!logFolder.exists()) logFolder.mkdirs(); - cleanupOldLogs(); - - // Befehle registrieren - getProxy().getPluginManager().registerCommand(this, new ReloadCommand()); - getProxy().getPluginManager().registerCommand(this, new MuteCommand()); - getProxy().getPluginManager().registerCommand(this, new SupportCommand()); - getProxy().getPluginManager().registerCommand(this, new ReplyCommand()); - getProxy().getPluginManager().registerCommand(this, new InfoCommand()); - getProxy().getPluginManager().registerCommand(this, new ChatToggleCommand()); - - getLogger().info("§aGlobalChat aktiviert (Relay-Mode, ToggleChat, Zensur, Logs, Support, ServerAliase)!"); - } - - @Override - public void onDisable() { - getLogger().info("§cGlobalChat deaktiviert!"); - try { - getProxy().unregisterChannel(CHANNEL_CONTROL); - getProxy().unregisterChannel(CHANNEL_CHAT); - } catch (Throwable ignored) { - getLogger().warning("Konnte Channels nicht deregistrieren."); - } - } - - // =========================== - // Konfiguration & Aliase - // =========================== - private void loadConfigData() { - try { - config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private void loadServerAliases() { - loadConfigData(); // Sichert, dass aktuelle Daten geladen sind (für Reload) - - if (config == null) return; - - if (config.getSection("server-aliases") == null) { - return; - } - serverAliases.clear(); - for (String key : config.getSection("server-aliases").getKeys()) { - serverAliases.put(key, config.getString("server-aliases." + key)); - } - getLogger().info("§eGeladene Server-Aliase: " + serverAliases.size()); - } - - private String getServerDisplayName(String rawServerName) { - if (serverAliases.containsKey(rawServerName)) { - return ChatColor.translateAlternateColorCodes('&', serverAliases.get(rawServerName)); - } - return rawServerName; // Fallback - } - - // =========================== - // Chatfilter & Global-Chat (RELAY MODE) - // =========================== - @EventHandler - public void onChat(ChatEvent e) { - if (!(e.getSender() instanceof ProxiedPlayer)) return; - if (e.isCommand()) return; - - ProxiedPlayer player = (ProxiedPlayer) e.getSender(); - String originalMsg = e.getMessage(); - - // ==================== CHAT LOCK / TOGGLE CHECK ==================== - if (chatLockPlayers.contains(player.getUniqueId())) { - // Chat ist deaktiviert: Nachricht geht direkt zum Server (für Lands/Quests) - return; - } - // ======================================================================== - - // Optional: Debugging (auskommentiert für performance) - // getLogger().info("ChatEvent: Spieler=" + player.getName() + ", Nachricht=" + originalMsg); - - // Versuche, Join-/Quit-Nachrichten zu filtern - if (suppressJoinQuit.contains(player.getUniqueId()) && - (originalMsg.contains("joined the Game") || originalMsg.contains("left the Game"))) { - getLogger().info("Unterdrücke Join-/Quit-Nachricht für " + player.getName() + ": " + originalMsg); - 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())); - } - - e.setCancelled(true); - - String serverName = player.getServer().getInfo().getName(); - String serverDisplay = getServerDisplayName(serverName); // NEU: Alias nutzen - - // Prefix/Suffix holen - String[] ps = getPrefixSuffix(player); - String prefix = ps[0] == null ? "" : ps[0].trim(); - String suffix = ps[1] == null ? "" : ps[1].trim(); - - String displayTag = ""; - if (!prefix.isEmpty()) { - displayTag = prefix; - } else if (!suffix.isEmpty()) { - displayTag = suffix; - } - - if (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " "; - - // Formatieren - StringBuilder out = new StringBuilder(); - out.append("§7[").append(serverDisplay).append("] "); // NEU: serverDisplay - if (!displayTag.isEmpty()) out.append(displayTag); - out.append(player.getName()); - out.append("§f: ").append(censoredMsg); - - String chatOut = out.toString(); - - // NEU: Nachricht als JSON an alle Server senden (Relay) - TextComponent chatComponent = new TextComponent(chatOut); - String jsonMessage = ComponentSerializer.toString(chatComponent); - byte[] data = jsonMessage.getBytes(StandardCharsets.UTF_8); - - for (ServerInfo server : getProxy().getServers().values()) { - if (server.getPlayers().isEmpty()) continue; // Optimierung - try { - server.sendData(CHANNEL_CHAT, data); - } catch (Exception ex) { - getLogger().warning("Konnte Chat-Nachricht nicht an " + server.getName() + " senden."); - } - } - - // Loggen - String logEntry = "[" + serverName + "] " + - (displayTag.isEmpty() ? "" : stripColor(displayTag) + " ") + - player.getName() + - ": " + originalMsg; - logMessage(logEntry); - } - - // =========================== - // Server connect (pre-switch suppression) - // =========================== - @EventHandler - public void onServerConnect(ServerConnectEvent e) { - if (e.isCancelled()) return; - - ProxiedPlayer player = e.getPlayer(); - ServerInfo target = e.getTarget(); - ServerInfo from = player.getServer() != null ? player.getServer().getInfo() : null; - - if (from == null || from.equals(target)) return; - - suppressJoinQuit.add(player.getUniqueId()); - getLogger().info("Markiert " + player.getName() + " für Join-/Quit-Unterdrückung"); - - try { - sendSuppressJoinQuit(from, player.getUniqueId()); - getLogger().info("Sent suppress quit message for " + player.getName() + " to server " + from.getName()); - } catch (Throwable ex) { - getLogger().warning("Fehler beim Senden der Quit-Unterdrückung an " + from.getName() + ": " + ex.getMessage()); - } - - try { - sendSuppressJoinQuit(target, player.getUniqueId()); - getLogger().info("Sent suppress join message for " + player.getName() + " to server " + target.getName()); - } catch (Throwable ex) { - getLogger().warning("Fehler beim Senden der Join-Unterdrückung an " + target.getName() + ": " + ex.getMessage()); - } - - getProxy().getScheduler().schedule(this, () -> { - suppressJoinQuit.remove(player.getUniqueId()); - getLogger().info("Entfernte Unterdrückung für " + player.getName()); - }, 2, java.util.concurrent.TimeUnit.SECONDS); - } - - // =========================== - // Server switch announcement (RELAY MODE) - // =========================== - @EventHandler - public void onServerSwitch(ServerSwitchEvent e) { - ProxiedPlayer player = e.getPlayer(); - ServerInfo from = e.getFrom(); - ServerInfo to = player.getServer() != null ? player.getServer().getInfo() : null; - - if (to == null) return; - if (from == null) return; // Erster Join - - if (from.getName().equalsIgnoreCase(to.getName())) return; - - String fromName = from.getName(); - String toName = to.getName(); - - // NEU: Aliase nutzen - String fromDisplay = getServerDisplayName(fromName); - String toDisplay = getServerDisplayName(toName); - - String[] ps = getPrefixSuffix(player); - String prefix = ps[0] == null ? "" : ps[0].trim(); - String suffix = ps[1] == null ? "" : ps[1].trim(); - - String displayTag = ""; - if (!prefix.isEmpty()) displayTag = prefix; - else if (!suffix.isEmpty()) displayTag = suffix; - - if (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " "; - - StringBuilder msg = new StringBuilder(); - msg.append("§7[").append(toDisplay).append("] "); // NEU: toDisplay - if (!displayTag.isEmpty()) msg.append(displayTag); - msg.append(player.getName()); - msg.append(" §7hat den Server gewechselt: §e") - .append(fromDisplay).append(" §7→ §e").append(toDisplay).append("§7."); // NEU: Displays - - String finalMsg = msg.toString(); - - // NEU: Switch Nachricht auch als JSON relays - TextComponent switchComponent = new TextComponent(finalMsg); - String jsonMessage = ComponentSerializer.toString(switchComponent); - byte[] data = jsonMessage.getBytes(StandardCharsets.UTF_8); - - for (ServerInfo server : getProxy().getServers().values()) { - if (server.getPlayers().isEmpty()) continue; - try { - server.sendData(CHANNEL_CHAT, data); - } catch (Exception ex) { - getLogger().warning("Konnte Switch-Nachricht nicht senden."); - } - } - - String logEntry = "[" + toName + "] " + - (displayTag.isEmpty() ? "" : stripColor(displayTag) + " ") + - player.getName() + " hat den Server gewechselt: " + fromName + " -> " + toName + "."; - logMessage(logEntry); - } - - private void sendSuppressJoinQuit(ServerInfo server, UUID playerId) { - if (server == null) return; - try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(baos)) { - out.writeUTF("suppress"); - out.writeUTF(playerId.toString()); - server.sendData(CHANNEL_CONTROL, baos.toByteArray()); - } catch (IOException ex) { - getLogger().warning("Fehler beim Senden der suppress-Nachricht an " + server.getName() + ": " + ex.getMessage()); - } - } - - private String repeat(String str, int count) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < count; i++) sb.append(str); - return sb.toString(); - } - - // =========================== - // Prefix/Suffix via LuckPerms - // =========================== - private String[] getPrefixSuffix(ProxiedPlayer player) { - String prefix = ""; - String suffix = ""; - - 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) { - user = null; - } - } - - if (user != null) { - CachedMetaData meta = user.getCachedData().getMetaData(); - if (meta != null) { - String p = meta.getPrefix(); - String s = meta.getSuffix(); - if (p != null) prefix = p; - if (s != null) suffix = s; - } - } - } - } catch (Throwable ignored) { - } - - if (prefix != null && !prefix.isEmpty()) prefix = ChatColor.translateAlternateColorCodes('&', prefix); - if (suffix != null && !suffix.isEmpty()) suffix = ChatColor.translateAlternateColorCodes('&', suffix); - - if (prefix == null) prefix = ""; - if (suffix == null) suffix = ""; - - return new String[]{prefix, suffix}; - } - - private String stripColor(String s) { - if (s == null) return ""; - return ChatColor.stripColor(s); - } - - // =========================== - // Filter einlesen - // =========================== - private void loadFilter() { - File file = new File(getDataFolder(), "filter.yml"); - if (!file.exists()) { - try { - getDataFolder().mkdirs(); - file.createNewFile(); - try (PrintWriter out = new PrintWriter(file)) { - out.println("badwords:"); - out.println(" - arsch"); - out.println(" - hurensohn"); - out.println(" - scheiße"); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - try { - List lines = Files.readAllLines(file.toPath()); - badWords.clear(); - for (String line : lines) { - line = line.trim(); - if (line.startsWith("-")) badWords.add(line.substring(1).trim()); - } - getLogger().info("§eGeladene Filter-Wörter: " + badWords.size()); - } catch (IOException e) { - e.printStackTrace(); - } - } - - // =========================== - // Logs aufräumen / schreiben - // =========================== - private void cleanupOldLogs() { - File[] files = logFolder.listFiles(); - if (files == null) return; - - long now = System.currentTimeMillis(); - long sevenDays = 1000L * 60 * 60 * 24 * 7; - - for (File f : files) { - if (now - f.lastModified() > sevenDays) { - f.delete(); - } - } - } - - private void logMessage(String message) { - String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); - File logFile = new File(logFolder, date + ".log"); - - try (BufferedWriter bw = new BufferedWriter(new FileWriter(logFile, true))) { - String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); - bw.write("[" + time + "] " + message); - bw.newLine(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private boolean isStaff(ProxiedPlayer p) { - if (p == null) return false; - Boolean reportedOp = playerIsOp.get(p.getUniqueId()); - if (reportedOp != null && reportedOp) return true; - - if (p.hasPermission("team")) return true; - if (p.hasPermission("bungeecord.admin")) return true; - if (p.hasPermission("globalchat.op")) return true; - if (p.hasPermission("*")) return true; - - if (p.hasPermission("bungeecord.command.alert")) return true; - if (p.hasPermission("bungeecord.command.reload")) return true; - if (p.hasPermission("bungeecord.command.kick")) return true; - if (p.hasPermission("bungeecord.command.send")) return true; - if (p.hasPermission("bungeecord.command.perms")) return true; - - return false; - } - - // =========================== - // Commands - // =========================== - public class ReloadCommand extends Command { - public ReloadCommand() { super("globalreload", "globalchat.reload"); } - @Override - public void execute(CommandSender sender, String[] args) { - loadFilter(); - loadServerAliases(); // NEU: Aliase neu laden - sender.sendMessage(new TextComponent("§aFilter und Server-Aliase wurden neu geladen!")); - } - } - - public class MuteCommand extends Command { - public MuteCommand() { super("globalmute", "globalchat.mute"); } - @Override - public void execute(CommandSender sender, String[] args) { - chatMuted = !chatMuted; - String status = chatMuted ? "§caktiviert" : "§aaufgehoben"; - for (ProxiedPlayer p : getProxy().getPlayers()) { - p.sendMessage(new TextComponent("§7[GlobalChat] §eDer globale Chat Mute wurde " + status + "§e!")); - } - getLogger().info("GlobalMute wurde " + (chatMuted ? "aktiviert" : "deaktiviert") + "."); - } - } - - public class SupportCommand extends Command { - public SupportCommand() { super("support"); } - @Override - public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { - sender.sendMessage(new TextComponent("§cNur Spieler können Support-Nachrichten senden.")); - return; - } - - ProxiedPlayer player = (ProxiedPlayer) sender; - if (args.length == 0) { - player.sendMessage(new TextComponent("§cBitte eine Nachricht angeben: /support ")); - return; - } - - String msg = String.join(" ", args); - - String serverName = player.getServer().getInfo().getName(); - String serverDisplay = getServerDisplayName(serverName); // NEU: Alias - - TextComponent supportMsg = new TextComponent("§7[Support] §b" + player.getName() + " §7vom Server §e" + serverDisplay + " §7: §f" + msg); // NEU: Display - 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() + " ")); - - for (ProxiedPlayer p : getProxy().getPlayers()) { - if (isStaff(p)) { - p.sendMessage(supportMsg); - lastSupportContact.put(p.getUniqueId(), player.getUniqueId()); - } - } - - player.sendMessage(new TextComponent("§aDeine Support-Nachricht wurde gesendet.")); - logMessage("[Support][" + serverName + "] " + player.getName() + ": " + msg); - } - } - - public class ReplyCommand extends Command { - public ReplyCommand() { super("reply"); } - @Override - public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) return; - - ProxiedPlayer staff = (ProxiedPlayer) sender; - UUID targetId = lastSupportContact.get(staff.getUniqueId()); - if (targetId == null) { - staff.sendMessage(new TextComponent("§cKein Spieler zum Antworten gefunden.")); - return; - } - - if (args.length == 0) { - staff.sendMessage(new TextComponent("§cBitte eine Nachricht angeben.")); - return; - } - - ProxiedPlayer target = getProxy().getPlayer(targetId); - if (target == null) { - staff.sendMessage(new TextComponent("§cSpieler ist nicht online.")); - return; - } - - String msg = String.join(" ", args); - target.sendMessage(new TextComponent("§7[Reply von §b" + staff.getName() + "§7]: §f" + msg)); - staff.sendMessage(new TextComponent("§aDeine Nachricht wurde an §b" + target.getName() + "§a gesendet.")); - } - } - - public class InfoCommand extends Command { - public InfoCommand() { super("info"); } - @Override - public void execute(CommandSender sender, String[] args) { - sender.sendMessage(new TextComponent("§8§m------------------------------")); - sender.sendMessage(new TextComponent("§6§lGlobalChat Info")); - sender.sendMessage(new TextComponent("§ePlugin-Name: §b" + getDescription().getName())); - sender.sendMessage(new TextComponent("§eVersion: §b" + getDescription().getVersion())); - sender.sendMessage(new TextComponent("§eErsteller: §bM_Viper")); - sender.sendMessage(new TextComponent("§8§m------------------------------")); - } - } - - public class ChatToggleCommand extends Command { - public ChatToggleCommand() { super("togglechat"); } - - @Override - public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { - sender.sendMessage(new TextComponent("§cNur Spieler können den Chat-Modus ändern.")); - return; - } - - ProxiedPlayer player = (ProxiedPlayer) sender; - UUID uuid = player.getUniqueId(); - - if (chatLockPlayers.contains(uuid)) { - chatLockPlayers.remove(uuid); - player.sendMessage(new TextComponent("§aGlobalChat aktiviert.")); - player.sendMessage(new TextComponent("§7Deine Nachrichten werden nun wieder global gesendet.")); - } else { - chatLockPlayers.add(uuid); - player.sendMessage(new TextComponent("§cGlobalChat deaktiviert.")); - player.sendMessage(new TextComponent("§7Deine Nachrichten gehen nur noch an den Server (z.B. für Lands oder Quests).")); - player.sendMessage(new TextComponent("§7Nutze erneut /togglechat, um den globalen Chat zu aktivieren.")); - } - } - } +package de.viper.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; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.HoverEvent.Action; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.ChatEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; +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; +import net.md_5.bungee.event.EventHandler; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.ConfigurationProvider; +import net.md_5.bungee.config.YamlConfiguration; + +import java.io.*; +import java.lang.Class; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +/** + * GlobalChat - Bungee Plugin + */ +public class GlobalChat extends Plugin implements Listener { + + private static final String CHANNEL_CONTROL = "global:control"; + private static final String CHANNEL_CHAT = "global:chat"; // Kanal für Chat-Relay + + private List badWords = new ArrayList<>(); + private File logFolder; + private boolean chatMuted = false; + + // Config Handling für BungeeCord + private File configFile; + private Configuration config; + private Map serverAliases = new HashMap<>(); + + // Map für gruppen-spezifische Formate aus der bungee.yml + private Map groupFormats = new HashMap<>(); + + // Map für manuelle Ränge (UUID Override) aus der bungee.yml + private final Map manualRanks = new ConcurrentHashMap<>(); + + // Optional: falls Spigot-Server OP-Info per PluginMessage sendet + private final Map playerIsOp = new ConcurrentHashMap<>(); + // Letzte Support-Kontakte (staff UUID -> target UUID) + private final Map lastSupportContact = new ConcurrentHashMap<>(); + // Tracking für unterdrückte Join-/Quit-Nachrichten + private final Set suppressJoinQuit = ConcurrentHashMap.newKeySet(); + + // Liste für Spieler, die Toggle-Chat aktiviert haben + private final Set chatLockPlayers = ConcurrentHashMap.newKeySet(); + + // =========================== + // Neu: Welcome-Nachrichten + // =========================== + private List welcomeMessages = new ArrayList<>(); + + private void loadWelcomeMessages() { + String fileName = "welcome.yml"; + File file = new File(getDataFolder(), fileName); + + if (!file.exists()) { + try { + getDataFolder().mkdirs(); + file.createNewFile(); + try (PrintWriter out = new PrintWriter(file)) { + out.println("welcome-messages:"); + out.println(" - \"&aWillkommen, %player%! Viel Spaß auf unserem Server!\""); + out.println(" - \"&aHey %player%, schön dich hier zu sehen! Los geht's!\""); + out.println(" - \"&a%player%, dein Abenteuer beginnt jetzt! Viel Spaß!\""); + out.println(" - \"&aWillkommen an Bord, %player%! Entdecke den Server!\""); + out.println(" - \"&a%player%, herzlich willkommen! Lass uns loslegen!\""); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + try { + List lines = Files.readAllLines(file.toPath()); + welcomeMessages.clear(); + for (String line : lines) { + line = line.trim(); + if (line.startsWith("-")) welcomeMessages.add(line.substring(1).trim()); + } + getLogger().info("§eGeladene Welcome-Nachrichten: " + welcomeMessages.size()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + 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)); + } + + @Override + public void onEnable() { + // Plugin channels registrieren + try { + getProxy().registerChannel(CHANNEL_CONTROL); + getProxy().registerChannel(CHANNEL_CHAT); + } catch (Throwable ignored) { + getLogger().warning("Konnte Channels nicht registrieren."); + } + + // Konfiguration initialisieren + if (!getDataFolder().exists()) getDataFolder().mkdirs(); + configFile = new File(getDataFolder(), "bungee.yml"); + + // Standard-Konfiguration kopieren, falls nicht vorhanden + if (!configFile.exists()) { + getLogger().info("bungee.yml nicht gefunden. Erstelle Standard..."); + try (InputStream in = getResourceAsStream("bungee.yml")) { + Files.copy(in, configFile.toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + getLogger().info("bungee.yml gefunden. Lade Konfiguration..."); + } + + loadConfigData(); + loadServerAliases(); + loadGroupFormats(); + loadManualRanks(); // NEU: UUID Ränge laden + + getProxy().getPluginManager().registerListener(this, this); + + loadFilter(); + loadWelcomeMessages(); + + logFolder = new File(getDataFolder(), "logs"); + if (!logFolder.exists()) logFolder.mkdirs(); + cleanupOldLogs(); + + // Befehle registrieren + getProxy().getPluginManager().registerCommand(this, new ReloadCommand()); + getProxy().getPluginManager().registerCommand(this, new MuteCommand()); + getProxy().getPluginManager().registerCommand(this, new SupportCommand()); + getProxy().getPluginManager().registerCommand(this, new ReplyCommand()); + getProxy().getPluginManager().registerCommand(this, new InfoCommand()); + getProxy().getPluginManager().registerCommand(this, new ChatToggleCommand()); + + getLogger().info("§aGlobalChat aktiviert (UUID-Overrides via bungee.yml)!"); + } + + @Override + public void onDisable() { + getLogger().info("§cGlobalChat deaktiviert!"); + try { + getProxy().unregisterChannel(CHANNEL_CONTROL); + getProxy().unregisterChannel(CHANNEL_CHAT); + } catch (Throwable ignored) { + getLogger().warning("Konnte Channels nicht deregistrieren."); + } + } + + // =========================== + // Konfiguration laden + // =========================== + private void loadConfigData() { + try { + config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void loadServerAliases() { + loadConfigData(); // Sichert, dass aktuelle Daten geladen sind (für Reload) + + if (config == null) return; + + serverAliases.clear(); + if (config.getSection("server-aliases") != null) { + for (String key : config.getSection("server-aliases").getKeys()) { + serverAliases.put(key, config.getString("server-aliases." + key)); + } + } + getLogger().info("§eGeladene Server-Aliase: " + serverAliases.size()); + } + + private void loadGroupFormats() { + loadConfigData(); + if (config == null) return; + + groupFormats.clear(); + if (config.getSection("group-formats") != null) { + for (String key : config.getSection("group-formats").getKeys()) { + groupFormats.put(key, config.getString("group-formats." + key)); + } + } + getLogger().info("§eGeladene Chat-Formate: " + groupFormats.size()); + } + + // NEU: UUID Ränge aus bungee.yml laden + private void loadManualRanks() { + loadConfigData(); // Reload sichern + if (config == null) { + getLogger().warning("§cConfig ist null! Kann bungee.yml nicht lesen."); + return; + } + + manualRanks.clear(); + + // Prüfen, ob Sektion existiert + if (config.getSection("manual-ranks") == null) { + getLogger().warning("§cDer Bereich 'manual-ranks' wurde in der bungee.yml NICHT gefunden!"); + getLogger().warning("§cTipp: Bitte lösche die alte bungee.yml und starte neu."); + return; + } + + int loadedCount = 0; + if (config.getSection("manual-ranks").getKeys().isEmpty()) { + getLogger().warning("§cDer Bereich 'manual-ranks' ist leer (keine Overrides geladen)."); + } else { + for (String uuidStr : config.getSection("manual-ranks").getKeys()) { + String groupName = config.getString("manual-ranks." + uuidStr); + if (groupName != null) { + try { + // UUID Prüfung nur zur Sicherheit + UUID.fromString(uuidStr); + manualRanks.put(uuidStr, groupName); + loadedCount++; + } catch (IllegalArgumentException ex) { + getLogger().warning("§cUngültige UUID in manual-ranks: " + uuidStr); + } + } + } + } + getLogger().info("§eGeladene UUID-Overrides: " + loadedCount); + } + + private String getDisplayName(String serverName) { + String displayName = serverAliases.getOrDefault(serverName, serverName); + return ChatColor.translateAlternateColorCodes('&', displayName); + } + + // =========================== + // Chatfilter & Global-Chat (RELAY MODE) + // =========================== + @EventHandler + public void onChat(ChatEvent e) { + if (!(e.getSender() instanceof ProxiedPlayer)) return; + if (e.isCommand()) return; + + ProxiedPlayer player = (ProxiedPlayer) e.getSender(); + + // ==================== CHAT LOCK / TOGGLE CHECK ==================== + if (chatLockPlayers.contains(player.getUniqueId())) { + return; // Nachricht geht direkt zum Server + } + // ======================================================================== + + String originalMsg = e.getMessage(); + + if (suppressJoinQuit.contains(player.getUniqueId()) && + (originalMsg.contains("joined§ Game") || originalMsg.contains("left§ Game"))) { + 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 + 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 (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " "; + + StringBuilder out = new StringBuilder(); + out.append("§7[").append(serverDisplay).append("§r§7] "); // FIX: Reset vor der Klammer + 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 + + 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 : getProxy().getServers().values()) { + if (server.getPlayers().isEmpty()) continue; + + try { + server.sendData(CHANNEL_CHAT, jsonMessage.getBytes(StandardCharsets.UTF_8)); + } catch (Exception ex) { + getLogger().warning("Konnte Chat-Nachricht nicht an Server " + server.getName() + " senden."); + } + } + + // Loggen (lokal auf Bungee) + String logEntry = "[" + serverName + "] " + + (displayTag.isEmpty() ? "" : stripColor(displayTag) + " ") + + player.getName() + + ": " + originalMsg; + logMessage(logEntry); + } + + // =========================== + // Server Connect & Switch + // =========================== + @EventHandler + public void onServerConnect(ServerConnectEvent e) { + if (e.isCancelled()) return; + + ProxiedPlayer player = e.getPlayer(); + ServerInfo target = e.getTarget(); + ServerInfo from = player.getServer() != null ? player.getServer().getInfo() : null; + + if (from == null || from.equals(target)) return; + + suppressJoinQuit.add(player.getUniqueId()); + getLogger().info("Markiert " + player.getName() + " für Join-/Quit-Unterdrückung"); + + try { + sendSuppressJoinQuit(from, player.getUniqueId()); + getLogger().info("Sent suppress quit message for " + player.getName() + " to server " + from.getName()); + } catch (Throwable ex) { + getLogger().warning("Fehler beim Senden der Quit-Unterdrückung: " + ex.getMessage()); + } + + try { + sendSuppressJoinQuit(target, player.getUniqueId()); + getLogger().info("Sent suppress join message for " + player.getName() + " to server " + target.getName()); + } catch (Throwable ex) { + getLogger().warning("Fehler beim Senden der Join-Unterdrückung: " + ex.getMessage()); + } + + getProxy().getScheduler().schedule(this, () -> { + suppressJoinQuit.remove(player.getUniqueId()); + getLogger().info("Entfernte Unterdrückung für " + player.getName()); + }, 2, java.util.concurrent.TimeUnit.SECONDS); + } + + @EventHandler + public void onServerSwitch(ServerSwitchEvent e) { + ProxiedPlayer player = e.getPlayer(); + ServerInfo from = e.getFrom(); + ServerInfo to = player.getServer() != null ? player.getServer().getInfo() : null; + + if (to == null || from == null) return; + if (from.getName().equalsIgnoreCase(to.getName())) return; + + String fromName = from.getName(); + String fromDisplay = getDisplayName(fromName); + + String toName = to.getName(); + String toDisplay = getDisplayName(toName); + + String[] ps = getPrefixSuffix(player); + String prefix = ps[0]; + String suffix = ps[1]; + + String displayTag = ""; + if (!prefix.isEmpty()) displayTag = prefix; + else if (!suffix.isEmpty()) displayTag = suffix; + + if (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " "; + + StringBuilder msg = new StringBuilder(); + msg.append("§7[").append(toDisplay).append("§r§7] "); // FIX: Reset vor der Klammer + if (!displayTag.isEmpty()) msg.append(displayTag); + + msg.append(player.getName()); + + msg.append(" §7hat den Server gewechselt: §e") + .append(fromDisplay).append(" §7→ §e").append(toDisplay).append("§7."); + + String finalMsg = msg.toString(); + + TextComponent switchComponent = new TextComponent(finalMsg); + String jsonMessage = ComponentSerializer.toString(switchComponent); + byte[] data = jsonMessage.getBytes(StandardCharsets.UTF_8); + + for (ServerInfo server : getProxy().getServers().values()) { + if (server.getPlayers().isEmpty()) continue; + try { + server.sendData(CHANNEL_CHAT, data); + } catch (Exception ex) { + getLogger().warning("Konnte Switch-Nachricht nicht senden."); + } + } + + String logEntry = "[" + toName + "] " + + (displayTag.isEmpty() ? "" : stripColor(displayTag) + " ") + + player.getName() + " hat den Server gewechselt: " + fromName + " -> " + toName + "."; + logMessage(logEntry); + } + + private void sendSuppressJoinQuit(ServerInfo server, UUID playerId) { + if (server == null) return; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(baos)) { + out.writeUTF("suppress"); + out.writeUTF(playerId.toString()); + server.sendData(CHANNEL_CONTROL, baos.toByteArray()); + } catch (IOException ex) { + getLogger().warning("Fehler beim Senden der suppress-Nachricht: " + ex.getMessage()); + } + } + + private String repeat(String str, int count) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; i++) sb.append(str); + return sb.toString(); + } + + // =========================== + // Prefix/Suffix (Logic: UUID Override > LuckPerms > Perm) + // =========================== + private String[] getPrefixSuffix(ProxiedPlayer player) { + String prefix = ""; + String suffix = ""; + String playerColor = "§f"; + String chatColor = "§f"; + + String groupName = "spieler"; + + // 0. UUID Override aus bungee.yml + // DEBUG LOG zum Finden des Fehlers: + // getLogger().info("DEBUG: Suche Override für UUID: " + player.getUniqueId().toString()); + if (manualRanks.containsKey(player.getUniqueId().toString())) { + groupName = manualRanks.get(player.getUniqueId().toString()); + // getLogger().info("DEBUG: Override gefunden -> " + groupName); + } + // 1. LuckPerms via Reflection + else { + String lpGroup = getLuckPermsGroup(player); + if (lpGroup != null && !lpGroup.isEmpty() && !lpGroup.equalsIgnoreCase("default")) { + groupName = lpGroup.toLowerCase(); + } + } + + // 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 groupName = "Spieler"; + } + + // 3. Farben aus bungee.yml anwenden + if (groupName != null && groupFormats.containsKey(groupName)) { + String rawFormat = groupFormats.get(groupName); + + if (rawFormat.contains(" || ")) { + String[] parts = rawFormat.split(" \\|\\| "); + prefix = ChatColor.translateAlternateColorCodes('&', parts[0]); + playerColor = ChatColor.translateAlternateColorCodes('&', parts[1]); + chatColor = ChatColor.translateAlternateColorCodes('&', parts[2]); + suffix = ""; + } else if (rawFormat.contains(": ")) { + String[] parts = rawFormat.split(": ", 2); + prefix = ChatColor.translateAlternateColorCodes('&', parts[0]); + chatColor = ChatColor.translateAlternateColorCodes('&', parts[1]); + playerColor = "§f"; + suffix = ""; + } else { + prefix = ChatColor.translateAlternateColorCodes('&', rawFormat); + suffix = ""; + playerColor = "§f"; + chatColor = "§f"; + } + + return new String[]{prefix, suffix, playerColor, chatColor}; + } + + // 4. LuckPerms Meta Fallback + 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) {} + } + if (user != null) { + CachedMetaData meta = user.getCachedData().getMetaData(); + 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); + } + } + } + } catch (Throwable ignored) {} + + if (prefix == null) prefix = ""; + if (suffix == null) suffix = ""; + + return new String[]{prefix, suffix, playerColor, chatColor}; + } + + 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(getDataFolder(), fileName); + + if (!file.exists()) { + getDataFolder().mkdirs(); + try (PrintWriter out = new PrintWriter(file)) { + out.println("badwords:"); + out.println(" - arsch"); + out.println(" - hurensohn"); + out.println(" - scheiße"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + try { + List lines = Files.readAllLines(file.toPath()); + badWords.clear(); + for (String line : lines) { + line = line.trim(); + if (line.startsWith("-")) badWords.add(line.substring(1).trim()); + } + getLogger().info("§eGeladene Filter-Wörter: " + badWords.size()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // =========================== + // Logs aufräumen / schreiben + // =========================== + private void cleanupOldLogs() { + File[] files = logFolder.listFiles(); + if (files == null) return; + + long now = System.currentTimeMillis(); + long sevenDays = 1000L * 60 * 60 * 24 * 7; + + for (File f : files) { + if (now - f.lastModified() > sevenDays) { + f.delete(); + } + } + } + + private void logMessage(String message) { + String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + File logFile = new File(logFolder, date + ".log"); + + try (BufferedWriter bw = new BufferedWriter(new FileWriter(logFile, true))) { + String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); + bw.write("[" + time + "] " + message); + bw.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // =========================== + // Staff-Check + // =========================== + private boolean isStaff(ProxiedPlayer p) { + if (p == null) return false; + Boolean reportedOp = playerIsOp.get(p.getUniqueId()); + if (reportedOp != null && reportedOp) return true; + + if (p.hasPermission("team")) return true; + if (p.hasPermission("bungeecord.admin")) return true; + if (p.hasPermission("globalchat.op")) return true; + if (p.hasPermission("*")) return true; + + if (p.hasPermission("bungeecord.command.alert")) return true; + if (p.hasPermission("bungeecord.command.reload")) return true; + if (p.hasPermission("bungeecord.command.kick")) return true; + if (p.hasPermission("bungeecord.command.send")) return true; + if (p.hasPermission("bungeecord.command.perms")) return true; + + return false; + } + + // =========================== + // Commands (Inner Classes) + // =========================== + public class ReloadCommand extends Command { + public ReloadCommand() { super("globalreload", "globalchat.reload"); } + @Override + public void execute(CommandSender sender, String[] args) { + loadFilter(); + loadConfigData(); + loadServerAliases(); + loadGroupFormats(); + loadManualRanks(); // NEU + sender.sendMessage(new TextComponent("§aFilter, Server-Aliase, Group-Formate und UUID-Overrides wurden neu geladen!")); + } + } + + public class MuteCommand extends Command { + public MuteCommand() { super("globalmute", "globalchat.mute"); } + @Override + public void execute(CommandSender sender, String[] args) { + chatMuted = !chatMuted; + String status = chatMuted ? "§caktiviert" : "§aaufgehoben"; + for (ProxiedPlayer p : getProxy().getPlayers()) { + p.sendMessage(new TextComponent("§7[GlobalChat] §eDer globale Chat Mute wurde " + status + "§e!")); + } + getLogger().info("GlobalMute wurde " + (chatMuted ? "aktiviert" : "deaktiviert") + "."); + } + } + + public class SupportCommand extends Command { + public SupportCommand() { super("support"); } + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof ProxiedPlayer)) { + sender.sendMessage(new TextComponent("§cNur Spieler können Support-Nachrichten senden.")); + return; + } + + ProxiedPlayer player = (ProxiedPlayer) sender; + if (args.length == 0) { + player.sendMessage(new TextComponent("§cBitte eine Nachricht angeben: /support ")); + return; + } + + String msg = String.join(" ", args); + String serverRaw = player.getServer().getInfo().getName(); + String serverDisplay = getDisplayName(serverRaw); + + 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() + " ")); + + for (ProxiedPlayer p : getProxy().getPlayers()) { + if (isStaff(p)) { + p.sendMessage(supportMsg); + lastSupportContact.put(p.getUniqueId(), player.getUniqueId()); + } + } + + player.sendMessage(new TextComponent("§aDeine Support-Nachricht wurde gesendet.")); + logMessage("[Support][" + serverRaw + "] " + player.getName() + ": " + msg); + } + } + + public class ReplyCommand extends Command { + public ReplyCommand() { super("reply"); } + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof ProxiedPlayer)) return; + + ProxiedPlayer staff = (ProxiedPlayer) sender; + UUID targetId = lastSupportContact.get(staff.getUniqueId()); + if (targetId == null) { + staff.sendMessage(new TextComponent("§cKein Spieler zum Antworten gefunden.")); + return; + } + + if (args.length == 0) { + staff.sendMessage(new TextComponent("§cBitte eine Nachricht angeben.")); + return; + } + + ProxiedPlayer target = getProxy().getPlayer(targetId); + if (target == null) { + staff.sendMessage(new TextComponent("§cSpieler ist nicht online.")); + return; + } + + String msg = String.join(" ", args); + target.sendMessage(new TextComponent("§7[Reply von §b" + staff.getName() + "§7]: §f" + msg)); + staff.sendMessage(new TextComponent("§aDeine Nachricht wurde an §b" + target.getName() + "§a gesendet.")); + } + } + + public class InfoCommand extends Command { + public InfoCommand() { super("info"); } + @Override + public void execute(CommandSender sender, String[] args) { + sender.sendMessage(new TextComponent("§8§m------------------------------")); + sender.sendMessage(new TextComponent("§6§lGlobalChat Info")); + sender.sendMessage(new TextComponent("§ePlugin-Name: §b" + getDescription().getName())); + sender.sendMessage(new TextComponent("§eVersion: §b" + getDescription().getVersion())); + sender.sendMessage(new TextComponent("§eErsteller: §bM_Viper")); + sender.sendMessage(new TextComponent("§8§m------------------------------")); + } + } + + public class ChatToggleCommand extends Command { + public ChatToggleCommand() { super("togglechat"); } + + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof ProxiedPlayer)) { + sender.sendMessage(new TextComponent("§cNur Spieler können den Chat-Modus ändern.")); + return; + } + + ProxiedPlayer player = (ProxiedPlayer) sender; + UUID uuid = player.getUniqueId(); + + if (chatLockPlayers.contains(uuid)) { + chatLockPlayers.remove(uuid); + player.sendMessage(new TextComponent("§aGlobalChat aktiviert.")); + player.sendMessage(new TextComponent("§7Deine Nachrichten werden nun wieder global gesendet.")); + } else { + chatLockPlayers.add(uuid); + player.sendMessage(new TextComponent("§cGlobalChat deaktiviert.")); + player.sendMessage(new TextComponent("§7Deine Nachrichten gehen nur noch an den Server (z.B. für Lands oder Quests).")); + player.sendMessage(new TextComponent("§7Nutze erneut /togglechat, um den globalen Chat zu aktivieren.")); + } + } + } + + // HELPER: LuckPerms Group via Reflection + private String getLuckPermsGroup(ProxiedPlayer player) { + try { + if (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) { return null; } + return null; + } } \ No newline at end of file