# app/notify.py from __future__ import annotations import os, re, hashlib, time, requests, smtplib, ssl from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from typing import Optional def _env(name: str, default: Optional[str] = None) -> Optional[str]: v = os.getenv(name) return v if v is not None and v != "" else default # ---------- Slack ---------- def send_slack(message: str, webhook_url: Optional[str] = None) -> bool: url = webhook_url or _env("SLACK_WEBHOOK_URL") if not url: return False try: r = requests.post(url, json={"text": message}, timeout=10) return r.status_code // 100 == 2 except Exception: return False # ---------- Email (SMTP) ---------- def send_email(subject: str, text: str, html: Optional[str] = None, to: Optional[list[str]] = None) -> bool: host = _env("SMTP_HOST") user = _env("SMTP_USER") pwd = _env("SMTP_PASS") port = int(_env("SMTP_PORT", "587")) sender = _env("SMTP_FROM", user) recipients = to or (_env("NOTIFY_TO","") or "").replace(";", ",").split(",") recipients = [x.strip() for x in recipients if x.strip()] if not (host and user and pwd and sender and recipients): return False msg = MIMEMultipart("alternative") msg["Subject"] = subject msg["From"] = sender msg["To"] = ", ".join(recipients) msg.attach(MIMEText(text, "plain", "utf-8")) if html: msg.attach(MIMEText(html, "html", "utf-8")) try: ctx = ssl.create_default_context() with smtplib.SMTP(host, port, timeout=15) as s: s.starttls(context=ctx) s.login(user, pwd) s.sendmail(sender, recipients, msg.as_string()) return True except Exception: return False def htmlify(title: str, url: str, synopsis: str, deadline_iso: str | None, deadline_text: str | None) -> str: dl = deadline_iso or "TBD" raw = f"