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:
104
app/config.py
Normal file
104
app/config.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""Настройки из переменных окружения (.env)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def _list(name: str) -> list[str]:
|
||||
return [x.strip() for x in os.getenv(name, "").split(",") if x.strip()]
|
||||
|
||||
|
||||
def _bool(name: str, default: bool) -> bool:
|
||||
v = os.getenv(name)
|
||||
if v is None or v == "":
|
||||
return default
|
||||
return v.strip().lower() in ("1", "true", "yes", "on")
|
||||
|
||||
|
||||
def _quiet(v: str | None) -> tuple[int, int] | None:
|
||||
# "23-8" -> (23, 8)
|
||||
if not v:
|
||||
return None
|
||||
try:
|
||||
a, b = v.split("-")
|
||||
return int(a), int(b)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Settings:
|
||||
tg_token: str
|
||||
owner_id: int
|
||||
openrouter_key: str
|
||||
model: str
|
||||
uni_email: str
|
||||
uni_password: str | None
|
||||
my_name: str
|
||||
mail_url: str
|
||||
teams_url: str
|
||||
data_dir: Path
|
||||
headless: bool
|
||||
chromium_path: str | None
|
||||
notify_min: int
|
||||
quiet_hours: tuple[int, int] | None
|
||||
digest_time: str | None
|
||||
tz: str
|
||||
reload_hours: float
|
||||
scan_interval: int
|
||||
batch_window: float
|
||||
vip: list[str]
|
||||
mute: list[str]
|
||||
rules_path: Path
|
||||
selectors_path: Path
|
||||
|
||||
@property
|
||||
def profile_dir(self) -> Path:
|
||||
return self.data_dir / "browser-profile"
|
||||
|
||||
@property
|
||||
def db_path(self) -> Path:
|
||||
return self.data_dir / "uni.db"
|
||||
|
||||
|
||||
def load() -> Settings:
|
||||
missing = [n for n in ("TG_TOKEN", "OWNER_ID", "OPENROUTER_API_KEY", "UNI_EMAIL") if not os.getenv(n)]
|
||||
if missing:
|
||||
raise SystemExit(f"Не заданы переменные окружения: {', '.join(missing)} (см. .env.example)")
|
||||
|
||||
data_dir = Path(os.getenv("DATA_DIR", ROOT / "data"))
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
return Settings(
|
||||
tg_token=os.environ["TG_TOKEN"],
|
||||
owner_id=int(os.environ["OWNER_ID"]),
|
||||
openrouter_key=os.environ["OPENROUTER_API_KEY"],
|
||||
model=os.getenv("LLM_MODEL", "~anthropic/claude-haiku-latest"),
|
||||
uni_email=os.environ["UNI_EMAIL"],
|
||||
uni_password=os.getenv("UNI_PASSWORD") or None,
|
||||
my_name=os.getenv("MY_NAME", ""),
|
||||
mail_url=os.getenv("MAIL_URL", "https://outlook.office.com/mail/"),
|
||||
teams_url=os.getenv("TEAMS_URL", "https://teams.microsoft.com/v2/"),
|
||||
data_dir=data_dir,
|
||||
headless=_bool("HEADLESS", False),
|
||||
chromium_path=os.getenv("CHROMIUM_PATH") or None,
|
||||
notify_min=int(os.getenv("NOTIFY_MIN_LEVEL", "2")),
|
||||
quiet_hours=_quiet(os.getenv("QUIET_HOURS")),
|
||||
digest_time=os.getenv("DIGEST_TIME") or None,
|
||||
tz=os.getenv("TZ", "Europe/Warsaw"),
|
||||
reload_hours=float(os.getenv("RELOAD_HOURS", "6")),
|
||||
scan_interval=int(os.getenv("SCAN_INTERVAL", "120")),
|
||||
batch_window=float(os.getenv("BATCH_WINDOW", "4")),
|
||||
vip=_list("VIP"),
|
||||
mute=_list("MUTE"),
|
||||
rules_path=Path(os.getenv("RULES_PATH", ROOT / "config" / "rules.md")),
|
||||
selectors_path=Path(os.getenv("SELECTORS_PATH", ROOT / "config" / "selectors.json")),
|
||||
)
|
||||
Reference in New Issue
Block a user