feat: Increase max email fetch results and enhance email body extraction

This commit is contained in:
bolade
2025-08-13 09:00:18 +01:00
parent 3ea27caca6
commit 411f47e039
3 changed files with 92 additions and 19 deletions
+1 -1
View File
@@ -364,7 +364,7 @@ def _sync_emails_once(cfg: dict) -> int:
days_back = max(1, delta_days) days_back = max(1, delta_days)
except Exception: except Exception:
pass pass
max_results = 5 max_results = 100
client = ZohoClient( client = ZohoClient(
email=cfg.get("zoho_email") or account_email, email=cfg.get("zoho_email") or account_email,
app_password=cfg.get("zoho_app_password"), app_password=cfg.get("zoho_app_password"),
+53 -10
View File
@@ -133,7 +133,6 @@ class ZohoClient:
date_header = email_message.get("Date", "") date_header = email_message.get("Date", "")
email_date = parse_email_date_safely(date_header) email_date = parse_email_date_safely(date_header)
# Ensure both dates are timezone-aware for comparison # Ensure both dates are timezone-aware for comparison
if email_date and latest_date: if email_date and latest_date:
print(f"📅 Email date: {email_date} Latest: {latest_date}") print(f"📅 Email date: {email_date} Latest: {latest_date}")
@@ -143,10 +142,18 @@ class ZohoClient:
if (email_date > latest_date) or first_time: if (email_date > latest_date) or first_time:
# Extract headers # Extract headers
print(f"📅 Email date: {email_date} Latest: {latest_date}") print(
subject = self._decode_header(email_message.get("Subject", "")) f"📅 Email date: {email_date} Latest: {latest_date}"
from_header = self._decode_header(email_message.get("From", "")) )
to_header = self._decode_header(email_message.get("To", "")) subject = self._decode_header(
email_message.get("Subject", "")
)
from_header = self._decode_header(
email_message.get("From", "")
)
to_header = self._decode_header(
email_message.get("To", "")
)
message_id = email_message.get("Message-ID", "") message_id = email_message.get("Message-ID", "")
in_reply_to = email_message.get("In-Reply-To", "") in_reply_to = email_message.get("In-Reply-To", "")
@@ -157,7 +164,6 @@ class ZohoClient:
# Get email body snippet # Get email body snippet
body = self._get_email_body(email_message) body = self._get_email_body(email_message)
email_data = { email_data = {
"id": num.decode(), "id": num.decode(),
"threadId": thread_id, "threadId": thread_id,
@@ -227,18 +233,22 @@ class ZohoClient:
return str(header_value) return str(header_value)
def _get_email_body(self, email_message) -> str: def _get_email_body(self, email_message) -> str:
"""Extract email body text""" """Extract email body text - get only the main content, not quoted replies"""
body = "" body = ""
if email_message.is_multipart(): if email_message.is_multipart():
# Get only the first text/plain part (main content)
for part in email_message.walk(): for part in email_message.walk():
if part.get_content_type() == "text/plain": if part.get_content_type() == "text/plain" and not part.get_filename():
try: try:
body += part.get_payload(decode=True).decode( content = part.get_payload(decode=True).decode(
"utf-8", errors="ignore" "utf-8", errors="ignore"
) )
# Take only the first text part we find
body = content
break # Stop after first text/plain part
except Exception: except Exception:
pass continue
else: else:
try: try:
body = email_message.get_payload(decode=True).decode( body = email_message.get_payload(decode=True).decode(
@@ -247,8 +257,41 @@ class ZohoClient:
except Exception: except Exception:
pass pass
# Optional: Clean up the body by removing quoted content
body = self._clean_email_body(body)
return body return body
def _clean_email_body(self, body: str) -> str:
"""Clean email body by removing quoted content and signatures"""
if not body:
return ""
lines = body.split("\n")
cleaned_lines = []
for line in lines:
line = line.strip()
# Stop at common quote indicators
if (
line.startswith("---- On ")
or line.startswith("On ")
and "wrote:" in line
or line.startswith("From:")
or line.startswith("> ")
or line.startswith("-----Original Message-----")
or line.startswith("---------- Forwarded message ---------")
):
break
cleaned_lines.append(line)
# Remove trailing empty lines
while cleaned_lines and not cleaned_lines[-1]:
cleaned_lines.pop()
return "\n".join(cleaned_lines)
def get_thread_messages(self, thread_id: str) -> List[Dict[str, Any]]: def get_thread_messages(self, thread_id: str) -> List[Dict[str, Any]]:
"""Get all messages in a thread (simplified for IMAP)""" """Get all messages in a thread (simplified for IMAP)"""
# For IMAP, we'll return a single message since thread grouping is more complex # For IMAP, we'll return a single message since thread grouping is more complex
+34 -4
View File
@@ -61,7 +61,17 @@ th { background: #0f152a; color: var(--muted); text-align: left; position: stick
tbody tr:hover { background: #0e1426; } tbody tr:hover { background: #0e1426; }
pre, code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } pre, code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
pre { background: #0b1121; padding: 0.75rem; border-radius: 8px; white-space: pre-wrap; border: 1px solid var(--border); } pre {
background: #0b1121;
padding: 0.75rem;
border-radius: 8px;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
border: 1px solid var(--border);
max-width: 100%;
overflow-x: auto;
}
/* Chat-style messages */ /* Chat-style messages */
.messages { display: flex; flex-direction: column; gap: 0.75rem; } .messages { display: flex; flex-direction: column; gap: 0.75rem; }
@@ -72,12 +82,25 @@ pre { background: #0b1121; padding: 0.75rem; border-radius: 8px; white-space: pr
max-width: 800px; width: fit-content; max-width: 800px; width: fit-content;
background: #0f152a; border: 1px solid var(--border); border-radius: 12px; background: #0f152a; border: 1px solid var(--border); border-radius: 12px;
padding: 0.75rem 0.9rem; box-shadow: 0 4px 20px rgba(0,0,0,0.2); padding: 0.75rem 0.9rem; box-shadow: 0 4px 20px rgba(0,0,0,0.2);
word-wrap: break-word;
overflow-wrap: break-word;
overflow-x: auto;
} }
.incoming .msg-bubble { background: #0f152a; } .incoming .msg-bubble { background: #0f152a; }
.outgoing .msg-bubble { background: var(--brand-weak); border-color: #345fb0; } .outgoing .msg-bubble { background: var(--brand-weak); border-color: #345fb0; }
.msg-meta { font-size: 0.78rem; color: var(--muted); margin-bottom: 0.35rem; } .msg-meta {
font-size: 0.78rem;
color: var(--muted);
margin-bottom: 0.35rem;
word-wrap: break-word;
overflow-wrap: break-word;
}
.msg-subject { font-size: 0.9rem; margin-bottom: 0.25rem; color: var(--text); } .msg-subject { font-size: 0.9rem; margin-bottom: 0.25rem; color: var(--text); }
.msg-body { font-size: 0.92rem; } .msg-body {
font-size: 0.92rem;
word-wrap: break-word;
overflow-wrap: break-word;
}
.row { display: flex; gap: 1rem; flex-wrap: wrap; } .row { display: flex; gap: 1rem; flex-wrap: wrap; }
.col { flex: 1 1 360px; } .col { flex: 1 1 360px; }
@@ -87,7 +110,14 @@ a { color: var(--brand); }
a:hover { text-decoration: underline; } a:hover { text-decoration: underline; }
/* Small helpers */ /* Small helpers */
.pill { padding: 0.15rem 0.5rem; border-radius: 999px; border: 1px solid var(--border); } .pill {
padding: 0.15rem 0.5rem;
border-radius: 999px;
border: 1px solid var(--border);
word-wrap: break-word;
overflow-wrap: break-word;
display: inline-block;
}
.right { text-align: right; } .right { text-align: right; }
.mt-1 { margin-top: 0.5rem; } .mt-2 { margin-top: 1rem; } .mt-1 { margin-top: 0.5rem; } .mt-2 { margin-top: 1rem; }
.mb-1 { margin-bottom: 0.5rem; } .mb-2 { margin-bottom: 1rem; } .mb-1 { margin-bottom: 0.5rem; } .mb-2 { margin-bottom: 1rem; }