mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-06 20:32:55 +08:00
* fix(imap): fix mojibake in nested emails, empty headers, and date handling - Add line-by-line mojibake fix fallback for complex emails with mixed content - Apply empty header cleanup globally to fix nested message/rfc822 parts - Add locale-independent date formatting (format_imap_date, format_rfc2822_date) - Fill missing Date header from created_at field - Fix getSubPart for non-multipart messages - Accept CREATE requests from clients (e.g. Gmail creating Drafts) - Strip whitespace from IMAP password - Use MIMEText instead of MIMEMultipart for sent mail generation - Keep body in original CTE encoding for correct BODYSTRUCTURE - Update CHANGELOG (zh/en) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: consolidate IMAP changelog entries into single line Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
2.2 KiB
Python
70 lines
2.2 KiB
Python
import logging
|
|
|
|
import httpx
|
|
from twisted.internet import defer, threads
|
|
|
|
from config import settings
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
_logger.setLevel(logging.INFO)
|
|
|
|
|
|
class BackendClient:
|
|
"""Async HTTP client for IMAP backend communication.
|
|
|
|
All public methods return Deferred via deferToThread to avoid
|
|
blocking the Twisted reactor with synchronous HTTP calls.
|
|
"""
|
|
|
|
def __init__(self, password: str):
|
|
self.password = password.strip()
|
|
self._client = httpx.Client(
|
|
base_url=settings.proxy_url,
|
|
headers={
|
|
"Authorization": f"Bearer {password}",
|
|
"x-custom-auth": settings.basic_password,
|
|
"Content-Type": "application/json",
|
|
},
|
|
timeout=settings.imap_http_timeout,
|
|
)
|
|
|
|
def _get_endpoint(self, mailbox_name: str) -> str:
|
|
if mailbox_name == "INBOX":
|
|
return "/api/mails"
|
|
elif mailbox_name == "SENT":
|
|
return "/api/sendbox"
|
|
raise ValueError(f"Unknown mailbox: {mailbox_name}")
|
|
|
|
def _sync_get_message_count(self, mailbox_name: str) -> int:
|
|
endpoint = self._get_endpoint(mailbox_name)
|
|
res = self._client.get(f"{endpoint}?limit=1&offset=0")
|
|
res.raise_for_status()
|
|
return res.json()["count"]
|
|
|
|
def _sync_get_messages(
|
|
self, mailbox_name: str, limit: int, offset: int
|
|
) -> tuple[list[dict], int | None]:
|
|
"""Fetch messages from backend.
|
|
|
|
Returns (results, count) where count is only valid when offset=0.
|
|
"""
|
|
endpoint = self._get_endpoint(mailbox_name)
|
|
res = self._client.get(f"{endpoint}?limit={limit}&offset={offset}")
|
|
res.raise_for_status()
|
|
data = res.json()
|
|
count = data.get("count") if offset == 0 else None
|
|
return data["results"], count
|
|
|
|
def get_message_count(self, mailbox_name: str) -> defer.Deferred:
|
|
return threads.deferToThread(self._sync_get_message_count, mailbox_name)
|
|
|
|
def get_messages(
|
|
self, mailbox_name: str, limit: int, offset: int
|
|
) -> defer.Deferred:
|
|
return threads.deferToThread(
|
|
self._sync_get_messages, mailbox_name, limit, offset
|
|
)
|
|
|
|
def close(self):
|
|
self._client.close()
|