From cc953e51d269aa9c31b15cb3b4f7eda6886c57f6 Mon Sep 17 00:00:00 2001 From: Verum Date: Mon, 23 Feb 2026 14:17:15 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A2=D0=BE=D1=87=D0=BA=D0=B0=20=D0=B2=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 284 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..d94f888 --- /dev/null +++ b/main.py @@ -0,0 +1,284 @@ +""" +Точка входа 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()