- init.js: DISABLE_AUTO_LOGIN=true 时不自动 bot.start(), 导出 start/stop/isRunning - 新增 route/bot.js: POST /api/bot/login|logout, GET /api/bot/status (按需触发登录) - login.js: bot 未运行时不展示残留二维码, 显示开始登录按钮 - wechat4u wechat.js: checkLogin 忙循环加 3s 间隔+指数退避 (原来毫秒级轰炸 login.wx.qq.com) - puppet-wechat4u: logout 后不自动 start (原来登出即自动重登) - 补丁脚本: deploy/linux/wxbot_on_demand_login_patch.py (可重放, 容器重建后一键恢复) - 文档: docs/WXBOT_ON_DEMAND_LOGIN.md - agents_health_check/self_todo_executor: 禁用 wxBotWebhook 自动重启 (重启=重登触发风控)
119 lines
5.1 KiB
Python
119 lines
5.1 KiB
Python
#!/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")
|