добавление модерации в имени и описании

This commit is contained in:
Professional 2025-05-25 22:36:17 +07:00
parent 05636e666b
commit fc3ff065aa
7 changed files with 1891 additions and 5 deletions

View File

@ -1,6 +1,7 @@
// Контроллер аутентификации // Контроллер аутентификации
const User = require('../models/User'); const User = require('../models/User');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const profanityFilter = require('../utils/profanityFilter');
// Вспомогательная функция для генерации JWT // Вспомогательная функция для генерации JWT
const generateToken = (id) => { const generateToken = (id) => {
@ -29,6 +30,12 @@ const registerUser = async (req, res, next) => {
throw new Error('Пожалуйста, выберите город.'); throw new Error('Пожалуйста, выберите город.');
} }
// Проверка имени на наличие запрещенных слов
if (profanityFilter.hasProfanity(name)) {
res.status(400);
throw new Error('Имя содержит запрещённые слова. Пожалуйста, используйте другое имя.');
}
// Проверка возраста (минимум 14 лет) // Проверка возраста (минимум 14 лет)
const birthDate = new Date(dateOfBirth); const birthDate = new Date(dateOfBirth);
const today = new Date(); const today = new Date();

View File

@ -2,6 +2,7 @@ const User = require('../models/User');
const cloudinary = require('../config/cloudinaryConfig'); // Импортируем настроенный Cloudinary SDK const cloudinary = require('../config/cloudinaryConfig'); // Импортируем настроенный Cloudinary SDK
const path = require('path'); // Может понадобиться для получения расширения файла const path = require('path'); // Может понадобиться для получения расширения файла
const ProfileView = require('../models/ProfileView'); const ProfileView = require('../models/ProfileView');
const profanityFilter = require('../utils/profanityFilter'); // Импортируем фильтр запрещённых слов
// @desc Обновить профиль пользователя // @desc Обновить профиль пользователя
// @route PUT /api/users/profile // @route PUT /api/users/profile
@ -15,6 +16,14 @@ const updateUserProfile = async (req, res, next) => {
const user = await User.findById(req.user._id); const user = await User.findById(req.user._id);
if (user) { if (user) {
// Проверяем имя на запрещённые слова, если оно было изменено
if (req.body.name && req.body.name !== user.name) {
if (profanityFilter.hasProfanity(req.body.name)) {
res.status(400);
throw new Error('Имя содержит запрещённые слова. Пожалуйста, используйте другое имя.');
}
}
// Обновляем только те поля, которые пользователь может изменять // Обновляем только те поля, которые пользователь может изменять
user.name = req.body.name || user.name; user.name = req.body.name || user.name;
@ -31,11 +40,13 @@ const updateUserProfile = async (req, res, next) => {
user.gender = req.body.gender; user.gender = req.body.gender;
} }
// Обрабатываем bio, учитывая null и пустые строки // Проверяем bio на запрещённые слова перед установкой
if (req.body.bio === null) { if (req.body.bio !== undefined && req.body.bio !== null) {
user.bio = ''; // Устанавливаем пустую строку, если null if (req.body.bio !== '' && profanityFilter.hasProfanity(req.body.bio)) {
} else { res.status(400);
user.bio = req.body.bio || user.bio; throw new Error('Ваша биография содержит запрещённые слова. Пожалуйста, отредактируйте текст.');
}
user.bio = req.body.bio;
} }
// Обновление местоположения (если передано) // Обновление местоположения (если передано)

View File

@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"bad-words": "^4.0.0",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"cloudinary": "^2.0.1", "cloudinary": "^2.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
@ -110,6 +111,24 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/bad-words": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/bad-words/-/bad-words-4.0.0.tgz",
"integrity": "sha512-fLjG/I0N3I7xhurqGnGitSRD10UeEE63a7hyXtutQDpxo4+Eal+i7veWeZxZJPNtsl6X1mUIoWPwt8gQ7NMQUw==",
"license": "MIT",
"dependencies": {
"badwords-list": "^2.0.1-4"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/badwords-list": {
"version": "2.0.1-4",
"resolved": "https://registry.npmjs.org/badwords-list/-/badwords-list-2.0.1-4.tgz",
"integrity": "sha512-FxfZUp7B9yCnesNtFQS9v6PvZdxTYa14Q60JR6vhjdQdWI4naTjJIyx22JzoER8ooeT8SAAKoHLjKfCV7XgYUQ==",
"license": "MIT"
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",

View File

@ -12,6 +12,7 @@
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"dependencies": { "dependencies": {
"bad-words": "^4.0.0",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"cloudinary": "^2.0.1", "cloudinary": "^2.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",

377
backend/utils/en.txt Normal file
View File

@ -0,0 +1,377 @@
2g1c
2 girls 1 cup
acrotomophilia
alabama hot pocket
alaskan pipeline
anal
anilingus
anus
apeshit
arsehole
ass
asshole
assmunch
auto erotic
autoerotic
babeland
baby batter
baby juice
ball gag
ball gravy
ball kicking
ball licking
ball sack
ball sucking
bangbros
bareback
barely legal
barenaked
bastard
bastardo
bastinado
bbw
bdsm
beaner
beaners
beaver cleaver
beaver lips
bestiality
big black
big breasts
big knockers
big tits
bimbos
birdlock
bitch
bitches
black cock
blonde action
blonde on blonde action
blowjob
blow job
blow your load
blue waffle
blumpkin
bollocks
bondage
boner
boob
boobs
booty call
brown showers
brunette action
bukkake
bulldyke
bullet vibe
bullshit
bung hole
bunghole
busty
butt
buttcheeks
butthole
camel toe
camgirl
camslut
camwhore
carpet muncher
carpetmuncher
chocolate rosebuds
circlejerk
cleveland steamer
clit
clitoris
clover clamps
clusterfuck
cock
cocks
coprolagnia
coprophilia
cornhole
coon
coons
creampie
cum
cumming
cunnilingus
cunt
darkie
date rape
daterape
deep throat
deepthroat
dendrophilia
dick
dildo
dingleberry
dingleberries
dirty pillows
dirty sanchez
doggie style
doggiestyle
doggy style
doggystyle
dog style
dolcett
domination
dominatrix
dommes
donkey punch
double dong
double penetration
dp action
dry hump
dvda
eat my ass
ecchi
ejaculation
erotic
erotism
escort
eunuch
faggot
fecal
felch
fellatio
feltch
female squirting
femdom
figging
fingerbang
fingering
fisting
foot fetish
footjob
frotting
fuck
fuck buttons
fuckin
fucking
fucktards
fudge packer
fudgepacker
futanari
gang bang
gay sex
genitals
giant cock
girl on
girl on top
girls gone wild
goatcx
goatse
god damn
gokkun
golden shower
goodpoop
goo girl
goregasm
grope
group sex
g-spot
guro
hand job
handjob
hard core
hardcore
hentai
homoerotic
honkey
hooker
hot carl
hot chick
how to kill
how to murder
huge fat
humping
incest
intercourse
jack off
jail bait
jailbait
jelly donut
jerk off
jigaboo
jiggaboo
jiggerboo
jizz
juggs
kike
kinbaku
kinkster
kinky
knobbing
leather restraint
leather straight jacket
lemon party
lolita
lovemaking
make me come
male squirting
masturbate
menage a trois
milf
missionary position
motherfucker
mound of venus
mr hands
muff diver
muffdiving
nambla
nawashi
negro
neonazi
nigga
nigger
nig nog
nimphomania
nipple
nipples
nsfw images
nude
nudity
nympho
nymphomania
octopussy
omorashi
one cup two girls
one guy one jar
orgasm
orgy
paedophile
paki
panties
panty
pedobear
pedophile
pegging
penis
phone sex
piece of shit
pissing
piss pig
pisspig
playboy
pleasure chest
pole smoker
ponyplay
poof
poon
poontang
punany
poop chute
poopchute
porn
porno
pornography
prince albert piercing
pthc
pubes
pussy
queaf
queef
quim
raghead
raging boner
rape
raping
rapist
rectum
reverse cowgirl
rimjob
rimming
rosy palm
rosy palm and her 5 sisters
rusty trombone
sadism
santorum
scat
schlong
scissoring
semen
sex
sexo
sexy
shaved beaver
shaved pussy
shemale
shibari
shit
shitblimp
shitty
shota
shrimping
skeet
slanteye
slut
s&m
smut
snatch
snowballing
sodomize
sodomy
spic
splooge
splooge moose
spooge
spread legs
spunk
strap on
strapon
strappado
strip club
style doggy
suck
sucks
suicide girls
sultry women
swastika
swinger
tainted love
taste my
tea bagging
threesome
throating
tied up
tight white
tit
tits
titties
titty
tongue in a
topless
tosser
towelhead
tranny
tribadism
tub girl
tubgirl
tushy
twat
twink
twinkie
two girls one cup
undressing
upskirt
urethra play
urophilia
vagina
venus mound
vibrator
violet wand
vorarephilia
voyeur
vulva
wank
wetback
wet dream
white power
wrapping men
wrinkled starfish
xx
xxx
yaoi
yellow showers
yiffy
zoophilia
🖕

View File

@ -0,0 +1,154 @@
// Утилита для фильтрации нежелательного контента
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
.filter(word => word && word.length >= 4) // Берем только слова длиннее 3 символов для производных
.map(word => {
// Создаем шаблон для каждого слова: допускает изменение окончаний и вставку символов
const baseWord = word.replace(/[еёо]/g, '[еёо]'); // Учитываем замену е/ё/о
return new RegExp(`\\b${baseWord}[а-яёa-z]*\\b`, 'i');
});
}
/**
* Проверяет, содержит ли текст запрещенные слова или их производные
* @param {string} text - Текст для проверки
* @return {boolean} - true, если текст содержит запрещенные слова, иначе false
*/
hasProfanity(text) {
if (!text) return false;
// Проверяем сначала стандартным методом библиотеки bad-words
if (this.filter.isProfane(text.toLowerCase())) {
return true;
}
// Дополнительная проверка на производные слова через регулярные выражения
const normalizedText = text.toLowerCase();
return this.wordPatterns.some(pattern => pattern.test(normalizedText));
}
/**
* Проверяет, содержит ли объект запрещенные слова в указанных полях
* @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;

1317
backend/utils/words.txt Normal file

File diff suppressed because it is too large Load Diff