新增 iMessage 通道适配器完整实现,包含: 1. 核心适配器与工具工厂导出 2. 运行时存储、反射防护、会话路由等基础组件 3. 消息信封、线程管理、回复上下文格式化 4. Tapback 表情反应处理、自定义异常体系 5. 审批按钮、联系人解析、速率限制功能 6. 文本净化、目标解析、缓存管理模块 7. 配置 schema、多账户支持、安装向导等配置模块 8. 审计日志、媒体AI处理等扩展功能
101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
from enum import StrEnum
|
|
from typing import Any
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
class ApprovalStatus(StrEnum):
|
|
PENDING = "pending"
|
|
APPROVED = "approved"
|
|
REJECTED = "rejected"
|
|
EXPIRED = "expired"
|
|
|
|
|
|
@dataclass
|
|
class ApprovalRequest:
|
|
request_id: str
|
|
channel_chat_id: str
|
|
channel_user_id: str
|
|
action: str
|
|
action_payload: dict[str, Any] = field(default_factory=dict)
|
|
status: ApprovalStatus = ApprovalStatus.PENDING
|
|
created_at: float = field(default_factory=time.time)
|
|
expires_at: float = field(default_factory=lambda: time.time() + 300)
|
|
approved_by: str = ""
|
|
result: Any = None
|
|
|
|
|
|
class ExecApprovalManager:
|
|
def __init__(self, approval_ttl_s: float = 300.0, max_pending: int = 10):
|
|
self._pending: dict[str, ApprovalRequest] = {}
|
|
self._history: list[ApprovalRequest] = []
|
|
self._approval_ttl = approval_ttl_s
|
|
self._max_pending = max_pending
|
|
|
|
def request_approval(
|
|
self,
|
|
request_id: str,
|
|
channel_chat_id: str,
|
|
channel_user_id: str,
|
|
action: str,
|
|
action_payload: dict[str, Any] | None = None,
|
|
) -> ApprovalRequest:
|
|
self._cleanup()
|
|
|
|
if len(self._pending) >= self._max_pending:
|
|
oldest = min(self._pending.values(), key=lambda r: r.created_at)
|
|
self._pending.pop(oldest.request_id, None)
|
|
|
|
req = ApprovalRequest(
|
|
request_id=request_id,
|
|
channel_chat_id=channel_chat_id,
|
|
channel_user_id=channel_user_id,
|
|
action=action,
|
|
action_payload=action_payload or {},
|
|
)
|
|
self._pending[request_id] = req
|
|
logger.info(f"[iMessage/Approval] New request: {request_id} action={action}")
|
|
return req
|
|
|
|
def approve(self, request_id: str, approved_by: str = "") -> ApprovalRequest | None:
|
|
self._cleanup()
|
|
req = self._pending.get(request_id)
|
|
if req is None:
|
|
return None
|
|
req.status = ApprovalStatus.APPROVED
|
|
req.approved_by = approved_by
|
|
self._pending.pop(request_id, None)
|
|
self._history.append(req)
|
|
logger.info(f"[iMessage/Approval] Approved: {request_id}")
|
|
return req
|
|
|
|
def reject(self, request_id: str, reason: str = "") -> ApprovalRequest | None:
|
|
self._cleanup()
|
|
req = self._pending.get(request_id)
|
|
if req is None:
|
|
return None
|
|
req.status = ApprovalStatus.REJECTED
|
|
self._pending.pop(request_id, None)
|
|
self._history.append(req)
|
|
logger.info(f"[iMessage/Approval] Rejected: {request_id} ({reason})")
|
|
return req
|
|
|
|
def get(self, request_id: str) -> ApprovalRequest | None:
|
|
return self._pending.get(request_id)
|
|
|
|
def list_pending(self) -> list[ApprovalRequest]:
|
|
self._cleanup()
|
|
return list(self._pending.values())
|
|
|
|
def _cleanup(self) -> None:
|
|
now = time.time()
|
|
expired = [rid for rid, req in self._pending.items() if req.expires_at < now]
|
|
for rid in expired:
|
|
req = self._pending.pop(rid)
|
|
req.status = ApprovalStatus.EXPIRED
|
|
self._history.append(req)
|