ForcePilot/backend/package/yuxi/channel/extensions/email_smtp/oauth2.py
Kris 59c6caaa64 feat(email-smtp): 新增SMTP/IMAP邮件渠道插件
实现完整的邮件收发渠道,支持IMAP IDLE实时收信、SMTP发信,包含附件校验、重复消息去重、OAuth2认证、邮件内容解析与引用剥离、邮件发送限流与连接池等功能
2026-05-21 10:45:56 +08:00

62 lines
2.1 KiB
Python

from __future__ import annotations
import time
from dataclasses import dataclass
import aiohttp
@dataclass
class OAuth2Token:
access_token: str
refresh_token: str
expires_at: float
token_type: str = "Bearer"
@property
def is_expired(self) -> bool:
return time.time() > self.expires_at - 60
class EmailOAuth2Manager:
MICROSOFT_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
def __init__(self, provider: str, client_id: str, client_secret: str):
self._provider = provider
self._client_id = client_id
self._client_secret = client_secret
self._token: OAuth2Token | None = None
def _token_url(self) -> str:
if self._provider in ("outlook", "office365", "hotmail", "live"):
return self.MICROSOFT_TOKEN_URL
return self.GOOGLE_TOKEN_URL
async def get_access_token(self, refresh_token: str) -> str:
if self._token and not self._token.is_expired:
return self._token.access_token
data = {
"client_id": self._client_id,
"client_secret": self._client_secret,
"refresh_token": refresh_token,
"grant_type": "refresh_token",
}
async with aiohttp.ClientSession() as session:
async with session.post(
self._token_url(), data=data, timeout=aiohttp.ClientTimeout(total=15)
) as resp:
if resp.status != 200:
raise RuntimeError(f"OAuth2 token refresh failed: {await resp.text()}")
token_data = await resp.json()
self._token = OAuth2Token(
access_token=token_data["access_token"],
refresh_token=token_data.get("refresh_token", refresh_token),
expires_at=time.time() + token_data.get("expires_in", 3600),
)
return self._token.access_token
def build_xoauth2_string(self, username: str, access_token: str) -> str:
return f"user={username}\x01auth=Bearer {access_token}\x01\x01"