/** * Test: Full Flow * * checksiteconfig -> hsw(n) -> getcaptcha * * Real sitekey from Stripe integration */ import { Logger } from '../src/utils/logger.js'; import { HswRunner } from '../src/sandbox/hsw_runner.js'; import { MotionGenerator } from '../src/generator/motion.js'; Logger.globalLevel = 'debug'; const logger = new Logger('FullFlow'); // Real config from log.txt const CONFIG = { host: 'b.stripecdn.com', sitekey: 'ec637546-e9b8-447a-ab81-b5fb6d228ab8', }; const HCAPTCHA_API = 'https://api.hcaptcha.com'; // Browser fingerprint for consistency const FINGERPRINT = { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', screenWidth: 1920, screenHeight: 1080, }; async function getVersion() { logger.info('Fetching hCaptcha version...'); const res = await fetch('https://js.hcaptcha.com/1/api.js'); const text = await res.text(); // Extract version from api.js const match = text.match(/v1\/([a-f0-9]+)\/static/); if (match) { logger.info(`Version: ${match[1]}`); return match[1]; } // Fallback return '9721ee268e2e8547d41c6d0d4d2f1144bd8b6eb7'; } async function checkSiteConfig(version) { logger.info('Step 1: checksiteconfig...'); const params = new URLSearchParams({ v: version, host: CONFIG.host, sitekey: CONFIG.sitekey, sc: '1', swa: '1', spst: '0', }); const url = `${HCAPTCHA_API}/checksiteconfig?${params}`; logger.debug(`URL: ${url}`); const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Origin': 'https://newassets.hcaptcha.com', 'Referer': 'https://newassets.hcaptcha.com/', 'User-Agent': FINGERPRINT.userAgent, }, }); const data = await res.json(); logger.info(`checksiteconfig response: pass=${data.pass}, c.type=${data.c?.type}`); logger.info(`Full response: ${JSON.stringify(data, null, 2)}`); if (!data.pass) { logger.error('checksiteconfig failed - captcha required'); } return data; } async function computeN(hsw, req) { logger.info('Step 2: Computing n value...'); const startTime = Date.now(); const n = await hsw.getN(req); const duration = Date.now() - startTime; logger.info(`n computed in ${duration}ms, length: ${n.length}`); logger.debug(`n preview: ${n.substring(0, 64)}...`); return n; } function generateMotion() { logger.info('Step 3: Generating motion data...'); const generator = new MotionGenerator({ screenWidth: FINGERPRINT.screenWidth, screenHeight: FINGERPRINT.screenHeight, checkboxPos: { x: 200, y: 300 }, }); const motion = generator.generate(); logger.info(`Motion: ${motion.mm.length} mouse moves, duration ${motion.mm[motion.mm.length-1][2] - motion.mm[0][2]}ms`); return motion; } async function getCaptcha(version, siteConfig, n, motionData, hswFn) { logger.info('Step 4: getcaptcha...'); const url = `${HCAPTCHA_API}/getcaptcha/${CONFIG.sitekey}`; // enc_get_req: true means we MUST encrypt logger.info('Building encrypted request (enc_get_req=true)...'); // Build raw payload as JSON string const rawPayload = { v: version, sitekey: CONFIG.sitekey, host: CONFIG.host, hl: 'en', motionData: JSON.stringify(motionData), n: n, c: JSON.stringify(siteConfig.c), }; const payloadStr = JSON.stringify(rawPayload); logger.info(`Raw payload size: ${payloadStr.length} chars`); // Encrypt with hsw(1, payload) const encrypted = await hswFn(1, payloadStr); logger.info(`Encrypted type: ${encrypted?.constructor?.name}, size: ${encrypted.length}`); // Convert Uint8Array to Buffer const encryptedBuffer = Buffer.from(encrypted); // Try multiple content types const contentTypes = [ 'application/x-protobuf', 'application/octet-stream', 'application/binary', ]; for (const contentType of contentTypes) { logger.info(`Trying Content-Type: ${contentType}...`); const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': contentType, 'Origin': 'https://newassets.hcaptcha.com', 'Referer': 'https://newassets.hcaptcha.com/', 'User-Agent': FINGERPRINT.userAgent, }, body: encryptedBuffer, }); const text = await res.text(); logger.info(`Response status: ${res.status}, body: ${text.substring(0, 200)}`); if (res.status === 200 && !text.includes('Unsupported')) { try { // Try to parse as JSON first const data = JSON.parse(text); return data; } catch (e) { // If not JSON, might be encrypted - try to decrypt logger.info('Response not JSON, trying to decrypt...'); try { const decrypted = await hswFn(0, new Uint8Array(Buffer.from(text))); logger.info(`Decrypted: ${decrypted?.substring?.(0, 200) || decrypted}`); return JSON.parse(decrypted); } catch (e2) { // Try treating text as binary try { const binaryResp = new Uint8Array(text.split('').map(c => c.charCodeAt(0))); const decrypted2 = await hswFn(0, binaryResp); return JSON.parse(decrypted2); } catch (e3) { logger.error(`Decrypt failed: ${e3.message}`); } } } } } return { success: false, error: 'All content types failed' }; } async function main() { console.log('\n' + '='.repeat(60)); logger.info('Starting full flow test'); console.log('='.repeat(60) + '\n'); try { // Initialize HSW runner const hsw = new HswRunner({ fingerprint: FINGERPRINT }); await hsw.init(); // Get current version const version = await getVersion(); // Step 1: checksiteconfig const siteConfig = await checkSiteConfig(version); if (!siteConfig.c || !siteConfig.c.req) { logger.error('No challenge request in response'); logger.info('Response:', JSON.stringify(siteConfig, null, 2)); return; } // Step 2: Compute n const n = await computeN(hsw, siteConfig.c.req); // Step 3: Generate motion const motionData = generateMotion(); // Step 4: getcaptcha const result = await getCaptcha(version, siteConfig, n, motionData, hsw.hswFn); console.log('\n' + '='.repeat(60)); if (result.generated_pass_UUID) { logger.success('🎉 SUCCESS! Got pass token:'); console.log(result.generated_pass_UUID.substring(0, 100) + '...'); } else if (result.pass === true) { logger.success('🎉 SUCCESS! Captcha passed'); console.log(JSON.stringify(result, null, 2)); } else { logger.warn('Captcha not passed. Response:'); console.log(JSON.stringify(result, null, 2)); } console.log('='.repeat(60) + '\n'); } catch (err) { logger.error(`Flow failed: ${err?.message || err}`); if (err?.stack) logger.error(err.stack); } } main();