ForcePilot/backend/package/yuxi/channels/adapters/feishu/card_action.py
Kris a6fa7245e5 feat(feishu): 完整实现飞书适配器核心模块
新增飞书机器人适配器全套功能,包括:
- 基础适配器入口与工具导出
- 消息格式化、卡片渲染、回复调度逻辑
- 会话ID生成、模型覆盖策略
- 消息发送缓存、顺序队列管理
- 飞书签名验证、加解密webhook请求
- 审批权限校验、机器人菜单事件处理
- 文档评论、钉消息、语音转码处理
- 静态/动态目录管理、子代理生命周期管理
- 各类工具集:聊天、云盘、文档、知识库API封装
2026-05-12 00:43:59 +08:00

74 lines
2.1 KiB
Python

from __future__ import annotations
import json
import time
from dataclasses import dataclass, field
from yuxi.utils.logging_config import logger
@dataclass
class CardActionEnvelope:
"""ocf1 交互信封"""
oc: str = "ocf1"
k: str = ""
a: str = ""
q: str = ""
m: dict = field(default_factory=dict)
c: dict | None = None
def decode_card_action(value: str) -> tuple[str, CardActionEnvelope | None]:
try:
data = json.loads(value) if isinstance(value, str) else value
if not isinstance(data, dict) or data.get("oc") != "ocf1":
return ("legacy", None)
envelope = CardActionEnvelope(
oc=data.get("oc", "ocf1"),
k=data.get("k", ""),
a=data.get("a", ""),
q=data.get("q", ""),
m=data.get("m", {}),
c=data.get("c"),
)
return ("structured", envelope)
except (json.JSONDecodeError, TypeError) as e:
logger.debug(f"[CardAction] Decode error: {e}")
return ("invalid:malformed", None)
def validate_card_context(
envelope: CardActionEnvelope,
open_id: str,
chat_id: str,
chat_type: str,
max_age_s: float = 300,
) -> bool:
if envelope.c is None:
return True
ctx_open_id = envelope.c.get("u", "")
ctx_chat_id = envelope.c.get("h", "")
ctx_chat_type = envelope.c.get("t", "")
created_at = envelope.c.get("e", 0)
if ctx_open_id and ctx_open_id != open_id:
logger.debug(f"[CardAction] Context user mismatch: {ctx_open_id} != {open_id}")
return False
if ctx_chat_id and ctx_chat_id != chat_id:
logger.debug(f"[CardAction] Context chat mismatch: {ctx_chat_id} != {chat_id}")
return False
if ctx_chat_type and ctx_chat_type != chat_type:
logger.debug(f"[CardAction] Context type mismatch: {ctx_chat_type} != {chat_type}")
return False
if created_at and (time.time() - created_at) > max_age_s:
logger.debug(f"[CardAction] Context expired: age={time.time() - created_at:.0f}s")
return False
return True