Upload folder via GUI - src
This commit is contained in:
@@ -4,7 +4,9 @@ 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.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
@@ -14,9 +16,11 @@ import net.viper.status.module.Module;
|
||||
import net.viper.status.modules.network.NetworkInfoModule;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
@@ -24,13 +28,18 @@ import java.net.InetSocketAddress;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -60,6 +69,23 @@ public class AntiBotModule implements Module, Listener {
|
||||
private boolean vpnBlockHosting = true;
|
||||
private int vpnCacheMinutes = 30;
|
||||
private int vpnTimeoutMs = 2500;
|
||||
private boolean securityLogEnabled = true;
|
||||
private String securityLogFileName = "antibot-security.log";
|
||||
private File securityLogFile;
|
||||
private final Object securityLogLock = new Object();
|
||||
|
||||
private boolean learningModeEnabled = true;
|
||||
private int learningScoreThreshold = 100;
|
||||
private int learningDecayPerSecond = 2;
|
||||
private int learningStateWindowSeconds = 120;
|
||||
private int learningRapidWindowMs = 1500;
|
||||
private int learningRapidPoints = 12;
|
||||
private int learningIpRateExceededPoints = 30;
|
||||
private int learningVpnProxyPoints = 40;
|
||||
private int learningVpnHostingPoints = 30;
|
||||
private int learningAttackModePoints = 12;
|
||||
private int learningHighCpsPoints = 10;
|
||||
private int learningRecentEventLimit = 30;
|
||||
|
||||
private final AtomicInteger currentSecondConnections = new AtomicInteger(0);
|
||||
private volatile long currentSecond = System.currentTimeMillis() / 1000L;
|
||||
@@ -75,6 +101,9 @@ public class AntiBotModule implements Module, Listener {
|
||||
private final Map<String, IpWindow> perIpWindows = new ConcurrentHashMap<String, IpWindow>();
|
||||
private final Map<String, Long> blockedIpsUntil = new ConcurrentHashMap<String, Long>();
|
||||
private final Map<String, VpnCacheEntry> vpnCache = new ConcurrentHashMap<String, VpnCacheEntry>();
|
||||
private final Map<String, RecentPlayerIdentity> recentIdentityByIp = new ConcurrentHashMap<String, RecentPlayerIdentity>();
|
||||
private final Map<String, LearningProfile> learningProfiles = new ConcurrentHashMap<String, LearningProfile>();
|
||||
private final Deque<String> learningRecentEvents = new ArrayDeque<String>();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@@ -89,6 +118,7 @@ public class AntiBotModule implements Module, Listener {
|
||||
this.plugin = (StatusAPI) plugin;
|
||||
ensureModuleConfigExists();
|
||||
loadConfig();
|
||||
ensureSecurityLogFile();
|
||||
|
||||
if (!enabled) {
|
||||
this.plugin.getLogger().info("[AntiBotModule] deaktiviert via " + CONFIG_FILE_NAME + " (antibot.enabled=false)");
|
||||
@@ -107,6 +137,10 @@ public class AntiBotModule implements Module, Listener {
|
||||
perIpWindows.clear();
|
||||
blockedIpsUntil.clear();
|
||||
vpnCache.clear();
|
||||
learningProfiles.clear();
|
||||
synchronized (learningRecentEvents) {
|
||||
learningRecentEvents.clear();
|
||||
}
|
||||
blockedIpsCurrentAttack.clear();
|
||||
attackMode = false;
|
||||
}
|
||||
@@ -119,6 +153,10 @@ public class AntiBotModule implements Module, Listener {
|
||||
perIpWindows.clear();
|
||||
blockedIpsUntil.clear();
|
||||
vpnCache.clear();
|
||||
learningProfiles.clear();
|
||||
synchronized (learningRecentEvents) {
|
||||
learningRecentEvents.clear();
|
||||
}
|
||||
blockedIpsCurrentAttack.clear();
|
||||
attackMode = false;
|
||||
attackCalmSince = 0L;
|
||||
@@ -127,6 +165,7 @@ public class AntiBotModule implements Module, Listener {
|
||||
lastCps = 0;
|
||||
peakCps.set(0);
|
||||
loadConfig();
|
||||
ensureSecurityLogFile();
|
||||
}
|
||||
|
||||
public Map<String, Object> buildSnapshot() {
|
||||
@@ -145,6 +184,8 @@ public class AntiBotModule implements Module, Listener {
|
||||
out.put("blocked_ips_active", blockedIpsUntil.size());
|
||||
out.put("blocked_connections_total", blockedConnectionsTotal.get());
|
||||
out.put("vpn_check_enabled", vpnCheckEnabled);
|
||||
out.put("learning_mode_enabled", learningModeEnabled);
|
||||
out.put("learning_profiles", learningProfiles.size());
|
||||
out.put("thresholds", buildThresholds());
|
||||
return out;
|
||||
}
|
||||
@@ -157,6 +198,8 @@ public class AntiBotModule implements Module, Listener {
|
||||
m.put("attack_calm_seconds", attackCalmSeconds);
|
||||
m.put("ip_connections_per_minute", ipConnectionsPerMinute);
|
||||
m.put("ip_block_seconds", ipBlockSeconds);
|
||||
m.put("learning_score_threshold", learningScoreThreshold);
|
||||
m.put("learning_decay_per_second", learningDecayPerSecond);
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -171,6 +214,8 @@ public class AntiBotModule implements Module, Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
cacheRecentIdentity(ip, event.getConnection(), System.currentTimeMillis());
|
||||
|
||||
recordConnection();
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
@@ -178,14 +223,36 @@ public class AntiBotModule implements Module, Listener {
|
||||
|
||||
Long blockedUntil = blockedIpsUntil.get(ip);
|
||||
if (blockedUntil != null && blockedUntil > now) {
|
||||
logSecurityEvent("ip_block_active", ip, event.getConnection(), "blocked_until_ms=" + blockedUntil);
|
||||
blockEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isIpRateExceeded(ip, now)) {
|
||||
blockIp(ip, now);
|
||||
blockEvent(event);
|
||||
return;
|
||||
if (learningModeEnabled) {
|
||||
evaluateLearningBaseline(ip, now);
|
||||
Long learningBlock = blockedIpsUntil.get(ip);
|
||||
if (learningBlock != null && learningBlock > now) {
|
||||
blockEvent(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
boolean ipRateExceeded = isIpRateExceeded(ip, now);
|
||||
if (ipRateExceeded) {
|
||||
if (learningModeEnabled) {
|
||||
int score = addLearningScore(ip, now, learningIpRateExceededPoints, "ip-rate-exceeded", true);
|
||||
logSecurityEvent("ip_rate_exceeded_scored", ip, event.getConnection(), "score=" + score + ", threshold=" + learningScoreThreshold);
|
||||
if (score >= learningScoreThreshold) {
|
||||
logSecurityEvent("learning_threshold_block", ip, event.getConnection(), "reason=ip-rate-exceeded, score=" + score);
|
||||
blockEvent(event);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
blockIp(ip, now);
|
||||
logSecurityEvent("ip_rate_limit_block", ip, event.getConnection(), "mode=direct");
|
||||
blockEvent(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (vpnCheckEnabled) {
|
||||
@@ -193,13 +260,47 @@ public class AntiBotModule implements Module, Listener {
|
||||
if (info != null) {
|
||||
boolean shouldBlock = (vpnBlockProxy && info.proxy) || (vpnBlockHosting && info.hosting);
|
||||
if (shouldBlock) {
|
||||
blockIp(ip, now);
|
||||
blockEvent(event);
|
||||
logSecurityEvent("vpn_detected", ip, event.getConnection(), "proxy=" + info.proxy + ", hosting=" + info.hosting);
|
||||
if (learningModeEnabled) {
|
||||
if (vpnBlockProxy && info.proxy) {
|
||||
addLearningScore(ip, now, learningVpnProxyPoints, "vpn-proxy", false);
|
||||
}
|
||||
if (vpnBlockHosting && info.hosting) {
|
||||
addLearningScore(ip, now, learningVpnHostingPoints, "vpn-hosting", false);
|
||||
}
|
||||
|
||||
int current = getLearningScore(ip, now);
|
||||
if (current >= learningScoreThreshold) {
|
||||
blockIp(ip, now);
|
||||
logSecurityEvent("learning_threshold_block", ip, event.getConnection(), "reason=vpn, score=" + current);
|
||||
recordLearningEvent("BLOCK " + ip + " reason=vpn score=" + current);
|
||||
blockEvent(event);
|
||||
}
|
||||
} else {
|
||||
blockIp(ip, now);
|
||||
logSecurityEvent("vpn_block", ip, event.getConnection(), "mode=direct, proxy=" + info.proxy + ", hosting=" + info.hosting);
|
||||
blockEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
if (!enabled || event == null || event.getPlayer() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
String ip = extractIpFromPlayer(player);
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cacheRecentIdentityDirect(ip, player.getName(), player.getUniqueId(), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private void blockEvent(PreLoginEvent event) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
@@ -218,6 +319,20 @@ public class AntiBotModule implements Module, Listener {
|
||||
return String.valueOf(conn.getAddress());
|
||||
}
|
||||
|
||||
private String extractIpFromPlayer(ProxiedPlayer player) {
|
||||
if (player == null || player.getAddress() == null) {
|
||||
return null;
|
||||
}
|
||||
if (player.getAddress() instanceof InetSocketAddress) {
|
||||
InetSocketAddress sa = (InetSocketAddress) player.getAddress();
|
||||
if (sa.getAddress() != null) {
|
||||
return sa.getAddress().getHostAddress();
|
||||
}
|
||||
return sa.getHostString();
|
||||
}
|
||||
return String.valueOf(player.getAddress());
|
||||
}
|
||||
|
||||
private void recordConnection() {
|
||||
long sec = System.currentTimeMillis() / 1000L;
|
||||
if (sec != currentSecond) {
|
||||
@@ -266,6 +381,27 @@ public class AntiBotModule implements Module, Listener {
|
||||
vpnCache.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, RecentPlayerIdentity> entry : recentIdentityByIp.entrySet()) {
|
||||
RecentPlayerIdentity id = entry.getValue();
|
||||
if (id == null || (now - id.updatedAtMs) > 600_000L) {
|
||||
recentIdentityByIp.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (learningModeEnabled) {
|
||||
long staleAfter = Math.max(60, learningStateWindowSeconds) * 1000L;
|
||||
for (Map.Entry<String, LearningProfile> entry : learningProfiles.entrySet()) {
|
||||
LearningProfile lp = entry.getValue();
|
||||
if (lp == null) {
|
||||
learningProfiles.remove(entry.getKey());
|
||||
continue;
|
||||
}
|
||||
if ((now - lp.lastSeenAt) > staleAfter && lp.score <= 0) {
|
||||
learningProfiles.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
@@ -356,6 +492,24 @@ public class AntiBotModule implements Module, Listener {
|
||||
vpnBlockHosting = parseBoolean(props.getProperty("antibot.vpn_check.block_hosting"), vpnBlockHosting);
|
||||
vpnCacheMinutes = parseInt(props.getProperty("antibot.vpn_check.cache_minutes"), vpnCacheMinutes);
|
||||
vpnTimeoutMs = parseInt(props.getProperty("antibot.vpn_check.timeout_ms"), vpnTimeoutMs);
|
||||
securityLogEnabled = parseBoolean(props.getProperty("antibot.security_log.enabled"), securityLogEnabled);
|
||||
securityLogFileName = props.getProperty("antibot.security_log.file", securityLogFileName).trim();
|
||||
if (securityLogFileName.isEmpty()) {
|
||||
securityLogFileName = "antibot-security.log";
|
||||
}
|
||||
|
||||
learningModeEnabled = parseBoolean(props.getProperty("antibot.learning.enabled"), learningModeEnabled);
|
||||
learningScoreThreshold = parseInt(props.getProperty("antibot.learning.score_threshold"), learningScoreThreshold);
|
||||
learningDecayPerSecond = parseInt(props.getProperty("antibot.learning.decay_per_second"), learningDecayPerSecond);
|
||||
learningStateWindowSeconds = parseInt(props.getProperty("antibot.learning.state_window_seconds"), learningStateWindowSeconds);
|
||||
learningRapidWindowMs = parseInt(props.getProperty("antibot.learning.rapid.window_ms"), learningRapidWindowMs);
|
||||
learningRapidPoints = parseInt(props.getProperty("antibot.learning.rapid.points"), learningRapidPoints);
|
||||
learningIpRateExceededPoints = parseInt(props.getProperty("antibot.learning.ip_rate_exceeded.points"), learningIpRateExceededPoints);
|
||||
learningVpnProxyPoints = parseInt(props.getProperty("antibot.learning.vpn_proxy.points"), learningVpnProxyPoints);
|
||||
learningVpnHostingPoints = parseInt(props.getProperty("antibot.learning.vpn_hosting.points"), learningVpnHostingPoints);
|
||||
learningAttackModePoints = parseInt(props.getProperty("antibot.learning.attack_mode.points"), learningAttackModePoints);
|
||||
learningHighCpsPoints = parseInt(props.getProperty("antibot.learning.high_cps.points"), learningHighCpsPoints);
|
||||
learningRecentEventLimit = parseInt(props.getProperty("antibot.learning.recent_events.limit"), learningRecentEventLimit);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[AntiBotModule] Fehler beim Laden von " + CONFIG_FILE_NAME + ": " + e.getMessage());
|
||||
}
|
||||
@@ -496,6 +650,266 @@ public class AntiBotModule implements Module, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluateLearningBaseline(String ip, long now) {
|
||||
LearningProfile profile = learningProfiles.computeIfAbsent(ip, k -> new LearningProfile(now));
|
||||
|
||||
synchronized (profile) {
|
||||
decayLearningProfile(profile, now);
|
||||
|
||||
long delta = now - profile.lastConnectionAt;
|
||||
if (profile.lastConnectionAt > 0 && delta <= Math.max(250L, learningRapidWindowMs)) {
|
||||
profile.rapidStreak++;
|
||||
int points = learningRapidPoints + Math.min(profile.rapidStreak, 5);
|
||||
profile.score += Math.max(1, points);
|
||||
recordLearningEvent("IP=" + ip + " +" + points + " rapid-connect score=" + profile.score);
|
||||
} else {
|
||||
profile.rapidStreak = 0;
|
||||
}
|
||||
|
||||
if (attackMode) {
|
||||
profile.score += Math.max(1, learningAttackModePoints);
|
||||
recordLearningEvent("IP=" + ip + " +" + learningAttackModePoints + " attack-mode score=" + profile.score);
|
||||
}
|
||||
|
||||
if (lastCps >= Math.max(1, maxCps)) {
|
||||
profile.score += Math.max(1, learningHighCpsPoints);
|
||||
recordLearningEvent("IP=" + ip + " +" + learningHighCpsPoints + " high-cps score=" + profile.score);
|
||||
}
|
||||
|
||||
profile.lastConnectionAt = now;
|
||||
profile.lastSeenAt = now;
|
||||
|
||||
if (profile.score >= learningScoreThreshold) {
|
||||
blockIp(ip, now);
|
||||
recordLearningEvent("BLOCK " + ip + " reason=learning-threshold score=" + profile.score);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int addLearningScore(String ip, long now, int points, String reason, boolean checkThreshold) {
|
||||
LearningProfile profile = learningProfiles.computeIfAbsent(ip, k -> new LearningProfile(now));
|
||||
synchronized (profile) {
|
||||
decayLearningProfile(profile, now);
|
||||
int add = Math.max(1, points);
|
||||
profile.score += add;
|
||||
profile.lastSeenAt = now;
|
||||
recordLearningEvent("IP=" + ip + " +" + add + " " + reason + " score=" + profile.score);
|
||||
|
||||
if (checkThreshold && profile.score >= learningScoreThreshold) {
|
||||
blockIp(ip, now);
|
||||
recordLearningEvent("BLOCK " + ip + " reason=" + reason + " score=" + profile.score);
|
||||
}
|
||||
|
||||
return profile.score;
|
||||
}
|
||||
}
|
||||
|
||||
private int getLearningScore(String ip, long now) {
|
||||
LearningProfile profile = learningProfiles.get(ip);
|
||||
if (profile == null) {
|
||||
return 0;
|
||||
}
|
||||
synchronized (profile) {
|
||||
decayLearningProfile(profile, now);
|
||||
return profile.score;
|
||||
}
|
||||
}
|
||||
|
||||
private void decayLearningProfile(LearningProfile profile, long now) {
|
||||
long elapsedMs = Math.max(0L, now - profile.lastScoreUpdateAt);
|
||||
if (elapsedMs > 0L) {
|
||||
long decay = (elapsedMs / 1000L) * Math.max(0, learningDecayPerSecond);
|
||||
if (decay > 0L) {
|
||||
profile.score = (int) Math.max(0L, profile.score - decay);
|
||||
}
|
||||
profile.lastScoreUpdateAt = now;
|
||||
}
|
||||
|
||||
long resetAfter = Math.max(30, learningStateWindowSeconds) * 1000L;
|
||||
if (profile.lastSeenAt > 0L && now - profile.lastSeenAt > resetAfter) {
|
||||
profile.score = 0;
|
||||
profile.rapidStreak = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void recordLearningEvent(String event) {
|
||||
String line = new SimpleDateFormat("HH:mm:ss").format(new Date()) + " " + event;
|
||||
synchronized (learningRecentEvents) {
|
||||
learningRecentEvents.addLast(line);
|
||||
while (learningRecentEvents.size() > Math.max(5, learningRecentEventLimit)) {
|
||||
learningRecentEvents.pollFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureSecurityLogFile() {
|
||||
if (plugin == null) {
|
||||
return;
|
||||
}
|
||||
if (!plugin.getDataFolder().exists()) {
|
||||
plugin.getDataFolder().mkdirs();
|
||||
}
|
||||
securityLogFile = new File(plugin.getDataFolder(), securityLogFileName);
|
||||
try {
|
||||
if (!securityLogFile.exists()) {
|
||||
securityLogFile.createNewFile();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[AntiBotModule] Konnte Sicherheitslog nicht erstellen: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void logSecurityEvent(String eventType, String ip, PendingConnection conn, String details) {
|
||||
if (!securityLogEnabled || plugin == null) {
|
||||
return;
|
||||
}
|
||||
if (securityLogFile == null) {
|
||||
ensureSecurityLogFile();
|
||||
if (securityLogFile == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String name = extractPlayerName(conn);
|
||||
String uuid = extractPlayerUuid(conn, name);
|
||||
|
||||
if ((name == null || name.isEmpty() || "unknown".equalsIgnoreCase(name)) && ip != null) {
|
||||
RecentPlayerIdentity cached = recentIdentityByIp.get(ip);
|
||||
if (cached != null) {
|
||||
if (cached.playerName != null && !cached.playerName.trim().isEmpty()) {
|
||||
name = cached.playerName;
|
||||
}
|
||||
if ((uuid == null || uuid.isEmpty() || "unknown".equalsIgnoreCase(uuid))
|
||||
&& cached.playerUuid != null && !cached.playerUuid.trim().isEmpty()) {
|
||||
uuid = cached.playerUuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
name = "unknown";
|
||||
}
|
||||
if (uuid == null || uuid.trim().isEmpty()) {
|
||||
uuid = "unknown";
|
||||
}
|
||||
|
||||
String line = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
|
||||
+ " | event=" + safeLog(eventType)
|
||||
+ " | ip=" + safeLog(ip)
|
||||
+ " | player=" + safeLog(name)
|
||||
+ " | uuid=" + safeLog(uuid)
|
||||
+ " | details=" + safeLog(details);
|
||||
|
||||
synchronized (securityLogLock) {
|
||||
try (BufferedWriter bw = new BufferedWriter(new FileWriter(securityLogFile, true))) {
|
||||
bw.write(line);
|
||||
bw.newLine();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[AntiBotModule] Sicherheitslog-Schreibfehler: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cacheRecentIdentity(String ip, PendingConnection conn, long now) {
|
||||
if (ip == null || ip.isEmpty() || conn == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name = extractPlayerName(conn);
|
||||
String uuid = extractPlayerUuid(conn, name);
|
||||
|
||||
if ((name == null || name.isEmpty()) && (uuid == null || uuid.isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
RecentPlayerIdentity identity = recentIdentityByIp.computeIfAbsent(ip, k -> new RecentPlayerIdentity());
|
||||
synchronized (identity) {
|
||||
if (name != null && !name.trim().isEmpty()) {
|
||||
identity.playerName = name.trim();
|
||||
}
|
||||
if (uuid != null && !uuid.trim().isEmpty()) {
|
||||
identity.playerUuid = uuid.trim();
|
||||
}
|
||||
identity.updatedAtMs = now;
|
||||
}
|
||||
}
|
||||
|
||||
private void cacheRecentIdentityDirect(String ip, String playerName, UUID playerUuid, long now) {
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name = playerName == null ? "" : playerName.trim();
|
||||
String uuid = playerUuid == null ? "" : playerUuid.toString();
|
||||
if (name.isEmpty() && uuid.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RecentPlayerIdentity identity = recentIdentityByIp.computeIfAbsent(ip, k -> new RecentPlayerIdentity());
|
||||
synchronized (identity) {
|
||||
if (!name.isEmpty()) {
|
||||
identity.playerName = name;
|
||||
}
|
||||
if (!uuid.isEmpty()) {
|
||||
identity.playerUuid = uuid;
|
||||
}
|
||||
identity.updatedAtMs = now;
|
||||
}
|
||||
}
|
||||
|
||||
private String extractPlayerName(PendingConnection conn) {
|
||||
if (conn == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
String raw = conn.getName();
|
||||
return raw == null ? "" : raw.trim();
|
||||
} catch (Exception ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String extractPlayerUuid(PendingConnection conn, String playerName) {
|
||||
if (conn != null) {
|
||||
try {
|
||||
UUID uuid = conn.getUniqueId();
|
||||
if (uuid != null) {
|
||||
return uuid.toString();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (playerName != null && !playerName.trim().isEmpty()) {
|
||||
UUID offlineUuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName.trim()).getBytes(StandardCharsets.UTF_8));
|
||||
return offlineUuid.toString();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private String safeLog(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return "-";
|
||||
}
|
||||
return input.replace("\n", " ").replace("\r", " ").trim();
|
||||
}
|
||||
|
||||
private List<String> getRecentLearningEvents(int max) {
|
||||
List<String> out = new ArrayList<String>();
|
||||
synchronized (learningRecentEvents) {
|
||||
int skip = Math.max(0, learningRecentEvents.size() - Math.max(1, max));
|
||||
int idx = 0;
|
||||
for (String line : learningRecentEvents) {
|
||||
if (idx++ < skip) {
|
||||
continue;
|
||||
}
|
||||
out.add(line);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private boolean parseBoolean(String s, boolean fallback) {
|
||||
if (s == null) return fallback;
|
||||
return Boolean.parseBoolean(s.trim());
|
||||
@@ -586,6 +1000,28 @@ public class AntiBotModule implements Module, Listener {
|
||||
boolean hosting;
|
||||
}
|
||||
|
||||
private static class LearningProfile {
|
||||
long lastConnectionAt;
|
||||
long lastScoreUpdateAt;
|
||||
long lastSeenAt;
|
||||
int rapidStreak;
|
||||
int score;
|
||||
|
||||
LearningProfile(long now) {
|
||||
this.lastConnectionAt = now;
|
||||
this.lastScoreUpdateAt = now;
|
||||
this.lastSeenAt = now;
|
||||
this.rapidStreak = 0;
|
||||
this.score = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RecentPlayerIdentity {
|
||||
String playerName;
|
||||
String playerUuid;
|
||||
long updatedAtMs;
|
||||
}
|
||||
|
||||
private class AntiBotCommand extends Command {
|
||||
AntiBotCommand() {
|
||||
super("antibot", "statusapi.antibot");
|
||||
@@ -611,6 +1047,16 @@ public class AntiBotModule implements Module, Listener {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Active IP Blocks: " + ChatColor.WHITE + blockedIpsUntil.size());
|
||||
sender.sendMessage(ChatColor.YELLOW + "Total blocked connections: " + ChatColor.WHITE + blockedConnectionsTotal.get());
|
||||
sender.sendMessage(ChatColor.YELLOW + "VPN Check: " + ChatColor.WHITE + vpnCheckEnabled);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Learning Mode: " + ChatColor.WHITE + learningModeEnabled
|
||||
+ ChatColor.GRAY + " (threshold=" + learningScoreThreshold + ")");
|
||||
|
||||
List<String> recent = getRecentLearningEvents(3);
|
||||
if (!recent.isEmpty()) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Learning Events:");
|
||||
for (String line : recent) {
|
||||
sender.sendMessage(ChatColor.GRAY + "- " + line);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user