Reflex/backend/utils/profanityFilter.js

173 lines
8.2 KiB
JavaScript
Raw Normal View History

// Утилита для фильтрации нежелательного контента
const Filter = require('bad-words');
const fs = require('fs');
const path = require('path');
// Список русских нецензурных слов и других запрещенных слов
const russianBadWords = [
'блядь', 'хуй', 'пизда', 'ебать', 'ебал', 'ебло', 'хуесос', 'пидор', 'пидорас',
'мудак', 'говно', 'залупа', 'хер', 'манда', 'спермоед', 'шлюха', 'соска', 'шалава',
'дебил', 'дрочить', 'дрочка', 'ебанутый', 'выродок', 'шмара', 'сука', 'членосос',
'гандон', 'гондон', 'гнида', 'вагина', 'хуи', 'жопа', 'шлюхи', 'проститутка'
];
// Список запрещенных слов и фраз для дейтинг-приложения
const datingAppBadWords = [
'проститутка', 'интим', 'секс за деньги', 'эскорт', 'интим услуги',
'заплачу', 'заплати', 'минет', 'массаж с интимом'
];
/**
* Загружает список запрещенных слов из файла
* @param {string} filePath - Путь к файлу со списком запрещенных слов
* @return {Array<string>} - Массив запрещенных слов
*/
function loadBadWordsFromFile(filePath) {
try {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf8');
const words = content.split('\n')
.map(word => word.trim())
.filter(word => word && !word.startsWith('//') && word.length >= 3);
console.log(`Загружено ${words.length} запрещенных слов из файла ${filePath}`);
return words;
}
} catch (error) {
console.error(`Ошибка при загрузке слов из файла ${filePath}:`, error.message);
}
return [];
}
class ProfanityFilter {
constructor() {
// Создаем экземпляр фильтра с английскими словами по умолчанию
this.filter = new Filter({ placeHolder: '*' });
// Загружаем слова из файлов
const russianFileWords = loadBadWordsFromFile(path.join(__dirname, 'words.txt'));
const englishFileWords = loadBadWordsFromFile(path.join(__dirname, 'en.txt'));
// Объединяем все источники запрещенных слов
const allBadWords = [
...russianBadWords,
...datingAppBadWords,
...russianFileWords,
...englishFileWords
];
// Добавляем все слова в фильтр
this.filter.addWords(...allBadWords);
// Создаем регулярные выражения для проверки производных слов
this.generateWordPatterns(allBadWords);
console.log(`Всего загружено ${allBadWords.length} запрещенных слов`);
}
/**
* Создает регулярные выражения для проверки производных слов
* @param {Array<string>} words - Список базовых запрещённых слов
*/
generateWordPatterns(words) {
this.wordPatterns = words
2025-05-25 22:43:02 +07:00
.filter(word => word && word.length >= 3) // Снижаем минимальную длину до 3 символов
.map(word => {
2025-05-25 22:43:02 +07:00
// Создаем шаблон с учетом возможных разделителей и замен символов
const baseWord = word
.replace(/[еёо]/g, '[еёо]') // Учитываем замену е/ё/о
.replace(/а/g, '[аa@]') // Учитываем замену а на a латинскую или @
.replace(/о/g, '[оo0]') // Учитываем замену о на o латинскую или 0
.replace(/е/g, '[еe]') // Учитываем замену е на e латинскую
.replace(/с/g, '[сc]') // Учитываем замену с на c латинскую
.replace(/р/g, '[рp]') // Учитываем замену р на p латинскую
.replace(/х/g, '[хx]') // Учитываем замену х на x латинскую
.replace(/у/g, '[уy]') // Учитываем замену у на y латинскую
.replace(/и/g, '[иi]'); // Учитываем замену и на i
// Создаем регулярку, которая учитывает:
// - возможные разделители между буквами (точки, пробелы, дефисы)
// - возможные окончания слов
const chars = baseWord.split('');
const patternWithSeparators = chars.join('[\\s\\._\\-]*');
return new RegExp(`\\b${patternWithSeparators}[а-яёa-z]*\\b`, 'i');
});
}
/**
* Проверяет, содержит ли текст запрещенные слова или их производные
* @param {string} text - Текст для проверки
* @return {boolean} - true, если текст содержит запрещенные слова, иначе false
*/
hasProfanity(text) {
if (!text) return false;
2025-05-25 22:43:02 +07:00
// Нормализуем текст, убирая лишние символы и разделители
const normalizedText = text.toLowerCase()
.replace(/[\s\._\-]+/g, ''); // Удаляем пробелы, точки, подчеркивания, дефисы
// Проверяем сначала стандартным методом библиотеки bad-words
2025-05-25 22:43:02 +07:00
if (this.filter.isProfane(normalizedText) || this.filter.isProfane(text.toLowerCase())) {
return true;
}
// Дополнительная проверка на производные слова через регулярные выражения
2025-05-25 22:43:02 +07:00
return this.wordPatterns.some(pattern => pattern.test(text.toLowerCase()));
}
/**
* Проверяет, содержит ли объект запрещенные слова в указанных полях
* @param {Object} obj - Объект для проверки
* @param {Array<string>} fields - Массив имен полей для проверки
* @return {Object} - Объект с результатами проверки { isProfane: boolean, field: string|null }
*/
validateObject(obj, fields) {
for (const field of fields) {
if (obj[field] && this.hasProfanity(obj[field])) {
return { isProfane: true, field };
}
}
return { isProfane: false, field: null };
}
/**
* Заменяет запрещенные слова в тексте на звездочки
* @param {string} text - Исходный текст
* @return {string} - Очищенный текст
*/
clean(text) {
if (!text) return '';
// Сначала используем стандартный метод библиотеки
let cleanedText = this.filter.clean(text);
// Затем проверяем на производные слова и заменяем их
this.wordPatterns.forEach(pattern => {
cleanedText = cleanedText.replace(pattern, match => '*'.repeat(match.length));
});
return cleanedText;
}
/**
* Добавляет новое запрещенное слово в фильтр
* @param {string|Array<string>} words - Слово или массив слов для добавления
*/
addWords(words) {
// Добавляем слова в стандартный фильтр
this.filter.addWords(words);
// Если слова переданы как массив
if (Array.isArray(words)) {
this.generateWordPatterns(words);
} else {
// Если передано одно слово
this.generateWordPatterns([words]);
}
}
}
// Экспортируем экземпляр фильтра для использования в приложении
const profanityFilter = new ProfanityFilter();
module.exports = profanityFilter;