recaptcha-v2-solver
Version:
ReCaptcha v2 bypass solution using three methods: Audio transcription, Visual (Gemini), and 2Captcha
507 lines (418 loc) • 18.5 kB
JavaScript
const puppeteerExtra = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const axios = require('axios');
const os = require('os');
const dotenv = require('dotenv');
const createLogger = require('../utils/logger');
const fs = require('fs').promises;
const path = require('path');
let logger = createLogger({ level: 'info' });
// Add this function to handle directory deletion
async function cleanupUserDataDirs(baseDir) {
try {
logger.info('Cleaning up previous Chrome user data...');
// Check if directory exists
try {
await fs.access(baseDir);
} catch {
// Directory doesn't exist, nothing to clean
return;
}
// Read all items in the directory
const items = await fs.readdir(baseDir);
// Delete each chrome-user-data directory
for (const item of items) {
if (item.startsWith('chrome-user-data-')) {
const fullPath = path.join(baseDir, item);
try {
await fs.rm(fullPath, { recursive: true, force: true });
logger.debug(`Deleted ${fullPath}`);
} catch (err) {
logger.warn(`Failed to delete ${fullPath}: ${err.message}`);
}
}
}
logger.info('Chrome user data cleanup completed');
} catch (error) {
logger.error(`Error cleaning up Chrome user data: ${error.message}`);
}
}
dotenv.config();
puppeteerExtra.use(StealthPlugin());
class ResultTracker {
constructor() {
this.results = [];
this.startTime = Date.now();
this.maxResults = 500;
this.firstProcessingTime = null;
}
addResult(result) {
if (!this.firstProcessingTime) {
this.firstProcessingTime = Date.now();
}
this.results.push({
success: result.token ? true : false, // Success is based on getting a token
timestamp: Date.now()
});
if (this.results.length > this.maxResults) {
this.results.shift();
}
// Automatically print stats after adding a result
this.printStats();
}
getStats() {
if (this.results.length === 0) return null;
const successCount = this.results.filter(r => r.success).length;
const successRate = (successCount / this.results.length) * 100;
let avgTimePerToken = 0;
if (successCount > 0) {
const totalElapsedSeconds = (Date.now() - this.startTime) / 1000;
avgTimePerToken = totalElapsedSeconds / successCount;
}
return {
successRate: successRate.toFixed(2),
avgTimePerToken: avgTimePerToken.toFixed(2),
totalAttempts: this.results.length,
successfulTokens: successCount
};
}
printStats() {
const stats = this.getStats();
if (!stats) return;
logger.info(`Stats: Success Rate: ${stats.successRate}% | Avg Time/Token: ${stats.avgTimePerToken}s | Total Attempts: ${stats.totalAttempts} | Successful Tokens: ${stats.successfulTokens}`);
}
}
// Add this as a global variable after the ResultTracker class
const resultTracker = new ResultTracker();
// Add this at the top level of the file, after other constants
let currentChromeDataDirIndex = 0;
// Add defaultUserAgents at the top of the file after the imports
const defaultUserAgents = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
];
// Modify extractCapchaTokens function
async function generateCaptchaTokensWith2Captcha({
// Core settings
eventEmitter,
tokensToGenerate = Infinity,
concurrentBrowsers = 6,
tabsPerBrowser = 1,
captchaUrl = 'https://www.google.com/recaptcha/api2/demo',
// Browser settings
browser = {
headless: true,
executablePath: os.platform().startsWith('win')
? "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
: "/usr/bin/google-chrome",
userDataDir: path.join(os.tmpdir(), 'recaptcha-solver-2captcha-chrome-data'),
userAgents: defaultUserAgents // Use defaultUserAgents as default value
},
// Proxy settings
proxy = {
enabled: false,
host: null,
port: null,
username: null,
password: null
},
// 2captcha configuration
"2captcha": config2captcha = {
apiKey: null
},
// Logger configuration
logger: loggerConfig = {
level: 'info' // 'error' | 'warn' | 'info' | 'debug' | 'silent'
}
} = {}) {
// Update the global logger with user config
logger = createLogger({ level: loggerConfig.level });
// Add cleanup call at the start
await cleanupUserDataDirs(browser.userDataDir);
if (!eventEmitter) {
throw new Error('eventEmitter is required');
}
if (!config2captcha.apiKey) {
throw new Error('2captcha API key is required');
}
// Convert proxy config to internal format if enabled
const proxyConfig = proxy.enabled ? {
host: proxy.host,
port: proxy.port,
username: proxy.username,
password: proxy.password
} : null;
logger.info('\n=== Starting 2Captcha Token Generation ===');
logger.info(`Concurrent Browsers: ${concurrentBrowsers}`);
logger.info(`Tabs per Browser: ${tabsPerBrowser}`);
logger.info(`Captcha URL: ${captchaUrl}`);
logger.info('=========================================\n');
let shouldContinue = true;
// Use provided user agents or fall back to default array
const activeUserAgents = browser.userAgents || defaultUserAgents;
while (shouldContinue) {
try {
// Launch browsers with unique data directories
const browsers = await Promise.all(
Array.from({ length: concurrentBrowsers }, async (_, index) => {
await new Promise(resolve => setTimeout(resolve, index * 1000));
currentChromeDataDirIndex = (currentChromeDataDirIndex % 20) + 1;
const chromeDataDir = `${browser.userDataDir}/chrome-user-data-${currentChromeDataDirIndex}`;
return launchBrowser(chromeDataDir, proxyConfig, browser.headless, activeUserAgents, browser);
})
);
try {
const allPromises = [];
let tokensGenerated = 0;
for (let browserIndex = 0; browserIndex < browsers.length; browserIndex++) {
const browser = browsers[browserIndex];
const tabPromises = [];
const remainingTokens = tokensToGenerate - tokensGenerated;
const tabsForThisBrowser = Math.min(tabsPerBrowser, remainingTokens);
for (let tabIndex = 0; tabIndex < tabsForThisBrowser; tabIndex++) {
const tabPromise = (async () => {
let page = null;
try {
page = await browser.newPage();
const result = await attemptCaptcha(page, config2captcha);
if (result && result.token) {
eventEmitter.emit('tokenGenerated', { token: result.token });
resultTracker.addResult({ success: true });
tokensGenerated++;
} else {
eventEmitter.emit('tokenError', { error: 'Failed to get token' });
resultTracker.addResult({ success: false });
}
} catch (error) {
logger.error('Error processing captcha:', error);
resultTracker.addResult({ success: false });
} finally {
if (page) {
await page.close().catch(err =>
logger.error('Error closing page:', err)
);
}
}
})();
tabPromises.push(tabPromise);
await new Promise(resolve => setTimeout(resolve, 500));
}
allPromises.push(...tabPromises);
}
await Promise.all(allPromises);
} finally {
// Close browsers
await Promise.all(browsers.map(browser => browser.close().catch(() => { })));
}
shouldContinue = false;
} catch (error) {
logger.error('Token generation error:', error.message);
resultTracker.addResult({ success: false });
shouldContinue = false;
}
}
}
// Update launchBrowser function to remove request interception setup
async function launchBrowser(userDataDir, proxyConfig = null, headless = true, activeUserAgents, browserConfig) {
const randomProfile = Math.floor(Math.random() * 4) + 1;
const browser = await puppeteerExtra.launch({
headless: headless,
executablePath: browserConfig.executablePath,
userDataDir: userDataDir,
protocolTimeout: 30000,
args: [
'--no-sandbox',
'--disable-gpu',
'--enable-webgl',
'--window-size=1920,1080',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-first-run',
'--no-default-browser-check',
'--password-store=basic',
'--disable-blink-features=AutomationControlled',
'--disable-features=IsolateOrigins,site-per-process',
'--lang=en',
'--disable-web-security',
'--flag-switches-begin --disable-site-isolation-trials --flag-switches-end',
`--profile-directory=Profile ${randomProfile}`,
proxyConfig ? `--proxy-server=${proxyConfig.host}:${proxyConfig.port}` : ''
].filter(Boolean),
ignoreDefaultArgs: ['--enable-automation', '--enable-blink-features=AutomationControlled'],
defaultViewport: null,
});
// Set user agent for all new pages
browser.on('targetcreated', async (target) => {
const page = await target.page();
if (page) {
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
delete navigator.__proto__.webdriver;
});
const randomUserAgent = activeUserAgents[Math.floor(Math.random() * activeUserAgents.length)];
await page.setUserAgent(randomUserAgent);
await page.setDefaultTimeout(30000);
await page.setDefaultNavigationTimeout(30000);
// Set proxy authentication if provided
if (proxyConfig?.username && proxyConfig?.password) {
await page.authenticate({
username: proxyConfig.username,
password: proxyConfig.password
});
}
}
});
return browser;
}
// Update solve2Captcha function to accept userAgent parameter
async function solve2Captcha(sitekey, pageUrl, apiKey, userAgent) {
try {
logger.info('Initiating 2captcha solve request...');
// Get the current page's user agent from the browser
const taskData = {
clientKey: apiKey,
task: {
type: "RecaptchaV2TaskProxyless",
websiteURL: pageUrl,
websiteKey: sitekey,
userAgent: userAgent, // Use the passed userAgent
isInvisible: false
}
};
const createTaskResponse = await axios.post('https://api.2captcha.com/createTask', taskData);
logger.debug('Create task response:', createTaskResponse.data);
if (createTaskResponse.data.errorId !== 0) {
throw new Error(`Failed to create captcha task: ${createTaskResponse.data.errorDescription}`);
}
const taskId = createTaskResponse.data.taskId;
logger.info('Got task ID:', taskId);
// Poll for the result
let attempts = 0;
const maxAttempts = 60;
while (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 10000));
const resultResponse = await axios.post('https://api.2captcha.com/getTaskResult', {
clientKey: apiKey,
taskId: taskId
});
if (resultResponse.data.status === 'ready') {
logger.info('Solution found!');
return resultResponse.data.solution.token;
}
attempts++;
}
throw new Error('Timeout waiting for captcha solution');
} catch (error) {
logger.error('Error in solve2Captcha:', error);
throw error;
}
}
// Add this new function for finding captcha frame
async function findCaptchaFrame(page, timeout = 10000) {
try {
const frame = await page.waitForFrame(frame =>
frame.url().includes('api2/anchor'),
{ timeout }
);
logger.info('Found anchor frame');
const checkbox = await frame.waitForSelector('.recaptcha-checkbox-border', {
visible: true,
timeout
});
const isClickable = await frame.evaluate(() => {
const checkbox = document.querySelector('.recaptcha-checkbox-border');
if (!checkbox) return false;
const style = window.getComputedStyle(checkbox);
return !checkbox.disabled &&
style.visibility !== 'hidden' &&
style.display !== 'none';
});
if (!isClickable) {
logger.warn('Checkbox found but not clickable');
return null;
}
logger.info('Checkbox is ready');
return { frame, checkbox };
} catch (error) {
logger.error('Error finding captcha:', error.message);
return null;
}
}
// Update the solveCaptchaChallenge function
async function solveCaptchaChallenge(page, config2captcha) {
try {
// Get the current page's user agent
const userAgent = await page.evaluate(() => navigator.userAgent);
// Use the frame detection method
const captchaElements = await findCaptchaFrame(page);
if (!captchaElements) {
logger.error('Failed to find captcha elements');
return null;
}
// Extract sitekey directly from the frame URL
const frameUrl = captchaElements.frame.url();
const sitekey = new URL(frameUrl).searchParams.get('k');
logger.info('Sitekey found in frame URL:', sitekey);
if (!sitekey) {
logger.error('Could not find reCAPTCHA sitekey in frame URL');
return null;
}
const pageUrl = page.url();
logger.info('Using page URL:', pageUrl);
logger.info('Using sitekey:', sitekey);
try {
// Pass userAgent to solve2Captcha
const solution = await solve2Captcha(sitekey, pageUrl, config2captcha.apiKey, userAgent);
logger.info('Got solution from 2captcha:', solution.slice(0, 50) + '...');
// Insert the solution
await page.evaluate((token) => {
document.querySelector('#g-recaptcha-response').value = token;
document.querySelector('#g-recaptcha-response').style.display = 'block';
try {
window.___grecaptcha_cfg.clients[0].K.K.callback(token);
} catch (e) {
const form = document.querySelector('form');
if (form) form.submit();
}
}, solution);
return solution;
} catch (error) {
logger.error('Error solving captcha with 2captcha:', error);
return null;
}
} catch (error) {
logger.error('Error in solveCaptchaChallenge:', error);
return null;
}
}
// Update the attemptCaptcha function
async function attemptCaptcha(page, config2captcha) {
try {
logger.info('Loading demo page...');
await page.goto('https://www.google.com/recaptcha/api2/demo', {
waitUntil: 'domcontentloaded',
timeout: 120000
});
// Start timing
const captchaStartTime = Date.now();
// Solve captcha using existing method
logger.info('Attempting to solve captcha...');
const solvedToken = await solveCaptchaChallenge(page, config2captcha);
if (solvedToken) {
const captchaSolveTime = (Date.now() - captchaStartTime) / 1000;
logger.info(`Successfully solved captcha in ${captchaSolveTime.toFixed(2)} seconds`);
return {
status: 'SUCCESS',
token: solvedToken
};
}
logger.warn('Failed to solve captcha');
return null;
} catch (error) {
logger.error('Error processing captcha:', error);
return null;
}
}
module.exports = generateCaptchaTokensWith2Captcha;