Переделал фильтр чата: слова теперь в JSON, добавлен поиск по фрагментам, улучшена защита от обхода фильтра

This commit is contained in:
loki5512344 2025-12-30 20:55:46 +01:00
parent 2292137986
commit cb60c29e91
4 changed files with 796 additions and 27 deletions

View file

@ -126,6 +126,9 @@ public final class LoChat extends JavaPlugin {
if (customCommandManager != null) {
customCommandManager.reload();
}
if (filterManager != null) {
filterManager.reload();
}
}
public static LoChat getInstance() {

View file

@ -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+", " ");

View file

@ -0,0 +1,624 @@
{
"words": [
"хуй",
"хуя",
"хуе",
"хуи",
"хую",
"хуё",
"пизд",
"пизда",
"пиздец",
"пизду",
"пизды",
"пиздо",
"блять",
"блядь",
"бля",
"бляд",
"ебать",
"ебал",
"ебало",
"ебать",
"ебет",
"ебут",
"ебу",
"ебё",
"ебан",
"сука",
"суки",
"суке",
"суку",
"сукой",
"суко",
"ебан",
"ебана",
"ебану",
"ебань",
"ебля",
"ебли",
"ебло",
"ебля",
"ебут",
"ебуть",
"ебёшь",
"ебёт",
"ебём",
"ебёте",
"ебут",
"ебаный",
"ебаная",
"ебаное",
"ебаные",
"ебаного",
"ебаной",
"ебаному",
"ебаным",
"ебаных",
"ебаным",
"ебаные",
"ебаными",
"ебаных",
"ебанный",
"ебанная",
"ебанное",
"ебанные",
"ебанного",
"ебанной",
"ебанному",
"ебанным",
"ебанных",
"ебанным",
"ебанные",
"ебанными",
"ебанных",
"заеб",
"заеба",
"заебе",
"заеби",
"заебу",
"заебё",
"уеб",
"уеба",
"уебе",
"уеби",
"уебу",
"уебё",
"выеб",
"выеба",
"выебе",
"выеби",
"выебу",
"выебё",
"проеб",
"проеба",
"проебе",
"проеби",
"проебу",
"проебё",
"долбоеб",
"долбоеба",
"долбоебе",
"долбоеби",
"долбоебу",
"долбоебё",
"мудак",
"мудака",
"мудаке",
"мудаки",
"мудаку",
"мудаком",
"мудаки",
"мудаков",
"мудакам",
"мудаками",
"мудаках",
"мудила",
"мудилы",
"мудиле",
"мудилу",
"мудилой",
"мудило",
"гандон",
"гондон",
"гандона",
"гондона",
"гандону",
"гондону",
"гандоном",
"гондоном",
"гандоне",
"гондоне",
"гандонов",
"гондонов",
"гандонам",
"гондонам",
"гандонами",
"гондонами",
"гандонах",
"гондонах",
"пидор",
"пидора",
"пидору",
"пидором",
"пидоре",
"пидоры",
"пидоров",
"пидорам",
"пидорами",
"пидорах",
"пидарас",
"пидараса",
"пидарасу",
"пидарасом",
"пидарасе",
"пидарасы",
"пидарасов",
"пидарасам",
"пидарасами",
"пидарасах",
"педик",
"педика",
"педику",
"педиком",
"педике",
"педики",
"педиков",
"педикам",
"педиками",
"педиках",
"гомик",
"гомика",
"гомику",
"гомиком",
"гомике",
"гомики",
"гомиков",
"гомикам",
"гомиками",
"гомиках",
"шлюха",
"шлюхи",
"шлюхе",
"шлюху",
"шлюхой",
"шлюхо",
"шлюх",
"блядина",
"блядины",
"блядине",
"блядину",
"блядиной",
"блядино",
"блядин",
"шлюшка",
"шлюшки",
"шлюшке",
"шлюшку",
"шлюшкой",
"шлюшко",
"шлюшек",
"проститутка",
"проститутки",
"проститутке",
"проститутку",
"проституткой",
"проститутко",
"проституток",
"проституткам",
"проститутками",
"проститутках",
"еблан",
"еблана",
"еблану",
"ебланом",
"еблане",
"ебланы",
"ебланов",
"ебланам",
"ебланами",
"ебланах",
"ебанутый",
"ебанутого",
"ебанутому",
"ебанутым",
"ебанутом",
"ебанутая",
"ебанутой",
"ебанутую",
"ебанутое",
"ебанутые",
"ебанутых",
"ебанутым",
"ебанутыми",
"ебанутых",
"ебанутый",
"ебанутого",
"ебанутому",
"ебанутым",
"ебанутом",
"ебанутая",
"ебанутой",
"ебанутую",
"ебанутое",
"ебанутые",
"ебанутых",
"ебанутым",
"ебанутыми",
"ебанутых",
"уебан",
"уебана",
"уебану",
"уебаном",
"уебане",
"уебаны",
"уебанов",
"уебанам",
"уебанами",
"уебанах",
"долбоёб",
"долбоёба",
"долбоёбу",
"долбоёбом",
"долбоёбе",
"долбоёбы",
"долбоёбов",
"долбоёбам",
"долбоёбами",
"долбоёбах",
"долбоеб",
"долбоеба",
"долбоебу",
"долбоебом",
"долбоебе",
"долбоебы",
"долбоебов",
"долбоебам",
"долбоебами",
"долбоебах",
"еблан",
"еблана",
"еблану",
"ебланом",
"еблане",
"ебланы",
"ебланов",
"ебланам",
"ебланами",
"ебланах",
"залупа",
"залупы",
"залупе",
"залупу",
"залупой",
"залупо",
"залуп",
"залупка",
"залупки",
"залупке",
"залупку",
"залупкой",
"залупко",
"залупок",
"залупкам",
"залупками",
"залупках",
"мразь",
"мрази",
"мрази",
"мразью",
"мразь",
"мразей",
"мразям",
"мразями",
"мразях",
"мразота",
"мразоты",
"мразоте",
"мразоту",
"мразотой",
"мразото",
"мразот",
"мразотой",
"мразотам",
"мразотами",
"мразотах",
"ублюдок",
"ублюдка",
"ублюдку",
"ублюдком",
"ублюдке",
"ублюдки",
"ублюдков",
"ублюдкам",
"ублюдками",
"ублюдках",
"выблядок",
"выблядка",
"выблядку",
"выблядком",
"выблядке",
"выблядки",
"выблядков",
"выблядкам",
"выблядками",
"выблядках",
"падла",
"падлы",
"падле",
"падлу",
"падлой",
"падло",
"падл",
"падлюга",
"падлюги",
"падлюге",
"падлюгу",
"падлюгой",
"падлюго",
"падлюг",
"падлюгам",
"падлюгами",
"падлюгах",
"гад",
"гада",
"гаду",
"гадом",
"гаде",
"гады",
"гадов",
"гадам",
"гадами",
"гадах",
"гадина",
"гадины",
"гадине",
"гадину",
"гадиной",
"гадино",
"гадин",
"гадинам",
"гадинами",
"гадинах",
"тварь",
"твари",
"твари",
"тварью",
"тварь",
"тварей",
"тварям",
"тварями",
"тварях",
"тварина",
"тварины",
"тварине",
"тварину",
"твариной",
"тварино",
"тварин",
"тваринам",
"тваринами",
"тваринах",
"сволочь",
"сволочи",
"сволочи",
"сволочью",
"сволочь",
"сволочей",
"сволочам",
"сволочами",
"сволочах",
"сволочка",
"сволочки",
"сволочке",
"сволочку",
"сволочкой",
"сволочко",
"сволочек",
"сволочкам",
"сволочками",
"сволочках",
"скотина",
"скотины",
"скотине",
"скотину",
"скотиной",
"скотино",
"скотин",
"скотинам",
"скотинами",
"скотинах",
"скотиняка",
"скотиняки",
"скотиняке",
"скотиняку",
"скотинякой",
"скотиняко",
"скотиняк",
"скотинякам",
"скотиняками",
"скотиняках",
"козел",
"козла",
"козлу",
"козлом",
"козле",
"козлы",
"козлов",
"козлам",
"козлами",
"козлах",
"козлина",
"козлины",
"козлине",
"козлину",
"козлиной",
"козлино",
"козлин",
"козлинам",
"козлинами",
"козлинах",
"козлище",
"козлища",
"козлищу",
"козлищем",
"козлище",
"козлищ",
"козлищам",
"козлищами",
"козлищах",
"козёл",
"козла",
"козлу",
"козлом",
"козле",
"козлы",
"козлов",
"козлам",
"козлами",
"козлах",
"козлина",
"козлины",
"козлине",
"козлину",
"козлиной",
"козлино",
"козлин",
"козлинам",
"козлинами",
"козлинах",
"козлище",
"козлища",
"козлищу",
"козлищем",
"козлище",
"козлищ",
"козлищам",
"козлищами",
"козлищах",
"козёл",
"козла",
"козлу",
"козлом",
"козле",
"козлы",
"козлов",
"козлам",
"козлами",
"козлах",
"козлина",
"козлины",
"козлине",
"козлину",
"козлиной",
"козлино",
"козлин",
"козлинам",
"козлинами",
"козлинах",
"козлище",
"козлища",
"козлищу",
"козлищем",
"козлище",
"козлищ",
"козлищам",
"козлищами",
"козлищах"
],
"fragments": [
"хуй",
"хуя",
"хуе",
"хуи",
"хую",
"хуё",
"пизд",
"бля",
"бляд",
"еб",
"еба",
"ебе",
"еби",
"ебу",
"ебё",
"ебан",
"ебал",
"ебан",
"ебля",
"ебли",
"ебло",
"ебут",
"ебуть",
"ебёшь",
"ебёт",
"ебём",
"ебёте",
"ебут",
"ебаный",
"ебаная",
"ебаное",
"ебаные",
"ебаного",
"ебаной",
"ебаному",
"ебаным",
"ебаных",
"ебаным",
"ебаные",
"ебаными",
"ебаных",
"ебанный",
"ебанная",
"ебанное",
"ебанные",
"ебанного",
"ебанной",
"ебанному",
"ебанным",
"ебанных",
"ебанным",
"ебанные",
"ебанными",
"ебанных",
"заеб",
"уеб",
"выеб",
"проеб",
"долбоеб",
"мудак",
"мудил",
"гандон",
"гондон",
"пидор",
"пидарас",
"педик",
"гомик",
"шлюх",
"блядин",
"проститутк",
"еблан",
"ебанут",
"уебан",
"долбоёб",
"долбоеб",
"залуп",
"мраз",
"мразот",
"ублюдок",
"выблядок",
"падл",
"падлюг",
"гад",
"гадин",
"твар",
"тварин",
"сволоч",
"скотин",
"козел",
"козл",
"козёл",
"козлищ"
]
}