diff --git a/bots/bot.py b/bots/bot.py index 607249b..768916e 100644 --- a/bots/bot.py +++ b/bots/bot.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -Точка входа для запуска бота. -С расширенной диагностикой событий. +Точка входа для запуска XMPP-бота. +С расширенной диагностикой событий и использованием Pydantic-конфига (AppConfig). """ import argparse @@ -12,14 +12,19 @@ import logging import signal from pathlib import Path -from core.utils.config_loader import BotConfig -from core.utils.logger import setup_logging -from core.xmpp.client import Bot +# Импорт нового загрузчика и модели конфига +from rag.utils.config_loader import load_config +from rag.config_models import AppConfig +from rag.utils.logger import setup_logging + +# Импорт класса Bot (который теперь принимает AppConfig) +from bots.xmpp.client import Bot logger = logging.getLogger(__name__) async def shutdown(sig, bot, stop_event): + """Graceful shutdown с обработкой сигналов.""" logger.info(f"Получен сигнал {sig.name}, начинаю graceful shutdown...") stop_event.set() try: @@ -27,40 +32,64 @@ async def shutdown(sig, bot, stop_event): await bot.disconnect() except Exception as e: logger.error(f"Ошибка отключения: {e}") + # Отменяем все задачи, кроме текущей tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] for task in tasks: task.cancel() await asyncio.gather(*tasks, return_exceptions=True) + # Закрываем HTTP-сессию RAG-клиента + try: + await bot.rag_client.close() + except Exception: + pass logger.info("Бот завершён") async def main_async(profile_dir: str): + """ + Асинхронная инициализация и запуск бота. + """ + # 1. Загружаем конфигурацию через Pydantic (AppConfig) try: - config = BotConfig(profile_dir) + config: AppConfig = load_config(profile_dir) except Exception as e: - logger.error(f"Ошибка конфига: {e}") + logger.error(f"Ошибка загрузки конфигурации: {e}") sys.exit(1) - setup_logging(config.name, config.log_file, log_level=logging.DEBUG) + # 2. Настраиваем логирование в режиме DEBUG для диагностики + # (можно переключить через --log-level, но пока жёстко) + setup_logging(config.name, config.log_file_path, log_level=logging.DEBUG) logger.info(f"Запуск бота {config.name} с профилем {profile_dir}") + # 3. Создаём экземпляр бота bot = Bot(config) - # Отключаем SRV и устанавливаем IP + # ---- ДЛЯ ОТЛАДКИ (как в старом коде) ---- + # Отключаем SRV-записи, чтобы явно указать сервер и порт. + # Если в конфиге есть xmpp_server и xmpp_port, используем их, + # иначе оставляем как есть (slixmpp попытается определить автоматически). bot.use_srv = False - bot.xmpp_server = "192.168.1.150" - bot.xmpp_port = 5222 - # Попробуем SSL, если не работает — отключим - # bot.xmpp_use_ssl = False # оставляем как есть + # Если в конфиге явно не заданы, оставляем значения по умолчанию, + # но для отладки можно жёстко прописать IP. + # В production лучше брать из конфига. + if config.xmpp_server: + bot.xmpp_server = config.xmpp_server + else: + # fallback для тестовой среды + bot.xmpp_server = "192.168.1.150" + if config.xmpp_port: + bot.xmpp_port = config.xmpp_port + else: + bot.xmpp_port = 5222 - # ---- ДОБАВЛЯЕМ ВСЕ ОБРАБОТЧИКИ ---- + # ---- ДОБАВЛЯЕМ ВСЕ ОБРАБОТЧИКИ СОБЫТИЙ (как в старом коде) ---- def on_session_start(event): logger.info("✅ СЕССИЯ УСПЕШНО УСТАНОВЛЕНА! Бот готов.") bot.send_presence(pshow='chat', pstatus=config.status_message) def on_failed_auth(event): logger.error("❌ ОШИБКА АВТОРИЗАЦИИ! Проверьте пароль и JID.") - # Принудительно завершаем бота, чтобы не висеть + # Принудительно завершаем бота asyncio.create_task(shutdown(None, bot, stop_event)) def on_disconnected(event): @@ -94,30 +123,45 @@ async def main_async(profile_dir: str): bot.add_event_handler("roster_update", on_roster_update) bot.add_event_handler("presence", on_presence) + # ---- ОБРАБОТКА СИГНАЛОВ ---- stop_event = asyncio.Event() - loop = asyncio.get_running_loop() for sig in (signal.SIGINT, signal.SIGTERM): loop.add_signal_handler( sig, lambda s=sig: asyncio.create_task(shutdown(s, bot, stop_event)) ) + # ---- ПОДКЛЮЧЕНИЕ ---- logger.info(f"Попытка подключения к {bot.xmpp_server}:{bot.xmpp_port}...") bot.connect() logger.info("connect() вызван, ожидаем событий...") + # Ждём сигнала остановки await stop_event.wait() def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--profile-dir', required=True) + """Синхронная точка входа.""" + parser = argparse.ArgumentParser(description="Запуск XMPP-бота") + parser.add_argument( + '--profile-dir', + required=True, + help="Путь к директории профиля бота (например, /usr/local/etc/bots/metabot)" + ) + parser.add_argument( + "--log-level", + default="debug", + choices=["debug", "info", "warning", "error", "critical"], + help="Уровень логирования (по умолчанию debug)" + ) args = parser.parse_args() + profile_dir = Path(args.profile_dir).resolve() if not profile_dir.exists(): print(f"Ошибка: директория {profile_dir} не существует") sys.exit(1) + # Создаём событийный цикл loop = asyncio.new_event_loop() asyncio.set_event_loop(loop)