#!/usr/bin/env python3 """ SalvageDB Bot - Главный файл запуска Модульная архитектура с разделением хэндлеров """ import asyncio import signal import sys import logging from aiogram import Bot, Dispatcher from aiogram.fsm.storage.memory import MemoryStorage from config.settings import BOT_TOKEN from database import DatabaseManager from middlewares.db import DbSessionMiddleware from utils.logging_config import setup_logging from utils.system_utils import get_operating_system, log_system_info # Импорт всех роутеров from handlers.main_handlers import router as main_router from handlers.vin_handlers import router as vin_router from handlers.payment_handlers import router as payment_router from handlers.admin.main_admin import router as admin_main_router # Глобальные переменные bot = None dp = None database_manager = None async def on_startup(): """Инициализация при запуске""" global database_manager logging.info("=== BOT STARTUP ===") log_system_info() # Инициализируем базу данных try: database_manager = DatabaseManager() await database_manager.initialize() logging.info("Database manager initialized successfully") except Exception as e: logging.error(f"Failed to initialize database: {e}") sys.exit(1) logging.info("Bot startup completed successfully") async def on_shutdown(): """Очистка при завершении""" global database_manager logging.info("=== BOT SHUTDOWN ===") if database_manager: await database_manager.close() logging.info("Database connections closed") logging.info("Bot shutdown completed") def setup_signal_handlers(): """Настройка обработчиков сигналов для корректного завершения""" def signal_handler(signum, frame): logging.info(f"Received signal {signum}, initiating graceful shutdown...") # Получаем текущий event loop try: loop = asyncio.get_running_loop() # Создаем задачу для завершения loop.create_task(shutdown_bot()) except RuntimeError: # Если loop не найден, завершаем принудительно logging.warning("No running event loop found, forcing exit...") sys.exit(0) # Регистрируем обработчики для разных сигналов signal.signal(signal.SIGINT, signal_handler) # Ctrl+C signal.signal(signal.SIGTERM, signal_handler) # Команда завершения # Для Windows добавляем обработку SIGBREAK if get_operating_system() == 'Windows': try: signal.signal(signal.SIGBREAK, signal_handler) except AttributeError: pass # SIGBREAK может быть недоступен в некоторых версиях async def shutdown_bot(): """Корректное завершение работы бота""" logging.info("Shutting down bot...") # Останавливаем polling if dp: await dp.stop_polling() # Закрываем сессию бота if bot: await bot.session.close() # Вызываем on_shutdown await on_shutdown() logging.info("Bot shutdown complete, exiting...") sys.exit(0) def setup_routers(dispatcher: Dispatcher): """Настройка всех роутеров""" # Порядок важен - более специфичные роутеры должны быть первыми # Админ роутеры (самый высокий приоритет) dispatcher.include_router(admin_main_router) # Платежные роутеры dispatcher.include_router(payment_router) # VIN роутеры dispatcher.include_router(vin_router) # Основные роутеры (самый низкий приоритет) dispatcher.include_router(main_router) logging.info("All routers configured successfully") async def main(): """Главная функция запуска бота""" global bot, dp # Настройка логирования setup_logging() # Настройка обработчиков сигналов setup_signal_handlers() logging.info("Starting SalvageDB Bot...") logging.info(f"Python version: {sys.version}") logging.info(f"Operating System: {get_operating_system()}") try: # Инициализация бота и диспетчера bot = Bot(token=BOT_TOKEN) dp = Dispatcher(storage=MemoryStorage()) # Инициализируем DatabaseManager сначала database_manager = DatabaseManager() await database_manager.initialize() # Настройка middleware для работы с базой данных dp.message.middleware(DbSessionMiddleware(database_manager)) dp.callback_query.middleware(DbSessionMiddleware(database_manager)) # Настройка всех роутеров setup_routers(dp) # Регистрация событий завершения (startup уже не нужен) dp.shutdown.register(on_shutdown) # Запуск polling logging.info("Starting bot polling...") await dp.start_polling( bot, allowed_updates=['message', 'callback_query', 'pre_checkout_query'], drop_pending_updates=True ) except KeyboardInterrupt: logging.info("Received KeyboardInterrupt, shutting down...") except Exception as e: logging.error(f"Fatal error in main: {e}") raise finally: # Финальная очистка if bot: await bot.session.close() logging.info("Bot stopped") if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: logging.info("Bot interrupted by user") except Exception as e: logging.error(f"Fatal error: {e}") sys.exit(1)