97 lines
3.4 KiB
Python
97 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Check shonuff@germainebrown.com inbox. Outputs JSON for LLM summary.
|
|
Silent if nothing new. Skips trusted senders (Germaine, mailer-daemon).
|
|
"""
|
|
import imaplib, email, json, os, hashlib
|
|
from email.utils import parsedate_to_datetime
|
|
|
|
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):
|
|
body = ""
|
|
# Try text/plain first
|
|
if msg.is_multipart():
|
|
for part in msg.walk():
|
|
if part.get_content_type() == "text/plain":
|
|
payload = part.get_payload(decode=True)
|
|
if payload:
|
|
body += payload.decode("utf-8", errors="replace")
|
|
# Fallback: extract text from text/html if no plain text found
|
|
if not body.strip():
|
|
for part in msg.walk():
|
|
if part.get_content_type() == "text/html":
|
|
payload = part.get_payload(decode=True)
|
|
if payload:
|
|
import html
|
|
text = payload.decode("utf-8", errors="replace")
|
|
text = re.sub(r'<[^>]+>', ' ', text)
|
|
text = html.unescape(text)
|
|
body = text
|
|
break
|
|
else:
|
|
payload = msg.get_payload(decode=True)
|
|
if payload:
|
|
ct = msg.get_content_type()
|
|
text = payload.decode("utf-8", errors="replace")
|
|
if ct == "text/html":
|
|
text = re.sub(r'<[^>]+>', ' ', text)
|
|
import html
|
|
text = html.unescape(text)
|
|
body = text
|
|
import re
|
|
body = re.sub(r'\s+', ' ', body).strip()
|
|
return body[:500]
|
|
|
|
def main():
|
|
seen = load_seen()
|
|
new_msgs = []
|
|
try:
|
|
conn = imaplib.IMAP4_SSL(HOST, PORT)
|
|
conn.login(USER, PW)
|
|
conn.select("INBOX")
|
|
status, data = conn.search(None, "UNSEEN")
|
|
if status == "OK" and data[0]:
|
|
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)"))
|
|
mid = safe_str(raw.get("Message-ID", str(num)))
|
|
date = safe_str(raw.get("Date", ""))
|
|
if any(t.lower() in sender.lower() for t in TRUSTED): continue
|
|
h = hashlib.md5(mid.encode()).hexdigest()
|
|
if h in seen: continue
|
|
seen.add(h)
|
|
new_msgs.append({"from": sender, "subject": subj, "date": date, "body_preview": get_text_body(raw)[:500], "uid": num.decode()})
|
|
conn.store(num, "+FLAGS", "\\Seen")
|
|
conn.logout()
|
|
save_seen(seen)
|
|
except Exception as e:
|
|
print(json.dumps({"error": str(e)}))
|
|
return
|
|
if new_msgs:
|
|
print(json.dumps({"messages": new_msgs}, indent=2))
|
|
|
|
if __name__ == "__main__":
|
|
main()
|