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()); // --- Helper Functions --- 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(); // Random age between 20-49 return { username, email, password, age }; } /** * Parses a locator string. If it's an HTML snippet, it generates a CSS selector. * If it's already a selector, it returns it as is. * @param {string} locatorStr - The locator string from the config. * @returns {string} A CSS selector. */ function parseLocator(locatorStr) { locatorStr = locatorStr.trim(); if (!locatorStr.startsWith('<')) { return locatorStr; // It's already a selector } 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'); // FIX: Handle numeric IDs by creating an attribute selector if (id) { if (/^\\d/.test(id)) { // If id starts with a digit 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}']`; console.warn(`Could not generate a reliable selector for: ${locatorStr}.`); return tagName; } async function handleCameraBoysGDPR(page) { // ... (This function remains the same) } // --- Main Execution --- async function main() { const config = loadConfig(); const sitesToProcess = config.sites || []; const browserConfig = config.browser || {}; const globalCreds = config.global_credentials || {}; const userDataDir = browserConfig.userDataDir; if (!userDataDir) { console.error('❌ userDataDir is not set in config.toml. This is required.'); return; } const browser = await puppeteer.launch({ headless: false, userDataDir: userDataDir, args: ["--start-maximized"] }); try { let isFirstSite = true; for (const site of sitesToProcess) { 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}`); } // If any credential is still missing, generate it randomly if (!creds.username || !creds.email || !creds.password || !creds.age) { const randomData = generateRandomData(); creds = { ...randomData, ...creds }; console.log('Filled in missing credentials with random data.'); } console.log(`\n--- Processing Site: ${site.name} (Category: ${site.category || 'N/A'}) ---`); 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' }); if (site.needsPreSubmitAction && site.name === 'CameraBoys') { await handleCameraBoysGDPR(page); } console.log('Dynamically filling out the form...'); const definedLocators = site.locators || {}; for (const fieldName in definedLocators) { if (fieldName === 'submit') continue; // Skip the submit button const selector = parseLocator(definedLocators[fieldName]); let valueToFill = creds[fieldName]; // Special case for 'confirmEmail', use the email value if (fieldName === 'confirmEmail') { valueToFill = creds['email']; } if (selector && valueToFill) { try { console.log(` - Filling field: ${fieldName}`); await page.type(selector, valueToFill, { delay: 100 }); } catch (e) { console.warn(` - Could not fill field '${fieldName}' with selector '${selector}'. Error: ${e.message}`); } } } console.log('Form filled. Attempting to submit...'); const submitSelector = parseLocator(definedLocators.submit); await page.click(submitSelector); console.log(`✅ Submission attempt for ${site.name} is complete.`); } } catch(error) { console.error('❌ A critical error occurred during execution:', error.message); } finally { console.log('\n🎉 All tasks complete. The browser window will remain open for inspection.'); } } main();