Dateien nach "StatusAPI/src/main/java/net/viper/status" hochladen

This commit is contained in:
2026-01-02 21:05:30 +00:00
parent 5adb7f5752
commit 187abfcbf5
2 changed files with 278 additions and 0 deletions

View File

@@ -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<String> names = new java.util.ArrayList<>();
java.util.List<String> 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<b.
*/
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);
}
}
/**
* Convenience: prüft cached state und falls update vorhanden, notifies online players with given permission(s).
*/
public void notifyOnlineOpsIfAvailable(String[] perms) {
String lv = getLatestVersion();
String url = getLatestUrl();
if (lv.isEmpty()) return;
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
try {
for (String perm : perms) {
if (p.hasPermission(perm)) {
p.sendMessage(new TextComponent("§6[StatusAPI] §eNeue Version verfügbar: §a" + lv));
p.sendMessage(new TextComponent("§eDownload: §b" + url));
break;
}
}
} catch (Exception ignored) {}
}
}
}

View File

@@ -0,0 +1,67 @@
package net.viper.status;
import net.md_5.bungee.api.ProxyServer;
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.event.EventHandler;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Plugin;
import java.util.concurrent.TimeUnit;
/**
* Listener: Benachrichtigt OP-Spieler beim Login, falls ein Update bekannt ist oder führt
* asynchronen Einzelcheck durch und benachrichtigt den Spieler danach.
*/
public class UpdateListener implements Listener {
private final Plugin plugin;
private final UpdateChecker checker;
private final String[] notifyPerms = new String[] { "statusapi.update.notify", "statusapi.notify", "bungeecord.command.alert" };
public UpdateListener(Plugin plugin, UpdateChecker checker) {
this.plugin = plugin;
this.checker = checker;
}
@EventHandler
public void onPostLogin(PostLoginEvent event) {
ProxiedPlayer player = event.getPlayer();
// Only notify players with the notify permission(s)
boolean hasPerm = false;
for (String perm : notifyPerms) {
if (player.hasPermission(perm)) { hasPerm = true; break; }
}
if (!hasPerm) return;
String currentVersion = plugin.getDescription() != null ? plugin.getDescription().getVersion() : "0.0.0";
// If we already have a cached update and it's newer -> 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));
}
}
});
}
}