treel / backend /ext /gmail.py
StarrySkyWorld's picture
aaa
d06f310
"""
临时 Google Mail 模块
基于 emailnator.com 的 API 实现
"""
import httpx
import re
from typing import Optional, List, Dict, Any
class TempGmail:
"""临时 Gmail 邮箱管理类"""
BASE_URL = "https://www.emailnator.com"
def __init__(self):
"""初始化客户端"""
self.client = httpx.Client(
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"Origin": self.BASE_URL,
"Referer": f"{self.BASE_URL}/",
"Sec-Ch-Ua": '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"X-Requested-With": "XMLHttpRequest",
},
follow_redirects=True,
timeout=30.0
)
self.xsrf_token: Optional[str] = None
self.email: Optional[str] = None
def _get_xsrf_token(self) -> str:
"""获取 XSRF Token"""
try:
# 访问主页获取 cookie 和 session
response = self.client.get(self.BASE_URL)
response.raise_for_status()
# 从 cookie 中提取 XSRF-TOKEN(可能是 URL 编码的)
xsrf_cookie = self.client.cookies.get("XSRF-TOKEN")
if xsrf_cookie:
# URL 解码 token
from urllib.parse import unquote
self.xsrf_token = unquote(xsrf_cookie)
return self.xsrf_token
raise Exception("无法从 Cookie 中获取 XSRF Token")
except Exception as e:
raise Exception(f"获取 XSRF Token 失败: {str(e)}")
def _ensure_token(self):
"""确保有有效的 token"""
if not self.xsrf_token:
self._get_xsrf_token()
def generate_email(self, email_types: Optional[List[str]] = None) -> str:
"""
生成临时邮箱地址
Args:
email_types: 邮箱类型列表,可选值: ["plusGmail", "dotGmail", "googleMail"]
默认为 ["plusGmail", "dotGmail"]
Returns:
生成的邮箱地址
"""
self._ensure_token()
if email_types is None:
email_types = ["plusGmail", "dotGmail"]
try:
response = self.client.post(
f"{self.BASE_URL}/generate-email",
json={"email": email_types},
headers={
"Content-Type": "application/json",
"X-Xsrf-Token": self.xsrf_token,
}
)
response.raise_for_status()
data = response.json()
if "email" in data:
self.email = data["email"][0] if isinstance(data["email"], list) else data["email"]
else:
# 如果返回格式不同,尝试直接获取
self.email = list(data.values())[0] if data else None
if not self.email:
raise Exception(f"无法从响应中提取邮箱地址: {data}")
return self.email
except httpx.HTTPStatusError as e:
raise Exception(f"生成邮箱失败 (HTTP {e.response.status_code}): {e.response.text}")
except Exception as e:
raise Exception(f"生成邮箱失败: {str(e)}")
def get_message_list(self, email: Optional[str] = None, filter_ads: bool = True) -> List[Dict[str, Any]]:
"""
获取邮件列表
Args:
email: 邮箱地址,如果不提供则使用当前生成的邮箱
filter_ads: 是否过滤广告邮件,默认为 True
Returns:
邮件列表
"""
self._ensure_token()
target_email = email or self.email
if not target_email:
raise Exception("请先生成邮箱或提供邮箱地址")
# 广告邮件的 messageID 列表
AD_MESSAGE_IDS = {'ADSVPN'}
try:
response = self.client.post(
f"{self.BASE_URL}/message-list",
json={"email": target_email},
headers={
"Content-Type": "application/json",
"X-Xsrf-Token": self.xsrf_token,
"Referer": f"{self.BASE_URL}/mailbox/",
}
)
response.raise_for_status()
data = response.json()
# 返回邮件列表,可能是 messageData 字段
messages = []
if isinstance(data, dict):
messages = data.get("messageData", [])
elif isinstance(data, list):
messages = data
# 过滤广告邮件
if filter_ads and messages:
messages = [
msg for msg in messages
if msg.get("messageID") not in AD_MESSAGE_IDS
]
return messages
except httpx.HTTPStatusError as e:
raise Exception(f"获取邮件列表失败 (HTTP {e.response.status_code}): {e.response.text}")
except Exception as e:
raise Exception(f"获取邮件列表失败: {str(e)}")
def get_message_content(self, message_id: str) -> Dict[str, Any]:
"""
获取邮件内容
Args:
message_id: 邮件 ID
Returns:
邮件内容
"""
self._ensure_token()
try:
response = self.client.post(
f"{self.BASE_URL}/message-list",
json={"email": self.email, "messageID": message_id},
headers={
"Content-Type": "application/json",
"X-Xsrf-Token": self.xsrf_token,
"Referer": f"{self.BASE_URL}/mailbox/",
}
)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
raise Exception(f"获取邮件内容失败 (HTTP {e.response.status_code}): {e.response.text}")
except Exception as e:
raise Exception(f"获取邮件内容失败: {str(e)}")
def close(self):
"""关闭客户端"""
self.client.close()
def __enter__(self):
"""上下文管理器入口"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""上下文管理器退出"""
self.close()
# 异步版本
class AsyncTempGmail:
"""异步临时 Gmail 邮箱管理类"""
BASE_URL = "https://www.emailnator.com"
def __init__(self):
"""初始化异步客户端"""
self.client = httpx.AsyncClient(
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"Origin": self.BASE_URL,
"Referer": f"{self.BASE_URL}/",
"Sec-Ch-Ua": '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"X-Requested-With": "XMLHttpRequest",
},
follow_redirects=True,
timeout=30.0
)
self.xsrf_token: Optional[str] = None
self.email: Optional[str] = None
async def _get_xsrf_token(self) -> str:
"""获取 XSRF Token"""
try:
# 访问主页获取 cookie 和 session
response = await self.client.get(self.BASE_URL)
response.raise_for_status()
# 从 cookie 中提取 XSRF-TOKEN(可能是 URL 编码的)
xsrf_cookie = self.client.cookies.get("XSRF-TOKEN")
if xsrf_cookie:
# URL 解码 token
from urllib.parse import unquote
self.xsrf_token = unquote(xsrf_cookie)
return self.xsrf_token
raise Exception("无法从 Cookie 中获取 XSRF Token")
except Exception as e:
raise Exception(f"获取 XSRF Token 失败: {str(e)}")
async def _ensure_token(self):
"""确保有有效的 token"""
if not self.xsrf_token:
await self._get_xsrf_token()
async def generate_email(self, email_types: Optional[List[str]] = None) -> str:
"""
生成临时邮箱地址
Args:
email_types: 邮箱类型列表,可选值: ["plusGmail", "dotGmail", "googleMail"]
默认为 ["plusGmail", "dotGmail"]
Returns:
生成的邮箱地址
"""
await self._ensure_token()
if email_types is None:
email_types = ["plusGmail", "dotGmail"]
try:
response = await self.client.post(
f"{self.BASE_URL}/generate-email",
json={"email": email_types},
headers={
"Content-Type": "application/json",
"X-Xsrf-Token": self.xsrf_token,
}
)
response.raise_for_status()
data = response.json()
if "email" in data:
self.email = data["email"][0] if isinstance(data["email"], list) else data["email"]
else:
self.email = list(data.values())[0] if data else None
if not self.email:
raise Exception(f"无法从响应中提取邮箱地址: {data}")
return self.email
except httpx.HTTPStatusError as e:
raise Exception(f"生成邮箱失败 (HTTP {e.response.status_code}): {e.response.text}")
except Exception as e:
raise Exception(f"生成邮箱失败: {str(e)}")
async def get_message_list(self, email: Optional[str] = None) -> List[Dict[str, Any]]:
"""
获取邮件列表
Args:
email: 邮箱地址,如果不提供则使用当前生成的邮箱
Returns:
邮件列表
"""
await self._ensure_token()
target_email = email or self.email
if not target_email:
raise Exception("请先生成邮箱或提供邮箱地址")
try:
response = await self.client.post(
f"{self.BASE_URL}/message-list",
json={"email": target_email},
headers={
"Content-Type": "application/json",
"X-Xsrf-Token": self.xsrf_token,
"Referer": f"{self.BASE_URL}/mailbox/",
}
)
response.raise_for_status()
data = response.json()
if isinstance(data, dict):
return data.get("messageData", [])
elif isinstance(data, list):
return data
else:
return []
except httpx.HTTPStatusError as e:
raise Exception(f"获取邮件列表失败 (HTTP {e.response.status_code}): {e.response.text}")
except Exception as e:
raise Exception(f"获取邮件列表失败: {str(e)}")
async def get_message_content(self, message_id: str) -> Dict[str, Any]:
"""
获取邮件内容
Args:
message_id: 邮件 ID
Returns:
邮件内容
"""
await self._ensure_token()
try:
response = await self.client.post(
f"{self.BASE_URL}/message-list",
json={"email": self.email, "messageID": message_id},
headers={
"Content-Type": "application/json",
"X-Xsrf-Token": self.xsrf_token,
"Referer": f"{self.BASE_URL}/mailbox/",
}
)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
raise Exception(f"获取邮件内容失败 (HTTP {e.response.status_code}): {e.response.text}")
except Exception as e:
raise Exception(f"获取邮件内容失败: {str(e)}")
async def close(self):
"""关闭客户端"""
await self.client.aclose()
async def __aenter__(self):
"""异步上下文管理器入口"""
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""异步上下文管理器退出"""
await self.close()