# -*- coding: utf-8 -*- """Astraea comms — Twilio (SMS + voice calls) + SMTP email, with a no-contact-order safety guard and formal message templates. stdlib-only (urllib + smtplib + email). """ import base64 import json import smtplib import urllib.parse import urllib.request from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart TWILIO_API = "https://api.twilio.com/2010-04-01/Accounts" def _twilio_request(settings, path, data): sid = settings.get("twilio_sid", "") token = settings.get("twilio_auth_token", "") if not sid or not token: raise RuntimeError("Twilio credentials not configured") url = f"{TWILIO_API}/{sid}/{path}.json" cred = base64.b64encode(f"{sid}:{token}".encode()).decode() body = urllib.parse.urlencode(data).encode() req = urllib.request.Request(url, data=body, headers={"Authorization": f"Basic {cred}", "Content-Type": "application/x-www-form-urlencoded"}) op = urllib.request.build_opener(urllib.request.ProxyHandler({})) with op.open(req, timeout=30) as r: return json.loads(r.read().decode()) def send_sms(settings, to, body): return _twilio_request(settings, "Messages", {"To": to, "From": settings.get("twilio_from", ""), "Body": body}) def make_call(settings, to, twiml_url): return _twilio_request(settings, "Calls", {"To": to, "From": settings.get("twilio_from", ""), "Url": twiml_url}) def send_email(settings, to, subject, body): host = settings.get("smtp_host", "") user = settings.get("smtp_user", "") pwd = settings.get("smtp_pass", "") frm = settings.get("email_from", "") or user if not host or not user or not frm: raise RuntimeError("Email (SMTP) not configured") port = int(settings.get("smtp_port", 587) or 587) msg = MIMEMultipart() msg["Subject"] = subject msg["From"] = frm msg["To"] = to msg.attach(MIMEText(body, "plain", "utf-8")) s = smtplib.SMTP(host, port, timeout=30) s.starttls() s.login(user, pwd) s.sendmail(frm, [to], msg.as_string()) s.quit() return {"ok": True, "to": to, "subject": subject} # ── Formal templates (the agent composes content; these wrap it professionally) ── def email_template(display_name, recipient, subject, body): return ( f"{recipient},\n\n" f"{body}\n\n" f"Respectfully,\n" f"{display_name}\n\n" f"---\nThis message was drafted on Astraea, an AI legal assistant, on my behalf. " f"Please direct any reply to me directly." ) def sms_template(body): return f"{body}\n\n— sent via Astraea legal assistant" def call_script(greeting, purpose, body, closing): return ( f"Good {greeting}. My name is [your name]. {purpose}.\n\n" f"{body}\n\n" f"{closing}" ) def no_contact_blocked(settings, recipient): """Return True if a no-contact order OR protection order is in effect AND the recipient looks like the protected/restrained party. Domestic-violence protection orders (RCW 7.105) carry the same no-contact force as a standalone no-contact order.""" if not (settings.get("no_contact_order") or settings.get("protection_order")): return False partner = (settings.get("partner_name") or "").strip().lower() if not partner: return True # order in effect but name unset — block to be safe return partner in (recipient or "").lower()