feat: support attachments in manual reply (photo/video/document/voice) - Add _extract_media: extract media info from Telegram messages - Add _download_tg_file: download files from Telegram API - Modify _handle_message: accumulate attachments in awaiting_reply, send with text - Modify _handle_update: process messages without text (media-only) - Modify smtp_client.send_reply: support MIME multipart with attachments - Add comprehensive tests (11 tests, all passing)
This commit is contained in:
@@ -2,6 +2,9 @@ import smtplib
|
||||
import logging
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.base import MIMEBase
|
||||
from email import encoders
|
||||
from pathlib import Path
|
||||
from src.config import EmailAccount
|
||||
from src.retry import retry
|
||||
|
||||
@@ -9,7 +12,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@retry()
|
||||
def send_reply(account: EmailAccount, to_addr: str, subject: str, body: str):
|
||||
def send_reply(account: EmailAccount, to_addr: str, subject: str, body: str,
|
||||
attachments: list[tuple[str, str]] | None = None):
|
||||
"""发送回复邮件,支持附件。
|
||||
|
||||
Args:
|
||||
attachments: [(本地文件路径, mime_type), ...] 列表
|
||||
"""
|
||||
if not account.smtp:
|
||||
raise RuntimeError(f"账号 {account.username} 未配置 SMTP")
|
||||
|
||||
@@ -26,13 +35,32 @@ def send_reply(account: EmailAccount, to_addr: str, subject: str, body: str):
|
||||
logger.info("SMTP 登录成功")
|
||||
|
||||
reply_subject = subject if subject.lower().startswith("re:") else f"Re: {subject}"
|
||||
logger.info("SMTP 发送: to=%s subject=%s body=%d 字符", to_addr, reply_subject, len(body))
|
||||
logger.info("SMTP 发送: to=%s subject=%s body=%d 字符 attachments=%d",
|
||||
to_addr, reply_subject, len(body), len(attachments or []))
|
||||
|
||||
if attachments:
|
||||
msg = MIMEMultipart()
|
||||
msg.attach(MIMEText(body, "plain", "utf-8"))
|
||||
for filepath, mime_type in attachments:
|
||||
p = Path(filepath)
|
||||
if not p.exists():
|
||||
logger.warning(" 附件不存在: %s", filepath)
|
||||
continue
|
||||
maintype, subtype = mime_type.split("/", 1)
|
||||
with open(p, "rb") as f:
|
||||
part = MIMEBase(maintype, subtype)
|
||||
part.set_payload(f.read())
|
||||
encoders.encode_base64(part)
|
||||
part.add_header("Content-Disposition", "attachment", filename=p.name)
|
||||
msg.attach(part)
|
||||
logger.info(" 添加附件: %s (%s, %d bytes)", p.name, mime_type, p.stat().st_size)
|
||||
else:
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg.attach(MIMEText(body, "plain", "utf-8"))
|
||||
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = account.username
|
||||
msg["To"] = to_addr
|
||||
msg["Subject"] = reply_subject
|
||||
msg.attach(MIMEText(body, "plain", "utf-8"))
|
||||
|
||||
conn.sendmail(account.username, [to_addr], msg.as_string())
|
||||
conn.quit()
|
||||
|
||||
Reference in New Issue
Block a user