Delete src/main/java/net/viper/status/modules/chat/ChatFilter.java via Git Manager GUI
This commit is contained in:
@@ -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"
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user