package net.viper.status; 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 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 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.*; import java.util.concurrent.TimeUnit; public class StatusAPI extends Plugin implements Runnable { private Thread thread; private int port = 9191; // Das neue Modul-System private ModuleManager moduleManager; // Alte Komponenten (UpdateChecker/FileDownloader bleiben hier, da sie Core-Updates steuern) private UpdateChecker updateChecker; private FileDownloader fileDownloader; @Override public void onEnable() { getLogger().info("StatusAPI Core wird initialisiert..."); // 1. Ordner sicherstellen if (!getDataFolder().exists()) { getDataFolder().mkdirs(); } // 2. Modul-System starten moduleManager = new ModuleManager(); // 3. MODULE REGISTRIEREN (Hier erweiterst du die API in Zukunft!) // Um ein neues Feature hinzuzufügen, erstelle eine Klasse, die 'Module' implementiert // und füge hier eine Zeile hinzu: moduleManager.registerModule(new MeinNeuesModul()); moduleManager.registerModule(new StatsModule()); // Statistik System laden moduleManager.registerModule(new VerifyModule()); // Verify Modul moduleManager.registerModule(new GlobalChatModule()); // GlobalChat moduleManager.registerModule(new NavigationModule()); //Server Switcher // 4. Alle Module aktivieren moduleManager.enableAll(this); // 5. WebServer Thread starten getLogger().info("Starte Web-Server auf Port " + port + "..."); thread = new Thread(this, "StatusAPI-HTTP-Server"); thread.start(); // 6. Update System Initialisieren String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0"; updateChecker = new UpdateChecker(this, currentVersion, 6); fileDownloader = new FileDownloader(this); File pluginFile = getFile(); File backupFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.bak"); // Backup Cleanup if (backupFile.exists()) { ProxyServer.getInstance().getScheduler().schedule(this, () -> { if (backupFile.exists()) backupFile.delete(); }, 1, TimeUnit.MINUTES); } // Sofortiger Check checkAndMaybeUpdate(); // Regelmäßiger Check ProxyServer.getInstance().getScheduler().schedule(this, this::checkAndMaybeUpdate, 6, 6, TimeUnit.HOURS); } @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) {} } } // --- Update Logik (unverändert) --- 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("----------------------------------------"); File pluginFile = getFile(); File newFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.new"); fileDownloader.downloadFile(url, newFile, () -> triggerUpdateScript(pluginFile, newFile)); } } catch (Exception e) { getLogger().severe("Fehler beim Update-Check: " + e.getMessage()); } } private void triggerUpdateScript(File currentFile, File newFile) { try { File pluginsFolder = currentFile.getParentFile(); File rootFolder = pluginsFolder.getParentFile(); File batFile = new File(rootFolder, "StatusAPI_Update_" + System.currentTimeMillis() + ".bat"); String batContent = "@echo off\n" + "echo Bitte warten, der Server fährt 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); } for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { p.disconnect("§cServer fährt für ein Update neu herunter. Bitte etwas warten."); } getLogger().info("Starte Update-Skript..."); Runtime.getRuntime().exec("cmd /c start \"Update_Proc\" \"" + batFile.getAbsolutePath() + "\""); ProxyServer.getInstance().stop(); } catch (Exception e) { getLogger().severe("Fehler beim Vorbereiten des Updates: " + e.getMessage()); } } // --- 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 && inputLine.startsWith("GET")) { Map data = new LinkedHashMap<>(); data.put("online", true); // Version & Info 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); // StatsModul holen (Service Locator) 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); // Stats Integration via Modul 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 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"); } }