"""Точка входа: python -m app.main""" from __future__ import annotations import asyncio import logging import signal from . import config from .bot import TgBot from .browser.session import BrowserManager from .db import DB from .llm import LLM from .pipeline import Pipeline log = logging.getLogger("uni-notify") async def main() -> None: logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") logging.getLogger("aiogram.event").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.WARNING) s = config.load() db = DB(s.db_path) llm = LLM(s.openrouter_key, s.model, s.tz) tg = TgBot(s, db, llm) pipeline = Pipeline(s, db, llm, tg.say) browser = BrowserManager(s, pipeline.submit, tg.say) tg.attach(browser, pipeline) await browser.start() await tg.say("🚀 uni-notify запущен. Проверяю сессии…", silent=True) async def first_check(): await asyncio.sleep(25) st = browser.states() if all(v == "ok" for v in st.values()): await tg.say("✅ Почта и Teams открыты, слежу.", silent=True) else: await tg.say(f"Состояние: {st}. Если нужен вход — /login") stop = asyncio.Event() loop = asyncio.get_running_loop() for sig in (signal.SIGINT, signal.SIGTERM): try: loop.add_signal_handler(sig, stop.set) except NotImplementedError: pass tasks = [ asyncio.create_task(tg.run(), name="telegram"), asyncio.create_task(pipeline.run(), name="pipeline"), asyncio.create_task(pipeline.digest_loop(), name="digest"), asyncio.create_task(browser.health_loop(), name="health"), asyncio.create_task(browser.scan_loop(), name="scan"), asyncio.create_task(first_check(), name="first_check"), ] waiter = asyncio.create_task(stop.wait()) done, _ = await asyncio.wait([waiter, *tasks[:2]], return_when=asyncio.FIRST_COMPLETED) for t in done: if t is not waiter and t.exception(): log.error("Задача %s упала: %r", t.get_name(), t.exception()) log.info("Останавливаюсь…") for t in tasks: t.cancel() await browser.close() await llm.aclose() await tg.bot.session.close() if __name__ == "__main__": asyncio.run(main())