#!/usr/bin/env python3 """给 wechat4u 打补丁: 降低微信风控识别特征 1. request.js: UA → 最新 Chrome 126 Windows (每账号可不同) 2. request.js: connection close → keep-alive 3. global.js: DeviceID 持久化 (不再每次随机) 4. wechat.js: syncPolling 加 25±5s 间隔 (模拟真人) """ import subprocess, os, re, shutil, time def run(cmd, timeout=30): r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) return (r.stdout + r.stderr).strip() TMP = "/tmp/wechat4u-patch" os.makedirs(TMP, exist_ok=True) # 1. 拷贝文件出容器 print("=== 1. 拷贝文件出容器 ===") files = { "request.js": "/app/node_modules/wechat4u/lib/util/request.js", "wechat.js": "/app/node_modules/wechat4u/lib/wechat.js", "global.js": "/app/node_modules/wechat4u/lib/util/global.js", } for name, path in files.items(): run(["docker", "cp", f"wxBotWebhook:{path}", f"{TMP}/{name}"]) print(f" {name}: {os.path.getsize(f'{TMP}/{name}')}b") # 2. 打补丁 print("\n=== 2. 打补丁 ===") # 2.1 request.js: UA + connection p = f"{TMP}/request.js" c = open(p, encoding='utf-8').read() old_ua = "defaults.headers['user-agent'] = defaults.headers['user-agent'] || 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36';" new_ua = "defaults.headers['user-agent'] = defaults.headers['user-agent'] || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36';" if old_ua in c: c = c.replace(old_ua, new_ua) print(" ✅ UA → Chrome 126 Windows") else: print(" ⚠️ UA 未匹配") old_conn = "defaults.headers['connection'] = defaults.headers['connection'] || 'close';" new_conn = "defaults.headers['connection'] = defaults.headers['connection'] || 'keep-alive';" if old_conn in c: c = c.replace(old_conn, new_conn) print(" ✅ connection → keep-alive") else: print(" ⚠️ connection 未匹配") # 补全浏览器 headers (Accept, Accept-Language 等) old_headers = "defaults.headers['connection'] = defaults.headers['connection'] || 'keep-alive';" if "Accept-Language" not in c: # 在 connection 行后插入完整浏览器 headers insert = """defaults.headers['Accept'] = defaults.headers['Accept'] || 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'; defaults.headers['Accept-Language'] = defaults.headers['Accept-Language'] || 'zh-CN,zh;q=0.9,en;q=0.8'; defaults.headers['Accept-Encoding'] = defaults.headers['Accept-Encoding'] || 'gzip, deflate, br'; defaults.headers['Upgrade-Insecure-Requests'] = defaults.headers['Upgrade-Insecure-Requests'] || '1'; defaults.headers['Sec-Fetch-Dest'] = defaults.headers['Sec-Fetch-Dest'] || 'document'; defaults.headers['Sec-Fetch-Mode'] = defaults.headers['Sec-Fetch-Mode'] || 'navigate'; defaults.headers['Sec-Fetch-Site'] = defaults.headers['Sec-Fetch-Site'] || 'none'; defaults.headers['Sec-Fetch-User'] = defaults.headers['Sec-Fetch-User'] || '?1'; """ c = c.replace(old_headers, old_headers + "\n" + insert) print(" ✅ 补全浏览器 headers") open(p, 'w', encoding='utf-8').write(c) # 2.2 global.js: DeviceID 持久化 p = f"{TMP}/global.js" c = open(p, encoding='utf-8').read() old_did = """function getDeviceID() { return 'e' + ('' + Math.random().toFixed(15)).substring(2, 17); }""" new_did = """var _persistDeviceID = ''; function getDeviceID() { if (!_persistDeviceID) { _persistDeviceID = 'e' + ('' + Math.random().toFixed(15)).substring(2, 17); } return _persistDeviceID; }""" if old_did in c: c = c.replace(old_did, new_did) print(" ✅ DeviceID 持久化 (同进程固定)") else: print(" ⚠️ DeviceID 未匹配") open(p, 'w', encoding='utf-8').write(c) # 2.3 wechat.js: syncPolling 加 25±5s 间隔 p = f"{TMP}/wechat.js" c = open(p, encoding='utf-8').read() # 找到 .then 里的 _this3.syncPolling(id); (立即递归) old_sync = """ }).then(function () { _this3.lastSyncTime = Date.now(); _this3.syncPolling(id); }).catch(function (err) {""" new_sync = """ }).then(function () { _this3.lastSyncTime = Date.now(); setTimeout(function () { _this3.syncPolling(id); }, 25000 + Math.floor(Math.random() * 5000)); }).catch(function (err) {""" if old_sync in c: c = c.replace(old_sync, new_sync) print(" ✅ syncPolling 间隔 25±5s") else: print(" ⚠️ syncPolling 未匹配") open(p, 'w', encoding='utf-8').write(c) # 3. 语法检查 (node) print("\n=== 3. 语法检查 ===") for name in ["request.js", "global.js", "wechat.js"]: r = run(["docker", "exec", "wxBotWebhook", "node", "--check", f"/tmp/{name}"], timeout=15) # node --check 需要文件在容器内, 先拷贝验证 run(["docker", "cp", f"{TMP}/{name}", f"wxBotWebhook:/tmp/{name}"]) r = run(["docker", "exec", "wxBotWebhook", "node", "--check", f"/tmp/{name}"], timeout=15) print(f" {name}: {'OK' if 'SyntaxError' not in r else r[:200]}") print("\n=== 补丁完成, 等待确认后拷回 ===") print("验证通过后: 拷回容器 + 重启 wxBotWebhook")