diff --git a/lib/apply/generic.mjs b/lib/apply/generic.mjs index e7be81b..3e57194 100644 --- a/lib/apply/generic.mjs +++ b/lib/apply/generic.mjs @@ -32,7 +32,14 @@ export async function apply(page, job, formFiller) { const hasLogin = !!(document.querySelector('input[type="password"]') || (text.includes('sign in') && text.includes('create account')) || (text.includes('log in') && text.includes('register'))); - const hasCaptcha = !!(document.querySelector('iframe[src*="recaptcha"], iframe[src*="captcha"], [class*="captcha"]')); + // Only block on visible CAPTCHAs — invisible reCAPTCHA (size=invisible) fires on submit and usually passes + const captchaFrames = Array.from(document.querySelectorAll('iframe[src*="recaptcha"], iframe[src*="captcha"]')); + const hasVisibleCaptcha = captchaFrames.some(f => { + if (f.src.includes('size=invisible')) return false; + const rect = f.getBoundingClientRect(); + return rect.width > 50 && rect.height > 50; + }); + const hasCaptcha = hasVisibleCaptcha; const isClosed = text.includes('no longer accepting') || text.includes('position has been filled') || text.includes('this job is no longer') || text.includes('job not found') || text.includes('this position is closed') || text.includes('listing has expired'); @@ -44,7 +51,8 @@ export async function apply(page, job, formFiller) { if (pageCheck.hasCaptcha) return { status: 'skipped_captcha', meta }; // Some pages land directly on the form; others need an Apply button click - const hasFormAlready = await page.$('form input[type="text"], form input[type="email"], form textarea'); + // Check if we landed directly on a form (with or without