File size: 3,548 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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// reCAPTCHA v2 audio-fallback flow.
//
// Strategy: click checkbox → if image challenge appears, switch to audio
// challenge → download audio → transcribe with Whisper → submit answer.
//
// Returns true on success, false otherwise. Throws nothing.

const audio = require('./audio');

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

function findFrame(page, predicate) {
  return page.frames().find((f) => {
    const u = f.url() || '';
    return predicate(u);
  });
}

async function solveRecaptchaV2(page, opts = {}) {
  const { timeout = 60000, attempts = 3 } = opts;
  const deadline = Date.now() + timeout;

  // 1) The checkbox (anchor) iframe — click it to start the challenge
  const anchor = findFrame(page, (u) => u.includes('/recaptcha/api2/anchor') || u.includes('/recaptcha/enterprise/anchor'));
  if (!anchor) return false;
  try {
    const cb = anchor.locator('#recaptcha-anchor');
    await cb.click({ timeout: 5000 });
  } catch {
    return false;
  }
  await sleep(1500);

  // 2) Wait for the bframe (challenge popup); if no popup, checkbox alone passed.
  let bframe = null;
  for (let i = 0; i < 10 && !bframe; i++) {
    bframe = findFrame(page, (u) => u.includes('/recaptcha/api2/bframe') || u.includes('/recaptcha/enterprise/bframe'));
    if (!bframe) await sleep(500);
  }
  if (!bframe) {
    // Token might already be set
    const ok = await page.evaluate(() => {
      const t = document.getElementById('g-recaptcha-response');
      return !!(t && t.value);
    });
    return ok;
  }

  // 3) Switch to audio challenge
  try {
    await bframe.locator('#recaptcha-audio-button').click({ timeout: 5000 });
  } catch {
    return false;
  }
  await sleep(1500);

  // Detect "automated requests" lockout
  const locked = await bframe
    .locator('text=/automated queries|try again later/i')
    .first()
    .isVisible({ timeout: 1000 })
    .catch(() => false);
  if (locked) return false;

  for (let i = 0; i < attempts && Date.now() < deadline; i++) {
    // 4) Get audio source URL
    const src = await bframe
      .locator('audio#audio-source, .rc-audiochallenge-tdownload-link')
      .first()
      .getAttribute('href')
      .catch(() => null) ||
      await bframe.locator('audio#audio-source').first().getAttribute('src').catch(() => null);
    if (!src) return false;

    // 5) Transcribe via local Whisper
    let answer = '';
    try {
      answer = (await audio.transcribeUrl(page, src)).trim().toLowerCase();
    } catch {
      return false;
    }
    if (!answer) continue;

    // 6) Type the answer + verify
    try {
      const input = bframe.locator('#audio-response');
      await input.fill('');
      await input.type(answer, { delay: 60 + Math.random() * 60 });
      await bframe.locator('#recaptcha-verify-button').click();
    } catch {
      return false;
    }
    await sleep(2500);

    // 7) Check if solved
    const solved = await page.evaluate(() => {
      const t = document.getElementById('g-recaptcha-response');
      return !!(t && t.value);
    });
    if (solved) return true;

    // If wrong, reCAPTCHA may show "Multiple solutions required" or refresh — loop.
    const errVisible = await bframe
      .locator('text=/incorrect|try again/i')
      .first()
      .isVisible({ timeout: 500 })
      .catch(() => false);
    if (errVisible) {
      try { await bframe.locator('#recaptcha-reload-button').click({ timeout: 1500 }); } catch {}
      await sleep(1500);
    }
  }
  return false;
}

module.exports = { solveRecaptchaV2 };