uni-notify: Outlook + Teams → Telegram с LLM-фильтром
- Playwright держит открытыми веб-версии Outlook и Teams (постоянный профиль, Xvfb) - watcher.js перехватывает уведомления клиента и изменения карточек в реальном времени - pipeline: дедуп, baseline, VIP/MUTE, оценка важности через Haiku (OpenRouter) - aiogram-бот: уведомления, дайджест, вопросы по журналу, /login с 2FA-кодом, ручное управление браузером - Docker/compose, systemd-юнит, e2e-тест на поддельных страницах Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194buWJjpDjU52JPX7PjsMs
This commit is contained in:
73
app/main.py
Normal file
73
app/main.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Точка входа: 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())
|
||||
Reference in New Issue
Block a user