changed to individual files instead one big ass script and added auto captcha solver functionality
This commit is contained in:
@@ -1,437 +1,46 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const toml = require('@iarna/toml');
|
||||
const puppeteer = require('puppeteer-extra');
|
||||
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
||||
puppeteer.use(StealthPlugin());
|
||||
|
||||
const FIELD_PATTERNS = {
|
||||
email: {
|
||||
priority: 1,
|
||||
types: ['email'],
|
||||
names: ['email', 'e-mail', 'mail', 'user_email', 'login_email', 'email_address', 'emailaddress'],
|
||||
ids: ['email', 'e-mail', 'mail', 'user-email', 'login-email'],
|
||||
placeholders: ['email', 'e-mail', 'email address', 'your email', 'enter email'],
|
||||
autocomplete: ['email']
|
||||
},
|
||||
confirmEmail: {
|
||||
priority: 2,
|
||||
types: ['email'],
|
||||
names: ['confirm_email', 'confirmemail', 'email_confirm', 'verify_email', 'email_verify'],
|
||||
ids: ['confirm-email', 'confirmemail', 'verify-email', 'email-confirm'],
|
||||
placeholders: ['confirm email', 'verify email', 're-enter email', 'repeat email'],
|
||||
mustHaveConfirmIndicator: true
|
||||
},
|
||||
firstName: {
|
||||
priority: 3,
|
||||
types: ['text'],
|
||||
names: ['first_name', 'firstname', 'fname', 'first-name', 'vorname', 'prenom', 'fname'],
|
||||
ids: ['first-name', 'firstname', 'fname', 'firstName', 'mce-fname'],
|
||||
placeholders: ['first name', 'firstname', 'first'],
|
||||
autocomplete: ['given-name', 'first-name']
|
||||
},
|
||||
lastName: {
|
||||
priority: 4,
|
||||
types: ['text'],
|
||||
names: ['last_name', 'lastname', 'lname', 'surname', 'family_name', 'last-name', 'lname'],
|
||||
ids: ['last-name', 'lastname', 'lname', 'surname', 'lastName', 'mce-lname'],
|
||||
placeholders: ['last name', 'lastname', 'surname', 'last'],
|
||||
autocomplete: ['family-name', 'last-name']
|
||||
},
|
||||
username: {
|
||||
priority: 5,
|
||||
types: ['text'],
|
||||
names: ['username', 'user_name', 'login', 'usr', 'uname', 'user-id', 'userid', 'account_name', 'login_id'],
|
||||
ids: ['username', 'user-name', 'login', 'usr', 'uname'],
|
||||
placeholders: ['username', 'user name', 'login', 'username or email'],
|
||||
autocomplete: ['username']
|
||||
},
|
||||
password: {
|
||||
priority: 6,
|
||||
types: ['password'],
|
||||
names: ['password', 'pass', 'pwd', 'user_password', 'login_password'],
|
||||
ids: ['password', 'pass', 'pwd'],
|
||||
placeholders: ['password', 'pass', 'enter password'],
|
||||
autocomplete: ['current-password', 'new-password']
|
||||
},
|
||||
fullName: {
|
||||
priority: 7,
|
||||
types: ['text'],
|
||||
names: ['full_name', 'fullname', 'name'],
|
||||
ids: ['full-name', 'fullname', 'name'],
|
||||
placeholders: ['full name', 'your name', 'complete name'],
|
||||
autocomplete: ['name']
|
||||
},
|
||||
phone: {
|
||||
priority: 8,
|
||||
types: ['tel', 'text'],
|
||||
names: ['phone', 'telephone', 'mobile', 'cell', 'cellphone', 'phone_number'],
|
||||
ids: ['phone', 'telephone', 'mobile', 'cell', 'tel'],
|
||||
placeholders: ['phone', 'telephone', 'mobile number'],
|
||||
autocomplete: ['tel']
|
||||
},
|
||||
age: {
|
||||
priority: 9,
|
||||
types: ['number', 'text'],
|
||||
names: ['age', 'user_age'],
|
||||
ids: ['age'],
|
||||
placeholders: ['age', 'your age']
|
||||
},
|
||||
company: {
|
||||
priority: 10,
|
||||
types: ['text'],
|
||||
names: ['company', 'organization', 'company_name', 'employer'],
|
||||
ids: ['company', 'organization', 'employer'],
|
||||
placeholders: ['company', 'organization'],
|
||||
autocomplete: ['organization']
|
||||
}
|
||||
};
|
||||
|
||||
function loadConfig() {
|
||||
try {
|
||||
const configPath = path.join(__dirname, 'config.toml');
|
||||
const tomlString = fs.readFileSync(configPath, 'utf-8');
|
||||
const config = toml.parse(tomlString);
|
||||
console.log('✅ Configuration loaded successfully.');
|
||||
return config;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load or parse config.toml:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function generateRandomData() {
|
||||
const randomString = Math.random().toString(36).substring(2, 10);
|
||||
const username = `user${randomString}`;
|
||||
const email = `${username}@gmail.com`;
|
||||
const password = `pass${randomString}`;
|
||||
const age = Math.floor(Math.random() * 30 + 20).toString();
|
||||
const firstName = 'John';
|
||||
const lastName = 'Doe';
|
||||
const phone = '+31610488717';
|
||||
const company = 'Example Corp';
|
||||
return { username, email, password, age, firstName, lastName, phone, company };
|
||||
}
|
||||
|
||||
function parseLocator(locatorStr) {
|
||||
locatorStr = locatorStr.trim();
|
||||
if (!locatorStr.startsWith('<')) {
|
||||
return locatorStr;
|
||||
}
|
||||
|
||||
const getAttr = (attr) => {
|
||||
const match = locatorStr.match(new RegExp(`${attr}=["']([^"']+)["']`));
|
||||
return match ? match[1] : null;
|
||||
};
|
||||
|
||||
const tagName = (locatorStr.match(/^<([a-zA-Z0-9]+)/) || [])[1] || '';
|
||||
const id = getAttr('id');
|
||||
if (id) {
|
||||
if (/^\d/.test(id)) {
|
||||
return `${tagName}[id='${id}']`;
|
||||
}
|
||||
return `${tagName}#${id}`;
|
||||
}
|
||||
|
||||
const name = getAttr('name');
|
||||
if (name) return `${tagName}[name='${name}']`;
|
||||
|
||||
const placeholder = getAttr('placeholder');
|
||||
if (placeholder) return `${tagName}[placeholder='${placeholder}']`;
|
||||
|
||||
const type = getAttr('type');
|
||||
if (type) return `${tagName}[type='${type}']`;
|
||||
|
||||
return tagName;
|
||||
}
|
||||
|
||||
async function detectFormFields(page) {
|
||||
console.log('🔍 Auto-detecting form fields...');
|
||||
|
||||
const detectedFields = await page.evaluate((patterns) => {
|
||||
const results = {};
|
||||
const usedElements = new Set();
|
||||
|
||||
// Get all visible input fields, excluding hidden fields
|
||||
const inputs = Array.from(document.querySelectorAll('input, select, textarea')).filter(input => {
|
||||
const type = input.type?.toLowerCase() || 'text';
|
||||
const style = window.getComputedStyle(input);
|
||||
const rect = input.getBoundingClientRect();
|
||||
|
||||
// Skip hidden fields and honeypots
|
||||
if (type === 'hidden' || type === 'submit' || type === 'button') return false;
|
||||
if (input.tabIndex === -1 && input.style.position === 'absolute') return false; // Likely honeypot
|
||||
if (rect.width === 0 || rect.height === 0) return false;
|
||||
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const calculateScore = (input, pattern, fieldName) => {
|
||||
let score = 0;
|
||||
const inputType = (input.type || 'text').toLowerCase();
|
||||
const inputName = (input.name || '').toLowerCase();
|
||||
const inputId = (input.id || '').toLowerCase();
|
||||
const placeholder = (input.placeholder || '').toLowerCase();
|
||||
const autocomplete = (input.autocomplete || '').toLowerCase();
|
||||
|
||||
// Type matching
|
||||
if (pattern.types && pattern.types.includes(inputType)) {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
// Name matching - exact matches get high scores
|
||||
if (pattern.names) {
|
||||
for (const name of pattern.names) {
|
||||
const nameLower = name.toLowerCase();
|
||||
if (inputName === nameLower) {
|
||||
score += 20; // Exact match
|
||||
break;
|
||||
} else if (inputName.includes(nameLower)) {
|
||||
// Partial match - be careful not to match fname with username
|
||||
if (fieldName === 'firstName' && (inputName.includes('user') || inputName.includes('login'))) {
|
||||
score -= 5; // Penalize if it looks like username
|
||||
} else if (fieldName === 'lastName' && (inputName.includes('user') || inputName.includes('login'))) {
|
||||
score -= 5;
|
||||
} else {
|
||||
score += 10;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ID matching - very specific
|
||||
if (pattern.ids) {
|
||||
for (const id of pattern.ids) {
|
||||
const idLower = id.toLowerCase();
|
||||
if (inputId === idLower) {
|
||||
score += 20; // Exact match
|
||||
break;
|
||||
} else if (inputId.includes(idLower)) {
|
||||
score += 10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder matching
|
||||
if (pattern.placeholders) {
|
||||
for (const ph of pattern.placeholders) {
|
||||
const phLower = ph.toLowerCase();
|
||||
if (placeholder === phLower) {
|
||||
score += 15;
|
||||
break;
|
||||
} else if (placeholder.includes(phLower)) {
|
||||
score += 8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Autocomplete matching
|
||||
if (pattern.autocomplete) {
|
||||
for (const ac of pattern.autocomplete) {
|
||||
if (autocomplete === ac.toLowerCase()) {
|
||||
score += 12;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check label text
|
||||
const labels = input.labels;
|
||||
if (labels && labels.length > 0) {
|
||||
const labelText = labels[0].textContent.toLowerCase();
|
||||
const fieldNameLower = fieldName.toLowerCase().replace(/([A-Z])/g, ' $1').trim();
|
||||
if (labelText.includes(fieldNameLower) || labelText.includes(pattern.names?.[0] || '')) {
|
||||
score += 15;
|
||||
}
|
||||
}
|
||||
|
||||
// Penalize if field appears to be a honeypot
|
||||
if (inputName.includes('b_') || inputId.includes('b_')) {
|
||||
score -= 30; // Likely Mailchimp honeypot
|
||||
}
|
||||
|
||||
// Penalize disabled/readonly
|
||||
if (input.disabled || input.readOnly) {
|
||||
score -= 20;
|
||||
}
|
||||
|
||||
// Special handling for confirmEmail
|
||||
if (pattern.mustHaveConfirmIndicator) {
|
||||
const hasConfirm = inputId.includes('confirm') || inputId.includes('verify') ||
|
||||
inputName.includes('confirm') || inputName.includes('verify') ||
|
||||
placeholder.includes('confirm') || placeholder.includes('verify') ||
|
||||
placeholder.includes('re-') || placeholder.includes('again');
|
||||
if (!hasConfirm) {
|
||||
score -= 15;
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
// Sort patterns by priority
|
||||
const sortedPatterns = Object.entries(patterns).sort((a, b) => a[1].priority - b[1].priority);
|
||||
|
||||
for (const [fieldName, pattern] of sortedPatterns) {
|
||||
let bestMatch = null;
|
||||
let bestScore = 0;
|
||||
let bestIndex = -1;
|
||||
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
if (usedElements.has(i)) continue;
|
||||
|
||||
const input = inputs[i];
|
||||
const score = calculateScore(input, pattern, fieldName);
|
||||
|
||||
if (score > bestScore && score >= 10) {
|
||||
bestScore = score;
|
||||
bestMatch = input;
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch && bestIndex >= 0) {
|
||||
usedElements.add(bestIndex);
|
||||
|
||||
// Generate selector
|
||||
let selector = '';
|
||||
if (bestMatch.id) {
|
||||
selector = `#${bestMatch.id}`;
|
||||
} else if (bestMatch.name) {
|
||||
selector = `[name="${bestMatch.name}"]`;
|
||||
} else if (bestMatch.placeholder) {
|
||||
selector = `[placeholder="${bestMatch.placeholder}"]`;
|
||||
} else {
|
||||
const tagName = bestMatch.tagName.toLowerCase();
|
||||
const type = bestMatch.type ? `[type="${bestMatch.type}"]` : '';
|
||||
const allSame = Array.from(document.querySelectorAll(`${tagName}${type}`));
|
||||
const index = allSame.indexOf(bestMatch) + 1;
|
||||
selector = `${tagName}${type}:nth-of-type(${index})`;
|
||||
}
|
||||
|
||||
results[fieldName] = {
|
||||
selector,
|
||||
confidence: bestScore,
|
||||
name: bestMatch.name,
|
||||
id: bestMatch.id
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}, FIELD_PATTERNS);
|
||||
|
||||
const detectedCount = Object.keys(detectedFields).length;
|
||||
if (detectedCount === 0) {
|
||||
console.log(' No fields detected');
|
||||
}
|
||||
|
||||
return detectedFields;
|
||||
}
|
||||
|
||||
async function detectSubmitButton(page) {
|
||||
console.log('🔍 Auto-detecting submit button...');
|
||||
|
||||
const submitSelector = await page.evaluate(() => {
|
||||
const buttons = Array.from(document.querySelectorAll('button[type="submit"], input[type="submit"], button, input[type="button"]'));
|
||||
let bestMatch = null;
|
||||
let bestScore = 0;
|
||||
|
||||
buttons.forEach(btn => {
|
||||
const text = (btn.textContent || btn.value || '').toLowerCase().trim();
|
||||
const type = btn.type?.toLowerCase() || '';
|
||||
const className = (btn.className || '').toLowerCase();
|
||||
let score = 0;
|
||||
|
||||
if (type === 'submit') score += 20;
|
||||
|
||||
if (text.includes('subscribe')) score += 15;
|
||||
else if (text.includes('sign up')) score += 12;
|
||||
else if (text.includes('submit')) score += 10;
|
||||
else if (text.includes('join')) score += 8;
|
||||
else if (text.includes('register')) score += 8;
|
||||
else if (text.includes('send')) score += 5;
|
||||
|
||||
if (className.includes('submit')) score += 8;
|
||||
if (className.includes('subscribe')) score += 8;
|
||||
|
||||
if (className.includes('close') || text.includes('close')) score -= 20;
|
||||
if (className.includes('cancel') || text.includes('cancel')) score -= 20;
|
||||
if (className.includes('back') || text.includes('back')) score -= 15;
|
||||
if (className.includes('drawer')) score -= 15;
|
||||
|
||||
const rect = btn.getBoundingClientRect();
|
||||
if (rect.width > 0 && rect.height > 0 && rect.top > 0) {
|
||||
score += 3;
|
||||
}
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMatch = btn;
|
||||
}
|
||||
});
|
||||
|
||||
if (bestMatch && bestScore >= 10) {
|
||||
if (bestMatch.id) return `#${bestMatch.id}`;
|
||||
if (bestMatch.name) return `[name="${bestMatch.name}"]`;
|
||||
|
||||
const className = bestMatch.className;
|
||||
if (className) {
|
||||
const classes = className.split(' ').filter(c => c.length > 0);
|
||||
for (const cls of classes) {
|
||||
const clsLower = cls.toLowerCase();
|
||||
if (clsLower.includes('submit') || clsLower.includes('subscribe')) {
|
||||
return `.${cls}`;
|
||||
}
|
||||
}
|
||||
for (const cls of classes) {
|
||||
if (!cls.toLowerCase().includes('close') && !cls.toLowerCase().includes('cancel')) {
|
||||
return `.${cls}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allBtns = Array.from(document.querySelectorAll('button'));
|
||||
const index = allBtns.indexOf(bestMatch) + 1;
|
||||
return `button:nth-of-type(${index})`;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (submitSelector) {
|
||||
console.log(` ✅ Submit button found: ${submitSelector}`);
|
||||
} else {
|
||||
console.log(' ❌ No submit button found');
|
||||
}
|
||||
|
||||
return submitSelector;
|
||||
}
|
||||
const { loadConfig, loadSitesFromFile } = require('./src/config');
|
||||
const { processSite } = require('./src/processor');
|
||||
const { createCLI, parseCLI } = require('./src/cli');
|
||||
|
||||
async function main() {
|
||||
const cliOptions = parseCLI();
|
||||
|
||||
if (cliOptions.help) {
|
||||
createCLI().help();
|
||||
return;
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
let sitesToProcess = config.sites || [];
|
||||
|
||||
const targetSite = process.argv[2];
|
||||
const logToFile = process.argv.includes('--log');
|
||||
const logFile = logToFile ? path.join(__dirname, `run_${new Date().toISOString().replace(/[:.]/g, '-')}.log`) : null;
|
||||
if (cliOptions.file) {
|
||||
console.log(`Loading sites from: ${cliOptions.file}`);
|
||||
sitesToProcess = loadSitesFromFile(cliOptions.file);
|
||||
console.log(`Loaded ${sitesToProcess.length} sites from file`);
|
||||
}
|
||||
|
||||
if (targetSite && !targetSite.startsWith('--')) {
|
||||
const logFile = cliOptions.log ? path.join(__dirname, `run_${new Date().toISOString().replace(/[:.]/g, '-')}.log`) : null;
|
||||
|
||||
if (cliOptions.target) {
|
||||
sitesToProcess = sitesToProcess.filter(site =>
|
||||
site.name.toLowerCase().includes(targetSite.toLowerCase()) ||
|
||||
site.url.toLowerCase().includes(targetSite.toLowerCase())
|
||||
site.name.toLowerCase().includes(cliOptions.target.toLowerCase()) ||
|
||||
site.url.toLowerCase().includes(cliOptions.target.toLowerCase())
|
||||
);
|
||||
if (sitesToProcess.length === 0) {
|
||||
console.error(`❌ No site found matching "${targetSite}"`);
|
||||
console.log('Available sites:');
|
||||
config.sites.forEach(site => console.log(` - ${site.name}: ${site.url}`));
|
||||
console.error(`No site found matching "${cliOptions.target}"`);
|
||||
return;
|
||||
}
|
||||
console.log(`🎯 Filtering to ${sitesToProcess.length} site(s) matching "${targetSite}"`);
|
||||
console.log(`Targeting ${sitesToProcess.length} site(s)`);
|
||||
}
|
||||
|
||||
if (cliOptions.keepOpen) {
|
||||
console.log('Keep-open mode: Tabs will stay open for verification');
|
||||
}
|
||||
|
||||
const browserConfig = config.browser || {};
|
||||
@@ -439,11 +48,11 @@ async function main() {
|
||||
const userDataDir = browserConfig.userDataDir;
|
||||
|
||||
if (!userDataDir) {
|
||||
console.error('❌ userDataDir is not set in config.toml. This is required.');
|
||||
console.error('userDataDir not set in config.toml');
|
||||
return;
|
||||
}
|
||||
|
||||
if (logToFile) {
|
||||
if (cliOptions.log && logFile) {
|
||||
const originalLog = console.log;
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
@@ -453,173 +62,58 @@ async function main() {
|
||||
fs.appendFileSync(logFile, `[${new Date().toISOString()}] [${type}] ${message}\n`);
|
||||
};
|
||||
|
||||
console.log = (...args) => {
|
||||
writeToLog('LOG', args);
|
||||
originalLog.apply(console, args);
|
||||
};
|
||||
|
||||
console.error = (...args) => {
|
||||
writeToLog('ERROR', args);
|
||||
originalError.apply(console, args);
|
||||
};
|
||||
|
||||
console.warn = (...args) => {
|
||||
writeToLog('WARN', args);
|
||||
originalWarn.apply(console, args);
|
||||
};
|
||||
|
||||
console.log(`📝 Logging to file: ${logFile}`);
|
||||
console.log = (...args) => { writeToLog('LOG', args); originalLog.apply(console, args); };
|
||||
console.error = (...args) => { writeToLog('ERROR', args); originalError.apply(console, args); };
|
||||
console.warn = (...args) => { writeToLog('WARN', args); originalWarn.apply(console, args); };
|
||||
console.log(`Logging to: ${logFile}`);
|
||||
}
|
||||
|
||||
const failedSites = [];
|
||||
const processedCount = { success: 0, failed: 0 };
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: false,
|
||||
userDataDir: userDataDir,
|
||||
args: ["--start-maximized"]
|
||||
args: ['--start-maximized', '--no-sandbox', '--disable-setuid-sandbox']
|
||||
});
|
||||
|
||||
try {
|
||||
let isFirstSite = true;
|
||||
for (const site of sitesToProcess) {
|
||||
let siteFailed = false;
|
||||
let failureReason = '';
|
||||
let creds;
|
||||
if (site.credentials && site.credentials.username) {
|
||||
creds = { ...globalCreds, ...site.credentials };
|
||||
console.log(`Using credentials specified for site: ${site.name}`);
|
||||
} else {
|
||||
creds = { ...globalCreds };
|
||||
console.log(`Using global credentials for site: ${site.name}`);
|
||||
}
|
||||
console.log(`\nProcessing ${sitesToProcess.length} sites...\n`);
|
||||
|
||||
for (let i = 0; i < sitesToProcess.length; i++) {
|
||||
const site = sitesToProcess[i];
|
||||
console.log(`[${i + 1}/${sitesToProcess.length}]`);
|
||||
|
||||
if (!creds.username || !creds.email || !creds.password) {
|
||||
const randomData = generateRandomData();
|
||||
creds = { ...randomData, ...creds };
|
||||
console.log('Filled in missing credentials with random data.');
|
||||
}
|
||||
|
||||
console.log(`\n${site.url}`);
|
||||
console.log(`${'-'.repeat(70)}`);
|
||||
const result = await processSite(browser, site, globalCreds, browserConfig, cliOptions.keepOpen);
|
||||
|
||||
const page = isFirstSite ? (await browser.pages())[0] : await browser.newPage();
|
||||
isFirstSite = false;
|
||||
|
||||
if (browserConfig.userAgent) {
|
||||
await page.setUserAgent(browserConfig.userAgent);
|
||||
}
|
||||
|
||||
await page.goto(site.url, { waitUntil: 'networkidle2' });
|
||||
|
||||
console.log('\n🔍 DETECTED FIELDS:');
|
||||
|
||||
let fieldsToFill = {};
|
||||
let detectedFields = {};
|
||||
const definedLocators = site.locators || {};
|
||||
|
||||
if (site.autoDetect !== false) {
|
||||
detectedFields = await detectFormFields(page);
|
||||
for (const [fieldName, fieldInfo] of Object.entries(detectedFields)) {
|
||||
fieldsToFill[fieldName] = fieldInfo.selector;
|
||||
}
|
||||
}
|
||||
|
||||
const hasEmailField = fieldsToFill.email || fieldsToFill.confirmEmail;
|
||||
if (!hasEmailField) {
|
||||
siteFailed = true;
|
||||
failureReason = 'No email field detected';
|
||||
console.warn(`\n ❌ ${failureReason}`);
|
||||
}
|
||||
|
||||
const hasSearchInput = await page.evaluate(() => {
|
||||
const inputs = document.querySelectorAll('input[type="search"], input[name*="search"], input[id*="search"]');
|
||||
return inputs.length > 0;
|
||||
});
|
||||
|
||||
if (hasSearchInput && !hasEmailField) {
|
||||
console.warn(` ⚠️ WARNING: This appears to be a SEARCH form, not a newsletter signup!`);
|
||||
siteFailed = true;
|
||||
failureReason = failureReason || 'Detected search form instead of newsletter';
|
||||
}
|
||||
|
||||
for (const [fieldName, locator] of Object.entries(definedLocators)) {
|
||||
if (fieldName !== 'submit') {
|
||||
fieldsToFill[fieldName] = parseLocator(locator);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(fieldsToFill).length > 0) {
|
||||
console.log(`\n ${'FIELD'.padEnd(12)} | ${'SELECTOR'.padEnd(35)} | ${'CONF'.padEnd(4)} | VALUE`);
|
||||
console.log(' ' + '-'.repeat(75));
|
||||
|
||||
for (const [fieldName, fieldInfo] of Object.entries(detectedFields)) {
|
||||
let valueToFill = creds[fieldName];
|
||||
|
||||
if (fieldName === 'confirmEmail') {
|
||||
valueToFill = creds['email'];
|
||||
}
|
||||
|
||||
const selector = fieldInfo.selector.length > 34 ? fieldInfo.selector.substring(0, 31) + '...' : fieldInfo.selector;
|
||||
const value = valueToFill ? valueToFill.substring(0, 20) : 'N/A';
|
||||
|
||||
console.log(` ${fieldName.padEnd(12)} | ${selector.padEnd(35)} | ${fieldInfo.confidence.toString().padEnd(4)} | ${value}`);
|
||||
|
||||
if (valueToFill) {
|
||||
try {
|
||||
await page.evaluate((sel, val) => {
|
||||
const el = document.querySelector(sel);
|
||||
if (el) {
|
||||
el.value = val;
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
}, fieldInfo.selector, valueToFill);
|
||||
} catch (e) {
|
||||
console.warn(` ✗ Failed to fill ${fieldName}: ${e.message}`);
|
||||
siteFailed = true;
|
||||
failureReason = `Failed to fill ${fieldName}: ${e.message}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(' ' + '-'.repeat(75));
|
||||
}
|
||||
|
||||
let submitSelector = null;
|
||||
if (definedLocators.submit) {
|
||||
submitSelector = parseLocator(definedLocators.submit);
|
||||
} else if (site.autoDetect !== false) {
|
||||
submitSelector = await detectSubmitButton(page);
|
||||
}
|
||||
|
||||
if (submitSelector) {
|
||||
try {
|
||||
await page.click(submitSelector);
|
||||
console.log(` ✓ Submitted (${submitSelector})`);
|
||||
} catch (submitError) {
|
||||
siteFailed = true;
|
||||
failureReason = failureReason || `Submit failed: ${submitError.message}`;
|
||||
console.error(` ✗ Submit failed: ${submitError.message}`);
|
||||
}
|
||||
} else {
|
||||
siteFailed = true;
|
||||
failureReason = failureReason || 'No submit button found';
|
||||
console.warn(` ✗ ${failureReason}`);
|
||||
}
|
||||
|
||||
if (siteFailed) {
|
||||
if (result.siteFailed) {
|
||||
failedSites.push({
|
||||
name: site.name,
|
||||
url: site.url,
|
||||
reason: failureReason,
|
||||
reason: result.failureReason,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
processedCount.failed++;
|
||||
} else {
|
||||
processedCount.success++;
|
||||
}
|
||||
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
}
|
||||
|
||||
console.log(`\n${'='.repeat(70)}`);
|
||||
console.log(`Done: ${processedCount.success} success, ${processedCount.failed} failed`);
|
||||
|
||||
} catch(error) {
|
||||
console.error('Error:', error.message);
|
||||
console.error('Fatal error:', error.message);
|
||||
} finally {
|
||||
if (cliOptions.keepOpen) {
|
||||
console.log(`\nBrowser kept open for verification`);
|
||||
console.log(` Close browser manually when done`);
|
||||
} else {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
if (failedSites.length > 0) {
|
||||
const reportPath = path.join(__dirname, 'failed_sites.txt');
|
||||
const now = new Date();
|
||||
@@ -638,7 +132,7 @@ async function main() {
|
||||
}
|
||||
|
||||
fs.appendFileSync(reportPath, reportContent);
|
||||
console.log(`\n${failedSites.length} site(s) failed. See: failed_sites.txt`);
|
||||
console.log(`${failedSites.length} failed. See: failed_sites.txt`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user