import imaplib import email from email.header import decode_header import os class MailFetcher: def __init__(self, email_user, email_pass, imap_server, port=993): self.email_user = email_user self.email_pass = email_pass self.imap_server = imap_server self.port = port self.connection = None def connect(self): try: self.connection = imaplib.IMAP4_SSL(self.imap_server, self.port) self.connection.login(self.email_user, self.email_pass) print(f"✅ 登录成功: {self.email_user}") except Exception as e: print(f"❌ 邮箱登录失败: {e}") self.connection = None def list_mailboxes(self): if self.connection: status, mailboxes = self.connection.list() if status == "OK": print("📂 邮箱文件夹列表:") for m in mailboxes: try: raw_name = m.decode().split(' "/" ')[-1].strip('"') decoded_name = imaplib.IMAP4._decode_utf7(raw_name) print(f"- 原始: {raw_name} | 解码: {decoded_name}") except Exception: print(m.decode()) def _decode_str(self, s): if not s: return "" decoded_parts = decode_header(s) decoded_str = "" for part, enc in decoded_parts: if isinstance(part, bytes): decoded_str += part.decode(enc or "utf-8", errors="ignore") else: decoded_str += part return decoded_str def _select_mailbox(self, folder="INBOX"): if not self.connection: return False # 先尝试直接选择 try: status, _ = self.connection.select(f'"{folder}"') if status == "OK": print(f"📂 已选择文件夹: {folder}") return True except: pass # 遍历所有文件夹尝试匹配 INBOX / 收件箱 try: status, mailboxes = self.connection.list() if status != "OK" or not mailboxes: print("❌ 无法获取文件夹列表") return False for box in mailboxes: raw_name = box.decode().split(' "/" ')[-1].strip('"') try: decoded_name = imaplib.IMAP4._decode_utf7(raw_name) except: decoded_name = raw_name if "收件箱" in decoded_name or "INBOX" in decoded_name.upper(): # select 时使用原始名字加双引号 status, _ = self.connection.select(f'"{raw_name}"') if status == "OK": print(f"📂 自动匹配文件夹: {decoded_name}") return True except Exception as e: print(f"❌ 遍历文件夹选择收件箱失败: {e}") return False print("❌ 无法选择收件箱文件夹") return False def fetch_from_all_folders(self, limit=5, download_attachments=False): if not self.connection: print("⚠️ 未建立IMAP连接") return [] emails = [] try: status, mailboxes = self.connection.list() if status != "OK" or not mailboxes: print("❌ 无法获取文件夹列表") return emails print("📂 邮箱文件夹列表:") for box in mailboxes: box_str = box.decode() if isinstance(box, bytes) else box parts = box_str.split(' "/" ') raw_name = parts[-1].strip('"') try: decoded_name = imaplib.IMAP4._decode_utf7(raw_name) except: decoded_name = raw_name print(f"- 原始: {raw_name} | 解码: {decoded_name}") # 尝试选择这个文件夹 try: status, _ = self.connection.select(raw_name) if status != "OK": continue print(f"✅ 成功选择文件夹: {decoded_name}") # 获取邮件ID status, data = self.connection.search(None, "ALL") if status != "OK" or not data or not data[0]: print(f"📭 文件夹 {decoded_name} 没有邮件") continue email_ids = data[0].split() latest_ids = email_ids[-limit:] if limit else email_ids for eid in reversed(latest_ids): status, msg_data = self.connection.fetch(eid, "(RFC822)") if status != "OK": continue msg = email.message_from_bytes(msg_data[0][1]) emails.append(msg) # 这里只是示例,你可以解析发件人、主题等 except Exception as e: print(f"❌ 无法选择或抓取 {decoded_name}: {e}") except Exception as e: print(f"❌ 遍历文件夹失败: {e}") return emails def fetch_latest_emails(self, limit=5, download_attachments=False, folder="INBOX"): emails = [] if not self.connection: print("⚠️ 未建立IMAP连接,无法获取邮件") return emails try: if not self._select_mailbox(folder): return [] status, data = self.connection.search(None, "ALL") if status != "OK" or not data or not data[0]: print("📭 没有找到邮件") return [] email_ids = data[0].split() latest_ids = email_ids[-limit:] if limit else email_ids for eid in reversed(latest_ids): status, msg_data = self.connection.fetch(eid, "(RFC822)") if status != "OK": continue msg = email.message_from_bytes(msg_data[0][1]) mail_from = self._decode_str(msg.get("From")) mail_subject = self._decode_str(msg.get("Subject")) mail_date = msg.get("Date") mail_body = "" attachments = [] if msg.is_multipart(): for part in msg.walk(): content_type = part.get_content_type() filename = part.get_filename() if filename and download_attachments: filename = self._decode_str(filename) os.makedirs("attachments", exist_ok=True) save_path = os.path.join("attachments", filename) with open(save_path, "wb") as f: f.write(part.get_payload(decode=True)) attachments.append(save_path) elif content_type == "text/plain" and not filename: charset = part.get_content_charset() or "utf-8" try: mail_body += part.get_payload(decode=True).decode(charset, errors="ignore") except: mail_body += part.get_payload(decode=True).decode("utf-8", errors="ignore") else: charset = msg.get_content_charset() or "utf-8" mail_body = msg.get_payload(decode=True).decode(charset, errors="ignore") emails.append({ "from": mail_from, "subject": mail_subject, "date": mail_date, "body": mail_body, "attachments": attachments }) except Exception as e: print(f"❌ 获取邮件失败: {e}") return emails def logout(self): if self.connection: try: try: self.connection.close() except: pass self.connection.logout() print("🔌 已断开连接") except Exception: print("🔌 断开连接失败")