diff --git a/StatusAPI/src/main/java/net/viper/status/UpdateChecker.java b/StatusAPI/src/main/java/net/viper/status/UpdateChecker.java new file mode 100644 index 0000000..ee509c5 --- /dev/null +++ b/StatusAPI/src/main/java/net/viper/status/UpdateChecker.java @@ -0,0 +1,211 @@ +package net.viper.status; + +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.chat.TextComponent; + +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; + +/** + * UpdateChecker: + * - Fragt die Gitea Releases API ab + * - Sucht in den Releases nach dem Asset "StatusAPI.jar" + * - Extrahiert die Version (tag_name bevorzugt, name als Fallback) + * - Cache die gefundene Version + URL (volatile fields) + */ +public class UpdateChecker implements Runnable { + + private final Plugin plugin; + private final String currentVersion; + private final int intervalHours; + + // Gitea Releases API für dein Repo + private final String apiUrl = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/Minecraft-BungeeCord-Status/releases"; + + // cached results (volatile für Thread-Sichtbarkeit) + private volatile String latestVersion = ""; + private volatile String latestUrl = ""; + + // Patterns for quick parse (we parse JSON as text; this is lightweight and robust for our needs) + 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); + private static final Pattern NAME_FIELD_PATTERN = Pattern.compile("\"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); + } + + @Override + public void run() { + // scheduled task calls checkNow + checkNow(); + } + + /** + * Führt einen sofortigen Check aus und updated cache (synchron for the calling thread). + */ + public void checkNow() { + try { + plugin.getLogger().info("Prüfe StatusAPI Releases via Gitea API..."); + + HttpURLConnection conn = (HttpURLConnection) new URL(apiUrl).openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("User-Agent", "StatusAPI-UpdateChecker/1.0"); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + + int code = conn.getResponseCode(); + if (code != 200) { + plugin.getLogger().warning("Gitea 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(); + + // Suche Release mit Asset "StatusAPI.jar" + String foundVersion = null; + String foundUrl = null; + + // Split releases by top-level objects: we simply find repeating {...} blocks and inspect them + // This is robust enough for Gitea JSON responses. + Pattern releasePattern = Pattern.compile("(?s)\\{.*?\\}"); + Matcher releaseMatcher = releasePattern.matcher(body); + while (releaseMatcher.find()) { + String block = releaseMatcher.group(); + + // extract asset names & download urls inside the block + java.util.List names = new java.util.ArrayList<>(); + java.util.List downloads = new java.util.ArrayList<>(); + + Matcher nm = ASSET_NAME_PATTERN.matcher(block); + while (nm.find()) names.add(nm.group(1)); + + Matcher dm = DOWNLOAD_PATTERN.matcher(block); + while (dm.find()) downloads.add(dm.group(1)); + + int pairs = Math.min(names.size(), downloads.size()); + for (int i = 0; i < pairs; i++) { + String name = names.get(i); + String dl = downloads.get(i); + if ("StatusAPI.jar".equalsIgnoreCase(name.trim())) { + // find tag_name or name field in the same block + Matcher tagM = TAG_NAME_PATTERN.matcher(block); + if (tagM.find()) { + foundVersion = tagM.group(1).trim(); + } else { + Matcher nameFieldM = NAME_FIELD_PATTERN.matcher(block); + if (nameFieldM.find()) { + foundVersion = nameFieldM.group(1).trim(); + } + } + foundUrl = dl; + break; + } + } + + if (foundUrl != null) break; + } + + if (foundVersion == null || foundUrl == null) { + plugin.getLogger().info("Kein Release mit Asset 'StatusAPI.jar' gefunden."); + return; + } + + // Normalize version + if (foundVersion.startsWith("v") || foundVersion.startsWith("V")) { + foundVersion = foundVersion.substring(1); + } + + plugin.getLogger().info("Gefundene Version: " + foundVersion + " (aktuell: " + currentVersion + ")"); + + // set cache + latestVersion = foundVersion; + latestUrl = foundUrl; + + } catch (Exception e) { + plugin.getLogger().log(Level.SEVERE, "Fehler beim Update-Check: " + e.getMessage(), e); + } + } + + /** + * Gibt die zuletzt gecachte Version (oder empty string). + */ + public String getLatestVersion() { + return latestVersion != null ? latestVersion : ""; + } + + /** + * Gibt die zuletzt gecachte download-URL (oder empty string). + */ + public String getLatestUrl() { + return latestUrl != null ? latestUrl : ""; + } + + /** + * Prüft ob die gecachte latestVersion größer ist als übergebene version. + */ + public boolean isUpdateAvailable(String currentVer) { + String lv = getLatestVersion(); + if (lv.isEmpty()) return false; + return compareVersions(lv, currentVer) > 0; + } + + /** + * Einfacher SemVer-Vergleich (1.2.3). Liefert >0 wenn a>b, 0 wenn gleich, <0 wenn a notify immediately + if (checker.isUpdateAvailable(currentVersion)) { + String lv = checker.getLatestVersion(); + String url = checker.getLatestUrl(); + player.sendMessage(new TextComponent("§6[StatusAPI] §eNeue Version verfügbar: §a" + lv)); + player.sendMessage(new TextComponent("§eDownload: §b" + url)); + return; + } + + // No cached update yet -> run an async check for this player and notify if found + ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { + // perform network call + checker.checkNow(); + + if (checker.isUpdateAvailable(currentVersion)) { + String lv = checker.getLatestVersion(); + String url = checker.getLatestUrl(); + + // ensure player is still online before sending message + ProxiedPlayer p = ProxyServer.getInstance().getPlayer(player.getUniqueId()); + if (p != null) { + p.sendMessage(new TextComponent("§6[StatusAPI] §eNeue Version verfügbar: §a" + lv)); + p.sendMessage(new TextComponent("§eDownload: §b" + url)); + } + } + }); + } +}