415gotit
This commit is contained in:
291
run_solver.js
Normal file
291
run_solver.js
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user