Переделал фильтр чата: слова теперь в JSON, добавлен поиск по фрагментам, улучшена защита от обхода фильтра
This commit is contained in:
parent
2292137986
commit
cb60c29e91
4 changed files with 796 additions and 27 deletions
|
|
@ -126,6 +126,9 @@ public final class LoChat extends JavaPlugin {
|
|||
if (customCommandManager != null) {
|
||||
customCommandManager.reload();
|
||||
}
|
||||
if (filterManager != null) {
|
||||
filterManager.reload();
|
||||
}
|
||||
}
|
||||
|
||||
public static LoChat getInstance() {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,111 @@
|
|||
package com.loki.lochat.managers;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.loki.lochat.LoChat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FilterManager {
|
||||
|
||||
private final LoChat plugin;
|
||||
private List<String> badWords = new ArrayList<>();
|
||||
private List<String> fragments = new ArrayList<>();
|
||||
|
||||
public FilterManager(LoChat plugin) {
|
||||
this.plugin = plugin;
|
||||
loadFilterWords();
|
||||
}
|
||||
|
||||
/**
|
||||
* Загружает плохие слова из JSON файла
|
||||
*/
|
||||
private void loadFilterWords() {
|
||||
badWords.clear();
|
||||
fragments.clear();
|
||||
|
||||
try {
|
||||
// Сначала пытаемся загрузить из data folder (если есть кастомный)
|
||||
File customFile = new File(plugin.getDataFolder(), "filter-words.json");
|
||||
if (customFile.exists()) {
|
||||
try (FileReader reader = new FileReader(customFile, StandardCharsets.UTF_8)) {
|
||||
JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
|
||||
loadFromJsonObject(json);
|
||||
plugin.getLogger().info("Загружены плохие слова из filter-words.json (data folder): " + badWords.size() + " слов, " + fragments.size() + " фрагментов");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Если кастомного нет, копируем из ресурсов
|
||||
plugin.saveResource("filter-words.json", false);
|
||||
|
||||
// Теперь загружаем из data folder
|
||||
if (customFile.exists()) {
|
||||
try (FileReader reader = new FileReader(customFile, StandardCharsets.UTF_8)) {
|
||||
JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
|
||||
loadFromJsonObject(json);
|
||||
plugin.getLogger().info("Загружено " + badWords.size() + " плохих слов и " + fragments.size() + " фрагментов из filter-words.json");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback на старый способ из config.yml
|
||||
List<String> configWords = plugin.getConfigManager().getFilterWords();
|
||||
if (configWords != null && !configWords.isEmpty()) {
|
||||
badWords.addAll(configWords);
|
||||
plugin.getLogger().warning("Используются слова из config.yml (filter-words.json не найден)");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().severe("Ошибка загрузки filter-words.json: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
// Fallback на старый способ
|
||||
List<String> configWords = plugin.getConfigManager().getFilterWords();
|
||||
if (configWords != null && !configWords.isEmpty()) {
|
||||
badWords.addAll(configWords);
|
||||
plugin.getLogger().info("Используются слова из config.yml (fallback)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Загружает данные из JSON объекта
|
||||
*/
|
||||
private void loadFromJsonObject(JsonObject json) {
|
||||
if (json.has("words")) {
|
||||
JsonArray wordsArray = json.getAsJsonArray("words");
|
||||
for (JsonElement element : wordsArray) {
|
||||
String word = element.getAsString();
|
||||
if (word != null && !word.trim().isEmpty()) {
|
||||
badWords.add(word.trim().toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json.has("fragments")) {
|
||||
JsonArray fragmentsArray = json.getAsJsonArray("fragments");
|
||||
for (JsonElement element : fragmentsArray) {
|
||||
String fragment = element.getAsString();
|
||||
if (fragment != null && !fragment.trim().isEmpty()) {
|
||||
fragments.add(fragment.trim().toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Перезагружает список плохих слов
|
||||
*/
|
||||
public void reload() {
|
||||
loadFilterWords();
|
||||
}
|
||||
|
||||
public enum FilterResult {
|
||||
|
|
@ -32,15 +127,16 @@ public class FilterManager {
|
|||
return FilterResult.ALLOWED;
|
||||
}
|
||||
|
||||
List<String> badWords = plugin.getConfigManager().getFilterWords();
|
||||
if (badWords == null || badWords.isEmpty()) {
|
||||
if (badWords.isEmpty() && fragments.isEmpty()) {
|
||||
return FilterResult.ALLOWED;
|
||||
}
|
||||
|
||||
// Нормализуем сообщение (убираем обход фильтра)
|
||||
String normalizedMessage = normalizeMessage(message);
|
||||
String lowerMessage = normalizedMessage.toLowerCase();
|
||||
String originalLower = message.toLowerCase();
|
||||
|
||||
// Проверяем полные слова
|
||||
for (String word : badWords) {
|
||||
if (word == null || word.trim().isEmpty()) {
|
||||
continue;
|
||||
|
|
@ -50,29 +146,45 @@ public class FilterManager {
|
|||
|
||||
// Проверяем точное совпадение слова (с границами слов)
|
||||
Pattern wordPattern = Pattern.compile("\\b" + Pattern.quote(lowerWord) + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
if (wordPattern.matcher(normalizedMessage).find()) {
|
||||
String action = plugin.getConfigManager().getFilterAction();
|
||||
return switch (action.toLowerCase()) {
|
||||
case "block" -> FilterResult.BLOCKED;
|
||||
case "warn" -> FilterResult.WARNED;
|
||||
default -> FilterResult.CENSORED;
|
||||
};
|
||||
if (wordPattern.matcher(normalizedMessage).find() || wordPattern.matcher(originalLower).find()) {
|
||||
return getFilterResult();
|
||||
}
|
||||
|
||||
// Также проверяем как подстроку (для частичных совпадений типа "пизд")
|
||||
if (lowerMessage.contains(lowerWord)) {
|
||||
String action = plugin.getConfigManager().getFilterAction();
|
||||
return switch (action.toLowerCase()) {
|
||||
case "block" -> FilterResult.BLOCKED;
|
||||
case "warn" -> FilterResult.WARNED;
|
||||
default -> FilterResult.CENSORED;
|
||||
};
|
||||
// Также проверяем как подстроку
|
||||
if (lowerMessage.contains(lowerWord) || originalLower.contains(lowerWord)) {
|
||||
return getFilterResult();
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем фрагменты (поиск по фрагментам типа "пизд" найдет "пизда", "пиздец" и т.д.)
|
||||
for (String fragment : fragments) {
|
||||
if (fragment == null || fragment.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String lowerFragment = fragment.toLowerCase().trim();
|
||||
|
||||
// Ищем фрагмент в нормализованном сообщении
|
||||
if (lowerMessage.contains(lowerFragment) || originalLower.contains(lowerFragment)) {
|
||||
return getFilterResult();
|
||||
}
|
||||
}
|
||||
|
||||
return FilterResult.ALLOWED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает результат фильтрации на основе настроек
|
||||
*/
|
||||
private FilterResult getFilterResult() {
|
||||
String action = plugin.getConfigManager().getFilterAction();
|
||||
return switch (action.toLowerCase()) {
|
||||
case "block" -> FilterResult.BLOCKED;
|
||||
case "warn" -> FilterResult.WARNED;
|
||||
default -> FilterResult.CENSORED;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Цензурирует сообщение, заменяя запрещенные слова
|
||||
*/
|
||||
|
|
@ -85,8 +197,7 @@ public class FilterManager {
|
|||
return message;
|
||||
}
|
||||
|
||||
List<String> badWords = plugin.getConfigManager().getFilterWords();
|
||||
if (badWords == null || badWords.isEmpty()) {
|
||||
if (badWords.isEmpty() && fragments.isEmpty()) {
|
||||
return message;
|
||||
}
|
||||
|
||||
|
|
@ -98,38 +209,53 @@ public class FilterManager {
|
|||
String result = message;
|
||||
String normalizedMessage = normalizeMessage(message);
|
||||
|
||||
// Заменяем полные слова
|
||||
for (String word : badWords) {
|
||||
if (word == null || word.trim().isEmpty()) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Заменяем фрагменты (находим слова содержащие фрагмент и заменяем их)
|
||||
for (String fragment : fragments) {
|
||||
if (fragment == null || fragment.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ищем слова содержащие фрагмент и заменяем весь фрагмент
|
||||
Pattern fragmentPattern = Pattern.compile("(?i)" + Pattern.quote(fragment));
|
||||
result = fragmentPattern.matcher(result).replaceAll(replacement);
|
||||
|
||||
// Также заменяем слова начинающиеся с фрагмента
|
||||
Pattern wordStartPattern = Pattern.compile("\\b" + Pattern.quote(fragment) + "\\w*", Pattern.CASE_INSENSITIVE);
|
||||
result = wordStartPattern.matcher(result).replaceAll(replacement);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Нормализует сообщение, убирая попытки обхода фильтра
|
||||
* Заменяет специальные символы на обычные буквы
|
||||
* Например: "пизд@" -> "пизда", "х*й" -> "хай" (но "хуй" найдет)
|
||||
*/
|
||||
private String normalizeMessage(String message) {
|
||||
if (message == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Заменяем символы обхода на буквы (для обнаружения х*й, х_уй и т.д.)
|
||||
// Заменяем символы обхода на буквы (для обнаружения х*й, х_уй, пизд@ и т.д.)
|
||||
String normalized = message;
|
||||
|
||||
// Заменяем символы обхода на буквы
|
||||
// Заменяем символы обхода на похожие буквы
|
||||
normalized = normalized.replace('*', 'а');
|
||||
normalized = normalized.replace('_', 'а');
|
||||
normalized = normalized.replace('-', 'а');
|
||||
|
|
@ -146,12 +272,27 @@ public class FilterManager {
|
|||
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.replace('&', 'а');
|
||||
normalized = normalized.replace('~', 'а');
|
||||
normalized = normalized.replace('`', 'а');
|
||||
normalized = normalized.replace('=', 'а');
|
||||
normalized = normalized.replace('+', 'а');
|
||||
normalized = normalized.replace('[', 'а');
|
||||
normalized = normalized.replace(']', 'а');
|
||||
normalized = normalized.replace('{', 'а');
|
||||
normalized = normalized.replace('}', 'а');
|
||||
normalized = normalized.replace('(', 'а');
|
||||
normalized = normalized.replace(')', 'а');
|
||||
normalized = normalized.replace('/', 'а');
|
||||
normalized = normalized.replace('\\', 'а');
|
||||
normalized = normalized.replace('?', 'а');
|
||||
normalized = normalized.replace('<', 'а');
|
||||
normalized = normalized.replace('>', 'а');
|
||||
|
||||
// Убираем множественные пробелы
|
||||
normalized = normalized.replaceAll("\\s+", " ");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue