Files
memind/scripts/patch-studio-wechat-aes.py
john 9b4a25799f 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>
2026-06-26 15:19:03 +08:00

106 lines
4.7 KiB
Python

#!/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")