This commit is contained in:
dela
2026-02-21 18:27:49 +08:00
parent 0ac4b23f07
commit 5dc86ccfbf
270 changed files with 49508 additions and 4636 deletions

291
run_solver.js Normal file
View File

@@ -0,0 +1,291 @@
'use strict';
/**
* Stripe hCaptcha 全流程 Runner
*
* 流程:
* 1. 解析 Stripe Checkout URL → pk_live + session_id
* 2. 调用 Stripe init API → sitekey + rqdata
* 3. hCaptcha 求解 → token
*
* 使用:
* node run_solver.js [stripe_checkout_url]
* node run_solver.js # 使用默认 URL 测试
*/
const { HCaptchaSolver } = require('./src/hcaptcha_solver');
const { Logger } = require('./src/utils/logger');
const logger = new Logger('run_solver');
// ══════════════════════════════════════════════════════════════
// 步骤 1: 从 Stripe Checkout URL 提取参数
// ══════════════════════════════════════════════════════════════
/**
* 解析 Stripe Checkout URL
* 格式: https://pay.xxx.com/c/pay/cs_live_XXXX#pk_live_XXXX...
*
* @param {string} url - Stripe Checkout URL
* @returns {{ sessionId: string, pkLive: string }}
*/
function parseStripeUrl(url) {
logger.info('=== 开始获取 Stripe Checkout 参数 ===');
logger.info(`URL: ${url.substring(0, 80)}...`);
// [步骤 1] 从 hash 解码 pk_live
logger.info('[步骤 1] 从 URL hash 解码 pk_live...');
const hashPart = url.split('#')[1] || '';
let pkLive = '';
if (hashPart) {
// hash 可能直接是 pk_live_XXXX 或 base64 编码的 JSON
if (hashPart.startsWith('pk_live_') || hashPart.startsWith('pk_test_')) {
pkLive = hashPart;
} else {
try {
const decoded = Buffer.from(hashPart, 'base64').toString();
const data = JSON.parse(decoded);
pkLive = data.pk || data.key || '';
} catch {
// 可能是 URL-encoded
try {
pkLive = decodeURIComponent(hashPart);
} catch {
pkLive = hashPart;
}
}
}
}
logger.info(` 提取到 pk_live: ${pkLive.substring(0, 50)}...`);
// [步骤 2] 从路径提取 session_id
const urlObj = new URL(url.split('#')[0]);
const pathParts = urlObj.pathname.split('/');
const sessionId = pathParts.find(p => p.startsWith('cs_live_') || p.startsWith('cs_test_')) || '';
logger.info(`[步骤 2] 提取到 Session ID: ${sessionId}`);
return { sessionId, pkLive };
}
// ══════════════════════════════════════════════════════════════
// 步骤 2: 调用 Stripe Init API 获取 hCaptcha 参数
// ══════════════════════════════════════════════════════════════
async function fetchStripeParams(sessionId, pkLive) {
const initUrl = `https://api.stripe.com/v1/payment_pages/${sessionId}/init`;
logger.info(`[步骤 3] 正在调用 Init API: ${initUrl.substring(0, 80)}...`);
const resp = await fetch(initUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Bearer ${pkLive}`,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'Origin': 'https://checkout.stripe.com',
'Referer': 'https://checkout.stripe.com/',
},
body: new URLSearchParams({
'key': pkLive,
}),
});
logger.info(`HTTP POST ${initUrl.substring(0, 80)}... → ${resp.status}`);
if (!resp.ok) {
const text = await resp.text();
throw new Error(`Stripe Init API 失败 (${resp.status}): ${text.substring(0, 200)}`);
}
const data = await resp.json();
// 从返回数据中提取 hCaptcha 相关字段
// Stripe 返回的 JSON 结构中通常包含 hcaptcha_site_key 和 hcaptcha_rqdata
let siteKey = '';
let rqdata = '';
let host = 'b.stripecdn.com';
// 递归搜索 JSON 中的 hcaptcha 字段
const findHcaptchaParams = (obj, path = '') => {
if (!obj || typeof obj !== 'object') return;
for (const [k, v] of Object.entries(obj)) {
const key = k.toLowerCase();
if (key.includes('hcaptcha_site_key') || key === 'site_key' || key === 'sitekey') {
if (typeof v === 'string' && v.length > 10) siteKey = v;
}
if (key.includes('hcaptcha_rqdata') || key === 'rqdata') {
if (typeof v === 'string' && v.length > 10) rqdata = v;
}
if (key === 'host' && typeof v === 'string' && v.includes('.')) {
host = v;
}
if (typeof v === 'object' && v !== null) {
findHcaptchaParams(v, `${path}.${k}`);
}
}
};
findHcaptchaParams(data);
// Stripe 常见的返回路径
if (!siteKey && data.hcaptcha_site_key) siteKey = data.hcaptcha_site_key;
if (!siteKey && data.captcha?.hcaptcha?.site_key) siteKey = data.captcha.hcaptcha.site_key;
if (!rqdata && data.hcaptcha_rqdata) rqdata = data.hcaptcha_rqdata;
if (!rqdata && data.captcha?.hcaptcha?.rqdata) rqdata = data.captcha.hcaptcha.rqdata;
logger.info(` 成功获取 rqdata: ${rqdata ? rqdata.substring(0, 50) + '...' : '(空)'}`);
logger.info(` 成功获取 site_key: ${siteKey}`);
logger.info('=== Stripe 参数获取完成 ===');
return { siteKey, rqdata, host };
}
// ══════════════════════════════════════════════════════════════
// 步骤 3: hCaptcha 求解
// ══════════════════════════════════════════════════════════════
async function solveHCaptcha(siteKey, host, rqdata) {
console.log('');
console.log('=== 开始求解 hCaptcha ===');
console.log(`Host: ${host}`);
console.log(`Sitekey: ${siteKey}`);
console.log('');
const solver = new HCaptchaSolver({
sitekey: siteKey,
host,
rqdata,
});
return solver.solve();
}
// ══════════════════════════════════════════════════════════════
// 直接用 sitekey 测试(跳过 Stripe 部分)
// ══════════════════════════════════════════════════════════════
async function directSolve(sitekey, host, rqdata) {
console.log('');
console.log('=== 直接模式 (跳过 Stripe) ===');
console.log(`Sitekey: ${sitekey}`);
console.log(`Host: ${host}`);
if (rqdata) console.log(`Rqdata: ${rqdata.substring(0, 50)}...`);
console.log('');
return solveHCaptcha(sitekey, host, rqdata);
}
// ══════════════════════════════════════════════════════════════
// 主入口
// ══════════════════════════════════════════════════════════════
async function main() {
const args = process.argv.slice(2);
// 配置项(可按需修改)
const CONFIG = {
// Stripe Checkout URL测试用
stripeUrl: 'https://pay.verdent.ai/c/pay/cs_live_a1H5uyD1bkpXKyqaw0BXzwzGrdzTngoNXBO6ejdyvCmswD9D6Cqzy7URwB#pk_live_51S5juuHIX9Hc8tITIZnW34rV6PJhIzl66WgEZ8kLv',
// 直接模式参数(跳过 Stripe API 调用,直接测试 hCaptcha
direct: {
sitekey: 'ec637546-e9b8-447a-ab81-b5fb6d228ab8',
host: 'b.stripecdn.com',
rqdata: '', // 留空则不附加 rqdata
},
};
let mode = 'direct'; // 默认直接模式
let stripeUrl = CONFIG.stripeUrl;
// 解析命令行参数
for (let i = 0; i < args.length; i++) {
if (args[i] === '--stripe' || args[i] === '-s') {
mode = 'stripe';
if (args[i + 1] && !args[i + 1].startsWith('-')) {
stripeUrl = args[++i];
}
} else if (args[i] === '--direct' || args[i] === '-d') {
mode = 'direct';
} else if (args[i] === '--sitekey') {
CONFIG.direct.sitekey = args[++i];
} else if (args[i] === '--host') {
CONFIG.direct.host = args[++i];
} else if (args[i] === '--rqdata') {
CONFIG.direct.rqdata = args[++i];
} else if (args[i] === '--help' || args[i] === '-h') {
console.log(`
Stripe hCaptcha Solver Runner
用法:
node run_solver.js 直接模式 (默认 sitekey)
node run_solver.js --direct --sitekey KEY --host H 指定参数直接模式
node run_solver.js --stripe [URL] Stripe 全流程模式
node run_solver.js --stripe https://pay.xxx.com/... 指定 Stripe URL
选项:
--direct, -d 直接模式 (跳过 Stripe, 默认)
--stripe, -s Stripe 全流程模式
--sitekey KEY hCaptcha sitekey
--host HOST hCaptcha host
--rqdata DATA 附加 rqdata
--help, -h 显示帮助
`);
process.exit(0);
} else if (args[i].startsWith('http')) {
mode = 'stripe';
stripeUrl = args[i];
}
}
console.log(`\n${'═'.repeat(60)}`);
console.log(' Stripe hCaptcha Solver Runner');
console.log(`${'═'.repeat(60)}\n`);
let result;
if (mode === 'stripe') {
// ── 完整 Stripe 流程 ──
const { sessionId, pkLive } = parseStripeUrl(stripeUrl);
if (!sessionId || !pkLive) {
logger.error('无法从 URL 提取 sessionId 或 pk_live');
process.exit(1);
}
const { siteKey, rqdata, host } = await fetchStripeParams(sessionId, pkLive);
if (!siteKey) {
logger.error('无法从 Stripe 获取 hCaptcha sitekey');
process.exit(1);
}
result = await solveHCaptcha(siteKey, host, rqdata);
} else {
// ── 直接测试模式 ──
result = await directSolve(
CONFIG.direct.sitekey,
CONFIG.direct.host,
CONFIG.direct.rqdata,
);
}
// ── 输出结果 ──
console.log('');
console.log('═'.repeat(60));
if (result.success) {
console.log(' ✅ 求解成功!');
if (typeof result.token === 'string') {
console.log(` Token: ${result.token.substring(0, 60)}...`);
}
} else {
console.log(' ❌ 求解失败');
if (result.error) console.log(` Error: ${result.error}`);
}
console.log('═'.repeat(60));
console.log('');
process.exit(result.success ? 0 : 1);
}
main().catch(err => {
logger.error(`未捕获异常: ${err.message}`);
console.error(err.stack);
process.exit(1);
});