From 3a7534c4eb56d75847284f363118c36780cec1fd Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sat, 3 Jan 2026 11:30:38 +0000 Subject: [PATCH] StatusAPI/src/main/java/net/viper/status/StatusAPI.java aktualisiert --- .../main/java/net/viper/status/StatusAPI.java | 268 ++++++++++-------- 1 file changed, 155 insertions(+), 113 deletions(-) diff --git a/StatusAPI/src/main/java/net/viper/status/StatusAPI.java b/StatusAPI/src/main/java/net/viper/status/StatusAPI.java index 240b7b9..7feee36 100644 --- a/StatusAPI/src/main/java/net/viper/status/StatusAPI.java +++ b/StatusAPI/src/main/java/net/viper/status/StatusAPI.java @@ -1,15 +1,16 @@ package net.viper.status; -import net.luckperms.api.LuckPerms; -import net.luckperms.api.LuckPermsProvider; -import net.luckperms.api.model.user.User; -import net.luckperms.api.query.QueryOptions; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Plugin; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.*; @@ -20,6 +21,7 @@ public class StatusAPI extends Plugin implements Runnable { private Thread thread; private int port = 9191; private UpdateChecker updateChecker; + private FileDownloader fileDownloader; @Override public void onEnable() { @@ -30,18 +32,33 @@ public class StatusAPI extends Plugin implements Runnable { thread = new Thread(this, "StatusAPI-HTTP-Server"); thread.start(); - // Start UpdateChecker: initialer Check (async) + regelmäßiger Schedule String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0"; updateChecker = new UpdateChecker(this, currentVersion, 6); // 6 Stunden Intervall + fileDownloader = new FileDownloader(this); - // initialer sofortiger Start (async) - ProxyServer.getInstance().getScheduler().runAsync(this, () -> updateChecker.checkNow()); + File pluginFile = getFile(); + File backupFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.bak"); - // planmäßiger Intervall (alle 6 Stunden) - ProxyServer.getInstance().getScheduler().schedule(this, updateChecker, 6, 6, TimeUnit.HOURS); + // --- AUTOMATISCHES BACKUP-CLEANUP --- + // Falls ein altes Update (.bak) im Ordner liegt, löschen wir es nach 1 Minute Startzeit. + // Wenn der Server kurz crashen würde, hast du noch 60 Sekunden Zeit, ihn zu stoppen, + // damit das Backup erhalten bleibt. Läuft er stabil, wird der Platz freigegeben. + if (backupFile.exists()) { + ProxyServer.getInstance().getScheduler().schedule(this, () -> { + if (backupFile.exists()) { + if (backupFile.delete()) { + getLogger().info("Altes Backup (.bak) wurde erfolgreich gelöscht."); + } + } + }, 1, TimeUnit.MINUTES); + } + // --------------------------------------- - // Register join listener to notify OPs on login - ProxyServer.getInstance().getPluginManager().registerListener(this, new UpdateListener(this, updateChecker)); + // Sofortiger Start-Check + checkAndMaybeUpdate(); + + // Regelmäßiger Check (alle 6 Stunden) + ProxyServer.getInstance().getScheduler().schedule(this, this::checkAndMaybeUpdate, 6, 6, TimeUnit.HOURS); } @Override @@ -49,9 +66,99 @@ public class StatusAPI extends Plugin implements Runnable { getLogger().info("Stoppe Web-Server..."); if (thread != null) { thread.interrupt(); + try { thread.join(1000); } catch (InterruptedException ignored) {} + } + } + + /** + * Prüft Update und startet Download falls nötig. + */ + private void checkAndMaybeUpdate() { + try { + updateChecker.checkNow(); + String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0"; + + if (updateChecker.isUpdateAvailable(currentVersion)) { + String newVersion = updateChecker.getLatestVersion(); + String url = updateChecker.getLatestUrl(); + getLogger().warning("----------------------------------------"); + getLogger().warning("Neue Version verfügbar: " + newVersion); + getLogger().warning("Starte automatisches Update..."); + getLogger().warning("----------------------------------------"); + + // Download Starten + File pluginFile = getFile(); + File newFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.new"); + + fileDownloader.downloadFile(url, newFile, () -> { + // Callback: Wenn Download erfolgreich + triggerUpdateScript(pluginFile, newFile); + }); + } + } catch (Exception e) { + getLogger().severe("Fehler beim Update-Check: " + e.getMessage()); + } + } + + /** + * Erstellt ein externes Batch-Skript, startet es und stoppt den Server. + * Das Skript führt den Datei-Tausch durch, wenn der Server weg ist. + */ + private void triggerUpdateScript(File currentFile, File newFile) { + try { + File pluginsFolder = currentFile.getParentFile(); + // Wir legen das Skript neben die Haupt-JAR (root) damit es nicht im Plugin-Ordner liegt + File rootFolder = pluginsFolder.getParentFile(); + File batFile = new File(rootFolder, "StatusAPI_Update_" + System.currentTimeMillis() + ".bat"); + + // Batch Inhalt + // 1. Wartet 5 Sekunden (Server fährt runter) + // 2. Geht in den Plugin Ordner + // 3. Sichert .jar zu .bak + // 4. Benennt .new zu .jar um + // 5. Löscht sich selbst + String batContent = "@echo off\n" + + "echo Bitte warten, der Server f\"ahrt herunter...\n" + + "timeout /t 5 /nobreak >nul\n" + + "cd /d \"" + pluginsFolder.getAbsolutePath().replace("\\", "/") + "\"\n" + + "echo Fuehre Datei-Tausch durch...\n" + + "if exist StatusAPI.jar.bak del StatusAPI.jar.bak\n" + + "if exist StatusAPI.jar (\n" + + " ren StatusAPI.jar StatusAPI.jar.bak\n" + + ")\n" + + "if exist StatusAPI.new.jar (\n" + + " ren StatusAPI.new.jar StatusAPI.jar\n" + + " echo Update erfolgreich!\n" + + ") else (\n" + + " echo FEHLER: StatusAPI.new.jar nicht gefunden!\n" + + " pause\n" + + ")\n" + + "del \"%~f0\""; + + try (PrintWriter out = new PrintWriter(batFile)) { + out.println(batContent); + } + + // Spieler kicken try { - thread.join(1000); - } catch (InterruptedException ignored) {} + for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { + p.disconnect("§cServer f\"ahrt f\"ur ein Update neu herunter. Bitte etwas warten."); + } + } catch (Exception ignored) {} + + // Skript starten + getLogger().info("Starte Update-Skript im Hintergrund..."); + try { + Runtime.getRuntime().exec("cmd /c start \"Update_Proc\" \"" + batFile.getAbsolutePath() + "\""); + } catch (IOException e) { + getLogger().warning("Konnte Skript nicht starten. Update wird manuell ben\"otigt."); + } + + // Server stoppen + ProxyServer.getInstance().stop(); + + } catch (Exception e) { + getLogger().severe("Fehler beim Vorbereiten des Updates: " + e.getMessage()); } } @@ -59,189 +166,124 @@ public class StatusAPI extends Plugin implements Runnable { 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) { - // Loop Check (timeout) - erlaubt Unterbrechungsschleife - } catch (IOException e) { + } 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 auf Port " + port + ": " + e.getMessage()); - e.printStackTrace(); } } private void handleConnection(Socket clientSocket) { - BufferedReader in = null; - OutputStream out = null; - - try { - in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8")); - out = clientSocket.getOutputStream(); + try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8")); + OutputStream out = clientSocket.getOutputStream()) { String inputLine = in.readLine(); - if (inputLine != null && inputLine.startsWith("GET")) { - Map data = new LinkedHashMap<>(); data.put("online", true); - // --- VERSION CLEANUP START --- + // Version String versionRaw = ProxyServer.getInstance().getVersion(); String versionClean = versionRaw; - if (versionRaw != null && versionRaw.contains(":")) { String[] parts = versionRaw.split(":"); - if (parts.length >= 3) { - versionClean = parts[2].trim(); - } + if (parts.length >= 3) versionClean = parts[2].trim(); } data.put("version", versionClean); - // --- VERSION CLEANUP ENDE --- - data.put("max_players", String.valueOf(ProxyServer.getInstance().getConfig().getPlayerLimit())); + // Motd String motd = "BungeeCord"; try { Iterator it = ProxyServer.getInstance().getConfig().getListeners().iterator(); if (it.hasNext()) { ListenerInfo listener = it.next(); - if (listener != null && listener.getMotd() != null) { - motd = listener.getMotd(); - } + if (listener != null && listener.getMotd() != null) motd = listener.getMotd(); } - } catch (Exception e) { - // Fallback bleibt "BungeeCord" - } + } catch (Exception ignored) {} data.put("motd", motd); - // --- LUCKPERMS INTEGRATION START --- - // LuckPerms API über Provider holen (Verhindert ClassCastException) - LuckPerms luckPermsApi = null; - try { - luckPermsApi = LuckPermsProvider.get(); - } catch (IllegalStateException e) { - // LuckPerms ist nicht geladen - } - + // LuckPerms (Optional) + 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()); - - // Prefix abfragen, falls LP gefunden wurde String prefix = ""; - if (luckPermsApi != null) { - User user = luckPermsApi.getUserManager().getUser(p.getUniqueId()); - if (user != null) { - // Context bestimmen (Global oder Server-spezifisch) - QueryOptions queryOptions = luckPermsApi.getContextManager().getQueryOptions(user) - .orElse(QueryOptions.defaultContextualOptions()); - - String lpPrefix = user.getCachedData().getMetaData(queryOptions).getPrefix(); - if (lpPrefix != null) { - prefix = lpPrefix; + 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); playersList.add(playerInfo); } data.put("players", playersList); - // --- LUCKPERMS INTEGRATION ENDE --- + // Response String json = buildJsonString(data); byte[] jsonBytes = json.getBytes("UTF-8"); - - // HTTP Response mit korrekter Byte-Length 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"); - response.append("\r\n"); - - // Header senden + response.append("Connection: close\r\n\r\n"); out.write(response.toString().getBytes("UTF-8")); - // Body senden out.write(jsonBytes); out.flush(); - } else { - // Ungültige Anfrage -> 400 - String resp = "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"; - out.write(resp.getBytes("UTF-8")); - out.flush(); } } catch (Exception e) { getLogger().severe("Fehler beim Verarbeiten der Anfrage: " + e.getMessage()); - e.printStackTrace(); - } finally { - // Sauber aufräumen - try { - if (out != null) out.close(); - if (in != null) in.close(); - if (clientSocket != null && !clientSocket.isClosed()) { - clientSocket.close(); - } - } catch (IOException e) { - // Ignorieren - } } } - /** - * Rekursiver JSON Builder, der nun auch Listen von Objekten (Maps) verarbeiten kann. - */ 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("\":"); - - // Wert verarbeiten (String, Boolean, List oder Map) - sb.append(valueToString(entry.getValue())); + sb.append("\"").append(escapeJson(entry.getKey())).append("\":").append(valueToString(entry.getValue())); } sb.append("}"); return sb.toString(); } - /** - * Hilfsmethode, um verschiedene Objekttypen in JSON-Strings umzuwandeln. - */ private String valueToString(Object value) { - if (value == null) { - return "null"; - } else if (value instanceof Boolean) { - return value.toString(); - } else if (value instanceof List) { + if (value == null) return "null"; + else if (value instanceof Boolean) return value.toString(); + 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); - // Wenn es eine Map ist (Player-Objekt), rufen wir buildJsonString rekursiv auf - if (item instanceof Map) { - sb.append(buildJsonString((Map) item)); - } else { - // Andernfalls String behandeln - sb.append("\"").append(escapeJson(String.valueOf(item))).append("\""); - } + if (item instanceof Map) sb.append(buildJsonString((Map) item)); + else sb.append("\"").append(escapeJson(String.valueOf(item))).append("\""); } sb.append("]"); return sb.toString(); - } else { - // Standard String Behandlung - return "\"" + escapeJson(String.valueOf(value)) + "\""; } + else return "\"" + escapeJson(String.valueOf(value)) + "\""; } private String escapeJson(String s) {