scripts/email_modes.py
scripts/email_modes.pyBrowse 56 files
783 tokens
3,040 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1"""Email modes A/B/C helpers + anti-phishing verification-link extraction.2 3Mode A (default): render a ready-to-send draft to disk; the operator sends it.4Mode B/C: the agent SENDS via a Hermes email mechanism (IMAP/SMTP gateway,5`himalaya`, AgentMail, or Gmail via `google-workspace`) and READS the reply to6resolve the verification link with `extract_verification_link`. Those transports7are driven by the agent through native tools; this module stays network-free so8the hermetic tests pass.9"""10from __future__ import annotations11 12import re13from pathlib import Path14 15import legal16import paths17 18_LINK_RE = re.compile(r"https?://[^\s\"'<>)\]]+", re.IGNORECASE)19_VERIFY_HINTS = ("opt", "remov", "verif", "confirm", "unsubscrib", "suppress", "delete", "privacy")20 21 22def render_draft(broker: dict, fields: dict, out_dir: Path | None = None) -> Path:23 """Mode A: write a ready-to-send opt-out email for the operator to send."""24 body = legal.render_optout_email(broker, fields)25 out_dir = out_dir or (paths.data_dir() / "drafts")26 out_dir.mkdir(parents=True, exist_ok=True)27 fp = out_dir / f"{broker.get('id', 'broker')}.txt"28 fp.write_text(body, encoding="utf-8")29 return fp30 31 32def render_request_draft(broker: dict, fields: dict, kind: str = "generic",33 out_dir: Path | None = None) -> Path:34 """Mode A: write a ready-to-send request of a specific KIND.35 36 kind: generic | ccpa | ccpa_agent | ccpa_indirect | gdpr. Used for indirect-exposure37 (ccpa_indirect) and explicit legal requests, where the generic opt-out wording is wrong.38 The filename is suffixed with the kind so an indirect request does not overwrite an opt-out draft.39 """40 body = legal.render_request(kind, broker, fields)41 out_dir = out_dir or (paths.data_dir() / "drafts")42 out_dir.mkdir(parents=True, exist_ok=True)43 suffix = "" if kind == "generic" else f"-{kind}"44 fp = out_dir / f"{broker.get('id', 'broker')}{suffix}.txt"45 fp.write_text(body, encoding="utf-8")46 return fp47 48 49def extract_verification_link(email_body: str, broker: dict | None = None) -> str | None:50 """Return the most likely opt-out/verification link from an email body.51 52 Anti-phishing: a link is only returned if its URL matches an opt-out hint53 and/or the broker's own domain; arbitrary links score 0 and are ignored.54 """55 candidates = _LINK_RE.findall(email_body or "")56 if not candidates:57 return None58 59 domain = ""60 if broker:61 url = (broker.get("optout") or {}).get("url") or (broker.get("search") or {}).get("url") or ""62 m = re.search(r"https?://([^/]+)", url)63 if m:64 domain = m.group(1).replace("www.", "")65 66 best_score, best_link = 0, None67 for link in candidates:68 low = link.lower()69 score = 070 if any(h in low for h in _VERIFY_HINTS):71 score += 272 if domain and domain in low:73 score += 374 if score > best_score:75 best_score, best_link = score, link76 return best_link77