""" Анти-мат система Удаляет сообщения с матом, выдаёт предупреждения """ 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}" не найдено в списке')