playwright / helpers /captcha /index.js
cv3inx's picture
Add local CAPTCHA solvers + integrate self-hosted turnstile-solver
a920dd4
Raw
History Blame Contribute Delete
3.26 kB
// Local-only CAPTCHA solvers — no third-party API services.
//
// What's included:
// - Audio CAPTCHA (Whisper, runs locally; English)
// - Text/OCR CAPTCHA (Tesseract.js, runs locally)
// - Math CAPTCHA (safe expression eval)
// - reCAPTCHA v2 (full audio-fallback flow)
// - Cloudflare Turnstile (stealth click + human mouse path)
// - Cookie jar (skip re-solving by reusing CF clearance per origin)
//
// What is NOT included (and why):
// - hCaptcha image grid / reCAPTCHA v2 image challenge / CF Turnstile
// image challenge — these need a trained CV classifier. There is no
// accurate, license-free, sub-1GB classifier for "select all cars/buses
// /traffic lights" tasks. To avoid pretending otherwise, those modes
// return false instead of guessing.
const audio = require('./audio');
const ocr = require('./ocr');
const math = require('./math');
const recaptcha = require('./recaptcha');
const turnstile = require('./turnstile');
const mouse = require('./mouse');
const cookies = require('./cookies');
// Dispatcher: figure out what CAPTCHA is on the page and try the right solver.
async function autoSolve(page, opts = {}) {
const html = await page.content().catch(() => '');
const lower = html.toLowerCase();
// Order matters — try the cheapest detectors first.
if (lower.includes('cf-turnstile') || page.frames().some((f) => /challenges\.cloudflare\.com/.test(f.url() || ''))) {
const ok = await turnstile.solveTurnstile(page, opts);
return { type: 'turnstile', solved: ok };
}
if (lower.includes('g-recaptcha') || page.frames().some((f) => /\/recaptcha\/api2\/anchor/.test(f.url() || ''))) {
const ok = await recaptcha.solveRecaptchaV2(page, opts);
return { type: 'recaptcha-v2', solved: ok };
}
// Math text? Look for "X + Y =" style on the page
const mathAns = math.extractAndSolve(await page.evaluate(() => document.body.innerText).catch(() => ''));
if (mathAns !== null) {
return { type: 'math', solved: false, answer: mathAns, hint: 'fill the answer into the captcha input field yourself' };
}
return { type: 'unknown', solved: false };
}
module.exports = {
// Audio (Whisper)
transcribe: audio.transcribe,
transcribeUrl: audio.transcribeUrl,
// OCR (Tesseract)
ocrBuffer: ocr.recognizeBuffer,
ocrUrl: ocr.recognizeUrl,
ocrElement: ocr.recognizeElement,
// Math
solveMath: math.safeEvalMath,
extractMath: math.extractAndSolve,
// reCAPTCHA v2
solveRecaptchaV2: recaptcha.solveRecaptchaV2,
// Turnstile
clickTurnstile: turnstile.clickTurnstile,
waitForTurnstileToken: turnstile.waitForToken,
solveTurnstile: turnstile.solveTurnstile,
solveTurnstileViaSelfHosted: turnstile.solveViaSelfHosted,
readTurnstileSitekey: turnstile.readSitekey,
clearChallenge: turnstile.clearChallenge,
clearChallengeAndAdoptCookies: turnstile.clearChallengeAndAdoptCookies,
turnstileSolverUrl: turnstile.solverUrl,
// Mouse (exposed for general use)
humanMove: mouse.humanMove,
humanClick: mouse.humanClick,
humanMoveAndClick: mouse.humanMoveAndClick,
// Cookie jar
saveCookies: cookies.saveCookies,
loadCookies: cookies.loadCookies,
clearCookies: cookies.clearCookies,
// Top-level dispatcher
autoSolve,
};