Files
reverse-skills-max/vendor-new/xbsReverseSkill/web-js-env-patcher/scripts/check_tls_clients.js

166 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
'use strict';
const { spawnSync } = require('child_process');
function parseArgs(argv) {
const args = { python: '', json: false, markdown: false };
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === '--python') args.python = argv[++i] || '';
else if (a === '--json') args.json = true;
else if (a === '--markdown') args.markdown = true;
else if (a === '--help' || a === '-h') args.help = true;
else throw new Error(`未知参数:${a}`);
}
if (!args.json && !args.markdown) args.markdown = true;
return args;
}
function usage() {
return `用法:
node scripts/check_tls_clients.js --markdown
node scripts/check_tls_clients.js --python python --json
说明:检测最终请求阶段可选的 TLS 指纹兼容客户端:Node.js CycleTLS / impers / curl-cffi-node、Python curl_cffi / cffi_curl / cyCronet;真实请求交付时必须再封装为 Session 模式。`;
}
function run(cmd, args, timeout = 12000) {
const ret = spawnSync(cmd, args, { encoding: 'utf8', timeout, windowsHide: true });
return {
ok: ret.status === 0,
status: ret.status,
stdout: (ret.stdout || '').trim(),
stderr: (ret.stderr || '').trim(),
error: ret.error ? ret.error.message : '',
command: [cmd].concat(args).join(' '),
};
}
function nodeRequire(name) {
const code = `
(async()=>{
const name=${JSON.stringify(name)};
try {
const m=require(name);
console.log(JSON.stringify({ok:true, loader:"require", keys:Object.keys(m).slice(0,20)}));
} catch (e1) {
try {
const m=await import(name);
console.log(JSON.stringify({ok:true, loader:"import", keys:Object.keys(m).slice(0,20)}));
} catch (e2) {
console.log(JSON.stringify({ok:false,error:e1.message + " / " + e2.message}));
process.exit(2);
}
}
})();`;
const ret = run(process.execPath, ['-e', code]);
try {
const parsed = JSON.parse(ret.stdout.replace(/^\uFEFF/, ''));
return { name, installed: !!parsed.ok, loader: parsed.loader || '', keys: parsed.keys || [], error: parsed.error || '' };
} catch {
return { name, installed: false, loader: '', keys: [], error: ret.stderr || ret.error || '检测失败' };
}
}
function pythonCandidates(explicit) {
const out = [];
if (explicit) out.push({ cmd: explicit, prefix: [] });
out.push({ cmd: 'python', prefix: [] });
out.push({ cmd: 'python3', prefix: [] });
out.push({ cmd: 'py', prefix: ['-3'] });
const seen = new Set();
return out.filter(x => {
const k = x.cmd + ' ' + x.prefix.join(' ');
if (seen.has(k)) return false;
seen.add(k);
return true;
});
}
function detectPythonModules(explicitPython) {
const modules = ['curl_cffi', 'cffi_curl', 'cycronet', 'cyCronet'];
const code = [
'import importlib, json',
`mods=${JSON.stringify(modules)}`,
'out={}',
'for name in mods:',
' try:',
' m=importlib.import_module(name)',
' out[name]={"installed": True, "version": getattr(m, "__version__", "")}',
' except Exception as e:',
' out[name]={"installed": False, "error": str(e)}',
'print(json.dumps(out, ensure_ascii=False))',
].join('\n');
for (const c of pythonCandidates(explicitPython)) {
const ret = run(c.cmd, c.prefix.concat(['-c', code]));
if (!ret.ok && !ret.stdout) continue;
try {
const parsed = JSON.parse(ret.stdout.replace(/^\uFEFF/, ''));
return { python: c.cmd, pythonArgsPrefix: c.prefix, modules: parsed };
} catch {}
}
return {
python: '',
pythonArgsPrefix: [],
modules: Object.fromEntries(modules.map(m => [m, { installed: false, error: '未找到可用 Python 或检测失败' }])),
};
}
function detect(args) {
const nodePackages = ['cycletls', '@luminati-io/cycletls', 'cycle-tls', 'cycleTls', 'impers', 'curl-cffi', 'curl-cffi-node'].map(nodeRequire);
const python = detectPythonModules(args.python);
const cycleTlsAvailable = nodePackages.some(p => p.installed && /cycle/i.test(p.name));
const impersAvailable = nodePackages.some(p => p.installed && p.name === 'impers');
const curlCffiNodeAvailable = nodePackages.some(p => p.installed && p.name === 'curl-cffi');
return {
node: {
executable: process.execPath,
packages: nodePackages,
cycleTlsAvailable,
impersAvailable,
curlCffiNodeAvailable,
nodeTlsAvailable: cycleTlsAvailable || impersAvailable || curlCffiNodeAvailable,
},
python,
pythonTlsAvailable: Object.values(python.modules).some(v => v && v.installed),
note: '这些客户端只用于授权范围内的最终低频请求验证;如果本 case 需要最终发送真实请求,应在前置阶段先选择 Node.js CycleTLS/impers/curl-cffi-node 或 Python curl_cffi/cyCronet,并在最终项目中封装为 Session 模式,不要等普通客户端失败后才考虑。',
};
}
function renderMarkdown(result) {
const lines = ['# TLS 指纹兼容客户端检测结果', '', `说明:${result.note}`, '', '## Node.js / CycleTLS / impers'];
lines.push(`- Node.js:${result.node.executable}`);
lines.push(`- 是否检测到 CycleTLS:${result.node.cycleTlsAvailable ? '是' : '否'}`);
lines.push(`- 是否检测到 impers:${result.node.impersAvailable ? '是' : '否'}`);
lines.push(`- 是否检测到 Node.js TLS 指纹兼容客户端:${result.node.nodeTlsAvailable ? '是' : '否'}`);
for (const p of result.node.packages) {
lines.push(`- ${p.name}:${p.installed ? '已安装' : '未安装'}${p.loader ? `(${p.loader})` : ''}${p.error ? `(${p.error})` : ''}`);
}
lines.push('', '## Python / curl_cffi / cffi_curl / cyCronet');
lines.push(`- Python:${result.python.python ? `${result.python.python} ${result.python.pythonArgsPrefix.join(' ')}`.trim() : '未检测到'}`);
lines.push(`- 是否检测到 Python TLS 客户端:${result.pythonTlsAvailable ? '是' : '否'}`);
for (const [name, info] of Object.entries(result.python.modules)) {
lines.push(`- ${name}:${info.installed ? '已安装' : '未安装'}${info.version ? `(版本 ${info.version})` : ''}${info.error && !info.installed ? `(${info.error})` : ''}`);
}
lines.push('', '## 下一步');
if (!result.node.nodeTlsAvailable && !result.pythonTlsAvailable) {
lines.push('- 未检测到可用 TLS 指纹兼容客户端;如果本 case 需要最终发送真实请求,需在前置阶段询问用户安装 CycleTLS / impers / curl_cffi / cyCronet,或改为只输出本地 sign 结果。');
} else {
lines.push('- 在任务确认阶段选择一个最终请求客户端,并限制为少量授权验证请求;最终项目中只保留所选客户端的 Session 请求逻辑。');
}
return lines.join('\n') + '\n';
}
try {
const args = parseArgs(process.argv);
if (args.help) { console.log(usage()); process.exit(0); }
const result = detect(args);
if (args.json) console.log(JSON.stringify(result, null, 2));
if (args.markdown) process.stdout.write(renderMarkdown(result));
} catch (err) {
console.error(err.message || String(err));
console.error(usage());
process.exit(1);
}