sdk/qq_mail/mail_fetcher.py

216 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("🔌 断开连接失败")