ForcePilot/backend/package/yuxi/channels/adapters/zalo_oa/pairing.py
Kris 30dd9e16f4 feat(zalo-oa): 实现完整的 Zalo OA 渠道适配器模块
新增 Zalo OA 官方账号完整集成能力,包含:
1. 基础通信能力:消息编解码、目标归一化、文本分块
2. 安全与校验:Webhook 签名验证、DM 策略管理、配对流程
3. 辅助工具:重复事件去重、请求限流、异常告警
4. 管理功能:账号多实例管理、配置验证、健康诊断
5. 扩展能力:媒体托管、视觉识别、TTS 语音合成
6. 运维支持:审计日志、状态监控、目录同步
2026-05-12 00:52:47 +08:00

109 lines
3.1 KiB
Python

from __future__ import annotations
import secrets
import time
from typing import Any
from yuxi.utils.logging_config import logger
PAIRING_CODE_BYTES = 4
PAIRING_CODE_TTL_SEC = 600
class PairingStore:
def __init__(self):
self._codes: dict[str, dict[str, Any]] = {}
def generate_code(self, user_id: str) -> str:
code = secrets.token_hex(PAIRING_CODE_BYTES).upper()[:8]
self._codes[code] = {
"user_id": user_id,
"created_at": time.time(),
"used": False,
}
return code
def verify_code(self, code: str, user_id: str) -> bool:
entry = self._codes.get(code)
if not entry:
return False
if time.time() - entry["created_at"] > PAIRING_CODE_TTL_SEC:
del self._codes[code]
return False
if entry["used"] or entry["user_id"] != user_id:
return False
entry["used"] = True
return True
def cleanup_expired(self):
now = time.time()
expired = [k for k, v in self._codes.items() if now - v["created_at"] > PAIRING_CODE_TTL_SEC]
for k in expired:
del self._codes[k]
def build_pairing_message(code: str, oa_name: str = "") -> str:
lines = [
"Welcome! This Zalo OA requires pairing to chat.",
f"Your pairing code: {code}",
"Reply with this code to complete pairing.",
f"This code expires in {PAIRING_CODE_TTL_SEC // 60} minutes.",
]
if oa_name:
lines.insert(0, f"Hello from {oa_name}!")
return "\n".join(lines)
def build_pairing_notification(user_id: str, oa_name: str) -> str:
return (
f"[Pairing] New pairing request from user {user_id}"
+ (f" to {oa_name}" if oa_name else "")
+ ". Please approve."
)
def build_pairing_success_message(oa_name: str = "") -> str:
return "Pairing successful! You can now chat with " + (oa_name or "this OA") + "."
async def send_pairing_message(
client: Any,
user_id: str,
code: str,
oa_name: str = "",
) -> bool:
message = build_pairing_message(code, oa_name)
try:
result = await client.send_message(
{
"recipient": {"user_id": user_id},
"message": {"text": message},
}
)
msg_id = result.get("message_id", "") if isinstance(result, dict) else ""
logger.info(f"[ZaloOA] Pairing code sent to {user_id}, msg_id={msg_id}")
return True
except Exception as e:
logger.error(f"[ZaloOA] Failed to send pairing code to {user_id}: {e}")
return False
async def send_pairing_success(
client: Any,
user_id: str,
oa_name: str = "",
) -> bool:
message = build_pairing_success_message(oa_name)
try:
await client.send_message(
{
"recipient": {"user_id": user_id},
"message": {"text": message},
}
)
logger.info(f"[ZaloOA] Pairing success sent to {user_id}")
return True
except Exception as e:
logger.error(f"[ZaloOA] Failed to send pairing success to {user_id}: {e}")
return False