from telebot.async_telebot import AsyncTeleBot from telebot.asyncio_handler_backends import BaseMiddleware import asyncio import os import sys import importlib from dotenv import load_dotenv import logging from logger import setup_logging from database import db from action_reporter import init_action_reporter from config import MODULES_DIR # Загружаем токен бота из .env load_dotenv() # Валидация переменных окружения def validate_env_vars(): """Проверяет наличие всех необходимых переменных окружения""" required_vars = { "BOT_TOKEN": "Токен бота", "ADMIN_CHAT_ID": "ID админ-чата", "LOG_THREAD_ID": "ID топика для логов" } missing_vars = [] for var_name, description in required_vars.items(): value = os.getenv(var_name) if not value or value.strip() == "" or value == "...": missing_vars.append(f"{var_name} ({description})") if missing_vars: print("\n❌ ОШИБКА: Не заполнены необходимые переменные окружения в файле .env:") for var in missing_vars: print(f" • {var}") print("\nПожалуйста, заполните файл .env на основе .env.example") sys.exit(1) # Проверяем переменные окружения validate_env_vars() bot = AsyncTeleBot(os.getenv("BOT_TOKEN"), parse_mode="html") # Загружаем ID админ-чата из .env и инициализируемся для логов в чат init_action_reporter(bot, os.getenv("ADMIN_CHAT_ID"), os.getenv("LOG_THREAD_ID")) # Получаем логгер для текущего модуля logger = logging.getLogger(__name__) # Middleware для автоматического обновления информации о пользователях в базе данных class UserUpdateMiddleware(BaseMiddleware): def __init__(self, db): super().__init__() # message - все обычные сообщения # chat_member - события изменения статуса участников чата self.update_types = ['message', 'chat_member'] self.db = db # Обработчик, вызываемый ДО обработки сообщения основными хэндлерами async def pre_process(self, message, data): # Логируем ВСЕ входящие сообщения для отладки logger.info(f"[MIDDLEWARE] Получено сообщение от {message.from_user.id}, тип: {message.content_type}, текст: {message.text if hasattr(message, 'text') else 'N/A'}") # Обработка пользователей, отправившие сообщение if message.content_type == 'text': self.db.add_or_update_user( user_id=message.from_user.id, nickname=message.from_user.first_name, tag=message.from_user.username ) # Обработка новых участников группы elif message.content_type == 'new_chat_members': for new_member in message.new_chat_members: self.db.add_or_update_user( user_id=new_member.id, nickname=new_member.first_name, tag=new_member.username ) return data # Обработчик, вызываемый ПОСЛЕ обработки сообщения основными хэндлерами async def post_process(self, message, data, exception): pass # Регистрируем middleware bot.setup_middleware(UserUpdateMiddleware(db)) # Загружает все модули из директории /modules async def load_modules(): # Переменная для подсчёта модулей loaded_count = 0 # Импортируем относительный путь проекта modules_path = os.path.join(os.path.dirname(__file__), MODULES_DIR) for filename in os.listdir(modules_path): # Если файл содержит в конце .py (кроме __init__.py) if filename.endswith(".py") and filename != "__init__.py": # Убираем расширение .py module_name = filename[:-3] try: # Импортируем модуль (modules.start) module = importlib.import_module(f"{MODULES_DIR}.{module_name}") # Если присутствует register_handlers if hasattr(module, "register_handlers"): module.register_handlers(bot) loaded_count += 1 logger.info(f"Модуль {module_name} успешно загружен.") # Если нет register_handlers else: # Записываем действие в логи logger.warning(f"Модуль {module_name} не содержит функцию register_handlers.") except Exception as e: # Записываем ошибку в логи logger.error(f"Ошибка при загрузке модуля {module_name}: {str(e)}") # Записываем отчет о модулях в логи logger.info(f"Загружено модулей: {loaded_count} шт. Бот запущен.") # Устанавливаем меню команд бота async def setup_bot_commands(): from telebot.types import BotCommand commands = [ BotCommand("start", "Начало работы с ботом"), BotCommand("help", "Справка по всем командам"), BotCommand("log", "Инструкция по созданию лога ошибки"), BotCommand("warn", "Выдать предупреждение. Использование: /warn help"), BotCommand("ban", "Забанить пользователя. Использование: /ban help"), BotCommand("unban", "Разбанить пользователя. Использование: /unban help"), BotCommand("mute", "Замутить пользователя. Использование: /mute help"), BotCommand("unmute", "Размутить пользователя. Использование: /unmute help"), BotCommand("badwords", "Управление списком бранных слов. /badwords help"), BotCommand("reset_violations", "Сбросить счётчик нарушений пользователя"), BotCommand("botdata", "Получить данные бота (только для админов)"), ] await bot.set_my_commands(commands) logger.info("Команды бота успешно установлены.") async def main(): # Инициализация логирования (должна быть первой) setup_logging() # Очищаем терминал os.system('clear') try: # Проверяем и загружаем модули await load_modules() # Устанавливаем команды бота await setup_bot_commands() # Запускаем бота await bot.infinity_polling() except Exception as e: # Записываем критическую ошибку в логи logger.critical(f"Критическая ошибка: {str(e)}") # Завершаем скрипт с критической ошибкой sys.exit(1) if __name__ == "__main__": asyncio.run(main())