168 lines
5.9 KiB
JavaScript
168 lines
5.9 KiB
JavaScript
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();
|