Delete _trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/ChatFilter.java via Git Manager GUI

This commit is contained in:
2026-05-07 19:39:51 +00:00
parent ccff5f1a69
commit 421c59d274

View File

@@ -1,331 +0,0 @@
package net.viper.status.modules.chat;
import net.viper.status.ratelimit.GlobalRateLimitFramework;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* Chat-Filter: Anti-Spam, Caps-Filter, Wort-Blacklist, Farbcode-Filter.
*
* Reihenfolge der Prüfungen in processChat():
* 1. Spam-Cooldown (zu schnell geschrieben?)
* 2. Gleiche Nachricht wiederholt?
* 3. Zu viele Großbuchstaben?
* 4. Verbotene Wörter → ersetzen durch ****
* 5. Farbcodes (& Codes) → nur mit Permission erlaubt
*/
public class ChatFilter {
private final ChatFilterConfig cfg;
private final GlobalRateLimitFramework rateLimiter = GlobalRateLimitFramework.getInstance();
// UUID → letzte Nachricht (für Duplikat-Check)
private final Map<UUID, String> lastMessageText = new ConcurrentHashMap<>();
// Kompilierte Regex-Pattern für Blacklist-Wörter
private final List<Pattern> blacklistPatterns = new ArrayList<>();
public ChatFilter(ChatFilterConfig cfg) {
this.cfg = cfg;
compilePatterns();
}
private void compilePatterns() {
blacklistPatterns.clear();
for (String word : cfg.blacklistWords) {
// Case-insensitiv, ganzes Wort oder Teilwort je nach Config
blacklistPatterns.add(Pattern.compile(
"(?i)" + Pattern.quote(word)));
}
}
// ===== Ergebnis-Klasse =====
public enum FilterResult {
ALLOWED, // Nachricht darf durch
BLOCKED, // Nachricht blockiert (Spam/Flood)
MODIFIED // Nachricht wurde verändert (Wörter ersetzt / Caps reduziert)
}
public static class FilterResponse {
public final FilterResult result;
public final String message; // ggf. modifizierte Nachricht
public final String denyReason; // Nachricht an den Spieler wenn BLOCKED
FilterResponse(FilterResult result, String message, String denyReason) {
this.result = result;
this.message = message;
this.denyReason = denyReason;
}
}
// ===== Haupt-Filtermethode =====
/**
* Wendet alle aktiven Filter auf eine Nachricht an.
*
* @param uuid UUID des sendenden Spielers
* @param message Originalnachricht
* @param isAdmin true → Farbcodes und Caps-Filter überspringen
* @param hasColorPerm true → &-Farbcodes erlaubt
* @param hasFormatPerm true → &l, &o etc. erlaubt
* @return FilterResponse mit Ergebnis und ggf. modifizierter Nachricht
*/
public FilterResponse filter(UUID uuid, String message, boolean isAdmin,
boolean hasColorPerm, boolean hasFormatPerm) {
// ── 1. Spam-Cooldown ──
if (cfg.antiSpamEnabled && !isAdmin) {
if (cfg.globalRateLimitEnabled) {
GlobalRateLimitFramework.Result rl = rateLimiter.check(
"chat.message",
uuid.toString(),
new GlobalRateLimitFramework.Rule(
true,
cfg.globalRateLimitWindowMs,
cfg.globalRateLimitMaxActions,
cfg.globalRateLimitBlockMs
)
);
if (rl.isBlocked()) {
return new FilterResponse(FilterResult.BLOCKED, message, cfg.spamMessage);
}
}
}
// ── 2. Duplikat-Check ──
if (cfg.duplicateCheckEnabled && !isAdmin) {
String lastText = lastMessageText.get(uuid);
if (message.equalsIgnoreCase(lastText)) {
return new FilterResponse(FilterResult.BLOCKED, message, cfg.duplicateMessage);
}
lastMessageText.put(uuid, message);
}
String result = message;
boolean modified = false;
// ── 3. Blacklist ──
if (cfg.blacklistEnabled) {
String filtered = applyBlacklist(result);
if (!filtered.equals(result)) {
result = filtered;
modified = true;
}
}
// ── 4. Caps-Filter ──
if (cfg.capsFilterEnabled && !isAdmin) {
String capped = applyCapsFilter(result);
if (!capped.equals(result)) {
result = capped;
modified = true;
}
}
// ── 5. Farbcodes filtern (nur wenn keine Permission) ──
if (!isAdmin) {
String colorFiltered = applyColorFilter(result, hasColorPerm, hasFormatPerm);
if (!colorFiltered.equals(result)) {
result = colorFiltered;
modified = true;
}
}
// ── 6. Anti-Werbung ──
if (cfg.antiAdEnabled && !isAdmin) {
if (containsAdvertisement(result)) {
return new FilterResponse(FilterResult.BLOCKED, result, cfg.antiAdMessage);
}
}
return new FilterResponse(
modified ? FilterResult.MODIFIED : FilterResult.ALLOWED,
result,
null
);
}
// ===== Einzelne Filter =====
private String applyBlacklist(String message) {
String result = message;
for (Pattern p : blacklistPatterns) {
result = p.matcher(result).replaceAll(buildStars(p.pattern()
.replace("(?i)", "").replace("\\Q", "").replace("\\E", "").length()));
}
return result;
}
private String applyCapsFilter(String message) {
// Zähle Großbuchstaben
int total = 0, upper = 0;
for (char c : message.toCharArray()) {
if (Character.isLetter(c)) { total++; if (Character.isUpperCase(c)) upper++; }
}
if (total < cfg.capsMinLength) return message; // Kurze Nachrichten ignorieren
double ratio = total > 0 ? (double) upper / total : 0;
if (ratio < cfg.capsMaxPercent / 100.0) return message;
// Zu viele Caps → alles lowercase
return message.toLowerCase();
}
/**
* Entfernt &-Farbcodes je nach Permission.
* hasColorPerm → &0-&9, &a-&f erlaubt
* hasFormatPerm → &l, &o, &n, &m, &k erlaubt
* Beide false → alle &-Codes entfernen
*/
private String applyColorFilter(String message, boolean hasColorPerm, boolean hasFormatPerm) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < message.length(); i++) {
char c = message.charAt(i);
if (c == '&' && i + 1 < message.length()) {
char next = Character.toLowerCase(message.charAt(i + 1));
boolean isColor = (next >= '0' && next <= '9') || (next >= 'a' && next <= 'f');
boolean isFormat = "lonmkr".indexOf(next) >= 0;
boolean isHex = next == '#';
if (isColor && hasColorPerm) { sb.append(c); continue; }
if (isFormat && hasFormatPerm) { sb.append(c); continue; }
if (isHex && hasColorPerm) { sb.append(c); continue; }
// Kein Recht → & und nächstes Zeichen überspringen
if (isColor || isFormat) { i++; continue; }
// Hex: &# + 6 Zeichen überspringen (i zeigt auf &, +1 = #, +2..+7 = RRGGBB)
if (isHex && i + 7 <= message.length()) { i += 7; continue; }
}
sb.append(c);
}
return sb.toString();
}
// ===== Anti-Werbung =====
// Vorkompilierte Patterns (einmalig beim Classload)
private static final Pattern PATTERN_IP =
Pattern.compile("\\b(\\d{1,3}[.,]){3}\\d{1,3}(:\\d{1,5})?\\b");
private static final Pattern PATTERN_DOMAIN_GENERIC =
Pattern.compile("(?i)\\b[a-z0-9-]{2,63}\\.[a-z]{2,10}(?:[/:\\d]\\S*)?\\b");
private static final Pattern PATTERN_URL_PREFIX =
Pattern.compile("(?i)(https?://|www\\.)\\S+");
/**
* Prüft ob die Nachricht Werbung enthält (IP, URL, fremde Domain).
* Domains auf der Whitelist werden ignoriert.
*
* Erkennt:
* - http:// / https:// / www. Prefixe
* - IPv4-Adressen (auch mit Port)
* - Domain-Namen mit konfigurierten TLDs (z.B. .net, .de, .com)
* - Verschleierungsversuche mit Leerzeichen um Punkte ("play . server . net")
*/
private boolean containsAdvertisement(String message) {
// Normalisierung: "play . server . net" → "play.server.net"
String normalized = message.replaceAll("\\s*\\.\\s*", ".");
// 1. Explizite URL-Prefixe
if (PATTERN_URL_PREFIX.matcher(normalized).find()) {
return !allMatchesWhitelisted(normalized, PATTERN_URL_PREFIX);
}
// 2. IP-Adressen (werden nie whitelisted)
if (PATTERN_IP.matcher(normalized).find()) {
return true;
}
// 3. Domains mit bekannten TLDs
if (!cfg.antiAdBlockedTlds.isEmpty()) {
java.util.regex.Matcher m = PATTERN_DOMAIN_GENERIC.matcher(normalized);
while (m.find()) {
String match = m.group();
String tld = extractTld(match);
if (cfg.antiAdBlockedTlds.contains(tld.toLowerCase())) {
if (!isOnWhitelist(match)) return true;
}
}
}
return false;
}
/** true wenn ALLE Treffer des Patterns auf der Whitelist stehen. */
private boolean allMatchesWhitelisted(String message, Pattern pattern) {
java.util.regex.Matcher m = pattern.matcher(message);
while (m.find()) {
if (!isOnWhitelist(m.group())) return false;
}
return true;
}
private boolean isOnWhitelist(String match) {
String lower = match.toLowerCase();
for (String entry : cfg.antiAdWhitelist) {
if (lower.contains(entry.toLowerCase())) return true;
}
return false;
}
private static String extractTld(String domain) {
String clean = domain.split("[/:]")[0];
int dot = clean.lastIndexOf('.');
return dot >= 0 ? clean.substring(dot + 1) : "";
}
private static String buildStars(int length) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Math.max(length, 4); i++) sb.append('*');
return sb.toString();
}
// ===== Cleanup beim Logout =====
public void cleanup(UUID uuid) {
lastMessageText.remove(uuid);
rateLimiter.clearActor(uuid.toString());
}
// ===== Konfigurationsklasse =====
public static class ChatFilterConfig {
// Anti-Spam
public boolean antiSpamEnabled = true;
public long spamCooldownMs = 1500; // Legacy-Feld fuer Kompatibilitaet
public int spamMaxMessages = 3; // Legacy-Feld fuer Kompatibilitaet
public String spamMessage = "&cBitte nicht so schnell schreiben!";
// Globales Rate-Limit-Framework
public boolean globalRateLimitEnabled = true;
public long globalRateLimitWindowMs = 2500;
public int globalRateLimitMaxActions = 3;
public long globalRateLimitBlockMs = 6000;
// Duplikat
public boolean duplicateCheckEnabled = true;
public String duplicateMessage = "&cBitte keine identischen Nachrichten senden.";
// Blacklist
public boolean blacklistEnabled = true;
public List<String> blacklistWords = new ArrayList<>();
// Caps
public boolean capsFilterEnabled = true;
public int capsMinLength = 6; // Mindestlänge für Caps-Check
public int capsMaxPercent = 70; // Max. % Großbuchstaben
// Anti-Werbung
public boolean antiAdEnabled = true;
public String antiAdMessage = "&cWerbung ist in diesem Chat nicht erlaubt!";
// Domains/Substrings die NICHT geblockt werden (z.B. eigene Serveradresse)
public List<String> antiAdWhitelist = new ArrayList<>();
// TLDs die als Werbung gewertet werden (leer = alle TLDs prüfen)
public List<String> antiAdBlockedTlds = new ArrayList<>(Arrays.asList(
"net", "com", "de", "org", "gg", "io", "eu", "tv", "xyz",
"info", "me", "cc", "co", "app", "online", "site", "fun"
));
}
}