feat: codex-register with Sub2API增强 + Playwright引擎
Some checks are pending
Docker Image CI / build-and-push-image (push) Waiting to run
Some checks are pending
Docker Image CI / build-and-push-image (push) Waiting to run
This commit is contained in:
153
src/services/outlook/base.py
Normal file
153
src/services/outlook/base.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""
|
||||
Outlook 服务基础定义
|
||||
包含枚举类型和数据类
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
|
||||
class ProviderType(str, Enum):
|
||||
"""Outlook 提供者类型"""
|
||||
IMAP_OLD = "imap_old" # 旧版 IMAP (outlook.office365.com)
|
||||
IMAP_NEW = "imap_new" # 新版 IMAP (outlook.live.com)
|
||||
GRAPH_API = "graph_api" # Microsoft Graph API
|
||||
|
||||
|
||||
class TokenEndpoint(str, Enum):
|
||||
"""Token 端点"""
|
||||
LIVE = "https://login.live.com/oauth20_token.srf"
|
||||
CONSUMERS = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
|
||||
COMMON = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
|
||||
|
||||
|
||||
class IMAPServer(str, Enum):
|
||||
"""IMAP 服务器"""
|
||||
OLD = "outlook.office365.com"
|
||||
NEW = "outlook.live.com"
|
||||
|
||||
|
||||
class ProviderStatus(str, Enum):
|
||||
"""提供者状态"""
|
||||
HEALTHY = "healthy" # 健康
|
||||
DEGRADED = "degraded" # 降级
|
||||
DISABLED = "disabled" # 禁用
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmailMessage:
|
||||
"""邮件消息数据类"""
|
||||
id: str # 消息 ID
|
||||
subject: str # 主题
|
||||
sender: str # 发件人
|
||||
recipients: List[str] = field(default_factory=list) # 收件人列表
|
||||
body: str = "" # 正文内容
|
||||
body_preview: str = "" # 正文预览
|
||||
received_at: Optional[datetime] = None # 接收时间
|
||||
received_timestamp: int = 0 # 接收时间戳
|
||||
is_read: bool = False # 是否已读
|
||||
has_attachments: bool = False # 是否有附件
|
||||
raw_data: Optional[bytes] = None # 原始数据(用于调试)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"subject": self.subject,
|
||||
"sender": self.sender,
|
||||
"recipients": self.recipients,
|
||||
"body": self.body,
|
||||
"body_preview": self.body_preview,
|
||||
"received_at": self.received_at.isoformat() if self.received_at else None,
|
||||
"received_timestamp": self.received_timestamp,
|
||||
"is_read": self.is_read,
|
||||
"has_attachments": self.has_attachments,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class TokenInfo:
|
||||
"""Token 信息数据类"""
|
||||
access_token: str
|
||||
expires_at: float # 过期时间戳
|
||||
token_type: str = "Bearer"
|
||||
scope: str = ""
|
||||
refresh_token: Optional[str] = None
|
||||
|
||||
def is_expired(self, buffer_seconds: int = 120) -> bool:
|
||||
"""检查 Token 是否已过期"""
|
||||
import time
|
||||
return time.time() >= (self.expires_at - buffer_seconds)
|
||||
|
||||
@classmethod
|
||||
def from_response(cls, data: Dict[str, Any], scope: str = "") -> "TokenInfo":
|
||||
"""从 API 响应创建"""
|
||||
import time
|
||||
return cls(
|
||||
access_token=data.get("access_token", ""),
|
||||
expires_at=time.time() + data.get("expires_in", 3600),
|
||||
token_type=data.get("token_type", "Bearer"),
|
||||
scope=scope or data.get("scope", ""),
|
||||
refresh_token=data.get("refresh_token"),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProviderHealth:
|
||||
"""提供者健康状态"""
|
||||
provider_type: ProviderType
|
||||
status: ProviderStatus = ProviderStatus.HEALTHY
|
||||
failure_count: int = 0 # 连续失败次数
|
||||
last_success: Optional[datetime] = None # 最后成功时间
|
||||
last_failure: Optional[datetime] = None # 最后失败时间
|
||||
last_error: str = "" # 最后错误信息
|
||||
disabled_until: Optional[datetime] = None # 禁用截止时间
|
||||
|
||||
def record_success(self):
|
||||
"""记录成功"""
|
||||
self.status = ProviderStatus.HEALTHY
|
||||
self.failure_count = 0
|
||||
self.last_success = datetime.now()
|
||||
self.disabled_until = None
|
||||
|
||||
def record_failure(self, error: str):
|
||||
"""记录失败"""
|
||||
self.failure_count += 1
|
||||
self.last_failure = datetime.now()
|
||||
self.last_error = error
|
||||
|
||||
def should_disable(self, threshold: int = 3) -> bool:
|
||||
"""判断是否应该禁用"""
|
||||
return self.failure_count >= threshold
|
||||
|
||||
def is_disabled(self) -> bool:
|
||||
"""检查是否被禁用"""
|
||||
if self.disabled_until and datetime.now() < self.disabled_until:
|
||||
return True
|
||||
return False
|
||||
|
||||
def disable(self, duration_seconds: int = 300):
|
||||
"""禁用提供者"""
|
||||
from datetime import timedelta
|
||||
self.status = ProviderStatus.DISABLED
|
||||
self.disabled_until = datetime.now() + timedelta(seconds=duration_seconds)
|
||||
|
||||
def enable(self):
|
||||
"""启用提供者"""
|
||||
self.status = ProviderStatus.HEALTHY
|
||||
self.disabled_until = None
|
||||
self.failure_count = 0
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"provider_type": self.provider_type.value,
|
||||
"status": self.status.value,
|
||||
"failure_count": self.failure_count,
|
||||
"last_success": self.last_success.isoformat() if self.last_success else None,
|
||||
"last_failure": self.last_failure.isoformat() if self.last_failure else None,
|
||||
"last_error": self.last_error,
|
||||
"disabled_until": self.disabled_until.isoformat() if self.disabled_until else None,
|
||||
}
|
||||
Reference in New Issue
Block a user