Исправлен фильтр чата: добавлена нормализация сообщений, защита от обхода фильтра, точный поиск слов с word boundaries
This commit is contained in:
parent
d49261988e
commit
2292137986
3 changed files with 126 additions and 10 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package com.loki.lochat.managers;
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
import com.loki.lochat.LoChat;
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.gradient.util.FoliaUtil;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
|
@ -61,9 +62,16 @@ public class ChatColorManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Асинхронное сохранение данных (для Folia совместимости)
|
||||||
|
*/
|
||||||
|
private void saveDataAsync() {
|
||||||
|
FoliaUtil.runAsync(plugin, this::saveData);
|
||||||
|
}
|
||||||
|
|
||||||
public void setChatColor(UUID playerId, String color) {
|
public void setChatColor(UUID playerId, String color) {
|
||||||
chatColors.put(playerId, color);
|
chatColors.put(playerId, color);
|
||||||
saveData();
|
saveDataAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getChatColor(UUID playerId) {
|
public String getChatColor(UUID playerId) {
|
||||||
|
|
@ -77,7 +85,7 @@ public class ChatColorManager {
|
||||||
public void removeChatColor(UUID playerId) {
|
public void removeChatColor(UUID playerId) {
|
||||||
chatColors.remove(playerId);
|
chatColors.remove(playerId);
|
||||||
dataConfig.set(playerId.toString(), null);
|
dataConfig.set(playerId.toString(), null);
|
||||||
saveData();
|
saveDataAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,49 @@ public class FilterManager {
|
||||||
WARNED
|
WARNED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет сообщение на наличие запрещенных слов
|
||||||
|
*/
|
||||||
public FilterResult checkMessage(String message) {
|
public FilterResult checkMessage(String message) {
|
||||||
if (!plugin.getConfigManager().isFilterEnabled()) {
|
if (!plugin.getConfigManager().isFilterEnabled()) {
|
||||||
return FilterResult.ALLOWED;
|
return FilterResult.ALLOWED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message == null || message.trim().isEmpty()) {
|
||||||
|
return FilterResult.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
List<String> badWords = plugin.getConfigManager().getFilterWords();
|
List<String> badWords = plugin.getConfigManager().getFilterWords();
|
||||||
String lowerMessage = message.toLowerCase();
|
if (badWords == null || badWords.isEmpty()) {
|
||||||
|
return FilterResult.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Нормализуем сообщение (убираем обход фильтра)
|
||||||
|
String normalizedMessage = normalizeMessage(message);
|
||||||
|
String lowerMessage = normalizedMessage.toLowerCase();
|
||||||
|
|
||||||
for (String word : badWords) {
|
for (String word : badWords) {
|
||||||
if (lowerMessage.contains(word.toLowerCase())) {
|
if (word == null || word.trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String lowerWord = word.toLowerCase().trim();
|
||||||
|
|
||||||
|
// Проверяем точное совпадение слова (с границами слов)
|
||||||
|
Pattern wordPattern = Pattern.compile("\\b" + Pattern.quote(lowerWord) + "\\b", Pattern.CASE_INSENSITIVE);
|
||||||
|
if (wordPattern.matcher(normalizedMessage).find()) {
|
||||||
String action = plugin.getConfigManager().getFilterAction();
|
String action = plugin.getConfigManager().getFilterAction();
|
||||||
return switch (action) {
|
return switch (action.toLowerCase()) {
|
||||||
|
case "block" -> FilterResult.BLOCKED;
|
||||||
|
case "warn" -> FilterResult.WARNED;
|
||||||
|
default -> FilterResult.CENSORED;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Также проверяем как подстроку (для частичных совпадений типа "пизд")
|
||||||
|
if (lowerMessage.contains(lowerWord)) {
|
||||||
|
String action = plugin.getConfigManager().getFilterAction();
|
||||||
|
return switch (action.toLowerCase()) {
|
||||||
case "block" -> FilterResult.BLOCKED;
|
case "block" -> FilterResult.BLOCKED;
|
||||||
case "warn" -> FilterResult.WARNED;
|
case "warn" -> FilterResult.WARNED;
|
||||||
default -> FilterResult.CENSORED;
|
default -> FilterResult.CENSORED;
|
||||||
|
|
@ -42,20 +73,89 @@ public class FilterManager {
|
||||||
return FilterResult.ALLOWED;
|
return FilterResult.ALLOWED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Цензурирует сообщение, заменяя запрещенные слова
|
||||||
|
*/
|
||||||
public String censorMessage(String message) {
|
public String censorMessage(String message) {
|
||||||
if (!plugin.getConfigManager().isFilterEnabled()) {
|
if (!plugin.getConfigManager().isFilterEnabled()) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message == null || message.trim().isEmpty()) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
List<String> badWords = plugin.getConfigManager().getFilterWords();
|
List<String> badWords = plugin.getConfigManager().getFilterWords();
|
||||||
|
if (badWords == null || badWords.isEmpty()) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
String replacement = plugin.getConfigManager().getFilterReplacement();
|
String replacement = plugin.getConfigManager().getFilterReplacement();
|
||||||
|
if (replacement == null) {
|
||||||
|
replacement = "***";
|
||||||
|
}
|
||||||
|
|
||||||
String result = message;
|
String result = message;
|
||||||
|
String normalizedMessage = normalizeMessage(message);
|
||||||
|
|
||||||
for (String word : badWords) {
|
for (String word : badWords) {
|
||||||
Pattern pattern = Pattern.compile("(?i)" + Pattern.quote(word));
|
if (word == null || word.trim().isEmpty()) {
|
||||||
result = pattern.matcher(result).replaceAll(replacement);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String lowerWord = word.toLowerCase().trim();
|
||||||
|
|
||||||
|
// Заменяем точные совпадения слов
|
||||||
|
Pattern wordPattern = Pattern.compile("\\b" + Pattern.quote(word) + "\\b", Pattern.CASE_INSENSITIVE);
|
||||||
|
result = wordPattern.matcher(result).replaceAll(replacement);
|
||||||
|
|
||||||
|
// Также заменяем как подстроку (для частичных совпадений)
|
||||||
|
Pattern substringPattern = Pattern.compile("(?i)" + Pattern.quote(word));
|
||||||
|
result = substringPattern.matcher(result).replaceAll(replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Нормализует сообщение, убирая попытки обхода фильтра
|
||||||
|
* Заменяет специальные символы на обычные буквы
|
||||||
|
*/
|
||||||
|
private String normalizeMessage(String message) {
|
||||||
|
if (message == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Заменяем символы обхода на буквы (для обнаружения х*й, х_уй и т.д.)
|
||||||
|
String normalized = message;
|
||||||
|
|
||||||
|
// Заменяем символы обхода на буквы
|
||||||
|
normalized = normalized.replace('*', 'а');
|
||||||
|
normalized = normalized.replace('_', 'а');
|
||||||
|
normalized = normalized.replace('-', 'а');
|
||||||
|
normalized = normalized.replace('.', 'а');
|
||||||
|
normalized = normalized.replace(',', 'а');
|
||||||
|
normalized = normalized.replace('|', 'и');
|
||||||
|
normalized = normalized.replace('1', 'и');
|
||||||
|
normalized = normalized.replace('!', 'и');
|
||||||
|
normalized = normalized.replace('0', 'о');
|
||||||
|
normalized = normalized.replace('3', 'з');
|
||||||
|
normalized = normalized.replace('4', 'ч');
|
||||||
|
normalized = normalized.replace('5', 'с');
|
||||||
|
normalized = normalized.replace('6', 'б');
|
||||||
|
normalized = normalized.replace('7', 'т');
|
||||||
|
normalized = normalized.replace('8', 'в');
|
||||||
|
normalized = normalized.replace('9', 'д');
|
||||||
|
normalized = normalized.replace('@', 'а');
|
||||||
|
normalized = normalized.replace('#', 'х');
|
||||||
|
normalized = normalized.replace('$', 'с');
|
||||||
|
normalized = normalized.replace('%', 'о');
|
||||||
|
normalized = normalized.replace('^', 'х');
|
||||||
|
normalized = normalized.replace('&', 'а');
|
||||||
|
|
||||||
|
// Убираем множественные пробелы
|
||||||
|
normalized = normalized.replaceAll("\\s+", " ");
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.loki.lochat.managers;
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
import com.loki.lochat.LoChat;
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.gradient.util.FoliaUtil;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
@ -69,7 +70,7 @@ public class IgnoreManager {
|
||||||
public boolean addIgnore(UUID player, UUID target) {
|
public boolean addIgnore(UUID player, UUID target) {
|
||||||
Set<UUID> ignored = ignoreMap.computeIfAbsent(player, k -> ConcurrentHashMap.newKeySet());
|
Set<UUID> ignored = ignoreMap.computeIfAbsent(player, k -> ConcurrentHashMap.newKeySet());
|
||||||
boolean added = ignored.add(target);
|
boolean added = ignored.add(target);
|
||||||
if (added) save();
|
if (added) saveAsync();
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +78,7 @@ public class IgnoreManager {
|
||||||
Set<UUID> ignored = ignoreMap.get(player);
|
Set<UUID> ignored = ignoreMap.get(player);
|
||||||
if (ignored == null) return false;
|
if (ignored == null) return false;
|
||||||
boolean removed = ignored.remove(target);
|
boolean removed = ignored.remove(target);
|
||||||
if (removed) save();
|
if (removed) saveAsync();
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,6 +93,13 @@ public class IgnoreManager {
|
||||||
|
|
||||||
public void clearIgnores(UUID player) {
|
public void clearIgnores(UUID player) {
|
||||||
ignoreMap.remove(player);
|
ignoreMap.remove(player);
|
||||||
save();
|
saveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Асинхронное сохранение данных (для Folia совместимости)
|
||||||
|
*/
|
||||||
|
private void saveAsync() {
|
||||||
|
FoliaUtil.runAsync(plugin, this::save);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue