ForcePilot/backend/package/yuxi/channels/adapters/wechat/selector.py
Kris 05abecc02b feat(wechat): 新增完整微信渠道适配器实现
该提交实现了支持企业微信、微信公众号、个人微信桥接三种模式的完整微信渠道适配器,包含以下核心模块:
1. 基础认证与配置相关:auth_adapter、config_reload、setup_contract等
2. 消息处理与格式转换:format、attachment_adapter、outbound_adapter等
3. 多模式客户端支持:wecom/mp子模块,包含加解密、消息收发能力
4. 辅助能力:限速器、防抖、会话绑定、事件映射、模板渲染等
5. 扩展能力:二维码登录、消息读取、特权用户、心跳监控等

实现了完整的微信生态对接能力,支持消息收发、事件处理、API调用限流、配置热重载等功能。
2026-05-12 00:51:04 +08:00

162 lines
5.0 KiB
Python

from __future__ import annotations
import time
from dataclasses import dataclass
from typing import Any, Literal
@dataclass
class SelectorOption:
id: str
text: str
is_default: bool = False
@dataclass
class SelectorItem:
question_key: str
title: str = ""
options: list[SelectorOption] | None = None
type: Literal["single", "multi"] = "single"
disable: bool = False
@dataclass
class SelectorCard:
task_id: str
title: str = "请选择"
description: str = ""
items: list[SelectorItem] | None = None
created_at: float = 0.0
expires_at: float = 0.0
class WeChatSelectorAdapter:
def __init__(self, default_ttl_hours: int = 24):
self._default_ttl_hours = default_ttl_hours
self._active_selectors: dict[str, SelectorCard] = {}
@staticmethod
def build_selector(
task_id: str,
title: str,
items: list[SelectorItem],
description: str = "",
ttl_hours: int = 24,
) -> SelectorCard:
now = time.time()
return SelectorCard(
task_id=task_id,
title=title,
description=description,
items=items,
created_at=now,
expires_at=now + ttl_hours * 3600,
)
@staticmethod
def build_instance_option(id: str, text: str, is_default: bool = False) -> SelectorOption:
return SelectorOption(id=id, text=text, is_default=is_default)
@staticmethod
def build_instance_item(
question_key: str,
title: str,
options: list[SelectorOption],
item_type: Literal["single", "multi"] = "single",
) -> SelectorItem:
return SelectorItem(
question_key=question_key,
title=title,
options=options,
type=item_type,
)
def create_selector(self, card: SelectorCard) -> str:
self._active_selectors[card.task_id] = card
return card.task_id
def get_selector(self, task_id: str) -> SelectorCard | None:
card = self._active_selectors.get(task_id)
if card and card.expires_at < time.time():
self._active_selectors.pop(task_id, None)
return None
return card
def is_expired(self, task_id: str) -> bool:
card = self._active_selectors.get(task_id)
if card is None:
return True
if card.expires_at < time.time():
self._active_selectors.pop(task_id, None)
return True
return False
def resolve_selections(self, task_id: str, selections: dict[str, list[str]]) -> dict[str, Any] | None:
card = self.get_selector(task_id)
if card is None or not card.items:
return None
resolved: dict[str, Any] = {"task_id": task_id, "answers": {}}
for item in card.items:
key = item.question_key
if key not in selections:
continue
picked = selections[key]
if item.options:
valid_ids = {opt.id for opt in item.options}
filtered = [v for v in picked if v in valid_ids]
resolved["answers"][key] = filtered if item.type == "multi" else (filtered[0] if filtered else "")
return resolved
def render_taskcard_payload(self, to_user: str, agent_id: str, card: SelectorCard) -> dict[str, Any]:
select_list: list[dict[str, Any]] = []
if card.items:
for item in card.items:
option_list: list[dict[str, Any]] = []
if item.options:
option_list = [
{"id": opt.id, "text": opt.text, "is_default": opt.is_default} for opt in item.options
]
select_list.append(
{
"question_key": item.question_key,
"title": item.title,
"type": item.type,
"disable": item.disable,
"option_list": option_list,
}
)
return {
"touser": to_user,
"agentid": agent_id,
"msgtype": "template_card",
"template_card": {
"card_type": "button_interaction",
"main_title": {"title": card.title, "desc": card.description},
"task_id": card.task_id,
"select_list": select_list,
"submit_button": {
"text": "确认",
"key": f"submit_{card.task_id}",
},
},
}
def list_active_selectors(self) -> list[dict[str, Any]]:
now = time.time()
expired = [tid for tid, c in self._active_selectors.items() if c.expires_at < now]
for tid in expired:
self._active_selectors.pop(tid, None)
return [
{
"task_id": c.task_id,
"title": c.title,
"expires_in": max(0, int(c.expires_at - now)),
}
for c in self._active_selectors.values()
]