refactor(mail): address PR #935 round-1 review
- models/mail.py: lowercase `list` generics per CLAUDE.md convention. - client/mail.py: _ocs_get now inspects ocs.meta.statuscode (re-raises >=400 as HTTPStatusError carrying the OCS code so callers' 404/403 handling applies) and guards response.json() against non-JSON bodies (RequestError). - Extract the duplicated _format_addresses + content reconstruction into vector/mail_content.py, used by both processor.py and context.py (fixes the SonarCloud new_duplicated_lines_density gate). - processor.py: add the missing mail_message Qdrant payload block so the computed mail metadata (subject/from/to/cc/date_int/has_attachments/ account_id/mailbox_id) is actually stored, not dropped. - Rename the list_messages `filter` param to `search_filter` (avoid shadowing builtins.filter); still maps to the OCS `filter` query param. - Docstring notes: has_more heuristic, attachment content size. - Tests: OCS meta-failure + non-JSON client paths; initial-sync scanner tests (tests/unit/vector/test_scanner_mail.py). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3074622455
commit
62ee3e9f32
@@ -0,0 +1,56 @@
|
||||
"""Shared reconstruction of mail-message content for indexing and context.
|
||||
|
||||
The vector processor (index-time) and search context expansion (query-time)
|
||||
must build the *identical* text for a mail message so chunk offsets align.
|
||||
Keeping that logic here — rather than copy-pasted in both call sites — is the
|
||||
single source of truth for the reconstruction.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from nextcloud_mcp_server.vector.html_processor import html_to_markdown
|
||||
|
||||
|
||||
def format_mail_addresses(addrs: list[dict[str, Any]] | None) -> str:
|
||||
"""Render a list of {label, email} address objects as a display string."""
|
||||
parts: list[str] = []
|
||||
for addr in addrs or []:
|
||||
label = addr.get("label")
|
||||
email = addr.get("email")
|
||||
if label and email and label != email:
|
||||
parts.append(f"{label} <{email}>")
|
||||
elif email:
|
||||
parts.append(email)
|
||||
elif label:
|
||||
parts.append(label)
|
||||
return ", ".join(parts)
|
||||
|
||||
|
||||
def build_mail_content(message: dict[str, Any]) -> str:
|
||||
"""Reconstruct the indexed text body for a mail message.
|
||||
|
||||
Layout (kept stable so index-time and query-time offsets match):
|
||||
<subject>
|
||||
From: <from>
|
||||
To: <to>
|
||||
<blank line>
|
||||
<body>
|
||||
|
||||
The body is the Mail OCS ``body`` field — sanitized HTML when
|
||||
``hasHtmlBody`` is set (converted to Markdown for embedding), otherwise
|
||||
plain text.
|
||||
"""
|
||||
subject = message.get("subject") or ""
|
||||
from_str = format_mail_addresses(message.get("from"))
|
||||
to_str = format_mail_addresses(message.get("to"))
|
||||
raw_body = message.get("body") or ""
|
||||
body_text = html_to_markdown(raw_body) if message.get("hasHtmlBody") else raw_body
|
||||
|
||||
content_parts = [subject]
|
||||
if from_str:
|
||||
content_parts.append(f"From: {from_str}")
|
||||
if to_str:
|
||||
content_parts.append(f"To: {to_str}")
|
||||
content_parts.append("") # Blank line
|
||||
content_parts.append(body_text)
|
||||
return "\n".join(content_parts)
|
||||
Reference in New Issue
Block a user