Files
ca_auto_table/spider/mail_.py
2025-11-20 11:42:18 +08:00

836 lines
34 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 asyncio
import imaplib
import email
import random
import socket
import string
import time
from email.header import decode_header
from datetime import timezone, timedelta
import email.utils
import aiohttp
import socks
import requests
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from functools import wraps
from loguru import logger
def retry(max_retries: int = 3, delay: float = 1.0, backoff: float = 1.0):
"""
通用重试装饰器
:param max_retries: 最大重试次数
:param delay: 每次重试的初始延迟(秒)
:param backoff: 每次重试延迟的递增倍数
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
retries = 0
current_delay = delay
while retries < max_retries:
try:
return func(*args, **kwargs)
except Exception as e:
retries += 1
if retries >= max_retries:
logger.warning(f"函数 {func.__name__} 在尝试了 {max_retries} 次后失败,错误信息: {e}")
return None # 重试次数用尽后返回 None
logger.warning(f"正在重试 {func.__name__} {retries + 1}/{max_retries} 因错误: {e}")
time.sleep(current_delay)
current_delay *= backoff
return None # 三次重试仍未成功,返回 None
return wrapper
return decorator
def async_retry(max_retries: int = 3, delay: float = 1.0, backoff: float = 1.0):
"""
支持异步函数的通用重试装饰器
:param max_retries: 最大重试次数
:param delay: 每次重试的初始延迟(秒)
:param backoff: 每次重试延迟的递增倍数
"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
retries = 0
current_delay = delay
while retries < max_retries:
try:
return await func(*args, **kwargs) # 直接执行原始方法
except Exception as e:
retries += 1
if retries >= max_retries:
logger.warning(f"函数 {func.__name__} 在尝试了 {max_retries} 次后失败,错误信息: {e}")
return None # 重试次数用尽后返回 None
logger.warning(f"正在重试 {func.__name__} {retries + 1}/{max_retries} 因错误: {e}")
await asyncio.sleep(current_delay) # 异步延迟
current_delay *= backoff # 根据backoff递增延迟
return None # 三次重试仍未成功,返回 None
return wrapper
return decorator
# 域名管理类 - 高内聚低耦合的域名管理方案
class DomainManager:
"""
域名管理器 - 统一管理所有邮箱域名相关操作
实现高内聚低耦合的设计原则
"""
def __init__(self):
# 域名列表 - 只需要在这里添加新域名
self._domains = [
"gmail.com",
"qianyouduo.com",
"rxybb.com",
"cqrxy.vip",
"0n.lv",
"qianyouduo.com",
"ziyouzuan.com",
"emaing.online",
"emaing.fun",
"emaing.asia",
"isemaing.site",
"emaing.cyou",
"emaing.site",
"emaing.icu",
"emaing.store",
"emaing.pw",
"emaing.xyz",
"qydkjgs.asia",
"qydgs.autos",
"qydkj.homes",
"qydkjgs.baby",
"qydkj.baby",
"qydkj.cyou",
"qydkjgs.autos",
"qydkj.autos",
"qydkjgs.cyou",
"qydkjgs.homes",
"qydgs.asia",
"qydkj.asia",
"qydgs.baby",
"qydgs.cyou",
"qydgs.homes",
"lulanjing.asia",
"lisihan.asia",
"mmwan.asia",
"xyttan.asia",
"zpaily.asia",
"youxinzhiguo.asia",
"huijinfenmu.asia",
"linghao.asia",
"cqhc.asia",
"huacun.asia",
"huachen.asia",
"yisabeier.asia",
"xinxinr.cyou",
"lilisi.asia",
"xybbwan.cyou",
"zhongjing.cyou",
"zprxy.cyou",
"cqhuacun.cyou",
"huazong.icu",
"huacun.cyou"
]
def get_domain_by_type(self, mail_type: int) -> str:
"""
根据邮箱类型获取域名
:param mail_type: 邮箱类型编号
:return: 对应的域名
"""
if 0 <= mail_type < len(self._domains):
return self._domains[mail_type]
return self._domains[1] # 默认返回 qianyouduo.com
def get_domain_type(self, domain: str) -> int:
"""
根据域名获取类型编号
:param domain: 域名
:return: 对应的类型编号如果不存在返回1
"""
try:
return self._domains.index(domain)
except ValueError:
return 1 # 默认返回 qianyouduo.com 的类型
def get_imap_server(self, mail_type: int) -> str:
"""
根据邮箱类型获取IMAP服务器地址
:param mail_type: 邮箱类型编号
:return: IMAP服务器地址
"""
domain = self.get_domain_by_type(mail_type)
return f"imap.{domain}"
def get_imap_server_by_domain(self, domain: str) -> str:
"""
根据域名获取IMAP服务器地址
:param domain: 域名
:return: IMAP服务器地址
"""
return f"imap.{domain}"
def is_valid_domain(self, domain: str) -> bool:
"""
检查域名是否在支持列表中
:param domain: 域名
:return: 是否支持该域名
"""
return domain in self._domains
def get_all_domains(self) -> list:
"""
获取所有支持的域名列表
:return: 域名列表的副本
"""
return self._domains.copy()
def get_domain_count(self) -> int:
"""
获取支持的域名总数
:return: 域名总数
"""
return len(self._domains)
def get_creatable_domains(self) -> list:
"""
获取可用于创建邮箱的域名列表排除gmail.com
:return: 可创建邮箱的域名列表
"""
return [domain for domain in self._domains if domain != "gmail.com"]
def get_creatable_domain_by_type(self, mail_type: int) -> str:
"""
根据邮箱类型获取可创建的域名排除gmail.com
:param mail_type: 邮箱类型编号
:return: 对应的域名如果是gmail.com则返回默认域名
"""
domain = self.get_domain_by_type(mail_type)
if domain == "gmail.com":
return self._domains[1] # 返回qianyouduo.com作为默认
return domain
# 邮箱模块
class Mail:
def __init__(self):
self.domain_manager = DomainManager()
self.api_host = 'http://111.10.175.206:5020'
def email_account_read(self, pk: int = None, account: str = None, status: bool = None, host: str = None,
proxy_account: str = None,
parent_account: str = None, order_by: str = None, level: int = None,
update_time_start: str = None, update_time_end: str = None, res_count: bool = False,
create_time_start: str = None, create_time_end: str = None, page: int = None,
limit: int = None) -> dict:
"""
读取mail账号
:param level: 邮箱等级(可选)
:param status: 状态(可选)
:param update_time_start: 更新时间起始(可选)
:param update_time_end: 更新时间结束(可选)
:param res_count: 返回总数 (可选)
:param parent_account: 母邮箱账号 (可选)
:param pk: 主键 (可选)
:param account: 账号 (可选)
:param host: 代理 (可选)
:param proxy_account: 代理账号 (可选)
:param order_by: 排序方式 (可选) id|create_time|update_time 前面加-表示倒序
:param create_time_start: 创建起始时间 (可选)
:param create_time_end: 创建结束时间 (可选)
:param page: 页码 (可选)
:param limit: 每页数量 (可选)
:return: 返回json 成功字段code=200
"""
if pk is not None:
url = f'{self.api_host}/mail/account/{pk}'
return requests.get(url).json()
url = f'{self.api_host}/mail/account'
data = dict()
if account is not None:
data['account'] = account
if status is not None:
data['status'] = status
if host is not None:
data['host'] = host
if proxy_account is not None:
data['proxy_account'] = proxy_account
if parent_account is not None:
data['parent_account'] = parent_account
if order_by is not None:
data['order_by'] = order_by
if level is not None:
data['level'] = level
if create_time_start is not None:
data['create_time_start'] = create_time_start
if create_time_end is not None:
data['create_time_end'] = create_time_end
if update_time_start is not None:
data['update_time_start'] = update_time_start
if update_time_end is not None:
data['update_time_end'] = update_time_end
if res_count:
data['res_count'] = res_count
if page is not None:
data['page'] = page
if limit is not None:
data['limit'] = limit
res = requests.get(url, params=data).json()
if res.get('code') not in [200, 400, 404]:
raise Exception(res)
return res
# 创建随机邮箱
@retry(max_retries=3, delay=1.0, backoff=1.0)
def email_create_random(self, count: int = 8, pwd: str = 'Zpaily88', mail_type: int = 1) -> str:
"""
创建邮箱
:param count: 邮箱长度(默认8位)
:param pwd: 邮箱密码(默认Zpaily88)
:param mail_type: 邮箱类型(1表示qianyouduo.com 2表示rxybb.com 3表示cqrxy.vip 4表示0n.lv 默认1)
:return: 邮箱账号
"""
headers = {
"Accept-Language": "zh-CN,zh;q=0.9",
"Authorization": "Basic YWRtaW5AcWlhbnlvdWR1by5jb206WnBhaWx5ODgh",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/json",
"Origin": "https://mail.qianyouduo.com",
"Pragma": "no-cache",
"Referer": "https://mail.qianyouduo.com/admin/api/doc",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"accept": "*/*",
"sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
url = "https://mail.qianyouduo.com/admin/api/v1/boxes"
name = ''.join(random.choices(string.ascii_letters + string.digits, k=count)).lower()
# 使用域名管理器获取可创建的域名排除gmail.com
mail_end = self.domain_manager.get_creatable_domain_by_type(mail_type)
data = {
"name": name,
"email": f"{name}@{mail_end}",
"passwordPlaintext": pwd
}
response = requests.post(url, headers=headers, json=data)
if 'Validation errors: [user] This combination of username and domain is already in database' in response.text:
return f'{name}@{mail_end}'
if response.status_code != 201:
raise Exception(response.status_code)
return f"{name}@{mail_end}"
# 异步创建随机邮箱
@async_retry(max_retries=3, delay=1.0, backoff=1.0)
async def _email_create_random(self, count: int = 8, pwd: str = 'Zpaily88', mail_type: int = 1) -> str:
"""
创建邮箱
:param count: 邮箱长度(默认8位)
:param pwd: 邮箱密码(默认Zpaily88)
:param mail_type: 邮箱类型(1表示qianyouduo.com 2表示rxybb.com 3表示cqrxy.vip 4表示0n.lv 默认1)
:return:邮箱账号
"""
headers = {
"Accept-Language": "zh-CN,zh;q=0.9",
"Authorization": "Basic YWRtaW5AcWlhbnlvdWR1by5jb206WnBhaWx5ODgh",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/json",
"Origin": "https://mail.qianyouduo.com",
"Pragma": "no-cache",
"Referer": "https://mail.qianyouduo.com/admin/api/doc",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"accept": "*/*",
"sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
url = "https://mail.qianyouduo.com/admin/api/v1/boxes"
name = ''.join(random.choices(string.ascii_letters + string.digits, k=count)).lower()
# 使用域名管理器获取可创建的域名排除gmail.com
mail_end = self.domain_manager.get_creatable_domain_by_type(mail_type)
data = {
"name": name,
"email": f"{name}@{mail_end}",
"passwordPlaintext": pwd
}
async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, json=data) as response:
status = response.status
text = await response.text()
if 'Validation errors: [user] This combination of username and domain is already in database' in text:
return f"{name}@{mail_end}"
if status != 201:
raise Exception(status)
return f"{name}@{mail_end}"
# 创建邮箱
@retry(max_retries=3, delay=1.0, backoff=1.0)
def email_create(self, account: str, pwd: str = 'Zpaily88') -> str | None:
"""
创建邮箱
:param account: 邮箱账号
:param pwd: 邮箱密码(默认Zpaily88)
:return:邮箱账号
"""
headers = {
"Accept-Language": "zh-CN,zh;q=0.9",
"Authorization": "Basic YWRtaW5AcWlhbnlvdWR1by5jb206WnBhaWx5ODgh",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/json",
"Origin": "https://mail.qianyouduo.com",
"Pragma": "no-cache",
"Referer": "https://mail.qianyouduo.com/admin/api/doc",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"accept": "*/*",
"sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
url = "https://mail.qianyouduo.com/admin/api/v1/boxes"
name = account.split('@')[0]
mail_end = account.split('@')[1]
# 排除gmail.com域名
if mail_end == "gmail.com":
return None
# 验证域名是否支持
if not self.domain_manager.is_valid_domain(mail_end):
raise ValueError(f"不支持的域名: {mail_end},支持的域名列表: {self.domain_manager.get_all_domains()}")
data = {
"name": name,
"email": f"{name}@{mail_end}",
"passwordPlaintext": pwd
}
response = requests.post(url, headers=headers, json=data)
print(f'创建邮箱响应: {response.status_code}')
if response.status_code not in [201, 400]:
raise Exception(response.status_code)
return f"{name}@{mail_end}"
# 异步创建邮箱
@async_retry(max_retries=3, delay=1.0, backoff=1.0)
async def _email_create(self, account: str, pwd: str = 'Zpaily88') -> str | None:
"""
创建邮箱
:param account: 邮箱账号
:param pwd: 邮箱密码(默认Zpaily88)
:return: 邮箱账号
"""
headers = {
"Accept-Language": "zh-CN,zh;q=0.9",
"Authorization": "Basic YWRtaW5AcWlhbnlvdWR1by5jb206WnBhaWx5ODgh",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/json",
"Origin": "https://mail.qianyouduo.com",
"Pragma": "no-cache",
"Referer": "https://mail.qianyouduo.com/admin/api/doc",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"accept": "*/*",
"sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
url = "https://mail.qianyouduo.com/admin/api/v1/boxes"
name = account.split('@')[0]
mail_end = account.split('@')[1]
# 排除gmail.com域名
if mail_end == "gmail.com":
return None
# 验证域名是否支持
if not self.domain_manager.is_valid_domain(mail_end):
raise ValueError(f"不支持的域名: {mail_end},支持的域名列表: {self.domain_manager.get_all_domains()}")
data = {
"name": name,
"email": f"{name}@{mail_end}",
"passwordPlaintext": pwd
}
async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, json=data) as response:
status = response.status
if status not in [201, 400]:
raise Exception(f'status code: {status}')
return f"{name}@{mail_end}"
# 删除邮箱
@retry(max_retries=3, delay=1.0, backoff=1.0)
def email_delete(self, account: str) -> bool:
"""
删除邮箱
:param account: 邮箱账号
:return: True表示删除成功False表示删除失败
"""
headers = {
"Accept-Language": "zh-CN,zh;q=0.9",
"Authorization": "Basic YWRtaW5AcWlhbnlvdWR1by5jb206WnBhaWx5ODgh",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/json",
"Origin": "https://mail.qianyouduo.com",
"Pragma": "no-cache",
"Referer": "https://mail.qianyouduo.com/admin/api/doc",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"accept": "*/*",
"sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
url = f"https://mail.qianyouduo.com/admin/api/v1/boxes/{account}"
if '@gmail.com' in account:
return False
response = requests.delete(url, headers=headers)
print(f'删除邮箱响应: --> {response.status_code}')
if response.status_code not in [204, 404]:
raise Exception(response.status_code)
return True
# 异步删除邮箱
@async_retry(max_retries=3, delay=1.0, backoff=1.0)
async def _email_delete(self, account: str) -> bool:
"""
删除邮箱
:param account: 邮箱账号
:return: True表示删除成功False表示删除失败
"""
headers = {
"Accept-Language": "zh-CN,zh;q=0.9",
"Authorization": "Basic YWRtaW5AcWlhbnlvdWR1by5jb206WnBhaWx5ODgh",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/json",
"Origin": "https://mail.qianyouduo.com",
"Pragma": "no-cache",
"Referer": "https://mail.qianyouduo.com/admin/api/doc",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"accept": "*/*",
"sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
}
url = f"https://mail.qianyouduo.com/admin/api/v1/boxes/{account}"
if '@gmail.com' in account:
return False
async with aiohttp.ClientSession() as session:
async with session.delete(url, headers=headers) as response:
status = response.status
if status not in [204, 404]:
raise Exception(f'status code: {status}')
return True
# 处理邮件正文
@staticmethod
def extract_body(msg):
"""
提取邮件正文,优先返回 HTML 文本
- 更健壮的字符集解析:优先使用 part 的 charset 信息,失败回退到 utf-8 / latin-1
- 仅处理 inline 的 text/html 与 text/plain 内容
"""
html_text = None
plain_text = None
def _decode_part(part):
payload = part.get_payload(decode=True)
if payload is None:
return None
# 优先从内容中解析 charset
charset = (part.get_content_charset() or part.get_param('charset') or 'utf-8')
try:
return payload.decode(charset, errors='replace')
except LookupError:
# 未知编码时回退
try:
return payload.decode('utf-8', errors='replace')
except Exception:
return payload.decode('latin-1', errors='replace')
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
content_disposition = part.get_content_disposition()
if content_type == "text/html" and (not content_disposition or content_disposition == "inline"):
html_text = _decode_part(part) or html_text
elif content_type == "text/plain" and (not content_disposition or content_disposition == "inline"):
plain_text = _decode_part(part) or plain_text
else:
content_type = msg.get_content_type()
if content_type == "text/html":
html_text = _decode_part(msg)
elif content_type == "text/plain":
plain_text = _decode_part(msg)
# 优先返回 HTML 文本,如果没有 HTML 文本,则返回纯文本
return html_text or plain_text or ""
# 转换邮件日期
@staticmethod
def convert_to_china_time(date_str):
"""
将邮件日期转换为10位时间戳中国时区
- 保留原始邮件的时区信息;若无时区,则按 UTC 处理
- 异常时返回当前时间戳,避免解析失败导致崩溃
"""
try:
email_date = email.utils.parsedate_to_datetime(date_str)
if email_date is None:
return int(time.time())
if email_date.tzinfo is None:
email_date = email_date.replace(tzinfo=timezone.utc)
china_time = email_date.astimezone(timezone(timedelta(hours=8)))
return int(china_time.timestamp())
except Exception:
return int(time.time())
# 获取邮件
def email_read(self, user: str, from_: str, limit: int = 1, is_del: bool = False) -> list | None:
"""
获取最新邮件
:param user: 母账号
:param from_: 发件人匹配关键字(可为邮箱或显示名,大小写不敏感)
:param limit: 获取邮件数量(默认1封)
:param is_del: 是否删除整个邮箱账号(非 Gmail 才会执行账号删除)
:return: 返回邮件列表,每个元素格式为:
{
"title": "邮件标题",
"from": "发件人",
"date": "邮件日期(中国时区时间戳)",
"content": "邮件正文",
"code": 200
}
"""
user_li = user.split('@')
domain = user_li[1]
# 使用域名管理器获取邮箱类型
if not self.domain_manager.is_valid_domain(domain):
return None
mail_type = self.domain_manager.get_domain_type(domain)
# 仅对 Gmail 进行点号归一化,其它域名按原样处理
local_part = user_li[0]
if domain == "gmail.com":
local_part = local_part.replace('.', '')
user = local_part + '@' + user_li[1]
proxy_host = None
proxy_port = None
proxy_user = None
proxy_pwd = None
if mail_type == 0:
res = self.email_account_read(parent_account=user, status=True, level=0)
if res['code'] != 200:
return None
pwd = res['items'][0]['parent_pwd']
proxy_host = res['items'][0]['host']
proxy_port = res['items'][0]['port']
proxy_user = res['items'][0]['proxy_account']
proxy_pwd = res['items'][0]['proxy_pwd']
else:
pwd = 'Zpaily88'
items = [] # 存储邮件列表
# 保存原始socket
original_socket = None
if proxy_host is not None and proxy_port is not None:
original_socket = socket.socket
if proxy_user is not None and proxy_pwd is not None:
socks.setdefaultproxy(socks.SOCKS5, proxy_host, int(proxy_port), True, proxy_user, proxy_pwd)
else:
socks.setdefaultproxy(socks.SOCKS5, proxy_host, int(proxy_port), True)
socket.socket = socks.socksocket
imap_server = None
had_error = False
try:
# 在设置代理后创建IMAP连接
imap_server = imaplib.IMAP4_SSL(self.domain_manager.get_imap_server(mail_type))
if not imap_server:
had_error = True
else:
# pwd去除空格
pwd = pwd.replace(' ', '')
# print(f'pwd: {pwd}')
imap_server.login(user, pwd)
status, _ = imap_server.select("INBOX")
if status != 'OK':
had_error = True
else:
status, email_ids = imap_server.search(None, "ALL")
if status != 'OK':
had_error = True
else:
email_id_list = email_ids[0].split()
# 获取最近limit条邮件ID
recent_ids = email_id_list[-20:] # 仍然获取最近20封以确保有足够的邮件可以筛选
found_count = 0 # 记录找到的符合条件的邮件数量
for email_id in recent_ids[::-1]: # 从最新的邮件开始处理
if found_count >= limit: # 如果已经找到足够数量的邮件,就退出循环
break
status, msg_data = imap_server.fetch(email_id, "(RFC822)")
for response in msg_data:
if isinstance(response, tuple):
msg = email.message_from_bytes(response[1])
# 兼容性发件人匹配:解析地址与显示名,大小写不敏感,支持子串匹配
from_field = msg.get("From", "")
addresses = email.utils.getaddresses([from_field])
needle = (from_ or "").lower()
candidates = []
for name, addr in addresses:
if name:
candidates.append(name.lower())
if addr:
candidates.append(addr.lower())
if any(needle in c for c in candidates):
# 标题解码,处理无标题或编码缺失的情况
raw_subject = msg.get("Subject")
subject = ""
if raw_subject is not None:
dh = decode_header(raw_subject)
if dh:
s, enc = dh[0]
if isinstance(s, bytes):
try:
subject = s.decode(enc or 'utf-8', errors='replace')
except LookupError:
subject = s.decode('utf-8', errors='replace')
else:
subject = s
item = {
"title": subject,
"from": msg["From"],
"content": self.extract_body(msg),
"code": 200
}
# 获取并转换邮件时间
date_str = msg["Date"]
if date_str:
item["date"] = self.convert_to_china_time(date_str)
items.append(item)
found_count += 1
if found_count >= limit: # 如果已经找到足够数量的邮件,就跳出内层循环
break
# 读取完成不再对单封邮件做删除标记与 expunge
except imaplib.IMAP4.error as e:
# items.append({'title': 'error', 'from': 'error', 'content': f'连接邮箱失败: {e}', 'code': 500})
had_error = True
except Exception as e:
# items.append({'title': 'error', 'from': 'error', 'content': f'获取邮件异常: {e}', 'code': 500})
had_error = True
finally:
try:
# 检查连接是否建立
if 'imap_server' in locals() and imap_server is not None:
try:
# 先检查是否处于已选择状态
if hasattr(imap_server, 'state') and imap_server.state == 'SELECTED':
imap_server.close()
except Exception as e:
logger.error(f"关闭IMAP文件夹时发生错误: {e}")
try:
# 无论如何尝试登出
imap_server.logout()
except Exception as e:
logger.error(f"登出IMAP服务器时发生错误: {e}")
# 在Windows上可能需要强制关闭socket
try:
if hasattr(imap_server, 'sock') and imap_server.sock is not None:
imap_server.sock.close()
except Exception as sock_err:
logger.error(f"强制关闭socket时发生错误: {sock_err}")
except Exception as outer_e:
logger.error(f"处理IMAP连接关闭时发生错误: {outer_e}")
finally:
# 重置socket设置如果使用了代理
if proxy_host is not None and original_socket is not None:
socket.socket = original_socket
# 若成功获取到至少一封匹配邮件且请求删除,则删除整个邮箱账号
if is_del and len(items) > 0:
try:
self.email_delete(user)
except Exception as del_err:
logger.error(f"删除邮箱账号失败: {del_err}")
if had_error:
return None
if len(items) == 0:
return None
return items # 返回邮件列表
async def main():
"""
使用示例:展示新的域名管理系统的使用方法
"""
mail = Mail()
mai = '0gz3vvd4@'+'qydgs.asia'
res = mail.email_create(mai)
print(f"创建的邮箱: {res}")
# random_email = mail.email_create_random(count=8, mail_type=1)
# print(f"创建的随机邮箱: {random_email}")
# 读取邮件
# res = mail.email_read('0gz3vvd4@qydgs.asia', '@', 1, is_del=True)
# print(f'读取的邮件: {res}')
# 删除邮箱
res = mail.email_delete(mai)
print(f"删除的邮箱: {res}")
mail_ = Mail()
# if __name__ == '__main__':
# asyncio.run(main())