using 2captcha api instead of manually solving with tesseract. ke.y is in script but i dont care
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 142 B |
Binary file not shown.
@@ -6,3 +6,10 @@ DATE | TIME | WEBSITE | REASON
|
||||
14/02/2026 | 22:46:37 | Gate6 (https://gate6.vn/) | Navigating frame was detached
|
||||
14/02/2026 | 22:46:37 | Kulala Land (https://kulalaland. | Navigating frame was detached
|
||||
14/02/2026 | 22:46:37 | sneakywholefoods (https://www.sn | Connection closed.
|
||||
14/02/2026 | 23:00:22 | Site_1 (https://jubina.com/list) | Failure keyword detected: "verification"
|
||||
14/02/2026 | 23:04:00 | Site_1 (https://jubina.com/list) | Submit failed: Execution context was destroyed, most likely because of a navigation.
|
||||
15/02/2026 | 00:06:51 | Site_1 (https://jubina.com/list) | Form still visible after submission (likely failed)
|
||||
15/02/2026 | 00:18:09 | Dutch News Test (https://www.dut | Form still visible after submission (likely failed)
|
||||
15/02/2026 | 00:18:27 | Site_1 (https://jubina.com/list) | Form still visible after submission (likely failed)
|
||||
15/02/2026 | 00:18:50 | Site_1 (https://jubina.com/list) | Form still visible after submission (likely failed)
|
||||
15/02/2026 | 00:20:19 | Site_1 (https://jubina.com/list) | Form still visible after submission (likely failed)
|
||||
|
||||
@@ -84,7 +84,7 @@ async function main() {
|
||||
const site = sitesToProcess[i];
|
||||
console.log(`[${i + 1}/${sitesToProcess.length}]`);
|
||||
|
||||
const result = await processSite(browser, site, globalCreds, browserConfig, cliOptions.keepOpen);
|
||||
const result = await processSite(browser, site, globalCreds, browserConfig, cliOptions.keepOpen, cliOptions.dryRun);
|
||||
|
||||
if (result.siteFailed) {
|
||||
failedSites.push({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
https://immaculatevegan.com/blogs/magazine
|
||||
https://gate6.vn/
|
||||
https://www.sprintzeal.com/blog
|
||||
https://www.sprintzeal.com/
|
||||
https://kulalaland.com/
|
||||
https://www.goplaces.co.uk/
|
||||
https://www.fiftyfiftybottles.com/
|
||||
|
||||
Generated
+20
@@ -10,9 +10,11 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"2captcha": "^3.0.5-2",
|
||||
"commander": "^14.0.3",
|
||||
"playwright": "^1.56.1",
|
||||
"playwright-extra": "^4.3.6",
|
||||
"pngjs": "^7.0.0",
|
||||
"puppeteer": "^24.30.0",
|
||||
"puppeteer-extra": "^3.3.6",
|
||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||
@@ -110,6 +112,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/2captcha": {
|
||||
"version": "3.0.5-2",
|
||||
"resolved": "https://registry.npmjs.org/2captcha/-/2captcha-3.0.5-2.tgz",
|
||||
"integrity": "sha512-p/C1UFss2PvBxYjypuRZ4zE1n3C5VK7hnNijnTetF9sKg5iifF6xfos3h3CJQLGB//0Ksa1C77ItM3gOhHy7fA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
@@ -1186,6 +1197,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"2captcha": "^3.0.5-2",
|
||||
"commander": "^14.0.3",
|
||||
"playwright": "^1.56.1",
|
||||
"playwright-extra": "^4.3.6",
|
||||
"pngjs": "^7.0.0",
|
||||
"puppeteer": "^24.30.0",
|
||||
"puppeteer-extra": "^3.3.6",
|
||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||
|
||||
+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
|
||||
};
|
||||
|
||||
+24
-20
@@ -10,36 +10,39 @@ function createCLI() {
|
||||
.option('-f, --file <path>', 'Load sites from text file (one URL per line)')
|
||||
.option('-k, --keep-open', 'Keep browser tabs open after processing for verification')
|
||||
.option('-l, --log', 'Save output to timestamped log file')
|
||||
.option('-d, --dry-run', 'Test mode - detect CAPTCHA but skip solving (no API calls)')
|
||||
.option('-h, --help', 'Show help message')
|
||||
.argument('[target]', 'Site name or URL to process (filters from config.toml sites)');
|
||||
|
||||
program.on('option:help', () => {
|
||||
console.log(`
|
||||
Newsletter Signup Automation Tool
|
||||
Newsletter Signup Automation Tool
|
||||
|
||||
Usage: node index.js [options] [site-name-or-url]
|
||||
Usage: node index.js [options] [site-name-or-url]
|
||||
|
||||
Options:
|
||||
-f, --file=<path> Load sites from text file (one URL per line)
|
||||
-k, --keep-open Keep browser tabs open after processing for verification
|
||||
-l, --log Save output to timestamped log file
|
||||
-h, --help Show this help message
|
||||
Options:
|
||||
-f, --file=<path> Load sites from text file (one URL per line)
|
||||
-k, --keep-open Keep browser tabs open after processing for verification
|
||||
-l, --log Save output to timestamped log file
|
||||
-d, --dry-run Test mode - detect CAPTCHA but skip solving (no API calls)
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
node index.js Process all sites from config.toml
|
||||
node index.js --file=sites.txt Process sites from file
|
||||
node index.js "Dutch News" Process specific site
|
||||
node index.js --file=sites.txt --keep-open Keep tabs open for verification
|
||||
node index.js --log Save output to log file
|
||||
Examples:
|
||||
node index.js Process all sites from config.toml
|
||||
node index.js --file=sites.txt Process sites from file
|
||||
node index.js "Dutch News" Process specific site
|
||||
node index.js --file=sites.txt --keep-open Keep tabs open for verification
|
||||
node index.js --log Save output to log file
|
||||
node index.js --dry-run Test without calling 2Captcha API
|
||||
|
||||
Text file format (sites.txt):
|
||||
# Lines starting with # are comments
|
||||
https://example.com/newsletter
|
||||
https://another-site.com/signup
|
||||
Text file format (sites.txt):
|
||||
# Lines starting with # are comments
|
||||
https://example.com/newsletter
|
||||
https://another-site.com/signup
|
||||
|
||||
Configuration:
|
||||
Edit config.toml to set credentials and default sites
|
||||
`);
|
||||
Configuration:
|
||||
Edit config.toml to set credentials and default sites
|
||||
`);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
@@ -59,6 +62,7 @@ function parseCLI(args = process.argv.slice(2)) {
|
||||
file: opts.file || null,
|
||||
keepOpen: opts.keepOpen || false,
|
||||
log: opts.log || false,
|
||||
dryRun: opts.dryRun || false,
|
||||
help: opts.help || false
|
||||
};
|
||||
}
|
||||
|
||||
+93
-29
@@ -3,8 +3,9 @@ const { detectFormFields } = require('./fields');
|
||||
const { detectSubmitButton } = require('./submit');
|
||||
const { detectCaptcha, solveSimpleCaptcha } = require('./captcha');
|
||||
const { classifyNewsletterForm } = require('./classifier');
|
||||
const { detectSubmissionResult } = require('./submission');
|
||||
|
||||
async function processSite(browser, site, globalCreds, browserConfig, keepOpen = false) {
|
||||
async function processSite(browser, site, globalCreds, browserConfig, keepOpen = false, dryRun = false) {
|
||||
let siteFailed = false;
|
||||
let failureReason = '';
|
||||
let page = null;
|
||||
@@ -42,38 +43,66 @@ async function processSite(browser, site, globalCreds, browserConfig, keepOpen =
|
||||
}
|
||||
|
||||
if (captchaInfo.some(c => c.type === 'mathCaptcha')) {
|
||||
const solution = await solveSimpleCaptcha(page);
|
||||
if (solution && solution.answer) {
|
||||
console.log(` Auto-solving math: ${solution.text} = ${solution.answer}`);
|
||||
try {
|
||||
await page.evaluate((answer) => {
|
||||
const input = document.querySelector('input[name*="captcha"], input[class*="captcha"], #captcha');
|
||||
if (input) {
|
||||
input.value = answer;
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
}, solution.answer);
|
||||
} catch (e) {
|
||||
console.warn(` Failed to fill math answer: ${e.message}`);
|
||||
if (dryRun) {
|
||||
console.log(` [DRY-RUN] Would solve math CAPTCHA`);
|
||||
} else {
|
||||
const solution = await solveSimpleCaptcha(page);
|
||||
if (solution && solution.answer) {
|
||||
console.log(` Auto-solving math: ${solution.text} = ${solution.answer}`);
|
||||
try {
|
||||
await page.evaluate((answer) => {
|
||||
const input = document.querySelector('input[name*="captcha"], input[class*="captcha"], #captcha');
|
||||
if (input) {
|
||||
input.value = answer;
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
}, solution.answer);
|
||||
} catch (e) {
|
||||
console.warn(` Failed to fill math answer: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (captchaInfo.some(c => c.type === 'imageCaptcha')) {
|
||||
const solution = await solveSimpleCaptcha(page);
|
||||
if (solution && solution.type === 'image' && solution.answer) {
|
||||
console.log(` OCR solved image CAPTCHA: "${solution.answer}"`);
|
||||
try {
|
||||
await page.evaluate((answer) => {
|
||||
const input = document.querySelector('input[name*="captcha"], input[class*="captcha"], input[id*="captcha"], #captcha, .captcha-input');
|
||||
if (input) {
|
||||
input.value = answer;
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
}, solution.answer);
|
||||
} catch (e) {
|
||||
console.warn(` Failed to fill OCR answer: ${e.message}`);
|
||||
if (captchaInfo.some(c => c.type === 'textCaptcha')) {
|
||||
console.log(` Text CAPTCHA detected - looking for input field...`);
|
||||
const textCaptchaFilled = await page.evaluate(() => {
|
||||
const input = document.querySelector('input[name*="captcha"], input[id*="captcha"], .captcha-input, #captcha, input[placeholder*="captcha" i]');
|
||||
if (input) {
|
||||
return {
|
||||
found: true,
|
||||
selector: input.id ? `#${input.id}` : (input.name ? `[name="${input.name}"]` : 'input[name*="captcha"]')
|
||||
};
|
||||
}
|
||||
return { found: false };
|
||||
});
|
||||
if (textCaptchaFilled.found) {
|
||||
console.log(` Text CAPTCHA input found: ${textCaptchaFilled.selector}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (captchaInfo.some(c => c.type === 'imageCaptcha' || c.type === 'textCaptcha')) {
|
||||
if (dryRun) {
|
||||
console.log(` [DRY-RUN] Would solve image/text CAPTCHA (no API calls)`);
|
||||
} else {
|
||||
const solution = await solveSimpleCaptcha(page);
|
||||
if (solution && solution.answer) {
|
||||
console.log(` OCR solved CAPTCHA: "${solution.answer}"`);
|
||||
try {
|
||||
await page.evaluate((answer) => {
|
||||
const input = document.querySelector('input[name*="captcha"], input[class*="captcha"], input[id*="captcha"], #captcha, .captcha-input');
|
||||
if (input) {
|
||||
input.value = answer;
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
console.log(` Filled CAPTCHA with: ${answer}`);
|
||||
}
|
||||
}, solution.answer);
|
||||
} catch (e) {
|
||||
console.warn(` Failed to fill CAPTCHA answer: ${e.message}`);
|
||||
}
|
||||
} else if (solution && solution.message) {
|
||||
console.log(` CAPTCHA solving failed: ${solution.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,8 +219,43 @@ async function processSite(browser, site, globalCreds, browserConfig, keepOpen =
|
||||
|
||||
if (submitSelector) {
|
||||
try {
|
||||
const urlBeforeSubmit = page.url();
|
||||
await page.click(submitSelector);
|
||||
console.log(` Submitted (${submitSelector})`);
|
||||
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
|
||||
let submissionResult;
|
||||
try {
|
||||
const urlAfterSubmit = page.url();
|
||||
if (urlAfterSubmit !== urlBeforeSubmit) {
|
||||
console.log(` Page navigated to: ${urlAfterSubmit}`);
|
||||
}
|
||||
|
||||
submissionResult = await detectSubmissionResult(page);
|
||||
} catch (resultError) {
|
||||
console.log(` Could not detect submission result: ${resultError.message}`);
|
||||
submissionResult = { failure: false, success: false, signals: ['Could not determine - page may have redirected'] };
|
||||
}
|
||||
|
||||
console.log(`\n Submission Result Analysis:`);
|
||||
|
||||
if (submissionResult.signals && submissionResult.signals.length > 0) {
|
||||
for (const signal of submissionResult.signals) {
|
||||
console.log(` - ${signal}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (submissionResult.failure) {
|
||||
siteFailed = true;
|
||||
failureReason = failureReason || submissionResult.failureReason || 'Form submission failed';
|
||||
console.error(` FAILED: ${submissionResult.failureReason}`);
|
||||
} else if (submissionResult.success) {
|
||||
console.log(` SUCCESS: Subscription appears to have succeeded`);
|
||||
} else {
|
||||
console.log(` Result: Unknown (page may have redirected to confirmation)`);
|
||||
}
|
||||
|
||||
} catch (submitError) {
|
||||
siteFailed = true;
|
||||
failureReason = failureReason || `Submit failed: ${submitError.message}`;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
async function detectSubmissionResult(page) {
|
||||
const result = await page.evaluate(() => {
|
||||
const results = {
|
||||
success: false,
|
||||
failure: false,
|
||||
failureReason: null,
|
||||
currentUrl: window.location.href,
|
||||
signals: []
|
||||
};
|
||||
|
||||
const errorSelectors = [
|
||||
'.error', '.Error', '.ERROR',
|
||||
'[class*="error" i]',
|
||||
'.alert-danger', '.alert-error',
|
||||
'[class*="failed" i]',
|
||||
'[class*="invalid" i]',
|
||||
'[class*="wrong" i]',
|
||||
'#error', '#Error',
|
||||
'[role="alert"]',
|
||||
'.message.error',
|
||||
'.error-message',
|
||||
'.form-error',
|
||||
'[data-form-error]'
|
||||
];
|
||||
|
||||
for (const selector of errorSelectors) {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) {
|
||||
const text = el.textContent?.toLowerCase() || '';
|
||||
const style = window.getComputedStyle(el);
|
||||
if (style.display !== 'none' &&
|
||||
style.visibility !== 'hidden' &&
|
||||
style.opacity !== '0' &&
|
||||
text.length > 0 &&
|
||||
text.length < 500) {
|
||||
results.failure = true;
|
||||
results.failureReason = `Error message detected: ${text.substring(0, 100)}`;
|
||||
results.signals.push(`Error: ${text.substring(0, 100)}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pageText = document.body.innerText.toLowerCase();
|
||||
|
||||
const successKeywords = [
|
||||
'thank you', 'thanks for', 'successfully', 'subscribed', 'subscription confirmed',
|
||||
'you are now', 'welcome', 'email confirmed', 'verification sent',
|
||||
'check your email', 'confirm your email', 'almost done',
|
||||
'signed up', 'signed up successfully', 'subscription successful'
|
||||
];
|
||||
|
||||
for (const keyword of successKeywords) {
|
||||
if (pageText.includes(keyword)) {
|
||||
results.success = true;
|
||||
results.signals.push(`Success keyword: "${keyword}"`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const failureKeywords = [
|
||||
'incorrect', 'invalid captcha', 'wrong captcha', 'incorrect captcha',
|
||||
'captcha incorrect', 'captcha invalid', 'captcha error',
|
||||
'please complete the captcha', 'complete the captcha',
|
||||
'try again', 'submission failed'
|
||||
];
|
||||
|
||||
if (!results.failure) {
|
||||
for (const keyword of failureKeywords) {
|
||||
if (pageText.includes(keyword)) {
|
||||
results.failure = true;
|
||||
results.failureReason = `Failure keyword detected: "${keyword}"`;
|
||||
results.signals.push(`Failure: ${keyword}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const formStillVisible = document.querySelector('form') !== null;
|
||||
const emailInputStillVisible = document.querySelector('input[type="email"]') !== null ||
|
||||
document.querySelector('input[name*="email" i]') !== null;
|
||||
|
||||
if (formStillVisible && emailInputStillVisible && !results.success) {
|
||||
results.failure = true;
|
||||
results.failureReason = results.failureReason || 'Form still visible after submission (likely failed)';
|
||||
results.signals.push('Form still visible - submission may have failed');
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
detectSubmissionResult
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
https://jubina.com/list
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 328 B |
Reference in New Issue
Block a user