From 5597f99a871d42e44e622e44e0362b78d73970c1 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Thu, 8 Jan 2026 14:57:53 +0000 Subject: [PATCH] Dateien nach "src/main/java/net/viper/status" hochladen --- .../java/net/viper/status/FileDownloader.java | 42 +++ src/main/java/net/viper/status/StatusAPI.java | 310 ++++++++++++++++++ .../java/net/viper/status/UpdateChecker.java | 144 ++++++++ 3 files changed, 496 insertions(+) create mode 100644 src/main/java/net/viper/status/FileDownloader.java create mode 100644 src/main/java/net/viper/status/StatusAPI.java create mode 100644 src/main/java/net/viper/status/UpdateChecker.java diff --git a/src/main/java/net/viper/status/FileDownloader.java b/src/main/java/net/viper/status/FileDownloader.java new file mode 100644 index 0000000..da566c8 --- /dev/null +++ b/src/main/java/net/viper/status/FileDownloader.java @@ -0,0 +1,42 @@ +package net.viper.status; + +import net.md_5.bungee.api.plugin.Plugin; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.function.Consumer; + +public class FileDownloader { + private final Plugin plugin; + + public FileDownloader(Plugin plugin) { this.plugin = plugin; } + + public void downloadFile(String urlString, File destination, Runnable onSuccess) { + plugin.getProxy().getScheduler().runAsync(plugin, () -> { + BufferedInputStream bufferedInputStream = null; + FileOutputStream fileOutputStream = null; + try { + URL url = new URL(urlString); + bufferedInputStream = new BufferedInputStream(url.openStream()); + fileOutputStream = new FileOutputStream(destination); + byte[] buffer = new byte[1024]; + int count; + while ((count = bufferedInputStream.read(buffer, 0, 1024)) != -1) { + fileOutputStream.write(buffer, 0, count); + } + fileOutputStream.close(); + bufferedInputStream.close(); + plugin.getProxy().getScheduler().schedule(plugin, onSuccess, 1, java.util.concurrent.TimeUnit.MILLISECONDS); + } catch (Throwable e) { + plugin.getLogger().warning("Download fehlgeschlagen: " + e.getMessage()); + if (destination.exists()) destination.delete(); + } finally { + if (fileOutputStream != null) try { fileOutputStream.close(); } catch (IOException ignored) {} + if (bufferedInputStream != null) try { bufferedInputStream.close(); } catch (IOException ignored) {} + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/net/viper/status/StatusAPI.java b/src/main/java/net/viper/status/StatusAPI.java new file mode 100644 index 0000000..a84addc --- /dev/null +++ b/src/main/java/net/viper/status/StatusAPI.java @@ -0,0 +1,310 @@ +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"); + } +} \ No newline at end of file diff --git a/src/main/java/net/viper/status/UpdateChecker.java b/src/main/java/net/viper/status/UpdateChecker.java new file mode 100644 index 0000000..c46733b --- /dev/null +++ b/src/main/java/net/viper/status/UpdateChecker.java @@ -0,0 +1,144 @@ +package net.viper.status; + +import net.md_5.bungee.api.plugin.Plugin; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UpdateChecker { + + private final Plugin plugin; + private final String currentVersion; + private final int intervalHours; + + // Neue Domain und korrekter API-Pfad für Releases + private final String apiUrl = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/StatusAPI/releases"; + + private volatile String latestVersion = ""; + private volatile String latestUrl = ""; + + private static final Pattern ASSET_NAME_PATTERN = Pattern.compile("\"name\"\\s*:\\s*\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); + private static final Pattern DOWNLOAD_PATTERN = Pattern.compile("\"browser_download_url\"\\s*:\\s*\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); + private static final Pattern TAG_NAME_PATTERN = Pattern.compile("\"tag_name\"\\s*:\\s*\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); + + public UpdateChecker(Plugin plugin, String currentVersion, int intervalHours) { + this.plugin = plugin; + this.currentVersion = currentVersion != null ? currentVersion : "0.0.0"; + this.intervalHours = Math.max(1, intervalHours); + } + + public void checkNow() { + try { + HttpURLConnection conn = (HttpURLConnection) new URL(apiUrl).openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("User-Agent", "StatusAPI-UpdateChecker/2.0"); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + + int code = conn.getResponseCode(); + if (code != 200) { + plugin.getLogger().warning("Gitea/Forgejo API nicht erreichbar (HTTP " + code + ")"); + return; + } + + StringBuilder sb = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"))) { + String line; + while ((line = br.readLine()) != null) sb.append(line).append("\n"); + } + + String body = sb.toString(); + + // Neu: Da die API ein JSON-Array von Releases zurückgibt, nehmen wir das erste (neueste) Release + // Wir suchen den ersten Block mit tag_name + String foundVersion = null; + Matcher tagM = TAG_NAME_PATTERN.matcher(body); + if (tagM.find()) { + foundVersion = tagM.group(1).trim(); + } + + if (foundVersion == null) { + plugin.getLogger().warning("Keine Version (Tag) im Release gefunden."); + return; + } + if (foundVersion.startsWith("v") || foundVersion.startsWith("V")) { + foundVersion = foundVersion.substring(1); + } + + String foundUrl = null; + + // Wir suchen im gesamten Body nach der JAR-Datei "StatusAPI.jar" + // Da das neueste Release zuerst kommt, brechen wir ab, sobald wir eine passende JAR finden + Matcher nameMatcher = ASSET_NAME_PATTERN.matcher(body); + Matcher downloadMatcher = DOWNLOAD_PATTERN.matcher(body); + + java.util.List names = new java.util.ArrayList<>(); + java.util.List urls = new java.util.ArrayList<>(); + + while (nameMatcher.find()) { + names.add(nameMatcher.group(1)); + } + while (downloadMatcher.find()) { + urls.add(downloadMatcher.group(1)); + } + + int pairs = Math.min(names.size(), urls.size()); + for (int i = 0; i < pairs; i++) { + String name = names.get(i).trim(); + String url = urls.get(i); + if ("StatusAPI.jar".equalsIgnoreCase(name)) { + foundUrl = url; + break; // Erste (also neueste) passende JAR nehmen + } + } + + if (foundUrl == null) { + plugin.getLogger().warning("Keine StatusAPI.jar im neuesten Release gefunden."); + return; + } + + plugin.getLogger().info("Gefundene Version: " + foundVersion + " (Aktuell: " + currentVersion + ")"); + latestVersion = foundVersion; + latestUrl = foundUrl; + + } catch (Exception e) { + plugin.getLogger().log(Level.SEVERE, "Fehler beim Update-Check: " + e.getMessage(), e); + } + } + + public String getLatestVersion() { + return latestVersion != null ? latestVersion : ""; + } + + public String getLatestUrl() { + return latestUrl != null ? latestUrl : ""; + } + + public boolean isUpdateAvailable(String currentVer) { + String lv = getLatestVersion(); + if (lv.isEmpty()) return false; + return compareVersions(lv, currentVer) > 0; + } + + private int compareVersions(String a, String b) { + try { + String[] aa = a.split("\\."); + String[] bb = b.split("\\."); + int len = Math.max(aa.length, bb.length); + for (int i = 0; i < len; i++) { + int ai = i < aa.length ? Integer.parseInt(aa[i].replaceAll("\\D", "")) : 0; + int bi = i < bb.length ? Integer.parseInt(bb[i].replaceAll("\\D", "")) : 0; + if (ai != bi) return Integer.compare(ai, bi); + } + return 0; + } catch (Exception ex) { + return a.compareTo(b); + } + } +} \ No newline at end of file