2026-02-14 10:29:28 -07:00
|
|
|
"""Utility functions for Gmail/Calendar tools."""
|
|
|
|
|
|
|
|
|
|
import base64
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
import mimetypes
|
2026-02-14 10:29:28 -07:00
|
|
|
import re
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
from email.mime.base import MIMEBase
|
2026-02-14 10:29:28 -07:00
|
|
|
from email.mime.multipart import MIMEMultipart
|
|
|
|
|
from email.mime.text import MIMEText
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
from email import encoders
|
2026-02-14 10:29:28 -07:00
|
|
|
from html.parser import HTMLParser
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
from pathlib import Path
|
2026-02-14 10:29:28 -07:00
|
|
|
from typing import Dict, List, Optional
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HTMLToText(HTMLParser):
|
|
|
|
|
"""Convert HTML to plain text."""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.text = []
|
|
|
|
|
self.skip = False
|
|
|
|
|
|
|
|
|
|
def handle_data(self, data):
|
|
|
|
|
if not self.skip:
|
|
|
|
|
self.text.append(data)
|
|
|
|
|
|
|
|
|
|
def handle_starttag(self, tag, attrs):
|
|
|
|
|
if tag in ["script", "style"]:
|
|
|
|
|
self.skip = True
|
|
|
|
|
elif tag == "br":
|
|
|
|
|
self.text.append("\n")
|
|
|
|
|
elif tag == "p":
|
|
|
|
|
self.text.append("\n\n")
|
|
|
|
|
|
|
|
|
|
def handle_endtag(self, tag):
|
|
|
|
|
if tag in ["script", "style"]:
|
|
|
|
|
self.skip = False
|
|
|
|
|
elif tag in ["p", "div"]:
|
|
|
|
|
self.text.append("\n")
|
|
|
|
|
|
|
|
|
|
def get_text(self):
|
|
|
|
|
return "".join(self.text).strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def html_to_text(html: str) -> str:
|
|
|
|
|
"""Convert HTML to plain text.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
html: HTML content
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Plain text content
|
|
|
|
|
"""
|
|
|
|
|
parser = HTMLToText()
|
|
|
|
|
parser.feed(html)
|
|
|
|
|
return parser.get_text()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_mime_message(
|
|
|
|
|
to: str,
|
|
|
|
|
subject: str,
|
|
|
|
|
body: str,
|
|
|
|
|
from_email: str = "me",
|
|
|
|
|
cc: Optional[List[str]] = None,
|
|
|
|
|
reply_to_message_id: Optional[str] = None,
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
attachments: Optional[List[str]] = None,
|
2026-02-14 10:29:28 -07:00
|
|
|
) -> Dict:
|
|
|
|
|
"""Create a MIME message for Gmail API.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
to: Recipient email address
|
|
|
|
|
subject: Email subject
|
|
|
|
|
body: Email body (plain text or HTML)
|
|
|
|
|
from_email: Sender email (default: "me")
|
|
|
|
|
cc: Optional list of CC recipients
|
|
|
|
|
reply_to_message_id: Optional message ID to reply to
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
attachments: Optional list of file paths to attach
|
2026-02-14 10:29:28 -07:00
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict with 'raw' key containing base64url-encoded message
|
|
|
|
|
"""
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
is_html = bool(re.search(r"<[a-z][\s\S]*>", body, re.IGNORECASE))
|
|
|
|
|
|
|
|
|
|
# Build the body part first
|
|
|
|
|
if attachments:
|
|
|
|
|
# mixed wraps body alternative + file parts
|
|
|
|
|
message = MIMEMultipart("mixed")
|
|
|
|
|
body_part = MIMEMultipart("alternative")
|
|
|
|
|
else:
|
|
|
|
|
message = MIMEMultipart("alternative")
|
|
|
|
|
body_part = message
|
|
|
|
|
|
2026-02-14 10:29:28 -07:00
|
|
|
message["To"] = to
|
|
|
|
|
message["From"] = from_email
|
|
|
|
|
message["Subject"] = subject
|
|
|
|
|
|
|
|
|
|
if cc:
|
|
|
|
|
message["Cc"] = ", ".join(cc)
|
|
|
|
|
|
|
|
|
|
if reply_to_message_id:
|
|
|
|
|
message["In-Reply-To"] = reply_to_message_id
|
|
|
|
|
message["References"] = reply_to_message_id
|
|
|
|
|
|
|
|
|
|
if is_html:
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
body_part.attach(MIMEText(html_to_text(body), "plain"))
|
|
|
|
|
body_part.attach(MIMEText(body, "html"))
|
2026-02-14 10:29:28 -07:00
|
|
|
else:
|
feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager
Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users
Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks
Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response
Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence
Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)
Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00
|
|
|
body_part.attach(MIMEText(body, "plain"))
|
|
|
|
|
|
|
|
|
|
if attachments:
|
|
|
|
|
message.attach(body_part)
|
|
|
|
|
for file_path in attachments:
|
|
|
|
|
path = Path(file_path)
|
|
|
|
|
mime_type, _ = mimetypes.guess_type(str(path))
|
|
|
|
|
main_type, sub_type = (mime_type or "application/octet-stream").split("/", 1)
|
|
|
|
|
with open(path, "rb") as f:
|
|
|
|
|
part = MIMEBase(main_type, sub_type)
|
|
|
|
|
part.set_payload(f.read())
|
|
|
|
|
encoders.encode_base64(part)
|
|
|
|
|
part.add_header("Content-Disposition", "attachment", filename=path.name)
|
|
|
|
|
message.attach(part)
|
2026-02-14 10:29:28 -07:00
|
|
|
|
|
|
|
|
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
|
|
|
|
return {"raw": raw_message}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_email_message(message: Dict) -> Dict:
|
|
|
|
|
"""Parse Gmail API message into readable format.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
message: Gmail API message object
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict with parsed fields: from, to, subject, date, body, snippet
|
|
|
|
|
"""
|
|
|
|
|
headers = {
|
|
|
|
|
h["name"].lower(): h["value"]
|
|
|
|
|
for h in message.get("payload", {}).get("headers", [])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = {
|
|
|
|
|
"id": message.get("id"),
|
|
|
|
|
"thread_id": message.get("threadId"),
|
|
|
|
|
"from": headers.get("from", ""),
|
|
|
|
|
"to": headers.get("to", ""),
|
|
|
|
|
"cc": headers.get("cc", ""),
|
|
|
|
|
"subject": headers.get("subject", ""),
|
|
|
|
|
"date": headers.get("date", ""),
|
|
|
|
|
"snippet": message.get("snippet", ""),
|
|
|
|
|
"labels": message.get("labelIds", []),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_email_body(message: Dict) -> str:
|
|
|
|
|
"""Extract email body from Gmail API message.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
message: Gmail API message object
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Email body as plain text
|
|
|
|
|
"""
|
|
|
|
|
payload = message.get("payload", {})
|
|
|
|
|
|
|
|
|
|
def get_body_from_part(part: Dict) -> Optional[str]:
|
|
|
|
|
"""Recursively extract body from MIME parts."""
|
|
|
|
|
mime_type = part.get("mimeType", "")
|
|
|
|
|
body_data = part.get("body", {}).get("data")
|
|
|
|
|
|
|
|
|
|
if body_data:
|
|
|
|
|
decoded = base64.urlsafe_b64decode(body_data).decode("utf-8", errors="ignore")
|
|
|
|
|
if mime_type == "text/html":
|
|
|
|
|
return html_to_text(decoded)
|
|
|
|
|
elif mime_type == "text/plain":
|
|
|
|
|
return decoded
|
|
|
|
|
|
|
|
|
|
# Check nested parts
|
|
|
|
|
for subpart in part.get("parts", []):
|
|
|
|
|
result = get_body_from_part(subpart)
|
|
|
|
|
if result:
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# Try to get body
|
|
|
|
|
body = get_body_from_part(payload)
|
|
|
|
|
|
|
|
|
|
if not body:
|
|
|
|
|
# Fallback to snippet
|
|
|
|
|
body = message.get("snippet", "")
|
|
|
|
|
|
|
|
|
|
return body
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_email_summary(emails: List[Dict], include_body: bool = False) -> str:
|
|
|
|
|
"""Format emails into a readable summary.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
emails: List of parsed email dicts
|
|
|
|
|
include_body: Whether to include full body
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Formatted string summary
|
|
|
|
|
"""
|
|
|
|
|
if not emails:
|
|
|
|
|
return "No emails found."
|
|
|
|
|
|
|
|
|
|
lines = []
|
|
|
|
|
for i, email_data in enumerate(emails, 1):
|
|
|
|
|
lines.append(f"{i}. From: {email_data['from']}")
|
|
|
|
|
lines.append(f" Subject: {email_data['subject']}")
|
|
|
|
|
lines.append(f" Date: {email_data['date']}")
|
2026-02-22 21:19:28 -07:00
|
|
|
lines.append(f" Message-ID: {email_data.get('id', 'N/A')}")
|
2026-02-14 10:29:28 -07:00
|
|
|
|
|
|
|
|
if include_body and "body" in email_data:
|
|
|
|
|
# Truncate long bodies
|
|
|
|
|
body = email_data["body"]
|
2026-02-22 21:19:28 -07:00
|
|
|
if len(body) > 2000:
|
|
|
|
|
body = body[:2000] + "..."
|
2026-02-14 10:29:28 -07:00
|
|
|
lines.append(f" Body: {body}")
|
|
|
|
|
else:
|
|
|
|
|
lines.append(f" Snippet: {email_data['snippet']}")
|
|
|
|
|
|
|
|
|
|
lines.append("") # Blank line
|
|
|
|
|
|
|
|
|
|
return "\n".join(lines)
|