From 4c2f6a214c09309e6b212fe7f9dc1a98c089f848 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Mon, 30 Mar 2026 18:45:03 +0000 Subject: [PATCH] Delete src/main/java/net/viper/status/StatusAPI.java via Git Manager GUI --- src/main/java/net/viper/status/StatusAPI.java | 801 ------------------ 1 file changed, 801 deletions(-) delete mode 100644 src/main/java/net/viper/status/StatusAPI.java diff --git a/src/main/java/net/viper/status/StatusAPI.java b/src/main/java/net/viper/status/StatusAPI.java deleted file mode 100644 index 5913cba..0000000 --- a/src/main/java/net/viper/status/StatusAPI.java +++ /dev/null @@ -1,801 +0,0 @@ -package net.viper.status; - -import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.chat.ClickEvent; -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.config.ListenerInfo; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.event.PostLoginEvent; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.api.plugin.Plugin; -import net.viper.status.module.ModuleManager; -import net.viper.status.stats.PlayerStats; -import net.viper.status.stats.StatsModule; -import net.viper.status.modules.verify.VerifyModule; -import net.viper.status.modules.globalchat.GlobalChatModule; -import net.viper.status.modules.navigation.NavigationModule; -import net.viper.status.modules.commandblocker.CommandBlockerModule; -import net.viper.status.modules.broadcast.BroadcastModule; -import net.viper.status.modules.AutoMessage.AutoMessageModule; -import net.viper.status.modules.customcommands.CustomCommandModule; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringReader; -import java.net.ServerSocket; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.*; -import java.util.concurrent.TimeUnit; -import net.md_5.bungee.event.EventHandler; - -/** - * StatusAPI - zentraler Bungee HTTP-Status- und Broadcast-Endpunkt - */ -public class StatusAPI extends Plugin implements Runnable, Listener { - - private Thread thread; - private int port = 9191; - - private ModuleManager moduleManager; - private Properties verifyProperties; - - // Speicher für Updates beim Join - private boolean updatePending = false; - private String latestVersionString = ""; - private final Set informedAdmins = new HashSet<>(); - - @Override - public void onEnable() { - getLogger().info("StatusAPI Core wird initialisiert..."); - - // Event Listener registrieren - getProxy().getPluginManager().registerListener(this, this); - - if (!getDataFolder().exists()) { - getDataFolder().mkdirs(); - } - - // Config mergen/updaten - mergeVerifyConfig(); - - moduleManager = new ModuleManager(); - - // Module registrieren - moduleManager.registerModule(new StatsModule()); - moduleManager.registerModule(new VerifyModule()); - moduleManager.registerModule(new GlobalChatModule()); - moduleManager.registerModule(new NavigationModule()); - moduleManager.registerModule(new BroadcastModule()); - moduleManager.registerModule(new CommandBlockerModule()); - moduleManager.registerModule(new AutoMessageModule()); - - // CustomCommandModule nur laden, wenn aktiviert - boolean customCommandsEnabled = Boolean.parseBoolean(getVerifyProperties().getProperty("customcommands.enabled", "false")); - if (customCommandsEnabled) { - moduleManager.registerModule(new CustomCommandModule()); // Korrigiert: Kein Argument im Konstruktor - getLogger().info("CustomCommandModule aktiviert."); - } else { - getLogger().info("CustomCommandModule deaktiviert (siehe verify.properties)."); - } - - moduleManager.enableAll(this); - - // WebServer starten - getLogger().info("Starte Web-Server auf Port " + port + "..."); - thread = new Thread(this, "StatusAPI-HTTP-Server"); - thread.start(); - - // Einfacher Update-Check (Nur Benachrichtigung für Admins beim Start oder beim Join) - checkForUpdatesAndNotify(); - } - - @EventHandler - public void onPlayerJoin(PostLoginEvent event) { - ProxiedPlayer player = event.getPlayer(); - - // Wir prüfen hier nur, ob ein Update aussteht. Der Rest wird im Scheduler verzögert geprüft. - if (updatePending && isAdmin(player)) { - // Verzögerung um 2 Sekunden - getProxy().getScheduler().schedule(this, () -> { - // Prüfen, ob Spieler noch online ist und noch nicht informiert wurde - if (player.isConnected() && !informedAdmins.contains(player.getUniqueId()) && isAdmin(player)) { - sendStyledUpdateMessage(player); - informedAdmins.add(player.getUniqueId()); - getLogger().info("Admin " + player.getName() + " wurde über das neue Update informiert."); - } - }, 2, TimeUnit.SECONDS); - } - } - - /** - * Sendet eine formatierte Nachricht mit klickbarem Link. - */ - private void sendStyledUpdateMessage(ProxiedPlayer player) { - String currentVersion = getDescription() != null ? getDescription().getVersion() : "Unbekannt"; - String downloadUrl = "https://git.viper.ipv64.net/M_Viper/StatusAPI/releases"; - - ComponentBuilder builder = new ComponentBuilder(""); - - // Zeile 1: Update Info - // Wir konvertieren Legacy-Strings zu BaseComponents und fügen sie an den Builder an - builder.append(TextComponent.fromLegacyText("§a[StatusAPI] ")) - .append(TextComponent.fromLegacyText("§eEine neue Version ist verfügbar: ")) - .append(TextComponent.fromLegacyText("§c" + latestVersionString)) - .append(TextComponent.fromLegacyText(" §7(Deine Version: ")) - .append(TextComponent.fromLegacyText("§c" + currentVersion)) - .append(TextComponent.fromLegacyText("§7)\n")); - - // Zeile 2: Download - builder.append(TextComponent.fromLegacyText("§7Download: ")); - - // Der Klickbare Teil - TextComponent link = new TextComponent("§e§nLink"); - link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, downloadUrl)); - link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText("§aKlicke hier um die Release-Seite zu öffnen"))); - - builder.append(link); - - // Korrektur: Nur die Components senden, nicht den Spieler als Argument - player.sendMessage(builder.create()); - } - - /** - * Überprüft auf Updates und benachrichtigt Spieler mit Admin-Rechten (OP/Owner). - */ - private void checkForUpdatesAndNotify() { - String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0"; - // Der UpdateChecker wird nur lokal instanziiert für den Check - UpdateChecker checker = new UpdateChecker(this, currentVersion, 6); - - try { - checker.checkNow(); - - if (checker.isUpdateAvailable(currentVersion)) { - String newVersion = checker.getLatestVersion(); - String url = checker.getLatestUrl(); - - getLogger().warning("------------------------------------------------"); - getLogger().warning("Neue Version verfügbar: " + newVersion); - getLogger().warning("Aktuelle Version: " + currentVersion); - getLogger().warning("Download: " + url); - getLogger().warning("------------------------------------------------"); - - // Info speichern (nur Version, URL ist statisch für den Chat-Link) - this.updatePending = true; - this.latestVersionString = newVersion; - - // Benachrichtigung an online Admins / OPs (mit Verzögerung, um konsistent mit Join zu sein) - boolean notifiedSomeone = false; - for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { - if (isAdmin(p)) { - final ProxiedPlayer player = p; - getProxy().getScheduler().schedule(this, () -> { - if (player.isConnected() && !informedAdmins.contains(player.getUniqueId())) { - sendStyledUpdateMessage(player); - informedAdmins.add(player.getUniqueId()); - } - }, 2, TimeUnit.SECONDS); - notifiedSomeone = true; - } - } - - if (!notifiedSomeone) { - getLogger().info("Keine Admins waren online, um benachrichtigt zu werden. Sie werden beim Join informiert."); - } - } else { - getLogger().info("Keine Updates verfügbar."); - } - } catch (Exception e) { - getLogger().severe("Fehler beim Update-Check: " + e.getMessage()); - } - } - - // Hilfsmethode zum Prüfen von Admin-Rechten - private boolean isAdmin(ProxiedPlayer player) { - return player.hasPermission("statusapi.admin") || player.hasPermission("bungeecord.command.server"); - } - - @Override - public void onDisable() { - getLogger().info("Stoppe Module..."); - if (moduleManager != null) { - moduleManager.disableAll(this); - } - - getLogger().info("Stoppe Web-Server..."); - if (thread != null) { - thread.interrupt(); - try { thread.join(1000); } catch (InterruptedException ignored) {} - } - } - - /** - * Hilfsmethode um Property-Werte zu bereinigen (unescape). - * Entfernt z.B. den Backslash vor URLs wie https\:// - */ - private String unescapePropertiesString(String input) { - if (input == null) return null; - try { - Properties p = new Properties(); - p.load(new StringReader("dummy=" + input)); - return p.getProperty("dummy"); - } catch (IOException e) { - return input; // Fallback, falls Parsing fehlschlägt - } - } - - // --- MERGE LOGIK --- - private void mergeVerifyConfig() { - try { - File file = new File(getDataFolder(), "verify.properties"); - - // 1. Das exakte Template definieren - List templateLines = Arrays.asList( - "# _____ __ __ ___ ____ ____", - "# / ___// /_____ _/ /___ _______/ | / __ \\/ _/", - "# \\__ \\/ __/ __ `/ __/ / / / ___/ /| | / /_/ // / ", - "# ___/ / /_/ /_/ / /_/ /_/ (__ ) ___ |/ ____// / ", - "# /____/\\__/\\__,_/\\__/\\__,_/____/_/ |_/_/ /___/ ", - " ", - "# ===========================", - "# GLOBALCHAT AKTIVIERUNG", - "# ===========================", - "chat.enabled=false", - "", - "# ------------------------------", - "# Broadcast", - "# ------------------------------", - "", - "broadcast.enabled=false", - "broadcast.prefix=[Broadcast]", - "broadcast.prefix-color=&c", - "broadcast.message-color=&f", - "broadcast.format=%prefixColored% %messageColored%", - "# broadcast.format kann angepasst werden; nutze Platzhalter: %name%, %prefix%, %prefixColored%, %message%, %messageColored%, %type%", - "", - "# ===========================", - "# NAVIGATION / SERVER SWITCHER", - "# ===========================", - "# Hier kannst du das interne Navigationssystem aktivieren/deaktivieren.", - "# Wenn aktiviert, erstellt das Plugin automatisch Befehle basierend auf den Servernamen (z.B. /lobby, /survival).", - "navigation.enabled=false", - "", - "# ===========================", - "# WORDPRESS / VERIFY EINSTELLUNGEN", - "# ===========================", - "wp_verify_url=https://deine-wp-domain.tld", - "", - "# ===========================", - "# SERVER KONFIGURATION", - "# ===========================", - "# Hier legst du für jeden Server alles fest:", - "# 1. Den Anzeigenamen für den Chat (z.B. &bLobby)", - "# 2. Die Server ID für WordPress (z.B. id=1)", - "# 3. Das Secret für WordPress (z.B. secret=...)", - "", - "# Server 1: Lobby", - "server.lobby=&bLobby", - "server.lobby.id=1", - "server.lobby.secret=GeheimesWortFuerLobby123", - "", - "# Server 2: Survival", - "server.survival=&aSurvival", - "server.survival.id=2", - "server.survival.secret=GeheimesWortFuerSurvival456", - "", - "# Server 3: SkyBlock", - "server.skyblock=&dSkyBlock", - "server.skyblock.id=3", - "server.skyblock.secret=GeheimesWortFuerSkyBlock789", - "", - "# ===========================", - "# Manuelle Ränge (Overrides)", - "# ===========================", - "# Syntax: override. = ", - "# WICHTIG: Die Gruppe (z.B. Owner) muss unten bei groupformat definiert sein!", - "", - "# Beispiel: Deinen UUID hier einfügen", - "override.uuid-hier-einfügen = Owner", - "", - "# ===========================", - "# Chat-Formate für Gruppen", - "# ===========================", - "", - "# Der Name hinter dem Punkt (z.B. Owner) muss exakt mit der LuckPerms Gruppe übereinstimmen.", - "# Nutze & für Farbcodes.", - "", - "# Ränge mit neuer Syntax: Rank || Spielerfarbe || Chatfarbe", - "# Beispiel: Rot (Rang) || Blau (Name) || Lila (Chat)", - "groupformat.owner=&c[Owner] || &b || &d", - "groupformat.admin=&4[Admin] || &9 || &c", - "groupformat.developer=&b[Dev] || &3 || &a", - "groupformat.premium=&6[Premium] || &e || &7", - "groupformat.spieler=&f[Spieler] || &7 || &8", - "", - "# ===========================", - "# AUTOMESSAGE", - "# ===========================", - "# Aktiviert den automatischen Nachrichten-Rundruf", - "automessage.enabled=false", - "", - "# Zeitintervall in Sekunden (Standard: 300 = 5 Minuten)", - "automessage.interval=300", - "", - "# Optional: Ein Prefix, das VOR jede Nachricht aus der Datei gesetzt wird.", - "# Wenn du das Prefix bereits IN der messages.txt hast, lass dieses Feld einfach leer.", - "automessage.prefix=", - "", - "# Der Name der Datei, in der die Nachrichten stehen (liegt im Plugin-Ordner)", - "automessage.file=messages.txt", - "", - "# ===========================", - "# COMMAND BLOCKER", - "# ===========================", - "commandblocker.enabled=true", - "commandblocker.bypass.permission=commandblocker.bypass", - "", - "# ===========================", - "# CUSTOM COMMANDS", - "# ===========================", - "# Aktiviert das Modul für benutzerdefinierte Befehle und Aliases.", - "# Wenn aktiviert, wird die Datei 'customcommands.yml' geladen.", - "customcommands.enabled=true" - ); - - // 2. Existierende Werte einlesen - Map existingValues = new HashMap<>(); - // Liste für Override-Keys, die nicht im Template stehen - List> pendingOverrides = new ArrayList<>(); - - if (file.exists()) { - List existing = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); - for (String line : existing) { - String trimmed = line.trim(); - if (!trimmed.isEmpty() && !trimmed.startsWith("#") && trimmed.contains("=")) { - int idx = trimmed.indexOf('='); - String key = trimmed.substring(0, idx).trim(); - String val = trimmed.substring(idx + 1).trim(); - // Wert speichern, Escaping entfernen für sauberen Output - existingValues.put(key, unescapePropertiesString(val)); - } - } - } - - // 3. Alle Override-Keys aus der alten Config sammeln - for (Map.Entry entry : existingValues.entrySet()) { - if (entry.getKey().startsWith("override.")) { - pendingOverrides.add(entry); - } - } - - // 4. Datei Zeile für Zeile basierend auf Template schreiben - List outputLines = new ArrayList<>(); - Set usedKeys = new HashSet<>(); - - for (int i = 0; i < templateLines.size(); i++) { - String line = templateLines.get(i); - String trimmed = line.trim(); - - // PRÜFUNG: Wenn wir am Anfang des nächsten Abschnitts sind ("Chat-Formate") - // Fügen wir die fehlenden Overrides VORHER ein. - if (trimmed.startsWith("#") && trimmed.contains("====")) { - if (i + 1 < templateLines.size()) { - String nextTrimmed = templateLines.get(i + 1).trim(); - // Wenn wir kurz vor "Chat-Formate" stehen und noch Overrides offen sind -> Einfügen - if (nextTrimmed.contains("Chat-Formate") && !pendingOverrides.isEmpty()) { - outputLines.add(""); - for (Map.Entry ov : pendingOverrides) { - if (!usedKeys.contains(ov.getKey())) { - // Bereinigten Wert schreiben - outputLines.add(ov.getKey() + "=" + ov.getValue()); - usedKeys.add(ov.getKey()); - } - } - pendingOverrides.clear(); - outputLines.add(""); - } - } - } - - // LOGIK: Template Zeile verarbeiten - if (!trimmed.startsWith("#") && !trimmed.isEmpty() && trimmed.contains("=")) { - String[] parts = trimmed.split("=", 2); - String key = parts[0].trim(); - - if (existingValues.containsKey(key)) { - // Wert aus alter Config übernehmen (bereinigt) - String cleanVal = unescapePropertiesString(existingValues.get(key)); - outputLines.add(key + "=" + cleanVal); - usedKeys.add(key); - - pendingOverrides.removeIf(e -> e.getKey().equals(key)); - } else { - // Standardwert aus Template übernehmen - outputLines.add(line); - } - } else { - outputLines.add(line); - } - } - - // Falls noch was übrig ist, am Ende anhängen - if (!pendingOverrides.isEmpty()) { - outputLines.add(""); - for (Map.Entry ov : pendingOverrides) { - outputLines.add(ov.getKey() + "=" + ov.getValue()); - } - } - - // 5. Datei schreiben - Files.write(file.toPath(), outputLines, StandardCharsets.UTF_8); - - // 6. Properties laden - verifyProperties = new Properties(); - try (FileInputStream fis = new FileInputStream(file)) { - verifyProperties.load(fis); - } - - getLogger().info("verify.properties erfolgreich aktualisiert."); - - } catch (IOException e) { - getLogger().severe("Fehler beim Merge der verify.properties: " + e.getMessage()); - } - } - - public Properties getVerifyProperties() { - synchronized (this) { - return verifyProperties; - } - } - - // --- WebServer & JSON --- - @Override - public void run() { - try (ServerSocket serverSocket = new ServerSocket(port)) { - serverSocket.setSoTimeout(1000); - while (!Thread.interrupted()) { - try { - Socket clientSocket = serverSocket.accept(); - handleConnection(clientSocket); - } catch (java.net.SocketTimeoutException e) {} - catch (IOException e) { - getLogger().warning("Fehler beim Akzeptieren einer Verbindung: " + e.getMessage()); - } - } - } catch (IOException e) { - getLogger().severe("Konnte ServerSocket nicht starten: " + e.getMessage()); - } - } - - private void handleConnection(Socket clientSocket) { - try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8")); - OutputStream out = clientSocket.getOutputStream()) { - - String inputLine = in.readLine(); - if (inputLine == null) return; - - String[] reqParts = inputLine.split(" "); - if (reqParts.length < 2) return; - String method = reqParts[0].trim(); - String path = reqParts[1].trim(); - - Map headers = new HashMap<>(); - String line; - while ((line = in.readLine()) != null && !line.isEmpty()) { - int idx = line.indexOf(':'); - if (idx > 0) { - String key = line.substring(0, idx).trim().toLowerCase(Locale.ROOT); - String val = line.substring(idx + 1).trim(); - headers.put(key, val); - } - } - - // --- POST /broadcast/cancel --- - if ("POST".equalsIgnoreCase(method) && (path.equalsIgnoreCase("/broadcast/cancel") || path.equalsIgnoreCase("/cancel"))) { - int contentLength = 0; - if (headers.containsKey("content-length")) { - try { contentLength = Integer.parseInt(headers.get("content-length")); } catch (NumberFormatException ignored) {} - } - char[] bodyChars = new char[Math.max(0, contentLength)]; - if (contentLength > 0) { - int read = 0; - while (read < contentLength) { - int r = in.read(bodyChars, read, contentLength - read); - if (r == -1) break; - read += r; - } - } - String body = new String(bodyChars); - String clientScheduleId = extractJsonString(body, "clientScheduleId"); - if (clientScheduleId == null || clientScheduleId.isEmpty()) { - sendHttpResponse(out, "{\"success\":false,\"error\":\"missing_clientScheduleId\"}", 400); - return; - } - Object mod = moduleManager.getModule("BroadcastModule"); - if (mod instanceof BroadcastModule) { - boolean ok = ((BroadcastModule) mod).cancelScheduled(clientScheduleId); - if (ok) sendHttpResponse(out, "{\"success\":true}", 200); - else sendHttpResponse(out, "{\"success\":false,\"error\":\"not_found\"}", 404); - return; - } else { - sendHttpResponse(out, "{\"success\":false,\"error\":\"no_broadcast_module\"}", 500); - return; - } - } - - // --- POST /broadcast --- - if ("POST".equalsIgnoreCase(method) && ("/broadcast".equalsIgnoreCase(path) || "/".equals(path) || path.isEmpty())) { - int contentLength = 0; - if (headers.containsKey("content-length")) { - try { contentLength = Integer.parseInt(headers.get("content-length")); } catch (NumberFormatException ignored) {} - } - - char[] bodyChars = new char[Math.max(0, contentLength)]; - if (contentLength > 0) { - int read = 0; - while (read < contentLength) { - int r = in.read(bodyChars, read, contentLength - read); - if (r == -1) break; - read += r; - } - } - String body = new String(bodyChars); - - String apiKeyHeader = headers.getOrDefault("x-api-key", headers.getOrDefault("x-apikey", "")); - - String message = extractJsonString(body, "message"); - String type = extractJsonString(body, "type"); - String prefix = extractJsonString(body, "prefix"); - String prefixColor = extractJsonString(body, "prefixColor"); - String bracketColor = extractJsonString(body, "bracketColor"); - String messageColor = extractJsonString(body, "messageColor"); - String sourceName = extractJsonString(body, "source"); - String scheduleTimeStr = extractJsonString(body, "scheduleTime"); - String recur = extractJsonString(body, "recur"); - String clientScheduleId = extractJsonString(body, "clientScheduleId"); - - if (sourceName == null || sourceName.isEmpty()) sourceName = "PulseCast"; - if (type == null || type.isEmpty()) type = "global"; - - if (scheduleTimeStr != null && !scheduleTimeStr.trim().isEmpty()) { - long scheduleMillis = 0L; - try { - scheduleMillis = Long.parseLong(scheduleTimeStr.trim()); - if (scheduleMillis < 1_000_000_000_000L) scheduleMillis = scheduleMillis * 1000L; - } catch (NumberFormatException ignored) { - try { - double d = Double.parseDouble(scheduleTimeStr.trim()); - long v = (long) d; - if (v < 1_000_000_000_000L) v = v * 1000L; - scheduleMillis = v; - } catch (Exception ex) { - sendHttpResponse(out, "{\"success\":false,\"error\":\"bad_scheduleTime\"}", 400); - return; - } - } - - try { - Object mod = moduleManager.getModule("BroadcastModule"); - if (mod instanceof BroadcastModule) { - BroadcastModule bm = (BroadcastModule) mod; - boolean ok = bm.scheduleBroadcast(scheduleMillis, sourceName, message, type, apiKeyHeader, - prefix, prefixColor, bracketColor, messageColor, (recur == null ? "none" : recur), (clientScheduleId == null ? null : clientScheduleId)); - - if (ok) sendHttpResponse(out, "{\"success\":true}", 200); - else sendHttpResponse(out, "{\"success\":false,\"error\":\"rejected\"}", 403); - return; - } else { - sendHttpResponse(out, "{\"success\":false,\"error\":\"no_broadcast_module\"}", 500); - return; - } - } catch (Throwable t) { - t.printStackTrace(); - sendHttpResponse(out, "{\"success\":false,\"error\":\"internal\"}", 500); - return; - } - } - - if (message == null || message.isEmpty()) { - String resp = "{\"success\":false,\"error\":\"missing_message\"}"; - sendHttpResponse(out, resp, 400); - return; - } - - try { - Object mod = moduleManager.getModule("BroadcastModule"); - if (mod instanceof BroadcastModule) { - BroadcastModule bm = (BroadcastModule) mod; - boolean ok = bm.handleBroadcast(sourceName, message, type, apiKeyHeader, prefix, prefixColor, bracketColor, messageColor); - - if (ok) sendHttpResponse(out, "{\"success\":true}", 200); - else sendHttpResponse(out, "{\"success\":false,\"error\":\"rejected\"}", 403); - return; - } else { - sendHttpResponse(out, "{\"success\":false,\"error\":\"no_broadcast_module\"}", 500); - return; - } - } catch (Throwable t) { - t.printStackTrace(); - sendHttpResponse(out, "{\"success\":false,\"error\":\"internal\"}", 500); - return; - } - } - - // --- GET Handler --- - if (inputLine != null && inputLine.startsWith("GET")) { - Map data = new LinkedHashMap<>(); - data.put("online", true); - - String versionRaw = ProxyServer.getInstance().getVersion(); - String versionClean = (versionRaw != null && versionRaw.contains(":")) ? versionRaw.split(":")[2].trim() : versionRaw; - data.put("version", versionClean); - data.put("max_players", String.valueOf(ProxyServer.getInstance().getConfig().getPlayerLimit())); - - String motd = "BungeeCord"; - try { - Iterator it = ProxyServer.getInstance().getConfig().getListeners().iterator(); - if (it.hasNext()) motd = it.next().getMotd(); - } catch (Exception ignored) {} - data.put("motd", motd); - - StatsModule statsModule = (StatsModule) moduleManager.getModule("StatsModule"); - - boolean luckPermsEnabled = ProxyServer.getInstance().getPluginManager().getPlugin("LuckPerms") != null; - List> playersList = new ArrayList<>(); - - for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { - Map playerInfo = new LinkedHashMap<>(); - playerInfo.put("name", p.getName()); - try { playerInfo.put("uuid", p.getUniqueId().toString()); } catch (Exception ignored) {} - - String prefix = ""; - if (luckPermsEnabled) { - try { - Class providerClass = Class.forName("net.luckperms.api.LuckPermsProvider"); - Object luckPermsApi = providerClass.getMethod("get").invoke(null); - Object userManager = luckPermsApi.getClass().getMethod("getUserManager").invoke(luckPermsApi); - Object user = userManager.getClass().getMethod("getUser", UUID.class).invoke(userManager, p.getUniqueId()); - if (user != null) { - Class queryOptionsClass = Class.forName("net.luckperms.api.query.QueryOptions"); - Object queryOptions = queryOptionsClass.getMethod("defaultContextualOptions").invoke(null); - Object cachedData = user.getClass().getMethod("getCachedData").invoke(user); - Object metaData = cachedData.getClass().getMethod("getMetaData", queryOptionsClass).invoke(cachedData, queryOptions); - Object result = metaData.getClass().getMethod("getPrefix").invoke(metaData); - if (result != null) prefix = (String) result; - } - } catch (Exception ignored) {} - } - playerInfo.put("prefix", prefix); - - if (statsModule != null) { - PlayerStats ps = statsModule.getManager().getIfPresent(p.getUniqueId()); - if (ps != null) { - playerInfo.put("playtime", ps.getPlaytimeWithCurrentSession()); - playerInfo.put("joins", ps.joins); - playerInfo.put("first_seen", ps.firstSeen); - playerInfo.put("last_seen", ps.lastSeen); - } - } - playersList.add(playerInfo); - } - data.put("players", playersList); - - String json = buildJsonString(data); - byte[] jsonBytes = json.getBytes("UTF-8"); - StringBuilder response = new StringBuilder(); - response.append("HTTP/1.1 200 OK\r\n"); - response.append("Content-Type: application/json; charset=UTF-8\r\n"); - response.append("Access-Control-Allow-Origin: *\r\n"); - response.append("Content-Length: ").append(jsonBytes.length).append("\r\n"); - response.append("Connection: close\r\n\r\n"); - out.write(response.toString().getBytes("UTF-8")); - out.write(jsonBytes); - out.flush(); - } - } catch (Exception e) { - getLogger().severe("Fehler beim Verarbeiten der Anfrage: " + e.getMessage()); - } - } - - private String extractJsonString(String json, String key) { - if (json == null || key == null) return null; - String search = "\"" + key + "\""; - int idx = json.indexOf(search); - if (idx < 0) return null; - int colon = json.indexOf(':', idx + search.length()); - if (colon < 0) return null; - int i = colon + 1; - while (i < json.length() && Character.isWhitespace(json.charAt(i))) i++; - if (i >= json.length()) return null; - char c = json.charAt(i); - if (c == '"') { - i++; - StringBuilder sb = new StringBuilder(); - boolean escape = false; - while (i < json.length()) { - char ch = json.charAt(i++); - if (escape) { - sb.append(ch); - escape = false; - } else { - if (ch == '\\') escape = true; - else if (ch == '"') break; - else sb.append(ch); - } - } - return sb.toString(); - } else { - StringBuilder sb = new StringBuilder(); - while (i < json.length()) { - char ch = json.charAt(i); - if (ch == ',' || ch == '}' || ch == '\n' || ch == '\r') break; - sb.append(ch); - i++; - } - return sb.toString().trim(); - } - } - - private void sendHttpResponse(OutputStream out, String json, int code) throws IOException { - byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); - StringBuilder response = new StringBuilder(); - response.append("HTTP/1.1 ").append(code).append(" ").append(code == 200 ? "OK" : "ERROR").append("\r\n"); - response.append("Content-Type: application/json; charset=UTF-8\r\n"); - response.append("Access-Control-Allow-Origin: *\r\n"); - response.append("Content-Length: ").append(jsonBytes.length).append("\r\n"); - response.append("Connection: close\r\n\r\n"); - out.write(response.toString().getBytes(StandardCharsets.UTF_8)); - out.write(jsonBytes); - out.flush(); - } - - private String buildJsonString(Map data) { - StringBuilder sb = new StringBuilder("{"); - boolean first = true; - for (Map.Entry entry : data.entrySet()) { - if (!first) sb.append(","); - first = false; - sb.append("\"").append(escapeJson(entry.getKey())).append("\":").append(valueToString(entry.getValue())); - } - sb.append("}"); - return sb.toString(); - } - - private String valueToString(Object value) { - if (value == null) return "null"; - else if (value instanceof Boolean) return value.toString(); - else if (value instanceof Number) return value.toString(); - else if (value instanceof Map) { - @SuppressWarnings("unchecked") - Map m = (Map) value; - return buildJsonString(m); - } else if (value instanceof List) { - StringBuilder sb = new StringBuilder("["); - List list = (List) value; - for (int i = 0; i < list.size(); i++) { - if (i > 0) sb.append(","); - Object item = list.get(i); - if (item instanceof Map) sb.append(buildJsonString((Map) item)); - else if (item instanceof Number) sb.append(item.toString()); - else sb.append("\"").append(escapeJson(String.valueOf(item))).append("\""); - } - sb.append("]"); - return sb.toString(); - } - else return "\"" + escapeJson(String.valueOf(value)) + "\""; - } - - private String escapeJson(String s) { - if (s == null) return ""; - return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r"); - } -} \ No newline at end of file