src/main/java/net/viper/status/StatusAPI.java aktualisiert

This commit is contained in:
2026-01-18 15:46:34 +00:00
parent 36363f09c2
commit 8277758e90

View File

@@ -10,71 +10,68 @@ import net.viper.status.stats.StatsModule;
import net.viper.status.modules.verify.VerifyModule; import net.viper.status.modules.verify.VerifyModule;
import net.viper.status.modules.globalchat.GlobalChatModule; import net.viper.status.modules.globalchat.GlobalChatModule;
import net.viper.status.modules.navigation.NavigationModule; import net.viper.status.modules.navigation.NavigationModule;
import net.viper.status.modules.commandblocker.CommandBlockerModule;
import net.viper.status.modules.broadcast.BroadcastModule;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringReader;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* StatusAPI - zentraler Bungee HTTP-Status- und Broadcast-Endpunkt * StatusAPI - zentraler Bungee HTTP-Status- und Broadcast-Endpunkt
*
* Ergänzungen:
* - BroadcastModule Registrierung
* - POST /broadcast UND POST / (Root) werden als Broadcasts behandelt
* - Unterstützung für prefix, prefixColor, messageColor Felder in JSON-Payload
* - Unterstützung für bracketColor (neu)
* - Unterstützung für scheduleTime (ms oder s), recur, clientScheduleId
* - API-Key Header (X-Api-Key) wird an BroadcastModule weitergereicht
*/ */
public class StatusAPI extends Plugin implements Runnable { public class StatusAPI extends Plugin implements Runnable {
private Thread thread; private Thread thread;
private int port = 9191; private int port = 9191;
// Das neue Modul-System
private ModuleManager moduleManager; private ModuleManager moduleManager;
// Alte Komponenten (UpdateChecker/FileDownloader bleiben hier, da sie Core-Updates steuern)
private UpdateChecker updateChecker; private UpdateChecker updateChecker;
private FileDownloader fileDownloader; private FileDownloader fileDownloader;
private Properties verifyProperties;
@Override @Override
public void onEnable() { public void onEnable() {
getLogger().info("StatusAPI Core wird initialisiert..."); getLogger().info("StatusAPI Core wird initialisiert...");
// 1. Ordner sicherstellen
if (!getDataFolder().exists()) { if (!getDataFolder().exists()) {
getDataFolder().mkdirs(); getDataFolder().mkdirs();
} }
// 2. Modul-System starten // Config mergen/updaten
mergeVerifyConfig();
moduleManager = new ModuleManager(); moduleManager = new ModuleManager();
// 3. MODULE REGISTRIEREN // Module registrieren
moduleManager.registerModule(new StatsModule()); // Statistik System laden moduleManager.registerModule(new StatsModule());
moduleManager.registerModule(new VerifyModule()); // Verify Modul moduleManager.registerModule(new VerifyModule());
moduleManager.registerModule(new GlobalChatModule()); // GlobalChat moduleManager.registerModule(new GlobalChatModule());
moduleManager.registerModule(new NavigationModule()); //Server Switcher moduleManager.registerModule(new NavigationModule());
// Broadcast Modul registrieren (neu) moduleManager.registerModule(new BroadcastModule());
moduleManager.registerModule(new net.viper.status.modules.broadcast.BroadcastModule()); moduleManager.registerModule(new CommandBlockerModule());
// 4. Alle Module aktivieren
moduleManager.enableAll(this); moduleManager.enableAll(this);
// 5. WebServer Thread starten // WebServer starten
getLogger().info("Starte Web-Server auf Port " + port + "..."); getLogger().info("Starte Web-Server auf Port " + port + "...");
thread = new Thread(this, "StatusAPI-HTTP-Server"); thread = new Thread(this, "StatusAPI-HTTP-Server");
thread.start(); thread.start();
// 6. Update System Initialisieren // Update System
String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0"; String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0";
updateChecker = new UpdateChecker(this, currentVersion, 6); updateChecker = new UpdateChecker(this, currentVersion, 6);
fileDownloader = new FileDownloader(this); fileDownloader = new FileDownloader(this);
@@ -82,16 +79,13 @@ public class StatusAPI extends Plugin implements Runnable {
File pluginFile = getFile(); File pluginFile = getFile();
File backupFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.bak"); File backupFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.bak");
// Backup Cleanup
if (backupFile.exists()) { if (backupFile.exists()) {
ProxyServer.getInstance().getScheduler().schedule(this, () -> { ProxyServer.getInstance().getScheduler().schedule(this, () -> {
if (backupFile.exists()) backupFile.delete(); if (backupFile.exists()) backupFile.delete();
},1, TimeUnit.MINUTES); },1, TimeUnit.MINUTES);
} }
// Sofortiger Check
checkAndMaybeUpdate(); checkAndMaybeUpdate();
// Regelmäßiger Check
ProxyServer.getInstance().getScheduler().schedule(this, this::checkAndMaybeUpdate, 6, 6, TimeUnit.HOURS); ProxyServer.getInstance().getScheduler().schedule(this, this::checkAndMaybeUpdate, 6, 6, TimeUnit.HOURS);
} }
@@ -109,7 +103,225 @@ public class StatusAPI extends Plugin implements Runnable {
} }
} }
// --- Update Logik (unverändert) --- /**
* Hilfsmethode um Property-Werte zu bereinigen (unescape).
* Entfernt z.B. den Backslash vor URLs wie https\://
*/
private String unescapePropertiesString(String input) {
if (input == null) return null;
try {
Properties p = new Properties();
p.load(new StringReader("dummy=" + input));
return p.getProperty("dummy");
} catch (IOException e) {
return input; // Fallback, falls Parsing fehlschlägt
}
}
// --- MERGE LOGIK ---
private void mergeVerifyConfig() {
try {
File file = new File(getDataFolder(), "verify.properties");
// 1. Das exakte Template definieren
List<String> templateLines = Arrays.asList(
"# _____ __ __ ___ ____ ____",
"# / ___// /_____ _/ /___ _______/ | / __ \\/ _/",
"# \\__ \\/ __/ __ `/ __/ / / / ___/ /| | / /_/ // / ",
"# ___/ / /_/ /_/ / /_/ /_/ (__ ) ___ |/ ____// / ",
"# /____/\\__/\\__,_/\\__/\\__,_/____/_/ |_/_/ /___/ ",
" ",
"# ===========================",
"# GLOBALCHAT AKTIVIERUNG",
"# ===========================",
"chat.enabled=false",
"",
"# ------------------------------",
"# Broadcast",
"# ------------------------------",
"",
"broadcast.enabled=false",
"broadcast.prefix=[Broadcast]",
"broadcast.prefix-color=&c",
"broadcast.message-color=&f",
"broadcast.format=%prefixColored% %messageColored%",
"# broadcast.format kann angepasst werden; nutze Platzhalter: %name%, %prefix%, %prefixColored%, %message%, %messageColored%, %type%",
"",
"# ===========================",
"# NAVIGATION / SERVER SWITCHER",
"# ===========================",
"# Hier kannst du das interne Navigationssystem aktivieren/deaktivieren.",
"# Wenn aktiviert, erstellt das Plugin automatisch Befehle basierend auf den Servernamen (z.B. /lobby, /survival).",
"navigation.enabled=false",
"",
"# ===========================",
"# WORDPRESS / VERIFY EINSTELLUNGEN",
"# ===========================",
"wp_verify_url=https://deine-wp-domain.tld",
"",
"# ===========================",
"# SERVER KONFIGURATION",
"# ===========================",
"# Hier legst du für jeden Server alles fest:",
"# 1. Den Anzeigenamen für den Chat (z.B. &bLobby)",
"# 2. Die Server ID für WordPress (z.B. id=1)",
"# 3. Das Secret für WordPress (z.B. secret=...)",
"",
"# Server 1: Lobby",
"server.lobby=&bLobby",
"server.lobby.id=1",
"server.lobby.secret=GeheimesWortFuerLobby123",
"",
"# Server 2: Survival",
"server.survival=&aSurvival",
"server.survival.id=2",
"server.survival.secret=GeheimesWortFuerSurvival456",
"",
"# Server 3: SkyBlock",
"server.skyblock=&dSkyBlock",
"server.skyblock.id=3",
"server.skyblock.secret=GeheimesWortFuerSkyBlock789",
"",
"# ===========================",
"# Manuelle Ränge (Overrides)",
"# ===========================",
"# Syntax: override.<Spieler-UUID> = <Gruppenname>",
"# WICHTIG: Die Gruppe (z.B. Owner) muss unten bei groupformat definiert sein!",
"",
"# Beispiel: Deinen UUID hier einfügen",
"override.uuid-hier-einfügen = Owner",
"override.uuid-hier-einfügen-2 = Admin",
"override.uuid-hier-einfügen-3 = Developer",
"",
"# ===========================",
"# Chat-Formate für Gruppen",
"# ===========================",
"",
"# Der Name hinter dem Punkt (z.B. Owner) muss exakt mit der LuckPerms Gruppe übereinstimmen.",
"# Nutze & für Farbcodes.",
"",
"# Ränge mit neuer Syntax: Rank || Spielerfarbe || Chatfarbe",
"# Beispiel: Rot (Rang) || Blau (Name) || Lila (Chat)",
"groupformat.owner=&c[Owner] || &b || &d",
"groupformat.admin=&4[Admin] || &9 || &c",
"groupformat.developer=&b[Dev] || &3 || &a",
"groupformat.premium=&6[Premium] || &e || &7",
"groupformat.spieler=&f[Spieler] || &7 || &8",
"",
"# ===========================",
"# COMMAND BLOCKER",
"# ===========================",
"commandblocker.enabled=true",
"commandblocker.bypass.permission=commandblocker.bypass"
);
// 2. Existierende Werte einlesen
Map<String, String> existingValues = new HashMap<>();
// Liste für Override-Keys, die nicht im Template stehen
List<Map.Entry<String, String>> pendingOverrides = new ArrayList<>();
if (file.exists()) {
List<String> existing = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
for (String line : existing) {
String trimmed = line.trim();
if (!trimmed.isEmpty() && !trimmed.startsWith("#") && trimmed.contains("=")) {
int idx = trimmed.indexOf('=');
String key = trimmed.substring(0, idx).trim();
String val = trimmed.substring(idx + 1).trim();
// Wert speichern, Escaping entfernen für sauberen Output
existingValues.put(key, unescapePropertiesString(val));
}
}
}
// 3. Alle Override-Keys aus der alten Config sammeln
for (Map.Entry<String, String> entry : existingValues.entrySet()) {
if (entry.getKey().startsWith("override.")) {
pendingOverrides.add(entry);
}
}
// 4. Datei Zeile für Zeile basierend auf Template schreiben
List<String> outputLines = new ArrayList<>();
Set<String> usedKeys = new HashSet<>();
for (int i = 0; i < templateLines.size(); i++) {
String line = templateLines.get(i);
String trimmed = line.trim();
// PRÜFUNG: Wenn wir am Anfang des nächsten Abschnitts sind ("Chat-Formate")
// Fügen wir die fehlenden Overrides VORHER ein.
if (trimmed.startsWith("#") && trimmed.contains("====")) {
if (i + 1 < templateLines.size()) {
String nextTrimmed = templateLines.get(i + 1).trim();
// Wenn wir kurz vor "Chat-Formate" stehen und noch Overrides offen sind -> Einfügen
if (nextTrimmed.contains("Chat-Formate") && !pendingOverrides.isEmpty()) {
outputLines.add("");
for (Map.Entry<String, String> ov : pendingOverrides) {
if (!usedKeys.contains(ov.getKey())) {
// Bereinigten Wert schreiben
outputLines.add(ov.getKey() + "=" + ov.getValue());
usedKeys.add(ov.getKey());
}
}
pendingOverrides.clear();
outputLines.add("");
}
}
}
// LOGIK: Template Zeile verarbeiten
if (!trimmed.startsWith("#") && !trimmed.isEmpty() && trimmed.contains("=")) {
String[] parts = trimmed.split("=", 2);
String key = parts[0].trim();
if (existingValues.containsKey(key)) {
// Wert aus alter Config übernehmen (bereinigt)
String cleanVal = unescapePropertiesString(existingValues.get(key));
outputLines.add(key + "=" + cleanVal);
usedKeys.add(key);
pendingOverrides.removeIf(e -> e.getKey().equals(key));
} else {
// Standardwert aus Template übernehmen
outputLines.add(line);
}
} else {
outputLines.add(line);
}
}
// Falls noch was übrig ist, am Ende anhängen
if (!pendingOverrides.isEmpty()) {
outputLines.add("");
for (Map.Entry<String, String> ov : pendingOverrides) {
outputLines.add(ov.getKey() + "=" + ov.getValue());
}
}
// 5. Datei schreiben
Files.write(file.toPath(), outputLines, StandardCharsets.UTF_8);
// 6. Properties laden
verifyProperties = new Properties();
try (FileInputStream fis = new FileInputStream(file)) {
verifyProperties.load(fis);
}
getLogger().info("verify.properties erfolgreich aktualisiert.");
} catch (IOException e) {
getLogger().severe("Fehler beim Merge der verify.properties: " + e.getMessage());
}
}
public Properties getVerifyProperties() {
synchronized (this) {
return verifyProperties;
}
}
// --- Update Logik ---
private void checkAndMaybeUpdate() { private void checkAndMaybeUpdate() {
try { try {
updateChecker.checkNow(); updateChecker.checkNow();
@@ -173,7 +385,6 @@ public class StatusAPI extends Plugin implements Runnable {
} }
// --- WebServer & JSON --- // --- WebServer & JSON ---
@Override @Override
public void run() { public void run() {
try (ServerSocket serverSocket = new ServerSocket(port)) { try (ServerSocket serverSocket = new ServerSocket(port)) {
@@ -192,15 +403,6 @@ public class StatusAPI extends Plugin implements Runnable {
} }
} }
/**
* Erweiterter HTTP-Handler:
* - GET weiterhin liefert Status JSON wie bisher
* - POST an /broadcast oder POST an / (Root) wird als Broadcast verarbeitet
* (Payload JSON mit message, type, prefix, prefixColor, bracketColor, messageColor)
* - Wenn Feld scheduleTime (ms oder s) vorhanden ist, wird Nachricht als geplante Nachricht registriert
* und an BroadcastModule.scheduleBroadcast weitergegeben.
* - POST /broadcast/cancel erwartet clientScheduleId im Body und ruft cancelScheduled(clientScheduleId) auf.
*/
private void handleConnection(Socket clientSocket) { private void handleConnection(Socket clientSocket) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8")); try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
OutputStream out = clientSocket.getOutputStream()) { OutputStream out = clientSocket.getOutputStream()) {
@@ -208,13 +410,11 @@ public class StatusAPI extends Plugin implements Runnable {
String inputLine = in.readLine(); String inputLine = in.readLine();
if (inputLine == null) return; if (inputLine == null) return;
// Request-Line zerlegen: METHOD PATH HTTP/VERSION
String[] reqParts = inputLine.split(" "); String[] reqParts = inputLine.split(" ");
if (reqParts.length < 2) return; if (reqParts.length < 2) return;
String method = reqParts[0].trim(); String method = reqParts[0].trim();
String path = reqParts[1].trim(); String path = reqParts[1].trim();
// Header einlesen (case-insensitive keys)
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
String line; String line;
while ((line = in.readLine()) != null && !line.isEmpty()) { while ((line = in.readLine()) != null && !line.isEmpty()) {
@@ -226,7 +426,7 @@ public class StatusAPI extends Plugin implements Runnable {
} }
} }
// --- POST /broadcast/cancel (optional) --- // --- POST /broadcast/cancel ---
if ("POST".equalsIgnoreCase(method) && (path.equalsIgnoreCase("/broadcast/cancel") || path.equalsIgnoreCase("/cancel"))) { if ("POST".equalsIgnoreCase(method) && (path.equalsIgnoreCase("/broadcast/cancel") || path.equalsIgnoreCase("/cancel"))) {
int contentLength = 0; int contentLength = 0;
if (headers.containsKey("content-length")) { if (headers.containsKey("content-length")) {
@@ -248,8 +448,8 @@ public class StatusAPI extends Plugin implements Runnable {
return; return;
} }
Object mod = moduleManager.getModule("BroadcastModule"); Object mod = moduleManager.getModule("BroadcastModule");
if (mod instanceof net.viper.status.modules.broadcast.BroadcastModule) { if (mod instanceof BroadcastModule) {
boolean ok = ((net.viper.status.modules.broadcast.BroadcastModule) mod).cancelScheduled(clientScheduleId); boolean ok = ((BroadcastModule) mod).cancelScheduled(clientScheduleId);
if (ok) sendHttpResponse(out, "{\"success\":true}", 200); if (ok) sendHttpResponse(out, "{\"success\":true}", 200);
else sendHttpResponse(out, "{\"success\":false,\"error\":\"not_found\"}", 404); else sendHttpResponse(out, "{\"success\":false,\"error\":\"not_found\"}", 404);
return; return;
@@ -259,7 +459,7 @@ public class StatusAPI extends Plugin implements Runnable {
} }
} }
// --- POST /broadcast oder POST / (Root) für register/send --- // --- POST /broadcast ---
if ("POST".equalsIgnoreCase(method) && ("/broadcast".equalsIgnoreCase(path) || "/".equals(path) || path.isEmpty())) { if ("POST".equalsIgnoreCase(method) && ("/broadcast".equalsIgnoreCase(path) || "/".equals(path) || path.isEmpty())) {
int contentLength = 0; int contentLength = 0;
if (headers.containsKey("content-length")) { if (headers.containsKey("content-length")) {
@@ -277,34 +477,28 @@ public class StatusAPI extends Plugin implements Runnable {
} }
String body = new String(bodyChars); String body = new String(bodyChars);
// Header X-Api-Key (case-insensitive)
String apiKeyHeader = headers.getOrDefault("x-api-key", headers.getOrDefault("x-apikey", "")); String apiKeyHeader = headers.getOrDefault("x-api-key", headers.getOrDefault("x-apikey", ""));
// parse minimal JSON fields we need
String message = extractJsonString(body, "message"); String message = extractJsonString(body, "message");
String type = extractJsonString(body, "type"); String type = extractJsonString(body, "type");
String prefix = extractJsonString(body, "prefix"); String prefix = extractJsonString(body, "prefix");
String prefixColor = extractJsonString(body, "prefixColor"); String prefixColor = extractJsonString(body, "prefixColor");
String bracketColor = extractJsonString(body, "bracketColor"); // HINZUGEFÜGT String bracketColor = extractJsonString(body, "bracketColor");
String messageColor = extractJsonString(body, "messageColor"); String messageColor = extractJsonString(body, "messageColor");
String sourceName = extractJsonString(body, "source"); String sourceName = extractJsonString(body, "source");
String scheduleTimeStr = extractJsonString(body, "scheduleTime"); // expecting millis OR seconds String scheduleTimeStr = extractJsonString(body, "scheduleTime");
String recur = extractJsonString(body, "recur"); String recur = extractJsonString(body, "recur");
String clientScheduleId = extractJsonString(body, "clientScheduleId"); String clientScheduleId = extractJsonString(body, "clientScheduleId");
if (sourceName == null || sourceName.isEmpty()) sourceName = "PulseCast"; if (sourceName == null || sourceName.isEmpty()) sourceName = "PulseCast";
if (type == null || type.isEmpty()) type = "global"; if (type == null || type.isEmpty()) type = "global";
// if scheduleTime present -> register schedule with BroadcastModule
if (scheduleTimeStr != null && !scheduleTimeStr.trim().isEmpty()) { if (scheduleTimeStr != null && !scheduleTimeStr.trim().isEmpty()) {
long scheduleMillis = 0L; long scheduleMillis = 0L;
try { try {
// scheduleTime may be numeric string (ms or s)
scheduleMillis = Long.parseLong(scheduleTimeStr.trim()); scheduleMillis = Long.parseLong(scheduleTimeStr.trim());
// if looks like seconds (less than 1e12), convert to ms
if (scheduleMillis < 1_000_000_000_000L) scheduleMillis = scheduleMillis * 1000L; if (scheduleMillis < 1_000_000_000_000L) scheduleMillis = scheduleMillis * 1000L;
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
// try to parse as double then convert
try { try {
double d = Double.parseDouble(scheduleTimeStr.trim()); double d = Double.parseDouble(scheduleTimeStr.trim());
long v = (long) d; long v = (long) d;
@@ -316,47 +510,12 @@ public class StatusAPI extends Plugin implements Runnable {
} }
} }
// BroadcastModule: scheduleBroadcast(timestampMillis, sourceName, message, type, apiKeyHeader, prefix, prefixColor, bracketColor, messageColor, recur, clientId)
try { try {
Object mod = moduleManager.getModule("BroadcastModule"); Object mod = moduleManager.getModule("BroadcastModule");
if (mod instanceof net.viper.status.modules.broadcast.BroadcastModule) { if (mod instanceof BroadcastModule) {
net.viper.status.modules.broadcast.BroadcastModule bm = BroadcastModule bm = (BroadcastModule) mod;
(net.viper.status.modules.broadcast.BroadcastModule) mod;
boolean ok = bm.scheduleBroadcast(scheduleMillis, sourceName, message, type, apiKeyHeader, boolean ok = bm.scheduleBroadcast(scheduleMillis, sourceName, message, type, apiKeyHeader,
prefix, prefixColor, bracketColor, messageColor, (recur == null ? "none" : recur), (clientScheduleId == null ? null : clientScheduleId)); // bracketColor HINZUGEFÜGT prefix, prefixColor, bracketColor, messageColor, (recur == null ? "none" : recur), (clientScheduleId == null ? null : clientScheduleId));
if (ok) {
sendHttpResponse(out, "{\"success\":true}", 200);
} else {
sendHttpResponse(out, "{\"success\":false,\"error\":\"rejected\"}", 403);
}
return;
} else {
sendHttpResponse(out, "{\"success\":false,\"error\":\"no_broadcast_module\"}", 500);
return;
}
} catch (Throwable t) {
t.printStackTrace();
sendHttpResponse(out, "{\"success\":false,\"error\":\"internal\"}", 500);
return;
}
}
// If no scheduleTime -> immediate broadcast
if (message == null || message.isEmpty()) {
String resp = "{\"success\":false,\"error\":\"missing_message\"}";
sendHttpResponse(out, resp, 400);
return;
}
try {
Object mod = moduleManager.getModule("BroadcastModule");
if (mod instanceof net.viper.status.modules.broadcast.BroadcastModule) {
net.viper.status.modules.broadcast.BroadcastModule bm =
(net.viper.status.modules.broadcast.BroadcastModule) mod;
boolean ok = bm.handleBroadcast(sourceName, message, type, apiKeyHeader, prefix, prefixColor, bracketColor, messageColor); // bracketColor HINZUGEFÜGT
if (ok) sendHttpResponse(out, "{\"success\":true}", 200); if (ok) sendHttpResponse(out, "{\"success\":true}", 200);
else sendHttpResponse(out, "{\"success\":false,\"error\":\"rejected\"}", 403); else sendHttpResponse(out, "{\"success\":false,\"error\":\"rejected\"}", 403);
@@ -372,12 +531,37 @@ public class StatusAPI extends Plugin implements Runnable {
} }
} }
// --- bisheriger GET-Handler (unverändert) --- if (message == null || message.isEmpty()) {
String resp = "{\"success\":false,\"error\":\"missing_message\"}";
sendHttpResponse(out, resp, 400);
return;
}
try {
Object mod = moduleManager.getModule("BroadcastModule");
if (mod instanceof BroadcastModule) {
BroadcastModule bm = (BroadcastModule) mod;
boolean ok = bm.handleBroadcast(sourceName, message, type, apiKeyHeader, prefix, prefixColor, bracketColor, messageColor);
if (ok) sendHttpResponse(out, "{\"success\":true}", 200);
else sendHttpResponse(out, "{\"success\":false,\"error\":\"rejected\"}", 403);
return;
} else {
sendHttpResponse(out, "{\"success\":false,\"error\":\"no_broadcast_module\"}", 500);
return;
}
} catch (Throwable t) {
t.printStackTrace();
sendHttpResponse(out, "{\"success\":false,\"error\":\"internal\"}", 500);
return;
}
}
// --- GET Handler ---
if (inputLine != null && inputLine.startsWith("GET")) { if (inputLine != null && inputLine.startsWith("GET")) {
Map<String, Object> data = new LinkedHashMap<>(); Map<String, Object> data = new LinkedHashMap<>();
data.put("online", true); data.put("online", true);
// Version & Info
String versionRaw = ProxyServer.getInstance().getVersion(); String versionRaw = ProxyServer.getInstance().getVersion();
String versionClean = (versionRaw != null && versionRaw.contains(":")) ? versionRaw.split(":")[2].trim() : versionRaw; String versionClean = (versionRaw != null && versionRaw.contains(":")) ? versionRaw.split(":")[2].trim() : versionRaw;
data.put("version", versionClean); data.put("version", versionClean);
@@ -390,7 +574,6 @@ public class StatusAPI extends Plugin implements Runnable {
} catch (Exception ignored) {} } catch (Exception ignored) {}
data.put("motd", motd); data.put("motd", motd);
// StatsModul holen (Service Locator)
StatsModule statsModule = (StatsModule) moduleManager.getModule("StatsModule"); StatsModule statsModule = (StatsModule) moduleManager.getModule("StatsModule");
boolean luckPermsEnabled = ProxyServer.getInstance().getPluginManager().getPlugin("LuckPerms") != null; boolean luckPermsEnabled = ProxyServer.getInstance().getPluginManager().getPlugin("LuckPerms") != null;
@@ -420,7 +603,6 @@ public class StatusAPI extends Plugin implements Runnable {
} }
playerInfo.put("prefix", prefix); playerInfo.put("prefix", prefix);
// Stats Integration via Modul
if (statsModule != null) { if (statsModule != null) {
PlayerStats ps = statsModule.getManager().getIfPresent(p.getUniqueId()); PlayerStats ps = statsModule.getManager().getIfPresent(p.getUniqueId());
if (ps != null) { if (ps != null) {
@@ -451,10 +633,6 @@ public class StatusAPI extends Plugin implements Runnable {
} }
} }
/**
* Minimaler JSON-String-Extractor (robust genug für einfache Payloads).
* Liefert null, wenn Schlüssel nicht gefunden oder kein String-Wert.
*/
private String extractJsonString(String json, String key) { private String extractJsonString(String json, String key) {
if (json == null || key == null) return null; if (json == null || key == null) return null;
String search = "\"" + key + "\""; String search = "\"" + key + "\"";
@@ -463,12 +641,11 @@ public class StatusAPI extends Plugin implements Runnable {
int colon = json.indexOf(':', idx + search.length()); int colon = json.indexOf(':', idx + search.length());
if (colon < 0) return null; if (colon < 0) return null;
int i = colon + 1; int i = colon + 1;
// skip spaces
while (i < json.length() && Character.isWhitespace(json.charAt(i))) i++; while (i < json.length() && Character.isWhitespace(json.charAt(i))) i++;
if (i >= json.length()) return null; if (i >= json.length()) return null;
char c = json.charAt(i); char c = json.charAt(i);
if (c == '"') { if (c == '"') {
i++; // skip opening quote i++;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
boolean escape = false; boolean escape = false;
while (i < json.length()) { while (i < json.length()) {
@@ -484,7 +661,6 @@ public class StatusAPI extends Plugin implements Runnable {
} }
return sb.toString(); return sb.toString();
} else { } else {
// not a quoted string -> read until comma or }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
while (i < json.length()) { while (i < json.length()) {
char ch = json.charAt(i); char ch = json.charAt(i);