"""
Анти-мат система
Удаляет сообщения с матом, выдаёт предупреждения
"""
import logging
import json
import re
from datetime import datetime
from aiogram import Router, F
from aiogram.types import Message
from aiogram.filters import Command
from aiogram.exceptions import TelegramBadRequest
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from database.db import AsyncSessionLocal
from database.models import User
from utils.decorators import admin_only
import config
logger = logging.getLogger(__name__)
router = Router()
# Глобальный словарь предупреждений: user_id -> count
warnings = {}
def load_bad_words() -> list:
"""Загрузка чёрного списка слов"""
try:
with open('data/bad_words.json', 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get('words', [])
except FileNotFoundError:
# Возвращаем базовый список
return [
'хуй', 'хуи', 'хуя', 'хуем', 'хую',
'пизд', 'пизде', 'пизду',
'ебал', 'ебать', 'ебуч', 'ебан',
'бляд', 'блять',
'сука', 'суки',
'мудак', 'мудаки',
'уебок', 'уебки',
'говно', 'говна',
'залуп', 'залупа',
'манда', 'манды',
'трах', 'трахал',
'еблан', 'ебланы',
'пидор', 'пидоры',
'долбоёб', 'долбоёбы',
'о хуе', 'на хуй', 'по хуй',
'за хуй', 'под хуй',
]
def load_exclamations() -> list:
"""Загрузка списка восклицаний (типа @user, !команда)"""
try:
with open('data/bad_words.json', 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get('exclamations', [])
except:
return ['@', '!', '/', 'http', 'www', 't.me']
def contains_bad_word(text: str) -> tuple:
"""
Проверка текста на мат
Возвращает: (есть_ли_мат, найденное_слово)
"""
if not text:
return False, None
text_lower = text.lower()
bad_words = load_bad_words()
exclamations = load_exclamations()
# Проверяем каждое плохое слово
for bad_word in bad_words:
# Простая проверка (слово содержится в тексте)
if bad_word.lower() in text_lower:
# Проверяем что это не часть другого слова (не ссылка, не упоминание)
skip = False
for excl in exclamations:
if excl in text_lower:
# Это может быть ссылка или упоминание - пропускаем
skip = True
break
if skip:
continue
# Проверяем что это отдельное слово (окружено пробелами или знаками препинания)
pattern = r'(^|\s|\.|,|!|\?|:|;)' + re.escape(bad_word) + r'($|\s|\.|,|!|\?|:|;)'
if re.search(pattern, text_lower, re.IGNORECASE):
return True, bad_word
return False, None
async def get_user_warnings(user_id: int) -> int:
"""Получить количество предупреждений пользователя"""
return warnings.get(user_id, 0)
async def add_warning(user_id: int) -> int:
"""Добавить предупреждение пользователю"""
current = warnings.get(user_id, 0)
warnings[user_id] = current + 1
return warnings[user_id]
async def clear_warnings(user_id: int):
"""Сбросить предупреждения пользователя"""
if user_id in warnings:
del warnings[user_id]
@router.message()
async def check_profanity(message: Message):
"""
Проверка всех сообщений на мат
"""
# Пропускаем сообщения от ботов
if message.from_user.is_bot:
return
# Пропускаем админов (им можно всё)
if config.is_admin(message.from_user.id):
return
# Пропускаем если нет текста
if not message.text:
return
# Проверяем на мат
has_bad_word, bad_word = contains_bad_word(message.text)
if has_bad_word:
logger.warning(f'Обнаружен мат от user_id={message.from_user.id}, слово: {bad_word}')
# Удаляем сообщение
try:
await message.delete()
logger.info(f'Сообщение удалено: message_id={message.message_id}')
except TelegramBadRequest as e:
logger.error(f'Не удалось удалить сообщение: {e}')
# Если не удалось удалить (нет прав), продолжаем но с предупреждением
# Добавляем предупреждение
warning_count = await add_warning(message.from_user.id)
# Получаем пользователя из БД
async with AsyncSessionLocal() as session:
stmt = select(User).where(User.user_id == message.from_user.id)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
user_name = user.full_name if user else f'user{message.from_user.id}'
# Пишем нарушителю в личку
try:
if warning_count == 1:
# Первое предупреждение
await message.bot.send_message(
message.from_user.id,
f'⚠️ Предупреждение #1\n\n'
f'{user_name}, в вашем сообщении обнаружена нецензурная лексика.\n\n'
f'❗ Правила чата:\n'
f'Запрещено использовать мат и оскорбления.\n\n'
f'📊 Ваши нарушения: 1 из 3\n\n'
f'При следующем нарушении будет публичное предупреждение.\n'
f'При третьем — бан.',
parse_mode='HTML'
)
# Тихое уведомление админу
try:
await message.bot.send_message(
config.ADMIN_USER_ID,
f'⚠️ Предупреждение выдано\n\n'
f'Нарушитель: {user_name}\n'
f'ID: {message.from_user.id}\n'
f'Слово: {bad_word}\n\n'
f'Это первое предупреждение.',
parse_mode='HTML'
)
except:
pass
elif warning_count == 2:
# Второе предупреждение
await message.bot.send_message(
message.from_user.id,
f'🚨 Предупреждение #2\n\n'
f'{user_name}, вы нарушили правила чата повторно!\n\n'
f'❗ Ваше сообщение удалено за:\n'
f'Использование нецензурной лексики.\n\n'
f'📊 Ваши нарушения: 2 из 3\n\n'
f'⚠️ Внимание!\n'
f'При следующем нарушении вы будете забанены.',
parse_mode='HTML'
)
# Публичное уведомление в чат
try:
await message.bot.send_message(
config.ADMIN_CHAT_ID,
f'⚠️ Внимание!\n\n'
f'Пользователь {user_name} получил второе предупреждение за мат.\n\n'
f'Пожалуйста, соблюдайте правила чата!',
parse_mode='HTML'
)
except:
pass
elif warning_count >= 3:
# Третье предупреждение — бан
await message.bot.send_message(
message.from_user.id,
f'🚫 ВЫ ЗАБАНЕНЫ\n\n'
f'{user_name}, вы получили третье предупреждение.\n\n'
f'Причина: Систематическое нарушение правил чата (мат).\n\n'
f'Вы больше не можете писать в чат.',
parse_mode='HTML'
)
# Бан в БД
async with AsyncSessionLocal() as session:
stmt = select(User).where(User.user_id == message.from_user.id)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
if user:
user.is_banned = True
await session.commit()
# Удаляем из чата
try:
await message.chat.ban(message.from_user.id)
except:
pass
# Уведомление админу
try:
await message.bot.send_message(
config.ADMIN_USER_ID,
f'🚫 ПОЛЬЗОВАТЕЛЬ ЗАБАНЕН\n\n'
f'Нарушитель: {user_name}\n'
f'ID: {message.from_user.id}\n'
f'Причина: 3 предупреждения за мат\n\n'
f'Пользователь удалён из чата.',
parse_mode='HTML'
)
except:
pass
except TelegramBadRequest as e:
logger.error(f'Не удалось отправить предупреждение: {e}')
# Логируем
logger.warning(f'Предупреждение #{warning_count} выдано user_id={message.from_user.id}')
@router.message(Command('warnings'))
@admin_only
async def cmd_warnings_list(message: Message):
"""Список предупреждений"""
if not warnings:
await message.answer('✅ Нарушений нет')
return
text = '⚠️ Предупреждения\n\n'
for user_id, count in warnings.items():
text += f'• ID {user_id} — {count} предупр.\n'
text += '\n💡 Для сброса: /clear_warnings <user_id>'
await message.answer(text, parse_mode='HTML')
@router.message(Command('clear_warnings'))
@admin_only
async def cmd_clear_warnings(message: Message):
"""Сбросить предупреждения пользователя"""
args = message.text.split()
if len(args) < 2:
await message.answer('Использование: /clear_warnings <user_id>', parse_mode='HTML')
return
try:
user_id = int(args[1])
except ValueError:
await message.answer('❌ Неверный ID')
return
await clear_warnings(user_id)
await message.answer(f'✅ Предупреждения пользователя {user_id} сброшены')
@router.message(Command('add_bad_word'))
@admin_only
async def cmd_add_bad_word(message: Message):
"""Добавить слово в чёрный список"""
args = message.text.split(maxsplit=1)
if len(args) < 2:
await message.answer('Использование: /add_bad_word слово', parse_mode='HTML')
return
word = args[1].strip().lower()
try:
with open('data/bad_words.json', 'r', encoding='utf-8') as f:
data = json.load(f)
except:
data = {'words': [], 'exclamations': []}
if word not in data['words']:
data['words'].append(word)
with open('data/bad_words.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
await message.answer(f'✅ Слово "{word}" добавлено в чёрный список')
else:
await message.answer(f'ℹ️ Слово "{word}" уже в списке')
@router.message(Command('remove_bad_word'))
@admin_only
async def cmd_remove_bad_word(message: Message):
"""Удалить слово из чёрного списка"""
args = message.text.split(maxsplit=1)
if len(args) < 2:
await message.answer('Использование: /remove_bad_word слово', parse_mode='HTML')
return
word = args[1].strip().lower()
try:
with open('data/bad_words.json', 'r', encoding='utf-8') as f:
data = json.load(f)
except:
await message.answer('❌ Файл не найден')
return
if word in data['words']:
data['words'].remove(word)
with open('data/bad_words.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
await message.answer(f'✅ Слово "{word}" удалено из чёрного списка')
else:
await message.answer(f'ℹ️ Слово "{word}" не найдено в списке')