Files
email_alerts_v2/main.py
T
2025-08-11 14:46:35 +01:00

141 lines
4.9 KiB
Python

from typing import List, Optional
from database import (
Message,
SessionLocal,
Thread,
create_db_tables,
get_thread_messages,
get_threads_requiring_reply,
ingest_emails,
)
from zoho_client import ZohoClient
def ingest_action(
account_email: str, days_back: int = 7, max_results: int = 50
) -> None:
create_db_tables()
client = ZohoClient(email=account_email)
inbox = client.fetch_folder_emails(
folder="INBOX", max_results=max_results, days_back=days_back
)
sent = client.fetch_folder_emails(
folder="Sent", max_results=max_results, days_back=days_back
)
client.close()
db = SessionLocal()
try:
ingest_emails(
db, account_email=account_email, emails=inbox, default_folder="INBOX"
)
ingest_emails(
db, account_email=account_email, emails=sent, default_folder="Sent"
)
threads: List[Thread] = get_threads_requiring_reply(db, account_email)
print(f"Threads requiring reply for {account_email}: {len(threads)}")
for t in threads:
print(
f"- Thread #{t.id} | Subject: {t.subject!r} | requires_reply={t.requires_reply}"
)
finally:
db.close()
def list_threads_action(
account_email: Optional[str] = None, limit: int = 20, only_requiring: bool = False
) -> None:
create_db_tables()
db = SessionLocal()
try:
q = db.query(Thread).order_by(Thread.updated_at.desc())
if account_email:
q = q.filter(Thread.account_email == account_email.lower())
if only_requiring:
q = q.filter(Thread.requires_reply.is_(True))
threads = q.limit(limit).all()
print(
f"Showing {len(threads)} threads"
+ (f" for {account_email}" if account_email else "")
)
for t in threads:
count = db.query(Message).filter(Message.thread_id == t.id).count()
print(
f"- id={t.id} msgs={count} requires_reply={t.requires_reply} subject={t.subject!r}"
)
finally:
db.close()
def show_thread_action(thread_id: int) -> None:
create_db_tables()
db = SessionLocal()
try:
thread = db.query(Thread).filter(Thread.id == thread_id).one_or_none()
if not thread:
print(f"Thread {thread_id} not found")
return
print(
f"Thread #{thread.id} subject={thread.subject!r} account={thread.account_email} requires_reply={thread.requires_reply}"
)
messages: List[Message] = get_thread_messages(db, thread.id)
for i, m in enumerate(messages, 1):
direction = "IN" if m.is_incoming else "OUT"
snippet = (m.body or "").strip().replace("\n", " ")
if len(snippet) > 140:
snippet = snippet[:140] + "..."
print(
f"[{i}] {m.date_sent} [{direction}] {m.folder} | from={m.from_email} -> to={m.to_email}\n"
f" subject={m.subject!r}\n"
f" message_id={m.message_id} in_reply_to={m.in_reply_to}\n"
f" body={snippet}"
)
finally:
db.close()
if __name__ == "__main__":
import argparse
import os
parser = argparse.ArgumentParser(description="Email alerts utility")
sub = parser.add_subparsers(dest="cmd", required=False)
p_ingest = sub.add_parser("ingest", help="Fetch INBOX and Sent and ingest into DB")
p_ingest.add_argument(
"--account", dest="account", default=os.getenv("ZOHO_EMAIL", "")
)
p_ingest.add_argument("--days-back", dest="days_back", type=int, default=7)
p_ingest.add_argument("--max-results", dest="max_results", type=int, default=50)
p_list = sub.add_parser("list-threads", help="List threads")
p_list.add_argument("--account", dest="account", default=None)
p_list.add_argument("--limit", dest="limit", type=int, default=20)
p_list.add_argument("--only-requiring", dest="only_req", action="store_true")
p_show = sub.add_parser("show-thread", help="Print all messages in a thread")
p_show.add_argument("thread_id", type=int)
args = parser.parse_args()
if args.cmd == "ingest":
acct = args.account or os.getenv("ZOHO_EMAIL", "")
if not acct:
raise SystemExit("Provide --account or set ZOHO_EMAIL")
ingest_action(acct, days_back=args.days_back, max_results=args.max_results)
elif args.cmd == "list-threads":
list_threads_action(
account_email=args.account, limit=args.limit, only_requiring=args.only_req
)
elif args.cmd == "show-thread":
show_thread_action(args.thread_id)
else:
# Default behavior: run ingest using env and then list requiring-reply threads
acct = os.getenv("ZOHO_EMAIL", "")
if not acct:
parser.print_help()
raise SystemExit(0)
ingest_action(acct)