Delete src/main/java/net/viper/status/modules/network/MultiAccountGuard.java via Git Manager GUI
This commit is contained in:
@@ -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<String, Integer> 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 <Name> 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<ProxiedPlayer> 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; }
|
||||
}
|
||||
Reference in New Issue
Block a user