From 31510f7cd27313b3ba3b78d6296e8c6fd77154a4 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sun, 24 May 2026 19:43:52 +0000 Subject: [PATCH] Delete src/main/java/net/viper/status/modules/network/MultiAccountGuard.java via Git Manager GUI --- .../modules/network/MultiAccountGuard.java | 445 ------------------ 1 file changed, 445 deletions(-) delete mode 100644 src/main/java/net/viper/status/modules/network/MultiAccountGuard.java diff --git a/src/main/java/net/viper/status/modules/network/MultiAccountGuard.java b/src/main/java/net/viper/status/modules/network/MultiAccountGuard.java deleted file mode 100644 index 6464d2c..0000000 --- a/src/main/java/net/viper/status/modules/network/MultiAccountGuard.java +++ /dev/null @@ -1,445 +0,0 @@ -package net.viper.status.modules.network; - -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.chat.TextComponent; -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.api.plugin.Plugin; -import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.event.EventPriority; -import net.luckperms.api.LuckPermsProvider; -import net.luckperms.api.model.user.User; -import net.luckperms.api.node.Node; -import net.luckperms.api.node.types.PermissionNode; -import net.viper.status.StatusAPI; -import net.viper.status.module.Module; -import net.viper.status.modules.antibot.AntiBotModule; - -import java.io.*; -import java.net.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.*; -import java.time.*; -import java.time.format.DateTimeFormatter; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; - -/** - * MultiAccountGuard - * - * Features: - * - IP-Check: blockiert zweiten Account von gleicher IP - * - Bypass NUR über LuckPerms (OP zählt nicht) - * - Persistentes Log in multiaccountguard.log - * - Staff-Benachrichtigung ingame (Permission: statusapi.staff.notify) - * - Temporärer IP-Bann nach X Versuchen (Integration mit AntiBotModule) - * - Discord-Webhook bei Konflikt - */ -public class MultiAccountGuard implements Module, Listener { - - private static final String CONFIG_FILE = "network-guard.properties"; - private static final String LOG_FILE = "multiaccountguard.log"; - public static final String BYPASS_PERM = "statusapi.multiaccountguard.bypass"; - public static final String STAFF_PERM = "statusapi.staff.notify"; - - private static final DateTimeFormatter LOG_FMT = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()); - - private Plugin plugin; - private Logger log; - private File logFile; - - // Config - private boolean enabled = true; - private boolean checkIp = true; - private boolean kickExisting = false; - private String kickMessage = "&cDu bist bereits mit einem anderen Account online!\n&7Bitte trenne deinen anderen Account zuerst."; - - // Staff-Benachrichtigung - private boolean staffNotifyEnabled = true; - private String staffNotifyFormat = "&8[&cMAG&8] &e{blocked} &7wurde blockiert &8(2. Account von &e{existing}&8) &7| IP: &f{ip}"; - - // Temporärer IP-Bann - private boolean tempBanEnabled = true; - private int tempBanMaxAttempts = 3; - private int tempBanDurationSecs = 300; - /** IP → Anzahl Konflikte seit letztem Reset */ - private final Map attemptsByIp = new ConcurrentHashMap<>(); - - // Webhook - private boolean webhookEnabled = true; - private String webhookUrl = ""; - private String webhookUsername = "StatusAPI"; - private String webhookThumbnailUrl = ""; - - @Override public String getName() { return "MultiAccountGuard"; } - - // ------------------------------------------------------------------------- - // Enable / Disable - // ------------------------------------------------------------------------- - - @Override - public void onEnable(Plugin plugin) { - this.plugin = plugin; - this.log = plugin.getLogger(); - this.logFile = new File(plugin.getDataFolder(), LOG_FILE); - - loadConfig(); - - if (!enabled) { - log.info("[MultiAccountGuard] Deaktiviert."); - return; - } - - ProxyServer.getInstance().getPluginManager().registerListener(plugin, this); - - log.info("[MultiAccountGuard] Aktiv | IP-Check=" + checkIp - + " | kickExisting=" + kickExisting - + " | staffNotify=" + staffNotifyEnabled - + " | tempBan=" + tempBanEnabled + "(max=" + tempBanMaxAttempts + ", " + tempBanDurationSecs + "s)" - + " | Webhook=" + (webhookEnabled && !webhookUrl.isEmpty())); - log.info("[MultiAccountGuard] Bypass NUR via LuckPerms: /lp user permission set " + BYPASS_PERM + " true"); - log.info("[MultiAccountGuard] Log-Datei: " + logFile.getAbsolutePath()); - } - - @Override - public void onDisable(Plugin plugin) {} - - // ------------------------------------------------------------------------- - // Event - // ------------------------------------------------------------------------- - - @EventHandler(priority = EventPriority.HIGHEST) - public void onPostLogin(PostLoginEvent event) { - if (!enabled) return; - - ProxiedPlayer joining = event.getPlayer(); - - if (hasBypass(joining)) { - log.info("[MultiAccountGuard] " + joining.getName() + " hat Bypass (LuckPerms) – übersprungen."); - return; - } - - UUID joiningUuid = joining.getUniqueId(); - String joiningIp = extractIp(joining.getSocketAddress()); - - if (joiningIp == null) { - log.warning("[MultiAccountGuard] Konnte IP von " + joining.getName() + " nicht lesen – übersprungen."); - return; - } - - log.info("[MultiAccountGuard] Login-Check: " + joining.getName() - + " | UUID=" + joiningUuid + " | IP=" + joiningIp); - - // Alle anderen Spieler (sich selbst per UUID ausschließen) - List others = new ArrayList<>(); - for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { - if (p.getUniqueId().equals(joiningUuid)) continue; - others.add(p); - } - - for (ProxiedPlayer online : others) { - if (hasBypass(online)) continue; - - String onlineIp = extractIp(online.getSocketAddress()); - if (onlineIp == null) continue; - - if (checkIp && joiningIp.equals(onlineIp)) { - log.warning("[MultiAccountGuard] KONFLIKT: " - + joining.getName() + " (" + joiningUuid + ")" - + " <-> " + online.getName() + " (" + online.getUniqueId() + ")" - + " IP=" + joiningIp); - handleConflict(joining, online, joiningIp); - return; - } - } - - log.info("[MultiAccountGuard] " + joining.getName() + " – kein Konflikt."); - } - - // ------------------------------------------------------------------------- - // Konflikt behandeln - // ------------------------------------------------------------------------- - - private void handleConflict(ProxiedPlayer joining, ProxiedPlayer existing, String ip) { - - TextComponent msg = new TextComponent( - ChatColor.translateAlternateColorCodes('&', kickMessage)); - - final String blockedName, allowedName; - final UUID blockedUuid, allowedUuid; - - if (kickExisting) { - existing.disconnect(msg); - blockedName = existing.getName(); blockedUuid = existing.getUniqueId(); - allowedName = joining.getName(); allowedUuid = joining.getUniqueId(); - log.warning("[MultiAccountGuard] Bestehender Account " + existing.getName() + " getrennt."); - } else { - joining.disconnect(msg); - blockedName = joining.getName(); blockedUuid = joining.getUniqueId(); - allowedName = existing.getName(); allowedUuid = existing.getUniqueId(); - log.warning("[MultiAccountGuard] Neuer Account " + joining.getName() + " blockiert."); - } - - // 1. Persistentes Log - writeLog(blockedName, blockedUuid, allowedName, allowedUuid, ip); - - // 2. Staff-Benachrichtigung - if (staffNotifyEnabled) { - notifyStaff(blockedName, allowedName, ip); - } - - // 3. Temporärer IP-Bann - if (tempBanEnabled) { - int attempts = attemptsByIp.merge(ip, 1, Integer::sum); - log.info("[MultiAccountGuard] IP " + ip + " hat " + attempts + "/" + tempBanMaxAttempts + " Versuche."); - if (attempts >= tempBanMaxAttempts) { - attemptsByIp.remove(ip); - banIp(ip); - } - } - - // 4. Discord-Webhook (async) - if (webhookEnabled && webhookUrl != null && !webhookUrl.isEmpty()) { - final String bn = blockedName, an = allowedName; - final UUID bu = blockedUuid, au = allowedUuid; - final int att = attemptsByIp.getOrDefault(ip, tempBanMaxAttempts); - ProxyServer.getInstance().getScheduler().runAsync(plugin, - () -> sendWebhook(bn, bu, an, au, ip, att)); - } - } - - // ------------------------------------------------------------------------- - // 1. Persistentes Log - // ------------------------------------------------------------------------- - - private void writeLog(String blockedName, UUID blockedUuid, - String allowedName, UUID allowedUuid, String ip) { - try { - if (!logFile.getParentFile().exists()) logFile.getParentFile().mkdirs(); - - String line = String.format("[%s] KONFLIKT | Geblockt: %s (%s) | Online: %s (%s) | IP: %s%n", - LOG_FMT.format(Instant.now()), - blockedName, blockedUuid, - allowedName, allowedUuid, - ip); - - Files.write(logFile.toPath(), line.getBytes(StandardCharsets.UTF_8), - StandardOpenOption.CREATE, StandardOpenOption.APPEND); - - } catch (Exception e) { - log.warning("[MultiAccountGuard] Log-Fehler: " + e.getMessage()); - } - } - - // ------------------------------------------------------------------------- - // 2. Staff-Benachrichtigung - // ------------------------------------------------------------------------- - - private void notifyStaff(String blockedName, String existingName, String ip) { - String raw = staffNotifyFormat - .replace("{blocked}", blockedName) - .replace("{existing}", existingName) - .replace("{ip}", ip); - String formatted = ChatColor.translateAlternateColorCodes('&', raw); - TextComponent msg = new TextComponent(formatted); - - int notified = 0; - for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { - if (p.hasPermission(STAFF_PERM)) { - p.sendMessage(msg); - notified++; - } - } - log.info("[MultiAccountGuard] Staff-Benachrichtigung gesendet an " + notified + " Spieler."); - } - - // ------------------------------------------------------------------------- - // 3. Temporärer IP-Bann via AntiBotModule - // ------------------------------------------------------------------------- - - private void banIp(String ip) { - try { - StatusAPI statusApi = (StatusAPI) ProxyServer.getInstance() - .getPluginManager().getPlugin("StatusAPI"); - if (statusApi == null) { - log.warning("[MultiAccountGuard] StatusAPI nicht gefunden – IP-Bann nicht möglich."); - return; - } - AntiBotModule antiBot = statusApi.getModuleManager().getModule(AntiBotModule.class); - if (antiBot == null) { - log.warning("[MultiAccountGuard] AntiBotModule nicht gefunden – IP-Bann nicht möglich."); - return; - } - antiBot.blockIpExternal(ip, tempBanDurationSecs); - log.warning("[MultiAccountGuard] IP " + ip + " für " + tempBanDurationSecs + "s gebannt (zu viele Multi-Account-Versuche)."); - - // Staff über den Bann informieren - String banMsg = ChatColor.translateAlternateColorCodes('&', - "&8[&cMAG&8] &7IP &f" + ip + " &7wurde für &c" + tempBanDurationSecs + "s &7gebannt &8(zu viele Versuche)."); - for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { - if (p.hasPermission(STAFF_PERM)) { - p.sendMessage(new TextComponent(banMsg)); - } - } - - // In Log schreiben - try { - String line = String.format("[%s] IP-BANN | IP: %s | Dauer: %ds%n", - LOG_FMT.format(Instant.now()), ip, tempBanDurationSecs); - Files.write(logFile.toPath(), line.getBytes(StandardCharsets.UTF_8), - StandardOpenOption.CREATE, StandardOpenOption.APPEND); - } catch (Exception ignored) {} - - } catch (Exception e) { - log.warning("[MultiAccountGuard] IP-Bann Fehler: " + e.getMessage()); - } - } - - // ------------------------------------------------------------------------- - // 4. Discord-Webhook - // ------------------------------------------------------------------------- - - private void sendWebhook(String blockedName, UUID blockedUuid, - String allowedName, UUID allowedUuid, - String ip, int attempts) { - StringBuilder fields = new StringBuilder(); - appendField(fields, "\uD83D\uDEAB Geblockter Account", - blockedName + "\n`" + blockedUuid + "`", false); - appendField(fields, "\u2705 Verbundener Account", - allowedName + "\n`" + allowedUuid + "`", false); - appendField(fields, "\uD83C\uDF10 IP", "`" + ip + "`", true); - appendField(fields, "Aktion", - kickExisting ? "Alter Account getrennt" : "Neuer Account blockiert", true); - if (tempBanEnabled) { - appendField(fields, "\u26A0\uFE0F Versuche", - attempts + " / " + tempBanMaxAttempts - + (attempts >= tempBanMaxAttempts ? " \u2192 IP gebannt!" : ""), true); - } - - String body = "{\"username\":\"" + esc(webhookUsername) + "\"," - + "\"embeds\":[{" - + "\"title\":\"\uD83D\uDD12 Multi-Account erkannt\"," - + "\"description\":\"Ein Spieler hat versucht mit einem zweiten Account beizutreten.\"," - + "\"color\":15158332," - + "\"fields\":[" + fields + "]," - + "\"footer\":{\"text\":\"StatusAPI \u2022 MultiAccountGuard\"}," - + "\"timestamp\":\"" + Instant.now() + "\"" - + (webhookThumbnailUrl != null && !webhookThumbnailUrl.isEmpty() - ? ",\"thumbnail\":{\"url\":\"" + esc(webhookThumbnailUrl) + "\"}" : "") - + "}]}"; - - HttpURLConnection conn = null; - try { - byte[] bytes = body.getBytes(StandardCharsets.UTF_8); - conn = (HttpURLConnection) new URL(webhookUrl).openConnection(); - conn.setRequestMethod("POST"); - conn.setConnectTimeout(5000); - conn.setReadTimeout(8000); - conn.setDoOutput(true); - conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); - conn.setRequestProperty("Content-Length", String.valueOf(bytes.length)); - try (OutputStream os = conn.getOutputStream()) { os.write(bytes); } - int code = conn.getResponseCode(); - if (code < 200 || code >= 300) log.warning("[MultiAccountGuard] Webhook HTTP " + code); - else log.info("[MultiAccountGuard] Webhook gesendet (HTTP " + code + ")"); - } catch (Exception e) { - log.warning("[MultiAccountGuard] Webhook-Fehler: " + e.getMessage()); - } finally { - if (conn != null) conn.disconnect(); - } - } - - private void appendField(StringBuilder sb, String name, String value, boolean inline) { - if (sb.length() > 0) sb.append(","); - sb.append("{\"name\":\"").append(esc(name)) - .append("\",\"value\":\"").append(esc(value)) - .append("\",\"inline\":").append(inline).append("}"); - } - - private String esc(String s) { - if (s == null) return ""; - return s.replace("\\","\\\\").replace("\"","\\\"") - .replace("\n","\\n").replace("\r","\\r"); - } - - // ------------------------------------------------------------------------- - // Bypass – NUR LuckPerms, kein OP-Fallback - // ------------------------------------------------------------------------- - - private boolean hasBypass(ProxiedPlayer player) { - try { - User user = LuckPermsProvider.get().getUserManager().getUser(player.getUniqueId()); - if (user == null) return false; - for (Node node : user.getNodes()) { - if (node instanceof PermissionNode) { - PermissionNode pn = (PermissionNode) node; - if (pn.getPermission().equalsIgnoreCase(BYPASS_PERM) && pn.getValue()) { - return true; - } - } - } - return false; - } catch (Exception e) { - log.warning("[MultiAccountGuard] LuckPerms-Check fehlgeschlagen für " + player.getName() + ": " + e.getMessage()); - return false; - } - } - - // ------------------------------------------------------------------------- - // Config - // ------------------------------------------------------------------------- - - private void loadConfig() { - File file = new File(plugin.getDataFolder(), CONFIG_FILE); - if (!file.exists()) { - log.info("[MultiAccountGuard] Config nicht gefunden – Defaults werden verwendet."); - return; - } - Properties p = new Properties(); - try (FileInputStream fis = new FileInputStream(file); - InputStreamReader r = new InputStreamReader(fis, StandardCharsets.UTF_8)) { - p.load(r); - - enabled = Boolean.parseBoolean(p.getProperty("multiaccountguard.enabled", "true")); - checkIp = Boolean.parseBoolean(p.getProperty("multiaccountguard.check_ip", "true")); - kickExisting = Boolean.parseBoolean(p.getProperty("multiaccountguard.kick_existing", "false")); - kickMessage = p.getProperty("multiaccountguard.kick_message", kickMessage); - - staffNotifyEnabled = Boolean.parseBoolean(p.getProperty("multiaccountguard.staff_notify.enabled", "true")); - staffNotifyFormat = p.getProperty("multiaccountguard.staff_notify.format", staffNotifyFormat); - - tempBanEnabled = Boolean.parseBoolean(p.getProperty("multiaccountguard.tempban.enabled", "true")); - tempBanMaxAttempts = parseInt(p.getProperty("multiaccountguard.tempban.max_attempts", "3"), 3); - tempBanDurationSecs = parseInt(p.getProperty("multiaccountguard.tempban.duration_secs", "300"), 300); - - webhookEnabled = Boolean.parseBoolean(p.getProperty("multiaccountguard.webhook.enabled", "true")); - webhookUrl = p.getProperty("networkinfo.webhook.url", "").trim(); - webhookUsername = p.getProperty("networkinfo.webhook.username", "StatusAPI").trim(); - webhookThumbnailUrl = p.getProperty("networkinfo.webhook.thumbnail_url", "").trim(); - - } catch (Exception e) { - log.warning("[MultiAccountGuard] Config-Fehler: " + e.getMessage()); - } - } - - private int parseInt(String s, int fallback) { - try { return Integer.parseInt(s == null ? "" : s.trim()); } - catch (Exception ignored) { return fallback; } - } - - // ------------------------------------------------------------------------- - - private String extractIp(SocketAddress addr) { - if (addr instanceof InetSocketAddress) - return ((InetSocketAddress) addr).getAddress().getHostAddress(); - return null; - } - - public boolean isEnabled() { return enabled; } - public boolean isCheckIp() { return checkIp; } - public boolean isKickExisting() { return kickExisting; } -} \ No newline at end of file