using 2captcha api instead of manually solving with tesseract. ke.y is in script but i dont care
This commit is contained in:
+172
-21
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user