mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-06-28 10:41:36 +08:00
feat(email): 新增 Outlook 收件箱功能
- 后端:GET /api/email-services/{id}/inbox 端点,通过 IMAPNewProvider 读取收件箱
- 前端:收件箱列表模态框(支持仅未读筛选、刷新)
- 前端:邮件正文弹窗
- Outlook 操作列新增「收件箱」按钮
This commit is contained in:
@@ -67,7 +67,7 @@ class ServiceTestResult(BaseModel):
|
||||
|
||||
class OutlookBatchImportRequest(BaseModel):
|
||||
"""Outlook 批量导入请求"""
|
||||
data: str # 多行数据,每行格式: 邮箱----密码 或 邮箱----密码----client_id----refresh_token
|
||||
data: str # 多行数据,每行格式: 邮箱----密码----client_id----refresh_token
|
||||
enabled: bool = True
|
||||
priority: int = 0
|
||||
|
||||
@@ -461,11 +461,8 @@ async def batch_import_outlook(request: OutlookBatchImportRequest):
|
||||
"""
|
||||
批量导入 Outlook 邮箱账户
|
||||
|
||||
支持两种格式:
|
||||
- 格式一(密码认证):邮箱----密码
|
||||
- 格式二(XOAUTH2 认证):邮箱----密码----client_id----refresh_token
|
||||
|
||||
每行一个账户,使用四个连字符(----)分隔字段
|
||||
格式(每行):邮箱----密码----client_id----refresh_token
|
||||
使用四个连字符(----)分隔字段
|
||||
"""
|
||||
lines = request.data.strip().split("\n")
|
||||
total = len(lines)
|
||||
@@ -484,14 +481,18 @@ async def batch_import_outlook(request: OutlookBatchImportRequest):
|
||||
|
||||
parts = line.split("----")
|
||||
|
||||
# 验证格式
|
||||
if len(parts) < 2:
|
||||
# 必须是四字段格式
|
||||
if len(parts) < 4:
|
||||
failed += 1
|
||||
errors.append(f"行 {i+1}: 格式错误,至少需要邮箱和密码")
|
||||
errors.append(
|
||||
f"行 {i+1}: 格式错误,必须为 邮箱----密码----client_id----refresh_token"
|
||||
)
|
||||
continue
|
||||
|
||||
email = parts[0].strip()
|
||||
password = parts[1].strip()
|
||||
client_id = parts[2].strip()
|
||||
refresh_token = parts[3].strip()
|
||||
|
||||
# 验证邮箱格式
|
||||
if "@" not in email:
|
||||
@@ -499,6 +500,12 @@ async def batch_import_outlook(request: OutlookBatchImportRequest):
|
||||
errors.append(f"行 {i+1}: 无效的邮箱地址: {email}")
|
||||
continue
|
||||
|
||||
# 验证 OAuth 字段非空
|
||||
if not client_id or not refresh_token:
|
||||
failed += 1
|
||||
errors.append(f"行 {i+1}: [{email}] client_id 或 refresh_token 不能为空")
|
||||
continue
|
||||
|
||||
# 检查是否已存在
|
||||
existing = db.query(EmailServiceModel).filter(
|
||||
EmailServiceModel.service_type == "outlook",
|
||||
@@ -513,17 +520,11 @@ async def batch_import_outlook(request: OutlookBatchImportRequest):
|
||||
# 构建配置
|
||||
config = {
|
||||
"email": email,
|
||||
"password": password
|
||||
"password": password,
|
||||
"client_id": client_id,
|
||||
"refresh_token": refresh_token,
|
||||
}
|
||||
|
||||
# 检查是否有 OAuth 信息(格式二)
|
||||
if len(parts) >= 4:
|
||||
client_id = parts[2].strip()
|
||||
refresh_token = parts[3].strip()
|
||||
if client_id and refresh_token:
|
||||
config["client_id"] = client_id
|
||||
config["refresh_token"] = refresh_token
|
||||
|
||||
# 创建服务记录
|
||||
try:
|
||||
service = EmailServiceModel(
|
||||
@@ -608,3 +609,86 @@ async def test_tempmail_service(request: TempmailTestRequest):
|
||||
except Exception as e:
|
||||
logger.error(f"测试临时邮箱失败: {e}")
|
||||
return {"success": False, "message": f"测试失败: {str(e)}"}
|
||||
|
||||
|
||||
# ============== 收件箱 ==============
|
||||
|
||||
@router.get("/{service_id}/inbox")
|
||||
async def get_outlook_inbox(
|
||||
service_id: int,
|
||||
count: int = Query(30, ge=1, le=100),
|
||||
only_unseen: bool = Query(False),
|
||||
):
|
||||
"""获取 Outlook 收件箱邮件列表"""
|
||||
with get_db() as db:
|
||||
service = db.query(EmailServiceModel).filter(EmailServiceModel.id == service_id).first()
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="服务不存在")
|
||||
if service.service_type != "outlook":
|
||||
raise HTTPException(status_code=400, detail="仅支持 Outlook 类型服务")
|
||||
|
||||
config = service.config or {}
|
||||
email_addr = config.get("email", "")
|
||||
client_id = config.get("client_id", "")
|
||||
refresh_token = config.get("refresh_token", "")
|
||||
|
||||
# client_id 为空时尝试使用全局默认值
|
||||
if not client_id:
|
||||
from ...config.settings import get_settings
|
||||
client_id = get_settings().outlook_default_client_id or ""
|
||||
|
||||
if not client_id or not refresh_token:
|
||||
raise HTTPException(status_code=400, detail="该账户缺少 OAuth 配置(client_id / refresh_token),无法读取收件箱")
|
||||
|
||||
try:
|
||||
from ...services.outlook.account import OutlookAccount
|
||||
from ...services.outlook.token_manager import TokenManager
|
||||
from ...services.outlook.providers.imap_new import IMAPNewProvider
|
||||
from ...services.outlook.providers.base import ProviderConfig
|
||||
|
||||
account = OutlookAccount(
|
||||
email=email_addr,
|
||||
password=config.get("password", ""),
|
||||
client_id=client_id,
|
||||
refresh_token=refresh_token,
|
||||
)
|
||||
provider_config = ProviderConfig(
|
||||
proxy_url=None,
|
||||
timeout=30,
|
||||
service_id=service_id,
|
||||
)
|
||||
provider = IMAPNewProvider(account, provider_config)
|
||||
|
||||
connected = provider.connect()
|
||||
if not connected:
|
||||
raise HTTPException(status_code=502, detail="IMAP 连接失败,请检查 OAuth 配置")
|
||||
|
||||
try:
|
||||
messages = provider.get_recent_emails(count=count, only_unseen=only_unseen)
|
||||
finally:
|
||||
provider.disconnect()
|
||||
|
||||
emails = []
|
||||
for m in messages:
|
||||
received_str = m.received_at.isoformat() if m.received_at else None
|
||||
emails.append({
|
||||
"id": m.id or "",
|
||||
"subject": m.subject or "",
|
||||
"sender": m.sender or "",
|
||||
"received_at": received_str,
|
||||
"body_preview": m.body_preview or (m.body or "")[:200],
|
||||
"body": m.body or "",
|
||||
"is_read": m.is_read,
|
||||
})
|
||||
|
||||
return {
|
||||
"email": email_addr,
|
||||
"total": len(emails),
|
||||
"emails": emails,
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"获取收件箱失败 service_id={service_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"获取收件箱失败: {str(e)}")
|
||||
|
||||
Reference in New Issue
Block a user