查到价后把币种、单位、数量、单价、备注写入报价单,避免销售只看到光秃秃的金额。 Co-authored-by: Cursor <cursoragent@cursor.com>
98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
"""
|
||
企微 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"},
|
||
)
|