""" Точка входа PrimoGuard Bot """ from asyncio import run import asyncio from typing import List from configs import settings from bot import bot, dp, BotInfo, WebhookManager, setup_middlewares, router from database import get_manager from middleware.loggers import logger __all__ = ("main",) def _start_background_tasks_safe() -> List[asyncio.Task]: """ Безопасный запуск фоновых задач с обработкой ошибок. Returns: List[asyncio.Task]: Список запущенных задач (пустой список если модуль не найден) """ try: from bot.tasks import start_background_tasks tasks = start_background_tasks() logger.info( f"🚀 Запущено {len(tasks)} фоновых задач", log_type="STARTUP" ) return tasks except ImportError: logger.warning( "⚠️ Модуль 'bot.tasks' не найден, фоновые задачи не запущены", log_type="STARTUP" ) return [] except Exception as e: logger.error( f"❌ Ошибка запуска фоновых задач: {e}", log_type="STARTUP" ) return [] async def setup_services(setup_webhook: bool = True) -> str: """ Инициализация всех сервисов: БД и бот. Args: setup_webhook: Устанавливать ли webhook в BotInfo.setup() Returns: str: Username бота """ # База данных manager = get_manager() await manager.init() stats = await manager.get_stats() logger.info( f"📊 БД инициализирована: {stats.get('total_banwords', 0)} банвордов", log_type="DATABASE" ) # Бот: получение информации (БЕЗ webhook) await BotInfo.setup(bots=bot, setup_webhook=setup_webhook) # ВАЖНО: Регистрируем middleware и роутеры ДО установки webhook setup_middlewares( dp=dp, bot=bot, enable_spam_check=settings.ANTI_SPAM, channel_ids=[], ) # Подключение маршрутов dp.include_router(router) logger.info("✓ Все handlers и middleware зарегистрированы", log_type="STARTUP") return BotInfo.username async def on_startup(app) -> None: """Выполняется при запуске webhook-сервера.""" # 1. Инициализируем всё БЕЗ webhook username = await setup_services(setup_webhook=False) # 2. Запускаем фоновые задачи background_tasks = _start_background_tasks_safe() app['background_tasks'] = background_tasks # 3. ТЕПЕРЬ устанавливаем webhook (когда всё готово) webhook = WebhookManager(bot, dp) if settings.WEBHOOK_URL: success = await webhook.setup( webhook_url=settings.WEBHOOK_URL, secret_token=settings.SECRET_TOKEN, drop_pending_updates=True ) if success: logger.success( f"✅ Бот @{username} запущен в режиме Webhook", log_type="STARTUP" ) else: logger.error( "❌ Не удалось установить webhook, но сервер запущен", log_type="STARTUP" ) else: logger.warning( "⚠️ WEBHOOK_URL не указан", log_type="STARTUP" ) async def on_shutdown(app) -> None: """Очистка ресурсов при остановке сервера.""" logger.info("👋 Остановка бота...", log_type="SHUTDOWN") try: # Отменяем фоновые задачи if 'background_tasks' in app and app['background_tasks']: tasks = app['background_tasks'] logger.info( f"⏸️ Остановка {len(tasks)} фоновых задач...", log_type="SHUTDOWN" ) for task in tasks: if not task.done(): task.cancel() # Ждём завершения с таймаутом 5 секунд try: await asyncio.wait_for( asyncio.gather(*tasks, return_exceptions=True), timeout=5.0 ) logger.info("✅ Фоновые задачи остановлены", log_type="SHUTDOWN") except asyncio.TimeoutError: logger.warning( "⚠️ Таймаут остановки фоновых задач (5 сек)", log_type="SHUTDOWN" ) # Закрываем соединения logger.info("📊 Закрытие базы данных...", log_type="SHUTDOWN") await get_manager().close() logger.info("🤖 Закрытие сессии бота...", log_type="SHUTDOWN") await bot.session.close() except Exception as e: logger.error(f"❌ Ошибка при закрытии: {e}", log_type="SHUTDOWN") logger.success("✅ Бот остановлен", log_type="SHUTDOWN") def start_webhook() -> None: """Запуск в режиме Webhook (синхронный).""" logger.setup() webhook = WebhookManager(bot, dp) # ВАЖНО: Конфигурируем webhook ПЕРЕД on_startup webhook.configure() # Добавляем startup/shutdown handlers webhook.app.on_startup.append(on_startup) webhook.app.on_shutdown.append(on_shutdown) # Запускаем webhook сервер logger.info("🌐 Запуск Webhook сервера...", log_type="MAIN") webhook.run() async def start_polling() -> None: """Запуск в режиме Polling (асинхронный).""" logger.setup() background_tasks: List[asyncio.Task] = [] try: # 1. Инициализируем сервисы username = await setup_services(setup_webhook=False) # 2. Запускаем фоновые задачи background_tasks = _start_background_tasks_safe() # 3. Удаляем webhook для polling режима webhook = WebhookManager(bot, dp) await webhook.delete(drop_pending_updates=True) logger.success( f"✅ Бот @{username} запущен в режиме Polling", log_type="STARTUP" ) # 4. Запускаем polling await dp.start_polling(bot, drop_pending_updates=True) except KeyboardInterrupt: logger.info("⚠️ Получен сигнал остановки (Ctrl+C)", log_type="MAIN") except Exception as e: logger.critical( f"🔥 Критическая ошибка: {e}", log_type="MAIN" ) raise finally: logger.info("🧹 Очистка ресурсов...", log_type="SHUTDOWN") try: # Отменяем фоновые задачи if background_tasks: logger.info( f"⏸️ Остановка {len(background_tasks)} фоновых задач...", log_type="SHUTDOWN" ) for task in background_tasks: if not task.done(): task.cancel() # Ждём завершения с таймаутом 5 секунд try: await asyncio.wait_for( asyncio.gather(*background_tasks, return_exceptions=True), timeout=5.0 ) logger.info("✅ Фоновые задачи остановлены", log_type="SHUTDOWN") except asyncio.TimeoutError: logger.warning( "⚠️ Таймаут остановки фоновых задач (5 сек)", log_type="SHUTDOWN" ) # Закрываем соединения logger.info("📊 Закрытие базы данных...", log_type="SHUTDOWN") await get_manager().close() logger.info("🤖 Закрытие сессии бота...", log_type="SHUTDOWN") await bot.session.close() logger.success("✅ Бот остановлен", log_type="SHUTDOWN") except Exception as e: logger.error( f"❌ Ошибка при очистке ресурсов: {e}", log_type="SHUTDOWN" ) def main() -> None: """Входная точка проекта.""" try: if settings.WEBHOOK: # ========== WEBHOOK РЕЖИМ ========== logger.info("🔧 Режим: Webhook", log_type="MAIN") start_webhook() else: # ========== POLLING РЕЖИМ ========== logger.info("🔧 Режим: Polling", log_type="MAIN") run(start_polling()) except KeyboardInterrupt: logger.info("⚠️ Остановка по сигналу Ctrl+C", log_type="MAIN") except Exception as e: logger.critical( f"🔥 Критическая ошибка при запуске: {e}", log_type="MAIN" ) raise if __name__ == "__main__": main()