#!/usr/bin/env python3 """Check shonuff@germainebrown.com inbox, find new replies, extract content. Outputs JSON for the cron agent to summarize. Silent if nothing new. """ import imaplib, email, json, os, hashlib from email.utils import parsedate_to_datetime from datetime import datetime, timezone HOST = "mail.germainebrown.com" PORT = 993 USER = "shonuff@germainebrown.com" PW = "Catches.bullets1985" SEEN_FILE = "/root/.hermes/scripts/.shonuff-processed.json" TRUSTED = ["g@germainebrown.com"] def load_seen(): if os.path.exists(SEEN_FILE): return set(json.load(open(SEEN_FILE))) return set() def save_seen(seen): json.dump(list(seen), open(SEEN_FILE, "w")) def safe_str(val): if val is None: return "" if isinstance(val, str): return val try: return str(val) except: return "" def get_text_body(msg): """Extract plain text from email, favoring text/plain.""" body = "" if msg.is_multipart(): for part in msg.walk(): ct = part.get_content_type() if ct == "text/plain": payload = part.get_payload(decode=True) if payload: body += payload.decode("utf-8", errors="replace") else: payload = msg.get_payload(decode=True) if payload: body = payload.decode("utf-8", errors="replace") # Strip excessive whitespace import re body = re.sub(r'\s+', ' ', body).strip() return body[:2000] # limit to 2000 chars def main(): seen = load_seen() new_messages = [] try: conn = imaplib.IMAP4_SSL(HOST, PORT) conn.login(USER, PW) conn.select("INBOX") status, data = conn.search(None, "UNSEEN") if status != "OK" or not data[0]: conn.logout() save_seen(seen) return # silent for num in data[0].split(): status, msg_data = conn.fetch(num, "(RFC822)") if status != "OK": continue raw = email.message_from_bytes(msg_data[0][1]) sender = safe_str(raw.get("From", "")).strip() subj = safe_str(raw.get("Subject", "(no subject)")) msg_id = safe_str(raw.get("Message-ID", str(num))) date = safe_str(raw.get("Date", "")) # Skip Germaine if any(t.lower() in sender.lower() for t in TRUSTED): continue # Skip self (shonuff's own Sent copies or BCC'd messages) if "shonuff@germainebrown.com" in sender.lower(): continue # Skip already processed h = hashlib.md5(msg_id.encode()).hexdigest() if h in seen: continue seen.add(h) body = get_text_body(raw) new_messages.append({ "from": sender, "subject": subj, "date": date, "body_preview": body[:500], "uid": num.decode(), }) # Mark as read so we don't re-process conn.store(num, "+FLAGS", "\\Seen") conn.logout() save_seen(seen) except Exception as e: print(json.dumps({"error": str(e)})) return if not new_messages: return # silent # Output JSON for the cron agent print(json.dumps({"messages": new_messages}, indent=2)) if __name__ == "__main__": main()