|
|
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; |