97 lines
3.5 KiB
Python
97 lines
3.5 KiB
Python
# -*- 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()
|