From 89149b506c259b69f5bb8b27033b1f41cd52a3b9 Mon Sep 17 00:00:00 2001 From: Zichao Lin Date: Thu, 2 Jul 2026 19:56:14 +0800 Subject: [PATCH] fix: add IMAP ID command for 126.com/163.com support These providers require a client identification (ID command, RFC 2971) before SELECT INBOX, otherwise they reject with 'SELECT Unsafe Login'. Extract shared login+select logic into _login_and_prepare helper. --- src/email_client.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/email_client.py b/src/email_client.py index fedaf0f..96e1379 100644 --- a/src/email_client.py +++ b/src/email_client.py @@ -48,16 +48,36 @@ def _get_text_from_msg(msg) -> str: return "" +_PROVIDERS_NEED_ID = ("163.com", "126.com") + + +def _check_provider(email_addr: str, providers: tuple[str, ...]) -> bool: + return any(email_addr.endswith(p) for p in providers) + + +def _send_id_command(conn): + imaplib.Commands["ID"] = "AUTH" + args = ("name", "AI-Mail-Bot", "version", "1.0.0", "vendor", "personal") + conn._simple_command("ID", '("' + '" "'.join(args) + '")') + + def _select_mailbox(conn, mailbox: str = "INBOX"): status, data = conn.select(mailbox) if status != "OK": raise RuntimeError(f"无法选择邮箱 {mailbox}: {data}") -def fetch_unseen_emails(account: EmailAccount) -> list[Email]: +def _login_and_prepare(account: EmailAccount): conn = imaplib.IMAP4_SSL(account.imap_server, account.imap_port) conn.login(account.username, account.password) + if _check_provider(account.username, _PROVIDERS_NEED_ID): + _send_id_command(conn) _select_mailbox(conn) + return conn + + +def fetch_unseen_emails(account: EmailAccount) -> list[Email]: + conn = _login_and_prepare(account) _, data = conn.uid("SEARCH", None, "UNSEEN") uids = data[0].split() if data[0] else [] @@ -84,9 +104,7 @@ def fetch_unseen_emails(account: EmailAccount) -> list[Email]: def mark_as_seen(account: EmailAccount, uids: list[bytes]): if not uids: return - conn = imaplib.IMAP4_SSL(account.imap_server, account.imap_port) - conn.login(account.username, account.password) - _select_mailbox(conn) + conn = _login_and_prepare(account) for uid in uids: conn.uid("STORE", uid, "+FLAGS", "\\Seen") conn.logout()