285 lines
9.5 KiB
Python
285 lines
9.5 KiB
Python
"""
|
||
Точка входа 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()
|