Files
inquiry_robot/inquiry-agent/agent/channel/wecom/parse.py
T
jillion886andCursor 99da1faf30 落地私聊空运询价与TMS报价回填,按后台模板出单。
查到价后把币种、单位、数量、单价、备注写入报价单,避免销售只看到光秃秃的金额。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 20:25:35 +08:00

98 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
企微 XML/字段解析 → InboundMessage。
本文件职责:从解密后的明文抽出 sender_id / message_id / content。
禁止:无 sender_id 时仍组装「可入业务」消息(调用方应丢弃);禁止再请求企微 userid。
"""
from __future__ import annotations
import xml.etree.ElementTree as ET
from typing import Mapping
from agent.channel.wecom.models import InboundMessage
def _text(root: ET.Element, tag: str) -> str:
node = root.find(tag)
if node is None or node.text is None:
return ""
return node.text.strip()
def parse_plain_xml(xml_text: str, *, default_agent_id: str = "") -> InboundMessage:
"""
解析企微回调明文 XML。
调用时机:验签解密成功后、入 inbox 前。
返回:InboundMessage;MsgType 非 text 时 content 可能为空,由 handler 决定是否忽略。
"""
root = ET.fromstring(xml_text)
msg_type = _text(root, "MsgType") or "unknown"
sender_id = _text(root, "FromUserName")
msg_id = _text(root, "MsgId") or _text(root, "MsgID")
create_time_raw = _text(root, "CreateTime")
create_time = int(create_time_raw) if create_time_raw.isdigit() else 0
agent_id = _text(root, "AgentID") or _text(root, "AgentId") or default_agent_id
content = _text(root, "Content") if msg_type == "text" else ""
# 事件类可带 Event Key;模板卡点击要把按钮键收成可入询价流程的正文
event = _text(root, "Event")
event_key = _text(root, "EventKey")
response_code = _text(root, "ResponseCode")
task_id = _text(root, "TaskId") or _text(root, "TaskID")
if not content and event.lower() in {"template_card_event", "click", "templatecardevent"}:
content = event_key
if not msg_id:
# 无 MsgId(如部分事件)时用发送人+时间拼幂等键,避免空键撞车
msg_id = f"{sender_id}:{create_time}:{msg_type}:{event}"
chat_type = "single"
chat_id = ""
# 企业微信群聊应用消息偶发带 ChatId;无则保持私聊语义
chat_id = _text(root, "ChatId") or _text(root, "ChatID")
if chat_id:
chat_type = "group"
return InboundMessage(
sender_id=sender_id,
message_id=msg_id,
content=content,
msg_type=msg_type,
chat_id=chat_id,
chat_type=chat_type,
agent_id=agent_id,
create_time=create_time,
media={},
raw={
"event": event,
"event_key": event_key,
"response_code": response_code,
"task_id": task_id,
"xml": xml_text,
},
)
def inbound_from_inject(payload: Mapping[str, object], *, default_agent_id: str = "") -> InboundMessage:
"""
本机联调注入:不经企微密文,直接构造 InboundMessage。
用途:并存期公网回调仍进现网时,用 POST /internal/dev/inject-text 测出站回复。
约束:必须带 sender_id(企微 userid),否则不得入队。
"""
sender_id = str(payload.get("sender_id") or "").strip()
content = str(payload.get("content") or "").strip()
message_id = str(payload.get("message_id") or "").strip()
if not message_id:
message_id = f"inject:{sender_id}:{int(payload.get('create_time') or 0)}:{hash(content) & 0xFFFFFFFF}"
return InboundMessage(
sender_id=sender_id,
message_id=message_id,
content=content,
msg_type="text",
chat_type="single",
agent_id=str(payload.get("agent_id") or default_agent_id),
create_time=int(payload.get("create_time") or 0),
raw={"source": "dev_inject"},
)