Add smart ACK provider for WeChat MP replies
Replace fixed ackText with a rule-based AckProvider that picks response templates by message type and intent (translate, summary, rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync, zero I/O, auto-falls back to config.ackText on any error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
|
||||
path = Path("/Users/john/Project/Memind/server.mjs")
|
||||
text = path.read_text()
|
||||
|
||||
if "function verifyWechatMpUrlChallenge" not in text:
|
||||
text = text.replace(
|
||||
' acceptLink: env.H5_WECHAT_MP_ACCEPT_LINK !== "0"\n };\n}',
|
||||
' acceptLink: env.H5_WECHAT_MP_ACCEPT_LINK !== "0",\n encodingAesKey: env.H5_WECHAT_MP_ENCODING_AES_KEY?.trim() ?? ""\n };\n}',
|
||||
1,
|
||||
)
|
||||
old_sig = """function verifyWechatMpSignature({ token, timestamp, nonce, signature }) {
|
||||
const digest = crypto27.createHash("sha1").update([token, timestamp, nonce].sort().join("")).digest("hex");
|
||||
return digest === String(signature ?? "");
|
||||
}"""
|
||||
new_sig = """function sha1WechatHex(parts) {
|
||||
return crypto27.createHash("sha1").update([...parts].sort().join("")).digest("hex");
|
||||
}
|
||||
function verifyWechatMpSignature({ token, timestamp, nonce, signature }) {
|
||||
return sha1WechatHex([token, timestamp, nonce]) === String(signature ?? "");
|
||||
}
|
||||
function verifyWechatMpMsgSignature({ token, timestamp, nonce, echoStr, msgSignature }) {
|
||||
return sha1WechatHex([token, timestamp, nonce, echoStr]) === String(msgSignature ?? "");
|
||||
}
|
||||
function decodeWechatMpAesKey(encodingAesKey) {
|
||||
const key = Buffer.from(`${String(encodingAesKey ?? "").trim()}=`, "base64");
|
||||
if (key.length !== 32) throw new Error("invalid encoding aes key");
|
||||
return key;
|
||||
}
|
||||
function decryptWechatMpPayload({ encodingAesKey, appId, encrypted }) {
|
||||
const key = decodeWechatMpAesKey(encodingAesKey);
|
||||
const iv = key.subarray(0, 16);
|
||||
const decipher = crypto27.createDecipheriv("aes-256-cbc", key, iv);
|
||||
decipher.setAutoPadding(false);
|
||||
const cipherBuf = Buffer.from(String(encrypted ?? ""), "base64");
|
||||
let decoded = Buffer.concat([decipher.update(cipherBuf), decipher.final()]);
|
||||
const pad = decoded.at(-1);
|
||||
if (!Number.isInteger(pad) || pad < 1 || pad > 32) throw new Error("invalid padding");
|
||||
decoded = decoded.subarray(0, decoded.length - pad);
|
||||
const content = decoded.subarray(16);
|
||||
const msgLen = content.readUInt32BE(0);
|
||||
const msg = content.subarray(4, 4 + msgLen).toString("utf8");
|
||||
const receivedAppId = content.subarray(4 + msgLen).toString("utf8");
|
||||
if (receivedAppId !== String(appId ?? "")) throw new Error("appid mismatch");
|
||||
return msg;
|
||||
}
|
||||
function verifyWechatMpUrlChallenge(query = {}, config = {}) {
|
||||
const timestamp = String(query.timestamp ?? "");
|
||||
const nonce = String(query.nonce ?? "");
|
||||
const echostr = String(query.echostr ?? "");
|
||||
const encryptType = String(query.encrypt_type ?? "").toLowerCase();
|
||||
if (encryptType === "aes") {
|
||||
const msgSignature = String(query.msg_signature ?? "");
|
||||
if (!config?.token || !config?.encodingAesKey || !config?.appId) {
|
||||
return { ok: false, status: 503, body: "wechat mp aes mode not configured" };
|
||||
}
|
||||
if (!verifyWechatMpMsgSignature({ token: config.token, timestamp, nonce, echoStr: echostr, msgSignature })) {
|
||||
return { ok: false, status: 403, body: "invalid signature" };
|
||||
}
|
||||
try {
|
||||
const plain = decryptWechatMpPayload({ encodingAesKey: config.encodingAesKey, appId: config.appId, encrypted: echostr });
|
||||
return { ok: true, status: 200, body: plain };
|
||||
} catch {
|
||||
return { ok: false, status: 403, body: "invalid echostr" };
|
||||
}
|
||||
}
|
||||
if (!verifyWechatMpSignature({ token: config?.token, timestamp, nonce, signature: String(query.signature ?? "") })) {
|
||||
return { ok: false, status: 403, body: "invalid signature" };
|
||||
}
|
||||
return { ok: true, status: 200, body: echostr };
|
||||
}"""
|
||||
if old_sig not in text:
|
||||
raise SystemExit("signature block not found")
|
||||
text = text.replace(old_sig, new_sig, 1)
|
||||
text = text.replace(
|
||||
""" const verifyRequest = (query = {}) => verifyWechatMpSignature({
|
||||
token: config.token,
|
||||
timestamp: String(query.timestamp ?? ""),
|
||||
nonce: String(query.nonce ?? ""),
|
||||
signature: String(query.signature ?? "")
|
||||
});""",
|
||||
""" const verifyRequest = (query = {}) => verifyWechatMpSignature({
|
||||
token: config.token,
|
||||
timestamp: String(query.timestamp ?? ""),
|
||||
nonce: String(query.nonce ?? ""),
|
||||
signature: String(query.signature ?? "")
|
||||
});
|
||||
const verifyUrlChallenge = (query = {}) => verifyWechatMpUrlChallenge(query, config);""",
|
||||
1,
|
||||
)
|
||||
text = text.replace(
|
||||
" verifyRequest,\n handleInboundMessage,",
|
||||
" verifyRequest,\n verifyUrlChallenge,\n handleInboundMessage,",
|
||||
1,
|
||||
)
|
||||
path.write_text(text)
|
||||
print("bundled server.mjs aes patch applied")
|
||||
else:
|
||||
print("bundled aes helpers already present")
|
||||
|
||||
release = Path("/Users/john/Project/releases/memind-live-before-20260626-095637-d51df2f/wechat-mp.mjs")
|
||||
if release.exists():
|
||||
Path("/Users/john/Project/Memind/wechat-mp.mjs").write_text(release.read_text())
|
||||
print("wechat-mp.mjs restored from release")
|
||||
Reference in New Issue
Block a user