| async function recaptchaV2({ domain, proxy, siteKey, action = "submit", isInvisible = false }, page) { |
| if (!domain) throw new Error("Missing domain parameter"); |
| if (!siteKey) throw new Error("Missing siteKey parameter"); |
|
|
| const timeout = global.timeOut || 60000; |
| let isResolved = false; |
|
|
| const cl = setTimeout(async () => { |
| if (!isResolved) { |
| throw new Error("Timeout Error"); |
| } |
| }, timeout); |
|
|
| try { |
| if (proxy?.username && proxy?.password) { |
| await page.authenticate({ |
| username: proxy.username, |
| password: proxy.password, |
| }); |
| } |
|
|
| const htmlContent = ` |
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>reCAPTCHA v2 Solver</title> |
| <style> |
| body { |
| font-family: Arial, sans-serif; |
| margin: 0; |
| padding: 20px; |
| background: #f5f5f5; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| min-height: 100vh; |
| } |
| .container { |
| background: white; |
| padding: 30px; |
| border-radius: 10px; |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
| text-align: center; |
| } |
| .status { |
| margin-top: 20px; |
| padding: 10px; |
| border-radius: 5px; |
| background: #f8f9fa; |
| } |
| button { |
| background: #007bff; |
| color: white; |
| border: none; |
| padding: 10px 20px; |
| border-radius: 5px; |
| cursor: pointer; |
| margin: 10px; |
| } |
| button:hover { |
| background: #0056b3; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h2>reCAPTCHA v2 Solver</h2> |
| <p>SiteKey: ${siteKey}</p> |
| <div id="recaptcha-container"> |
| <div class="g-recaptcha" |
| data-sitekey="${siteKey}" |
| data-callback="recaptchaCallback" |
| data-expired-callback="recaptchaExpired" |
| data-error-callback="recaptchaError" |
| data-size="${isInvisible ? 'invisible' : 'normal'}" |
| data-theme="light"> |
| </div> |
| </div> |
| ${isInvisible ? '<button onclick="executeInvisible()">Execute reCAPTCHA</button>' : ''} |
| <button onclick="checkToken()">Check Token</button> |
| <div class="status" id="status">Waiting for reCAPTCHA...</div> |
| </div> |
| |
| <script> |
| // Global variables |
| window.recaptchaToken = null; |
| window.recaptchaSolved = false; |
| |
| // Callback functions |
| window.recaptchaCallback = function(token) { |
| console.log('reCAPTCHA token received:', token); |
| window.recaptchaToken = token; |
| window.recaptchaSolved = true; |
| |
| document.getElementById('status').innerHTML = '✅ reCAPTCHA Solved! Token: ' + token.substring(0, 20) + '...'; |
| document.getElementById('status').style.background = '#d4edda'; |
| document.getElementById('status').style.color = '#155724'; |
| |
| // Store token in multiple ways |
| var input = document.createElement('input'); |
| input.type = 'hidden'; |
| input.name = 'g-recaptcha-response'; |
| input.value = token; |
| input.id = 'recaptcha-token-input'; |
| document.body.appendChild(input); |
| |
| localStorage.setItem('recaptcha_token', token); |
| }; |
| |
| window.recaptchaExpired = function() { |
| console.log('reCAPTCHA expired'); |
| window.recaptchaToken = null; |
| window.recaptchaSolved = false; |
| document.getElementById('status').innerHTML = '❌ reCAPTCHA Expired - Refreshing...'; |
| document.getElementById('status').style.background = '#fff3cd'; |
| document.getElementById('status').style.color = '#856404'; |
| |
| var existing = document.getElementById('recaptcha-token-input'); |
| if (existing) existing.remove(); |
| |
| // Auto-refresh after expiration |
| setTimeout(() => { |
| if (window.grecaptcha) { |
| grecaptcha.reset(); |
| } |
| }, 1000); |
| }; |
| |
| window.recaptchaError = function() { |
| console.log('reCAPTCHA error'); |
| document.getElementById('status').innerHTML = '❌ reCAPTCHA Error'; |
| document.getElementById('status').style.background = '#f8d7da'; |
| document.getElementById('status').style.color = '#721c24'; |
| }; |
| |
| window.executeInvisible = function() { |
| if (window.grecaptcha) { |
| grecaptcha.execute(); |
| } |
| }; |
| |
| window.checkToken = function() { |
| const token = window.recaptchaToken || document.getElementById('recaptcha-token-input')?.value; |
| if (token) { |
| document.getElementById('status').innerHTML = 'Token: ' + token; |
| } else { |
| document.getElementById('status').innerHTML = 'No token yet'; |
| } |
| }; |
| |
| // Auto-execute for invisible reCAPTCHA |
| window.onload = function() { |
| setTimeout(function() { |
| // For invisible reCAPTCHA, auto-execute |
| if (${isInvisible} && window.grecaptcha) { |
| grecaptcha.execute(); |
| } |
| |
| // For visible reCAPTCHA, try to find and click |
| if (!${isInvisible}) { |
| var iframe = document.querySelector('iframe[src*="recaptcha"]'); |
| if (iframe) { |
| console.log('Attempting to interact with reCAPTCHA'); |
| var rect = iframe.getBoundingClientRect(); |
| var clickEvent = new MouseEvent('click', { |
| view: window, |
| bubbles: true, |
| cancelable: true, |
| clientX: rect.left + rect.width / 2, |
| clientY: rect.top + rect.height / 2 |
| }); |
| iframe.dispatchEvent(clickEvent); |
| } |
| } |
| }, 2000); |
| }; |
| </script> |
| |
| <!-- Load reCAPTCHA API --> |
| <script src="https://www.google.com/recaptcha/api.js" async defer></script> |
| </body> |
| </html> |
| `; |
|
|
| |
| await page.setRequestInterception(true); |
| page.removeAllListeners("request"); |
| |
| page.on("request", async (request) => { |
| const url = request.url(); |
| |
| |
| if ([domain, domain + "/"].includes(url) && request.resourceType() === "document") { |
| await request.respond({ |
| status: 200, |
| contentType: "text/html", |
| body: htmlContent, |
| }); |
| } |
| |
| else if (request.resourceType() === 'image' || |
| request.resourceType() === 'stylesheet' || |
| request.resourceType() === 'font') { |
| await request.abort(); |
| } |
| else { |
| await request.continue(); |
| } |
| }); |
|
|
| |
| await page.goto(domain, { |
| waitUntil: "domcontentloaded", |
| timeout: timeout |
| }); |
|
|
| |
| await page.waitForSelector('.g-recaptcha', { timeout: 10000 }); |
|
|
| |
| const token = await page.waitForFunction(() => { |
| |
| const input = document.querySelector('#recaptcha-token-input'); |
| if (input && input.value && input.value.length > 10) { |
| return input.value; |
| } |
| |
| |
| const stored = localStorage.getItem('recaptcha_token'); |
| if (stored && stored.length > 10) { |
| return stored; |
| } |
| |
| |
| if (window.recaptchaToken && window.recaptchaToken.length > 10) { |
| return window.recaptchaToken; |
| } |
| |
| return null; |
| }, { timeout, polling: 100 }); |
|
|
| const tokenValue = await token.jsonValue(); |
| |
| isResolved = true; |
| clearTimeout(cl); |
|
|
| if (!tokenValue || tokenValue.length < 10) { |
| throw new Error("Failed to get valid reCAPTCHA token"); |
| } |
|
|
| console.log('Successfully obtained reCAPTCHA token'); |
| return { token: tokenValue, type: 'recaptcha_v2' }; |
|
|
| } catch (error) { |
| clearTimeout(cl); |
| |
| |
| try { |
| const fallbackToken = await page.evaluate(() => { |
| const input = document.querySelector('#recaptcha-token-input'); |
| return input ? input.value : null; |
| }); |
| |
| if (fallbackToken && fallbackToken.length > 10) { |
| return { token: fallbackToken, type: 'recaptcha_v2' }; |
| } |
| } catch (e) { |
| |
| } |
| |
| throw new Error(`reCAPTCHA solving failed: ${error.message}`); |
| } |
| } |
|
|
| module.exports = recaptchaV2; |