File size: 3,255 Bytes
a920dd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 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,
};