@wecom/wecom-openclaw-plugin 2026.4.2 → 2026.4.3-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +395 -10
- package/dist/index.d.ts +2 -4
- package/dist/index.js +58 -0
- package/dist/src/accounts.d.ts +57 -0
- package/dist/src/accounts.js +222 -0
- package/dist/src/agent/api-client.d.ts +94 -0
- package/dist/src/agent/api-client.js +292 -0
- package/dist/src/agent/handler.d.ts +63 -0
- package/dist/src/agent/handler.js +551 -0
- package/dist/src/agent/index.d.ts +5 -0
- package/dist/src/agent/index.js +5 -0
- package/dist/src/agent/webhook.d.ts +24 -0
- package/dist/src/agent/webhook.js +228 -0
- package/dist/src/agent/xml.d.ts +21 -0
- package/dist/src/agent/xml.js +41 -0
- package/dist/src/channel.js +579 -0
- package/dist/src/chat-queue.d.ts +31 -0
- package/dist/src/chat-queue.js +45 -0
- package/dist/{esm/types/src → src}/const.d.ts +46 -0
- package/dist/{esm/src → src}/const.js +77 -30
- package/dist/{esm/src → src}/dm-policy.js +6 -9
- package/dist/src/dynamic-agent.d.ts +37 -0
- package/dist/src/dynamic-agent.js +60 -0
- package/dist/src/dynamic-routing.d.ts +65 -0
- package/dist/src/dynamic-routing.js +58 -0
- package/dist/{esm/src → src}/group-policy.js +8 -22
- package/dist/src/http.d.ts +26 -0
- package/dist/src/http.js +93 -0
- package/dist/{esm/types/src → src}/interface.d.ts +0 -2
- package/dist/src/interface.js +4 -0
- package/dist/src/mcp/index.js +6 -0
- package/dist/{esm/src → src}/mcp/interceptors/biz-error.js +2 -6
- package/dist/{esm/src → src}/mcp/interceptors/index.js +6 -10
- package/dist/{esm/src → src}/mcp/interceptors/msg-media.js +6 -10
- package/dist/{esm/src → src}/mcp/interceptors/smartpage-create.js +2 -6
- package/dist/{esm/src → src}/mcp/interceptors/smartpage-export.js +2 -6
- package/dist/src/mcp/interceptors/types.js +7 -0
- package/dist/{esm/src → src}/mcp/schema.js +1 -4
- package/dist/{esm/src → src}/mcp/tool.js +4 -8
- package/dist/{esm/src → src}/mcp/transport.js +15 -16
- package/dist/{esm/src → src}/media-handler.js +6 -10
- package/dist/{esm/src → src}/media-uploader.js +64 -10
- package/dist/{esm/src → src}/message-parser.js +1 -4
- package/dist/{esm/types/src → src}/message-sender.d.ts +18 -0
- package/dist/{esm/src → src}/message-sender.js +45 -11
- package/dist/{esm/src → src}/monitor.js +145 -292
- package/dist/{esm/src → src}/onboarding.js +22 -26
- package/dist/{esm/types/src → src}/openclaw-compat.d.ts +49 -0
- package/dist/{esm/src → src}/openclaw-compat.js +133 -27
- package/dist/{esm/src → src}/reqid-store.js +4 -7
- package/dist/{esm/src → src}/runtime.js +2 -5
- package/dist/src/shared/command-auth.d.ts +23 -0
- package/dist/src/shared/command-auth.js +73 -0
- package/dist/src/shared/xml-parser.d.ts +46 -0
- package/dist/src/shared/xml-parser.js +210 -0
- package/dist/{esm/src → src}/state-dir-resolve.js +3 -7
- package/dist/{esm/src → src}/state-manager.js +84 -16
- package/dist/src/target.d.ts +35 -0
- package/dist/src/target.js +67 -0
- package/dist/src/template-card-manager.d.ts +55 -0
- package/dist/src/template-card-manager.js +205 -0
- package/dist/{esm/src → src}/template-card-parser.js +17 -8
- package/dist/{esm/src → src}/timeout.js +2 -5
- package/dist/src/types/account.d.ts +29 -0
- package/dist/src/types/account.js +4 -0
- package/dist/src/types/config.d.ts +91 -0
- package/dist/src/types/config.js +7 -0
- package/dist/src/types/constants.d.ts +42 -0
- package/dist/src/types/constants.js +42 -0
- package/dist/src/types/index.d.ts +7 -0
- package/dist/src/types/index.js +5 -0
- package/dist/src/types/message.d.ts +238 -0
- package/dist/src/types/message.js +5 -0
- package/dist/{esm/types/src → src}/utils.d.ts +41 -0
- package/dist/{esm/src → src}/utils.js +24 -10
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +24 -0
- package/dist/src/webhook/command-auth.d.ts +47 -0
- package/dist/src/webhook/command-auth.js +96 -0
- package/dist/src/webhook/gateway.d.ts +36 -0
- package/dist/src/webhook/gateway.js +235 -0
- package/dist/src/webhook/handler.d.ts +18 -0
- package/dist/src/webhook/handler.js +420 -0
- package/dist/src/webhook/helpers.d.ts +156 -0
- package/dist/src/webhook/helpers.js +833 -0
- package/dist/src/webhook/http.d.ts +26 -0
- package/dist/src/webhook/http.js +94 -0
- package/dist/src/webhook/index.d.ts +11 -0
- package/dist/src/webhook/index.js +14 -0
- package/dist/src/webhook/media.d.ts +29 -0
- package/dist/src/webhook/media.js +88 -0
- package/dist/src/webhook/monitor.d.ts +59 -0
- package/dist/src/webhook/monitor.js +1161 -0
- package/dist/src/webhook/state.d.ts +220 -0
- package/dist/src/webhook/state.js +491 -0
- package/dist/src/webhook/target.d.ts +41 -0
- package/dist/src/webhook/target.js +132 -0
- package/dist/src/webhook/types.d.ts +347 -0
- package/dist/src/webhook/types.js +33 -0
- package/dist/src/webhook/video-frame.d.ts +13 -0
- package/dist/src/webhook/video-frame.js +30 -0
- package/package.json +15 -7
- package/dist/cjs/index.js +0 -45
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/src/channel.js +0 -339
- package/dist/cjs/src/channel.js.map +0 -1
- package/dist/cjs/src/const.js +0 -137
- package/dist/cjs/src/const.js.map +0 -1
- package/dist/cjs/src/dm-policy.js +0 -89
- package/dist/cjs/src/dm-policy.js.map +0 -1
- package/dist/cjs/src/group-policy.js +0 -133
- package/dist/cjs/src/group-policy.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/biz-error.js +0 -70
- package/dist/cjs/src/mcp/interceptors/biz-error.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/index.js +0 -73
- package/dist/cjs/src/mcp/interceptors/index.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/msg-media.js +0 -159
- package/dist/cjs/src/mcp/interceptors/msg-media.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js +0 -157
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js.map +0 -1
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js +0 -93
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js.map +0 -1
- package/dist/cjs/src/mcp/schema.js +0 -104
- package/dist/cjs/src/mcp/schema.js.map +0 -1
- package/dist/cjs/src/mcp/tool.js +0 -208
- package/dist/cjs/src/mcp/tool.js.map +0 -1
- package/dist/cjs/src/mcp/transport.js +0 -417
- package/dist/cjs/src/mcp/transport.js.map +0 -1
- package/dist/cjs/src/media-handler.js +0 -129
- package/dist/cjs/src/media-handler.js.map +0 -1
- package/dist/cjs/src/media-uploader.js +0 -284
- package/dist/cjs/src/media-uploader.js.map +0 -1
- package/dist/cjs/src/message-parser.js +0 -134
- package/dist/cjs/src/message-parser.js.map +0 -1
- package/dist/cjs/src/message-sender.js +0 -89
- package/dist/cjs/src/message-sender.js.map +0 -1
- package/dist/cjs/src/monitor.js +0 -991
- package/dist/cjs/src/monitor.js.map +0 -1
- package/dist/cjs/src/onboarding.js +0 -182
- package/dist/cjs/src/onboarding.js.map +0 -1
- package/dist/cjs/src/openclaw-compat.js +0 -400
- package/dist/cjs/src/openclaw-compat.js.map +0 -1
- package/dist/cjs/src/reqid-store.js +0 -88
- package/dist/cjs/src/reqid-store.js.map +0 -1
- package/dist/cjs/src/runtime.js +0 -16
- package/dist/cjs/src/runtime.js.map +0 -1
- package/dist/cjs/src/state-dir-resolve.js +0 -35
- package/dist/cjs/src/state-dir-resolve.js.map +0 -1
- package/dist/cjs/src/state-manager.js +0 -158
- package/dist/cjs/src/state-manager.js.map +0 -1
- package/dist/cjs/src/template-card-parser.js +0 -641
- package/dist/cjs/src/template-card-parser.js.map +0 -1
- package/dist/cjs/src/timeout.js +0 -42
- package/dist/cjs/src/timeout.js.map +0 -1
- package/dist/cjs/src/utils.js +0 -60
- package/dist/cjs/src/utils.js.map +0 -1
- package/dist/cjs/src/version.js +0 -26
- package/dist/cjs/src/version.js.map +0 -1
- package/dist/esm/index.js +0 -41
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/src/channel.js +0 -337
- package/dist/esm/src/channel.js.map +0 -1
- package/dist/esm/src/const.js.map +0 -1
- package/dist/esm/src/dm-policy.js.map +0 -1
- package/dist/esm/src/group-policy.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/biz-error.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/index.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/msg-media.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/smartpage-create.js.map +0 -1
- package/dist/esm/src/mcp/interceptors/smartpage-export.js.map +0 -1
- package/dist/esm/src/mcp/schema.js.map +0 -1
- package/dist/esm/src/mcp/tool.js.map +0 -1
- package/dist/esm/src/mcp/transport.js.map +0 -1
- package/dist/esm/src/media-handler.js.map +0 -1
- package/dist/esm/src/media-uploader.js.map +0 -1
- package/dist/esm/src/message-parser.js.map +0 -1
- package/dist/esm/src/message-sender.js.map +0 -1
- package/dist/esm/src/monitor.js.map +0 -1
- package/dist/esm/src/onboarding.js.map +0 -1
- package/dist/esm/src/openclaw-compat.js.map +0 -1
- package/dist/esm/src/reqid-store.js.map +0 -1
- package/dist/esm/src/runtime.js.map +0 -1
- package/dist/esm/src/state-dir-resolve.js.map +0 -1
- package/dist/esm/src/state-manager.js.map +0 -1
- package/dist/esm/src/template-card-parser.js.map +0 -1
- package/dist/esm/src/timeout.js.map +0 -1
- package/dist/esm/src/utils.js.map +0 -1
- package/dist/esm/src/version.js +0 -23
- package/dist/esm/src/version.js.map +0 -1
- package/dist/esm/types/index.d.ts +0 -9
- package/dist/esm/types/src/version.d.ts +0 -2
- /package/dist/{esm/types/src → src}/channel.d.ts +0 -0
- /package/dist/{esm/types/src → src}/dm-policy.d.ts +0 -0
- /package/dist/{esm/types/src → src}/group-policy.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/index.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/biz-error.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/index.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/msg-media.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-create.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-export.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/interceptors/types.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/schema.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/tool.d.ts +0 -0
- /package/dist/{esm/types/src → src}/mcp/transport.d.ts +0 -0
- /package/dist/{esm/types/src → src}/media-handler.d.ts +0 -0
- /package/dist/{esm/types/src → src}/media-uploader.d.ts +0 -0
- /package/dist/{esm/types/src → src}/message-parser.d.ts +0 -0
- /package/dist/{esm/types/src → src}/monitor.d.ts +0 -0
- /package/dist/{esm/types/src → src}/onboarding.d.ts +0 -0
- /package/dist/{esm/types/src → src}/reqid-store.d.ts +0 -0
- /package/dist/{esm/types/src → src}/runtime.d.ts +0 -0
- /package/dist/{esm/types/src → src}/state-dir-resolve.d.ts +0 -0
- /package/dist/{esm/types/src → src}/state-manager.d.ts +0 -0
- /package/dist/{esm/types/src → src}/template-card-parser.d.ts +0 -0
- /package/dist/{esm/types/src → src}/timeout.d.ts +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Webhook HTTP 入口
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 管理 AgentWebhookTarget 注册表(多账号共用同一 path 时按签名选中)
|
|
6
|
+
* 2. GET → echostr URL 验证
|
|
7
|
+
* 3. POST → XML body 解密 → 调用 handleAgentWebhook
|
|
8
|
+
*/
|
|
9
|
+
import crypto from "node:crypto";
|
|
10
|
+
import { WecomCrypto } from "@wecom/aibot-node-sdk";
|
|
11
|
+
import { extractEncryptFromXml } from "./xml.js";
|
|
12
|
+
import { parseXml, extractAgentId } from "../shared/xml-parser.js";
|
|
13
|
+
import { handleAgentWebhook } from "./index.js";
|
|
14
|
+
import { WEBHOOK_PATHS, LIMITS } from "../const.js";
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Target Registry
|
|
17
|
+
// ============================================================================
|
|
18
|
+
const agentTargets = new Map();
|
|
19
|
+
export function registerAgentWebhookTarget(target) {
|
|
20
|
+
const list = agentTargets.get(target.path) ?? [];
|
|
21
|
+
list.push(target);
|
|
22
|
+
agentTargets.set(target.path, list);
|
|
23
|
+
}
|
|
24
|
+
export function deregisterAgentWebhookTarget(accountId) {
|
|
25
|
+
for (const [path, list] of agentTargets) {
|
|
26
|
+
const filtered = list.filter((t) => t.agent.accountId !== accountId);
|
|
27
|
+
if (filtered.length === 0) {
|
|
28
|
+
agentTargets.delete(path);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
agentTargets.set(path, filtered);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Helpers
|
|
37
|
+
// ============================================================================
|
|
38
|
+
function normalizeWebhookPath(raw) {
|
|
39
|
+
const trimmed = raw.trim();
|
|
40
|
+
if (!trimmed)
|
|
41
|
+
return "/";
|
|
42
|
+
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
43
|
+
if (withSlash.length > 1 && withSlash.endsWith("/"))
|
|
44
|
+
return withSlash.slice(0, -1);
|
|
45
|
+
return withSlash;
|
|
46
|
+
}
|
|
47
|
+
function resolvePath(req) {
|
|
48
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
49
|
+
return normalizeWebhookPath(url.pathname || "/");
|
|
50
|
+
}
|
|
51
|
+
function resolveQueryParams(req) {
|
|
52
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
53
|
+
return url.searchParams;
|
|
54
|
+
}
|
|
55
|
+
function resolveSignatureParam(params) {
|
|
56
|
+
return (params.get("msg_signature") ??
|
|
57
|
+
params.get("msgsignature") ??
|
|
58
|
+
params.get("signature") ??
|
|
59
|
+
"");
|
|
60
|
+
}
|
|
61
|
+
async function readTextBody(req, maxBytes) {
|
|
62
|
+
const chunks = [];
|
|
63
|
+
let total = 0;
|
|
64
|
+
return await new Promise((resolve) => {
|
|
65
|
+
req.on("data", (chunk) => {
|
|
66
|
+
total += chunk.length;
|
|
67
|
+
if (total > maxBytes) {
|
|
68
|
+
resolve({ ok: false, error: "payload too large" });
|
|
69
|
+
req.destroy();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
chunks.push(chunk);
|
|
73
|
+
});
|
|
74
|
+
req.on("end", () => {
|
|
75
|
+
resolve({ ok: true, value: Buffer.concat(chunks).toString("utf8") });
|
|
76
|
+
});
|
|
77
|
+
req.on("error", (err) => {
|
|
78
|
+
resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function normalizeAgentIdValue(value) {
|
|
83
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
84
|
+
return value;
|
|
85
|
+
const raw = String(value ?? "").trim();
|
|
86
|
+
if (!raw)
|
|
87
|
+
return undefined;
|
|
88
|
+
const parsed = Number(raw);
|
|
89
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
90
|
+
}
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Main HTTP handler
|
|
93
|
+
// ============================================================================
|
|
94
|
+
export function createWecomAgentWebhookHandler(runtime) {
|
|
95
|
+
return (req, res) => handleWecomAgentWebhookRequest(req, res, runtime);
|
|
96
|
+
}
|
|
97
|
+
export async function handleWecomAgentWebhookRequest(req, res, runtime) {
|
|
98
|
+
const path = resolvePath(req);
|
|
99
|
+
const reqId = crypto.randomUUID().slice(0, 8);
|
|
100
|
+
// 检查是否为 Agent 路径
|
|
101
|
+
const isAgentPath = path === WEBHOOK_PATHS.AGENT ||
|
|
102
|
+
path === WEBHOOK_PATHS.AGENT_PLUGIN ||
|
|
103
|
+
path.startsWith(`${WEBHOOK_PATHS.AGENT}/`) ||
|
|
104
|
+
path.startsWith(`${WEBHOOK_PATHS.AGENT_PLUGIN}/`);
|
|
105
|
+
const targets = agentTargets.get(path) ?? [];
|
|
106
|
+
if (targets.length === 0) {
|
|
107
|
+
if (isAgentPath) {
|
|
108
|
+
res.statusCode = 404;
|
|
109
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
110
|
+
res.end("agent not configured - Agent 模式未配置");
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const query = resolveQueryParams(req);
|
|
116
|
+
const timestamp = query.get("timestamp") ?? "";
|
|
117
|
+
const nonce = query.get("nonce") ?? "";
|
|
118
|
+
const signature = resolveSignatureParam(query);
|
|
119
|
+
// ── GET: echostr URL 验证 ──────────────────────────────────────────
|
|
120
|
+
if (req.method === "GET") {
|
|
121
|
+
const echostr = query.get("echostr") ?? "";
|
|
122
|
+
// 用签名匹配正确的 target
|
|
123
|
+
const matched = targets.filter((t) => {
|
|
124
|
+
const wc = new WecomCrypto(t.agent.token, t.agent.encodingAESKey, t.agent.corpId);
|
|
125
|
+
return wc.verifySignature(signature, timestamp, nonce, echostr);
|
|
126
|
+
});
|
|
127
|
+
if (matched.length !== 1) {
|
|
128
|
+
const reason = matched.length === 0 ? "account_not_found" : "account_conflict";
|
|
129
|
+
res.statusCode = 401;
|
|
130
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
131
|
+
res.end(JSON.stringify({ error: reason, message: `Agent callback ${reason}: signature verification failed.` }));
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
const selected = matched[0];
|
|
135
|
+
try {
|
|
136
|
+
const wc = new WecomCrypto(selected.agent.token, selected.agent.encodingAESKey, selected.agent.corpId);
|
|
137
|
+
const plain = wc.decrypt(echostr);
|
|
138
|
+
res.statusCode = 200;
|
|
139
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
140
|
+
res.end(plain);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
res.statusCode = 400;
|
|
145
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
146
|
+
res.end("decrypt failed - 解密失败,请检查 EncodingAESKey");
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// ── POST: XML 消息回调 ─────────────────────────────────────────────
|
|
151
|
+
if (req.method !== "POST")
|
|
152
|
+
return false;
|
|
153
|
+
const rawBody = await readTextBody(req, LIMITS.MAX_REQUEST_BODY_SIZE);
|
|
154
|
+
if (!rawBody.ok) {
|
|
155
|
+
res.statusCode = 400;
|
|
156
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
157
|
+
res.end(rawBody.error || "invalid payload");
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
let encrypted = "";
|
|
161
|
+
try {
|
|
162
|
+
encrypted = extractEncryptFromXml(rawBody.value);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
res.statusCode = 400;
|
|
166
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
167
|
+
res.end("invalid xml - 缺少 Encrypt 字段");
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
// 签名匹配
|
|
171
|
+
const matched = targets.filter((t) => {
|
|
172
|
+
const wc = new WecomCrypto(t.agent.token, t.agent.encodingAESKey, t.agent.corpId);
|
|
173
|
+
return wc.verifySignature(signature, timestamp, nonce, encrypted);
|
|
174
|
+
});
|
|
175
|
+
if (matched.length !== 1) {
|
|
176
|
+
const reason = matched.length === 0 ? "account_not_found" : "account_conflict";
|
|
177
|
+
res.statusCode = 401;
|
|
178
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
179
|
+
res.end(JSON.stringify({ error: reason, message: `Agent callback ${reason}: signature verification failed.` }));
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
const selected = matched[0];
|
|
183
|
+
let decrypted = "";
|
|
184
|
+
let parsed = null;
|
|
185
|
+
try {
|
|
186
|
+
const wc = new WecomCrypto(selected.agent.token, selected.agent.encodingAESKey, selected.agent.corpId);
|
|
187
|
+
decrypted = wc.decrypt(encrypted);
|
|
188
|
+
parsed = parseXml(decrypted);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
res.statusCode = 400;
|
|
192
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
193
|
+
res.end("decrypt failed - 解密失败,请检查 EncodingAESKey");
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
if (!parsed) {
|
|
197
|
+
res.statusCode = 400;
|
|
198
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
199
|
+
res.end("invalid xml - XML 解析失败");
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
// agentId 一致性校验(仅告警)
|
|
203
|
+
const inboundAgentId = normalizeAgentIdValue(extractAgentId(parsed));
|
|
204
|
+
if (inboundAgentId !== undefined &&
|
|
205
|
+
selected.agent.agentId !== undefined &&
|
|
206
|
+
inboundAgentId !== selected.agent.agentId) {
|
|
207
|
+
selected.runtime.error?.(`[wecom] inbound(agent): reqId=${reqId} accountId=${selected.agent.accountId} agentId_mismatch expected=${selected.agent.agentId} actual=${inboundAgentId}`);
|
|
208
|
+
}
|
|
209
|
+
const core = runtime;
|
|
210
|
+
selected.runtime.log?.(`[wecom] inbound(agent): reqId=${reqId} method=POST remote=${req.socket?.remoteAddress ?? "unknown"} accountId=${selected.agent.accountId}`);
|
|
211
|
+
return handleAgentWebhook({
|
|
212
|
+
req,
|
|
213
|
+
res,
|
|
214
|
+
verifiedPost: {
|
|
215
|
+
timestamp,
|
|
216
|
+
nonce,
|
|
217
|
+
signature,
|
|
218
|
+
encrypted,
|
|
219
|
+
decrypted,
|
|
220
|
+
parsed,
|
|
221
|
+
},
|
|
222
|
+
agent: selected.agent,
|
|
223
|
+
config: selected.config,
|
|
224
|
+
core,
|
|
225
|
+
log: selected.runtime.log,
|
|
226
|
+
error: selected.runtime.error,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeCom XML 加解密辅助函数
|
|
3
|
+
* 用于 Agent 模式处理 XML 格式回调
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 从 XML 密文中提取 Encrypt 字段
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractEncryptFromXml(xml: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* 从 XML 中提取 ToUserName (CorpID)
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractToUserNameFromXml(xml: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* 构建加密 XML 响应
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildEncryptedXmlResponse(params: {
|
|
17
|
+
encrypt: string;
|
|
18
|
+
signature: string;
|
|
19
|
+
timestamp: string;
|
|
20
|
+
nonce: string;
|
|
21
|
+
}): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeCom XML 加解密辅助函数
|
|
3
|
+
* 用于 Agent 模式处理 XML 格式回调
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 从 XML 密文中提取 Encrypt 字段
|
|
7
|
+
*/
|
|
8
|
+
export function extractEncryptFromXml(xml) {
|
|
9
|
+
const match = /<Encrypt><!\[CDATA\[(.*?)\]\]><\/Encrypt>/s.exec(xml);
|
|
10
|
+
if (!match?.[1]) {
|
|
11
|
+
// 尝试不带 CDATA 的格式
|
|
12
|
+
const altMatch = /<Encrypt>(.*?)<\/Encrypt>/s.exec(xml);
|
|
13
|
+
if (!altMatch?.[1]) {
|
|
14
|
+
throw new Error("Invalid XML: missing Encrypt field");
|
|
15
|
+
}
|
|
16
|
+
return altMatch[1];
|
|
17
|
+
}
|
|
18
|
+
return match[1];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 从 XML 中提取 ToUserName (CorpID)
|
|
22
|
+
*/
|
|
23
|
+
export function extractToUserNameFromXml(xml) {
|
|
24
|
+
const match = /<ToUserName><!\[CDATA\[(.*?)\]\]><\/ToUserName>/s.exec(xml);
|
|
25
|
+
if (!match?.[1]) {
|
|
26
|
+
const altMatch = /<ToUserName>(.*?)<\/ToUserName>/s.exec(xml);
|
|
27
|
+
return altMatch?.[1] ?? "";
|
|
28
|
+
}
|
|
29
|
+
return match[1];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 构建加密 XML 响应
|
|
33
|
+
*/
|
|
34
|
+
export function buildEncryptedXmlResponse(params) {
|
|
35
|
+
return `<xml>
|
|
36
|
+
<Encrypt><![CDATA[${params.encrypt}]]></Encrypt>
|
|
37
|
+
<MsgSignature><![CDATA[${params.signature}]]></MsgSignature>
|
|
38
|
+
<TimeStamp>${params.timestamp}</TimeStamp>
|
|
39
|
+
<Nonce><![CDATA[${params.nonce}]]></Nonce>
|
|
40
|
+
</xml>`;
|
|
41
|
+
}
|