diff --git a/main.py b/main.py index fea0d4b..19db78c 100644 --- a/main.py +++ b/main.py @@ -48,7 +48,7 @@ def _email_poller(cfg): logger.info("邮件轮询线程已启动") while _running: try: - for acct_idx, mail in poll_accounts(cfg): + for acct_idx, mail in poll_accounts(cfg, skip_uids=_processing_uids): if not _running: return with _pending_lock: diff --git a/src/email_client.py b/src/email_client.py index 7f347ad..82e56d7 100644 --- a/src/email_client.py +++ b/src/email_client.py @@ -109,8 +109,10 @@ def _login_and_prepare(account: EmailAccount, timeout: int = 30, select_inbox: b return conn -def fetch_unseen_emails(account: EmailAccount) -> list[Email]: +def fetch_unseen_emails(account: EmailAccount, skip_uids: set[bytes] | None = None) -> list[Email]: conn = _login_and_prepare(account, select_inbox=False) + if skip_uids is None: + skip_uids = set() # 列出所有文件夹 _, folder_data = conn.list() @@ -120,8 +122,6 @@ def fetch_unseen_emails(account: EmailAccount) -> list[Email]: if item is None: continue line = item.decode("utf-8", errors="replace") - # IMAP LIST 格式: (flags) "delimiter" "folder_name" - # 用正则提取最后一个引号内的内容 m = re.search(r'"([^"]*)"\s*$', line) if m: folders.append(m.group(1)) @@ -130,6 +130,7 @@ def fetch_unseen_emails(account: EmailAccount) -> list[Email]: logger.info("扫描文件夹: %s", ", ".join(folders)) emails = [] + skipped = 0 for folder in folders: try: status, _ = conn.select(folder, readonly=True) @@ -140,9 +141,15 @@ def fetch_unseen_emails(account: EmailAccount) -> list[Email]: uids = data[0].split() if data[0] else [] if not uids: continue - logger.info(" %s: %d 封未读", folder, len(uids)) - for uid in uids: + # 先过滤掉正在处理的 UID,避免下载不需要的邮件 + new_uids = [uid for uid in uids if uid not in skip_uids] + skipped += len(uids) - len(new_uids) + if not new_uids: + continue + logger.info(" %s: %d 封未读 (%d 封跳过)", folder, len(new_uids), len(uids) - len(new_uids)) + + for uid in new_uids: try: _, msg_data = conn.uid("FETCH", uid, "RFC822") if msg_data[0] is None: diff --git a/src/summarizer.py b/src/summarizer.py index 01d50a0..ce22906 100644 --- a/src/summarizer.py +++ b/src/summarizer.py @@ -8,11 +8,11 @@ from src.tg_bot import send_summary, send_thinking, edit_message, format_summary logger = logging.getLogger(__name__) -def poll_accounts(cfg: Config) -> Generator[tuple[int, Email], None, None]: +def poll_accounts(cfg: Config, skip_uids: set[bytes] | None = None) -> Generator[tuple[int, Email], None, None]: for idx, acct in enumerate(cfg.email_accounts): try: logger.info(f"检查邮箱: {acct.username}") - emails = fetch_unseen_emails(acct) + emails = fetch_unseen_emails(acct, skip_uids=skip_uids) if emails: logger.info(f" 发现 {len(emails)} 封新邮件") for mail in emails: diff --git a/src/tg_bot.py b/src/tg_bot.py index dc3fde8..432fa9c 100644 --- a/src/tg_bot.py +++ b/src/tg_bot.py @@ -193,7 +193,8 @@ def _summary_keyboard(can_reply: bool = True, is_promotion: bool = False, links: for link in (links or []): url = link.get("url", "") text = link.get("text", "打开链接")[:30] - if url.startswith("http"): + # 校验 URL:合法 http/https,无多重协议头,长度合理 + if url.startswith("http") and url.count("://") == 1 and len(url) < 200: kb.append([{"text": f"🔗 {text}", "url": url}]) return {"inline_keyboard": kb}