From cb60c29e917b677ce19f537b5fd66d163c334741 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 30 Dec 2025 20:55:46 +0100 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=20=D1=87?= =?UTF-8?q?=D0=B0=D1=82=D0=B0:=20=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=20=D1=82?= =?UTF-8?q?=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B2=20JSON,=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BF=D0=BE=D0=B8=D1=81?= =?UTF-8?q?=D0=BA=20=D0=BF=D0=BE=20=D1=84=D1=80=D0=B0=D0=B3=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D0=BC,=20=D1=83=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B7=D0=B0=D1=89=D0=B8=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BE=D1=82=20=D0=BE=D0=B1=D1=85=D0=BE=D0=B4=D0=B0=20=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +- src/main/java/com/loki/lochat/LoChat.java | 3 + .../loki/lochat/managers/FilterManager.java | 191 +++++- src/main/resources/filter-words.json | 624 ++++++++++++++++++ 4 files changed, 796 insertions(+), 27 deletions(-) create mode 100644 src/main/resources/filter-words.json diff --git a/build.gradle b/build.gradle index d34f034..fe43de9 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { } group = 'com.loki' -version = '1.5.3' +version = '1.5.4' repositories { mavenCentral() @@ -17,6 +17,7 @@ dependencies { compileOnly 'me.clip:placeholderapi:2.11.6' compileOnly 'org.black_ixx:playerpoints:3.2.7' compileOnly 'net.luckperms:api:5.4' + implementation 'com.google.code.gson:gson:2.10.1' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0' @@ -42,7 +43,7 @@ test { } jar { - archiveFileName = 'LoChat-1.5.3.jar' + archiveFileName = 'LoChat-1.5.4.jar' from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } diff --git a/src/main/java/com/loki/lochat/LoChat.java b/src/main/java/com/loki/lochat/LoChat.java index 5be3a41..37041ff 100644 --- a/src/main/java/com/loki/lochat/LoChat.java +++ b/src/main/java/com/loki/lochat/LoChat.java @@ -126,6 +126,9 @@ public final class LoChat extends JavaPlugin { if (customCommandManager != null) { customCommandManager.reload(); } + if (filterManager != null) { + filterManager.reload(); + } } public static LoChat getInstance() { diff --git a/src/main/java/com/loki/lochat/managers/FilterManager.java b/src/main/java/com/loki/lochat/managers/FilterManager.java index 9ee1db6..b20eb3f 100644 --- a/src/main/java/com/loki/lochat/managers/FilterManager.java +++ b/src/main/java/com/loki/lochat/managers/FilterManager.java @@ -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 badWords = new ArrayList<>(); + private List 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 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 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 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 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+", " "); diff --git a/src/main/resources/filter-words.json b/src/main/resources/filter-words.json new file mode 100644 index 0000000..4bdd05c --- /dev/null +++ b/src/main/resources/filter-words.json @@ -0,0 +1,624 @@ +{ + "words": [ + "хуй", + "хуя", + "хуе", + "хуи", + "хую", + "хуё", + "пизд", + "пизда", + "пиздец", + "пизду", + "пизды", + "пиздо", + "блять", + "блядь", + "бля", + "бляд", + "ебать", + "ебал", + "ебало", + "ебать", + "ебет", + "ебут", + "ебу", + "ебё", + "ебан", + "сука", + "суки", + "суке", + "суку", + "сукой", + "суко", + "ебан", + "ебана", + "ебану", + "ебань", + "ебля", + "ебли", + "ебло", + "ебля", + "ебут", + "ебуть", + "ебёшь", + "ебёт", + "ебём", + "ебёте", + "ебут", + "ебаный", + "ебаная", + "ебаное", + "ебаные", + "ебаного", + "ебаной", + "ебаному", + "ебаным", + "ебаных", + "ебаным", + "ебаные", + "ебаными", + "ебаных", + "ебанный", + "ебанная", + "ебанное", + "ебанные", + "ебанного", + "ебанной", + "ебанному", + "ебанным", + "ебанных", + "ебанным", + "ебанные", + "ебанными", + "ебанных", + "заеб", + "заеба", + "заебе", + "заеби", + "заебу", + "заебё", + "уеб", + "уеба", + "уебе", + "уеби", + "уебу", + "уебё", + "выеб", + "выеба", + "выебе", + "выеби", + "выебу", + "выебё", + "проеб", + "проеба", + "проебе", + "проеби", + "проебу", + "проебё", + "долбоеб", + "долбоеба", + "долбоебе", + "долбоеби", + "долбоебу", + "долбоебё", + "мудак", + "мудака", + "мудаке", + "мудаки", + "мудаку", + "мудаком", + "мудаки", + "мудаков", + "мудакам", + "мудаками", + "мудаках", + "мудила", + "мудилы", + "мудиле", + "мудилу", + "мудилой", + "мудило", + "гандон", + "гондон", + "гандона", + "гондона", + "гандону", + "гондону", + "гандоном", + "гондоном", + "гандоне", + "гондоне", + "гандонов", + "гондонов", + "гандонам", + "гондонам", + "гандонами", + "гондонами", + "гандонах", + "гондонах", + "пидор", + "пидора", + "пидору", + "пидором", + "пидоре", + "пидоры", + "пидоров", + "пидорам", + "пидорами", + "пидорах", + "пидарас", + "пидараса", + "пидарасу", + "пидарасом", + "пидарасе", + "пидарасы", + "пидарасов", + "пидарасам", + "пидарасами", + "пидарасах", + "педик", + "педика", + "педику", + "педиком", + "педике", + "педики", + "педиков", + "педикам", + "педиками", + "педиках", + "гомик", + "гомика", + "гомику", + "гомиком", + "гомике", + "гомики", + "гомиков", + "гомикам", + "гомиками", + "гомиках", + "шлюха", + "шлюхи", + "шлюхе", + "шлюху", + "шлюхой", + "шлюхо", + "шлюх", + "блядина", + "блядины", + "блядине", + "блядину", + "блядиной", + "блядино", + "блядин", + "шлюшка", + "шлюшки", + "шлюшке", + "шлюшку", + "шлюшкой", + "шлюшко", + "шлюшек", + "проститутка", + "проститутки", + "проститутке", + "проститутку", + "проституткой", + "проститутко", + "проституток", + "проституткам", + "проститутками", + "проститутках", + "еблан", + "еблана", + "еблану", + "ебланом", + "еблане", + "ебланы", + "ебланов", + "ебланам", + "ебланами", + "ебланах", + "ебанутый", + "ебанутого", + "ебанутому", + "ебанутым", + "ебанутом", + "ебанутая", + "ебанутой", + "ебанутую", + "ебанутое", + "ебанутые", + "ебанутых", + "ебанутым", + "ебанутыми", + "ебанутых", + "ебанутый", + "ебанутого", + "ебанутому", + "ебанутым", + "ебанутом", + "ебанутая", + "ебанутой", + "ебанутую", + "ебанутое", + "ебанутые", + "ебанутых", + "ебанутым", + "ебанутыми", + "ебанутых", + "уебан", + "уебана", + "уебану", + "уебаном", + "уебане", + "уебаны", + "уебанов", + "уебанам", + "уебанами", + "уебанах", + "долбоёб", + "долбоёба", + "долбоёбу", + "долбоёбом", + "долбоёбе", + "долбоёбы", + "долбоёбов", + "долбоёбам", + "долбоёбами", + "долбоёбах", + "долбоеб", + "долбоеба", + "долбоебу", + "долбоебом", + "долбоебе", + "долбоебы", + "долбоебов", + "долбоебам", + "долбоебами", + "долбоебах", + "еблан", + "еблана", + "еблану", + "ебланом", + "еблане", + "ебланы", + "ебланов", + "ебланам", + "ебланами", + "ебланах", + "залупа", + "залупы", + "залупе", + "залупу", + "залупой", + "залупо", + "залуп", + "залупка", + "залупки", + "залупке", + "залупку", + "залупкой", + "залупко", + "залупок", + "залупкам", + "залупками", + "залупках", + "мразь", + "мрази", + "мрази", + "мразью", + "мразь", + "мразей", + "мразям", + "мразями", + "мразях", + "мразота", + "мразоты", + "мразоте", + "мразоту", + "мразотой", + "мразото", + "мразот", + "мразотой", + "мразотам", + "мразотами", + "мразотах", + "ублюдок", + "ублюдка", + "ублюдку", + "ублюдком", + "ублюдке", + "ублюдки", + "ублюдков", + "ублюдкам", + "ублюдками", + "ублюдках", + "выблядок", + "выблядка", + "выблядку", + "выблядком", + "выблядке", + "выблядки", + "выблядков", + "выблядкам", + "выблядками", + "выблядках", + "падла", + "падлы", + "падле", + "падлу", + "падлой", + "падло", + "падл", + "падлюга", + "падлюги", + "падлюге", + "падлюгу", + "падлюгой", + "падлюго", + "падлюг", + "падлюгам", + "падлюгами", + "падлюгах", + "гад", + "гада", + "гаду", + "гадом", + "гаде", + "гады", + "гадов", + "гадам", + "гадами", + "гадах", + "гадина", + "гадины", + "гадине", + "гадину", + "гадиной", + "гадино", + "гадин", + "гадинам", + "гадинами", + "гадинах", + "тварь", + "твари", + "твари", + "тварью", + "тварь", + "тварей", + "тварям", + "тварями", + "тварях", + "тварина", + "тварины", + "тварине", + "тварину", + "твариной", + "тварино", + "тварин", + "тваринам", + "тваринами", + "тваринах", + "сволочь", + "сволочи", + "сволочи", + "сволочью", + "сволочь", + "сволочей", + "сволочам", + "сволочами", + "сволочах", + "сволочка", + "сволочки", + "сволочке", + "сволочку", + "сволочкой", + "сволочко", + "сволочек", + "сволочкам", + "сволочками", + "сволочках", + "скотина", + "скотины", + "скотине", + "скотину", + "скотиной", + "скотино", + "скотин", + "скотинам", + "скотинами", + "скотинах", + "скотиняка", + "скотиняки", + "скотиняке", + "скотиняку", + "скотинякой", + "скотиняко", + "скотиняк", + "скотинякам", + "скотиняками", + "скотиняках", + "козел", + "козла", + "козлу", + "козлом", + "козле", + "козлы", + "козлов", + "козлам", + "козлами", + "козлах", + "козлина", + "козлины", + "козлине", + "козлину", + "козлиной", + "козлино", + "козлин", + "козлинам", + "козлинами", + "козлинах", + "козлище", + "козлища", + "козлищу", + "козлищем", + "козлище", + "козлищ", + "козлищам", + "козлищами", + "козлищах", + "козёл", + "козла", + "козлу", + "козлом", + "козле", + "козлы", + "козлов", + "козлам", + "козлами", + "козлах", + "козлина", + "козлины", + "козлине", + "козлину", + "козлиной", + "козлино", + "козлин", + "козлинам", + "козлинами", + "козлинах", + "козлище", + "козлища", + "козлищу", + "козлищем", + "козлище", + "козлищ", + "козлищам", + "козлищами", + "козлищах", + "козёл", + "козла", + "козлу", + "козлом", + "козле", + "козлы", + "козлов", + "козлам", + "козлами", + "козлах", + "козлина", + "козлины", + "козлине", + "козлину", + "козлиной", + "козлино", + "козлин", + "козлинам", + "козлинами", + "козлинах", + "козлище", + "козлища", + "козлищу", + "козлищем", + "козлище", + "козлищ", + "козлищам", + "козлищами", + "козлищах" + ], + "fragments": [ + "хуй", + "хуя", + "хуе", + "хуи", + "хую", + "хуё", + "пизд", + "бля", + "бляд", + "еб", + "еба", + "ебе", + "еби", + "ебу", + "ебё", + "ебан", + "ебал", + "ебан", + "ебля", + "ебли", + "ебло", + "ебут", + "ебуть", + "ебёшь", + "ебёт", + "ебём", + "ебёте", + "ебут", + "ебаный", + "ебаная", + "ебаное", + "ебаные", + "ебаного", + "ебаной", + "ебаному", + "ебаным", + "ебаных", + "ебаным", + "ебаные", + "ебаными", + "ебаных", + "ебанный", + "ебанная", + "ебанное", + "ебанные", + "ебанного", + "ебанной", + "ебанному", + "ебанным", + "ебанных", + "ебанным", + "ебанные", + "ебанными", + "ебанных", + "заеб", + "уеб", + "выеб", + "проеб", + "долбоеб", + "мудак", + "мудил", + "гандон", + "гондон", + "пидор", + "пидарас", + "педик", + "гомик", + "шлюх", + "блядин", + "проститутк", + "еблан", + "ебанут", + "уебан", + "долбоёб", + "долбоеб", + "залуп", + "мраз", + "мразот", + "ублюдок", + "выблядок", + "падл", + "падлюг", + "гад", + "гадин", + "твар", + "тварин", + "сволоч", + "скотин", + "козел", + "козл", + "козёл", + "козлищ" + ] +} +