/** * WeCom XML 解析器 * 用于 Agent 模式解析 XML 格式消息 */ import { XMLParser } from "fast-xml-parser"; import type { WecomAgentInboundMessage } from "../types/index.js"; const xmlParser = new XMLParser({ ignoreAttributes: false, trimValues: true, processEntities: false, parseTagValue: false, parseAttributeValue: false, }); /** * 解析 XML 字符串为消息对象 */ export function parseXml(xml: string): WecomAgentInboundMessage { const obj = xmlParser.parse(xml); const root = obj?.xml ?? obj; return root ?? {}; } /** * 从 XML 中提取消息类型 */ export function extractMsgType(msg: WecomAgentInboundMessage): string { return String(msg.MsgType ?? "").toLowerCase(); } /** * 从 XML 中提取发送者 ID */ export function extractFromUser(msg: WecomAgentInboundMessage): string { return String(msg.FromUserName ?? ""); } /** * 从 XML 中提取文件名(主要用于 file 消息) */ export function extractFileName(msg: WecomAgentInboundMessage): string | undefined { const raw = (msg as any).FileName ?? (msg as any).Filename ?? (msg as any).fileName ?? (msg as any).filename; if (raw == null) return undefined; if (typeof raw === "string") return raw.trim() || undefined; if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint") return String(raw); if (Array.isArray(raw)) { const merged = raw.map((v) => (v == null ? "" : String(v))).join("\n").trim(); return merged || undefined; } if (typeof raw === "object") { const obj = raw as Record; const text = (typeof obj["#text"] === "string" ? obj["#text"] : typeof obj["_text"] === "string" ? obj["_text"] : typeof obj["text"] === "string" ? obj["text"] : undefined); if (text && text.trim()) return text.trim(); } const s = String(raw); return s.trim() || undefined; } /** * 从 XML 中提取接收者 ID (CorpID) */ export function extractToUser(msg: WecomAgentInboundMessage): string { return String(msg.ToUserName ?? ""); } /** * 从 XML 中提取群聊 ID */ export function extractChatId(msg: WecomAgentInboundMessage): string | undefined { return msg.ChatId ? String(msg.ChatId) : undefined; } /** * 从 XML 中提取 AgentID(兼容 AgentID/agentid 等大小写) */ export function extractAgentId(msg: WecomAgentInboundMessage): string | number | undefined { const raw = (msg as any).AgentID ?? (msg as any).AgentId ?? (msg as any).agentid ?? (msg as any).agentId; if (raw == null) return undefined; if (typeof raw === "string") return raw.trim() || undefined; if (typeof raw === "number") return raw; const asString = String(raw).trim(); return asString || undefined; } /** * 从 XML 中提取消息内容 */ export function extractContent(msg: WecomAgentInboundMessage): string { const msgType = extractMsgType(msg); const asText = (value: unknown): string => { if (value == null) return ""; if (typeof value === "string") return value; if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value); if (Array.isArray(value)) return value.map(asText).filter(Boolean).join("\n"); if (typeof value === "object") { const obj = value as Record; // fast-xml-parser 在某些情况下(例如带属性)会把文本放在 "#text" if (typeof obj["#text"] === "string") return obj["#text"]; if (typeof obj["_text"] === "string") return obj["_text"]; if (typeof obj["text"] === "string") return obj["text"]; try { return JSON.stringify(obj); } catch { return String(value); } } return String(value); }; switch (msgType) { case "text": return asText(msg.Content); case "voice": // 语音识别结果 return asText(msg.Recognition) || "[语音消息]"; case "image": return `[图片] ${asText(msg.PicUrl)}`; case "file": return "[文件消息]"; case "video": return "[视频消息]"; case "location": return `[位置] ${asText(msg.Label)} (${asText(msg.Location_X)}, ${asText(msg.Location_Y)})`; case "link": return `[链接] ${asText(msg.Title)}\n${asText(msg.Description)}\n${asText(msg.Url)}`; case "event": return `[事件] ${asText(msg.Event)} - ${asText(msg.EventKey)}`; default: return `[${msgType || "未知消息类型"}]`; } } /** * 从 XML 中提取媒体 ID (Image, Voice, Video) * 根据官方文档,MediaId 在 Agent 回调中直接位于根节点 */ export function extractMediaId(msg: WecomAgentInboundMessage): string | undefined { const raw = (msg as any).MediaId ?? (msg as any).MediaID ?? (msg as any).mediaid ?? (msg as any).mediaId; if (raw == null) return undefined; if (typeof raw === "string") return raw.trim() || undefined; if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint") return String(raw); if (Array.isArray(raw)) { const merged = raw.map((v) => (v == null ? "" : String(v))).join("\n").trim(); return merged || undefined; } if (typeof raw === "object") { const obj = raw as Record; const text = (typeof obj["#text"] === "string" ? obj["#text"] : typeof obj["_text"] === "string" ? obj["_text"] : typeof obj["text"] === "string" ? obj["text"] : undefined); if (text && text.trim()) return text.trim(); try { const s = JSON.stringify(obj); return s.trim() || undefined; } catch { const s = String(raw); return s.trim() || undefined; } } const s = String(raw); return s.trim() || undefined; } /** * 从 XML 中提取 MsgId(用于去重) */ export function extractMsgId(msg: WecomAgentInboundMessage): string | undefined { const raw = (msg as any).MsgId ?? (msg as any).MsgID ?? (msg as any).msgid ?? (msg as any).msgId; if (raw == null) return undefined; if (typeof raw === "string") return raw.trim() || undefined; if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint") return String(raw); if (Array.isArray(raw)) { const merged = raw.map((v) => (v == null ? "" : String(v))).join("\n").trim(); return merged || undefined; } if (typeof raw === "object") { const obj = raw as Record; const text = (typeof obj["#text"] === "string" ? obj["#text"] : typeof obj["_text"] === "string" ? obj["_text"] : typeof obj["text"] === "string" ? obj["text"] : undefined); if (text && text.trim()) return text.trim(); try { const s = JSON.stringify(obj); return s.trim() || undefined; } catch { const s = String(raw); return s.trim() || undefined; } } const s = String(raw); return s.trim() || undefined; }