using 2captcha api instead of manually solving with tesseract. ke.y is in script but i dont care

This commit is contained in:
2026-02-15 12:11:31 +01:00
parent ddb712ba4a
commit 4ed259ca5a
15 changed files with 419 additions and 72 deletions
+172 -21
View File
@@ -1,4 +1,9 @@
const Tesseract = require('tesseract.js');
const https = require('https');
const http = require('http');
const TwoCaptcha = require('2captcha');
const solver = new TwoCaptcha.Solver('9ed0ef51badf9a017ac50aea413d8001');
const CAPTCHA_PATTERNS = {
recaptcha: {
@@ -23,6 +28,18 @@ const CAPTCHA_PATTERNS = {
}
};
function fetchImageAsBuffer(url) {
return new Promise((resolve, reject) => {
const client = url.startsWith('https') ? https : http;
client.get(url, (res) => {
const chunks = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => resolve(Buffer.concat(chunks)));
res.on('error', reject);
}).on('error', reject);
});
}
async function detectCaptcha(page) {
const captchaInfo = await page.evaluate((patterns) => {
const results = [];
@@ -103,30 +120,163 @@ async function solveSimpleCaptcha(page) {
});
if (imageCaptchaInfo) {
console.log(' Attempting OCR-based CAPTCHA solving...');
try {
const { data: { text } } = await Tesseract.recognize(
imageCaptchaInfo.src,
'eng',
{ logger: m => {} }
);
console.log(' Attempting 2Captcha image solving...');
console.log(` CAPTCHA image selector: ${imageCaptchaInfo.selector}`);
// Wait for CAPTCHA image to fully load before capturing
console.log(' Waiting for CAPTCHA image to load...');
let imageLoaded = false;
const maxWaitMs = 5000;
const checkIntervalMs = 200;
let waitedMs = 0;
while (!imageLoaded && waitedMs < maxWaitMs) {
imageLoaded = await page.evaluate((selector) => {
const el = document.querySelector(selector);
return el && el.naturalWidth > 0 && el.naturalHeight > 0;
}, imageCaptchaInfo.selector).catch(() => false);
const cleanedText = text.replace(/[^a-zA-Z0-9]/g, '').trim();
if (cleanedText && cleanedText.length >= 3) {
console.log(` OCR read: "${cleanedText}"`);
return {
type: 'image',
answer: cleanedText,
originalText: text,
imageSrc: imageCaptchaInfo.src
};
if (!imageLoaded) {
await new Promise(r => setTimeout(r, checkIntervalMs));
waitedMs += checkIntervalMs;
}
} catch (ocrError) {
console.log(` OCR failed: ${ocrError.message}`);
}
return { type: 'image', message: 'Image CAPTCHA detected - OCR failed, manual solving required' };
if (imageLoaded) {
console.log(` CAPTCHA image loaded successfully (waited ${waitedMs}ms)`);
} else {
console.log(' WARNING: CAPTCHA may not have fully loaded, proceeding anyway...');
}
// Additional small delay to ensure rendering is complete
await new Promise(r => setTimeout(r, 300));
try {
let imageData;
// Get CAPTCHA element bounding box
const captchaInfo = await page.evaluate((selector) => {
const img = document.querySelector(selector);
if (!img) return null;
const rect = img.getBoundingClientRect();
return {
src: img.src,
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height
};
}, imageCaptchaInfo.selector);
if (captchaInfo && captchaInfo.width > 0) {
console.log(' CAPTCHA at:', captchaInfo.x, captchaInfo.y, captchaInfo.width, 'x', captchaInfo.height);
// Try to get element screenshot directly
try {
const captchaElement = await page.$(imageCaptchaInfo.selector);
if (captchaElement) {
const elementScreenshot = await captchaElement.screenshot({ type: 'png' });
if (Buffer.isBuffer(elementScreenshot)) {
imageData = elementScreenshot.toString('base64');
} else {
imageData = Buffer.from(elementScreenshot).toString('base64');
}
console.log(' Element screenshot captured, size:', imageData.length, 'bytes (base64)');
}
} catch (elemError) {
console.log(' Element screenshot failed:', elemError.message);
// Fallback: capture full page and crop
const fullScreenshot = await page.screenshot({ type: 'png', encoding: 'binary' });
const screenshotBuffer = Buffer.from(fullScreenshot, 'binary');
const png = require('pngjs').PNG.sync.read(screenshotBuffer);
const cropX = Math.max(0, Math.floor(captchaInfo.x));
const cropY = Math.max(0, Math.floor(captchaInfo.y));
const cropW = Math.floor(captchaInfo.width);
const cropH = Math.floor(captchaInfo.height);
console.log(' Cropping to:', cropX, cropY, cropW, cropH);
const cropped = new require('pngjs').PNG({ width: cropW, height: cropH });
for (let y = 0; y < cropH; y++) {
for (let x = 0; x < cropW; x++) {
const srcIdx = ((cropY + y) * png.width + (cropX + x)) * 4;
const dstIdx = (y * cropW + x) * 4;
cropped.data[dstIdx] = png.data[srcIdx];
cropped.data[dstIdx + 1] = png.data[srcIdx + 1];
cropped.data[dstIdx + 2] = png.data[srcIdx + 2];
cropped.data[dstIdx + 3] = png.data[srcIdx + 3];
}
}
imageData = require('pngjs').PNG.sync.write(cropped).toString('base64');
console.log(' Cropped image size:', imageData.length, 'bytes (base64)');
}
} else if (captchaInfo && imageCaptchaInfo.src.startsWith('data:')) {
const base64 = imageCaptchaInfo.src.replace(/^data:image\/\w+;base64,/, '');
imageData = base64;
}
console.log(' Sending to 2Captcha (human workers)...');
const result = await solver.imageCaptcha(imageData, {
numeric: 0,
minLength: 1,
maxLength: 10,
phrase: 0,
caseSensitive: 1,
calc: 0,
lang: 'en'
});
console.log(` 2Captcha solved: "${result.data}"`);
return {
type: 'image',
answer: result.data.trim(),
originalText: result.data,
service: '2captcha',
imageSrc: imageCaptchaInfo.src
};
} catch (error) {
console.log(` 2Captcha failed: ${error.message}`);
console.log(' Falling back to Tesseract OCR...');
try {
const { data: { text, confidence } } = await Tesseract.recognize(
imageCaptchaInfo.src,
'eng',
{ logger: m => {} }
);
console.log(` OCR raw output: "${text}"`);
console.log(` OCR confidence: ${confidence}%`);
const cleanedText = text.replace(/[^a-zA-Z0-9]/g, '').trim();
if (cleanedText && cleanedText.length >= 3) {
console.log(` OCR fallback read: "${cleanedText}"`);
return {
type: 'image',
answer: cleanedText,
originalText: text,
confidence: confidence,
service: 'tesseract-fallback',
imageSrc: imageCaptchaInfo.src
};
}
} catch (ocrError) {
console.log(` OCR fallback also failed: ${ocrError.message}`);
}
return {
type: 'image',
answer: null,
message: `Both 2Captcha and OCR failed: ${error.message}`,
imageSrc: imageCaptchaInfo.src
};
}
}
return null;
@@ -135,5 +285,6 @@ async function solveSimpleCaptcha(page) {
module.exports = {
CAPTCHA_PATTERNS,
detectCaptcha,
solveSimpleCaptcha
solveSimpleCaptcha,
fetchImageAsBuffer
};