Files
uni-notify/app/config.py
Claude da28d0ac1c 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
2026-11-13 14:20:14 +01:00

105 lines
3.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Настройки из переменных окружения (.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")),
)