diff --git a/src/main/java/net/viper/status/modules/verify/VerifyModule.java b/src/main/java/net/viper/status/modules/verify/VerifyModule.java new file mode 100644 index 0000000..f065a12 --- /dev/null +++ b/src/main/java/net/viper/status/modules/verify/VerifyModule.java @@ -0,0 +1,248 @@ +package net.viper.status.modules.verify; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.api.plugin.Plugin; +import net.viper.status.module.Module; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * VerifyModule: Multi-Server Support. + * Liest pro Server die passende ID und das Secret aus der verify.properties. + */ +public class VerifyModule implements Module { + + private String wpVerifyUrl; + // Speichert für jeden Servernamen (z.B. "Lobby") die passende Konfiguration + private final Map serverConfigs = new HashMap<>(); + + @Override + public String getName() { + return "VerifyModule"; + } + + @Override + public void onEnable(Plugin plugin) { + loadConfig(plugin); + + // Befehl registrieren + ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new VerifyCommand()); + + plugin.getLogger().info("VerifyModule aktiviert. " + serverConfigs.size() + " Server-Konfigurationen geladen."); + } + + @Override + public void onDisable(Plugin plugin) { + // Befehl muss nicht manuell entfernt werden, BungeeCord übernimmt das beim Plugin-Stop + } + + // --- Konfiguration Laden & Kopieren --- + private void loadConfig(Plugin plugin) { + String fileName = "verify.properties"; + File configFile = new File(plugin.getDataFolder(), fileName); + Properties props = new Properties(); + + // 1. Datei kopieren, falls sie noch nicht existiert + if (!configFile.exists()) { + plugin.getDataFolder().mkdirs(); + try (InputStream in = plugin.getResourceAsStream(fileName); + OutputStream out = new FileOutputStream(configFile)) { + if (in == null) { + plugin.getLogger().warning("Standard-config '" + fileName + "' nicht in JAR gefunden. Erstelle manuell."); + return; + } + byte[] buffer = new byte[1024]; + int length; + while ((length = in.read(buffer)) > 0) { + out.write(buffer, 0, length); + } + plugin.getLogger().info("Konfigurationsdatei '" + fileName + "' erstellt."); + } catch (Exception e) { + plugin.getLogger().severe("Fehler beim Erstellen der Config: " + e.getMessage()); + return; + } + } + + // 2. Eigentliche Config laden + try (InputStream in = new FileInputStream(configFile)) { + props.load(in); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + // Globale URL + this.wpVerifyUrl = props.getProperty("wp_verify_url", "https://deine-wp-domain.tld"); + + // Server-Configs parsen (z.B. server.Lobby.id) + this.serverConfigs.clear(); + for (String key : props.stringPropertyNames()) { + if (key.startsWith("server.")) { + // Key Struktur: server..id oder .secret + String[] parts = key.split("\\."); + if (parts.length == 3) { + String serverName = parts[1]; + String type = parts[2]; + + // Eintrag in der Map erstellen oder holen + ServerConfig config = serverConfigs.computeIfAbsent(serverName, k -> new ServerConfig()); + + if ("id".equalsIgnoreCase(type)) { + try { + config.serverId = Integer.parseInt(props.getProperty(key)); + } catch (NumberFormatException e) { + plugin.getLogger().warning("Ungültige Server ID für " + serverName); + } + } else if ("secret".equalsIgnoreCase(type)) { + config.sharedSecret = props.getProperty(key); + } + } + } + } + } + + // --- Hilfsklasse für die Daten eines Servers --- + private static class ServerConfig { + int serverId = 0; + String sharedSecret = ""; + } + + // --- Die Command Klasse --- + private class VerifyCommand extends Command { + + public VerifyCommand() { + super("verify"); + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof ProxiedPlayer)) { + sender.sendMessage(ChatColor.RED + "Nur Spieler können diesen Befehl benutzen."); + return; + } + + ProxiedPlayer p = (ProxiedPlayer) sender; + if (args.length != 1) { + p.sendMessage(ChatColor.YELLOW + "Benutzung: /verify "); + return; + } + + // --- WICHTIG: Servernamen ermitteln --- + String serverName = p.getServer().getInfo().getName(); + + // Konfiguration für diesen Server laden + ServerConfig config = serverConfigs.get(serverName); + + // Check ob Konfig existiert + if (config == null || config.serverId == 0 || config.sharedSecret.isEmpty()) { + p.sendMessage(ChatColor.RED + "✗ Dieser Server ist nicht in der Verify-Konfiguration hinterlegt."); + p.sendMessage(ChatColor.GRAY + "Aktueller Servername: " + ChatColor.WHITE + serverName); + p.sendMessage(ChatColor.GRAY + "Bitte kontaktiere einen Admin."); + return; + } + + String token = args[0].trim(); + String playerName = p.getName(); + + HttpURLConnection conn = null; + try { + Charset utf8 = Charset.forName("UTF-8"); + // Wir signieren Name + Token mit dem SERVER-SPECIFISCHEN Secret + String signature = hmacSHA256(playerName + token, config.sharedSecret, utf8); + + // Payload aufbauen mit der SERVER-SPECIFISCHEN ID + String payload = "{\"player\":\"" + escapeJson(playerName) + "\",\"token\":\"" + escapeJson(token) + "\",\"server_id\":" + config.serverId + ",\"signature\":\"" + signature + "\"}"; + + URL url = new URL(wpVerifyUrl + "/wp-json/mc-gallery/v1/verify"); + conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(5000); + conn.setReadTimeout(7000); + conn.setDoOutput(true); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json; charset=utf-8"); + + try (OutputStream os = conn.getOutputStream()) { + os.write(payload.getBytes(utf8)); + } + + int code = conn.getResponseCode(); + String resp; + + if (code >= 200 && code < 300) { + resp = streamToString(conn.getInputStream(), utf8); + } else { + resp = streamToString(conn.getErrorStream(), utf8); + } + + // Antwort verarbeiten + if (resp != null && !resp.isEmpty() && resp.trim().startsWith("{")) { + boolean isSuccess = resp.contains("\"success\":true"); + String message = "Ein unbekannter Fehler ist aufgetreten."; + + int keyIndex = resp.indexOf("\"message\":\""); + if (keyIndex != -1) { + int startIndex = keyIndex + 11; + int endIndex = resp.indexOf("\"", startIndex); + if (endIndex != -1) { + message = resp.substring(startIndex, endIndex); + } + } + + if (isSuccess) { + p.sendMessage(ChatColor.GREEN + "✓ " + message); + p.sendMessage(ChatColor.GRAY + "Du kannst nun Bilder hochladen!"); + } else { + p.sendMessage(ChatColor.RED + "✗ " + message); + } + } else { + p.sendMessage(ChatColor.RED + "✗ Fehler beim Verbinden mit der Webseite (Code: " + code + ")"); + } + + } catch (Exception ex) { + p.sendMessage(ChatColor.RED + "✗ Ein interner Fehler ist aufgetreten."); + ProxyServer.getInstance().getLogger().warning("Verify error: " + ex.getMessage()); + ex.printStackTrace(); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + } + + // --- Helper Methoden --- + private static String hmacSHA256(String data, String key, Charset charset) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key.getBytes(charset), "HmacSHA256")); + byte[] raw = mac.doFinal(data.getBytes(charset)); + StringBuilder sb = new StringBuilder(); + for (byte b : raw) sb.append(String.format("%02x", b)); + return sb.toString(); + } + + private static String streamToString(InputStream in, Charset charset) throws IOException { + if (in == null) return ""; + try (BufferedReader br = new BufferedReader(new InputStreamReader(in, charset))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) sb.append(line); + return sb.toString(); + } + } + + private static String escapeJson(String s) { + return s.replace("\\", "\\\\").replace("\"","\\\"").replace("\n","\\n").replace("\r","\\r"); + } +} \ No newline at end of file