Created
March 16, 2026 14:43
-
-
Save vampirepapi/d465bf7f5c9e1fde6605d27e469b51b0 to your computer and use it in GitHub Desktop.
Instahyre Auto-Apply Script - Automatically applies to all matching jobs on Instahyre with bulk-apply popup handling, pagination, and a floating status panel.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ============================================ | |
| // INSTAHYRE AUTO-APPLY SCRIPT | |
| // ============================================ | |
| // Paste this in browser console (F12 β Console) | |
| // on https://www.instahyre.com/candidate/opportunities/ | |
| // | |
| // Features: | |
| // - Opens each job listing and clicks "Apply" | |
| // - Handles bulk-apply popups (selects all similar jobs) | |
| // - Auto-advances through the job detail modal | |
| // - Navigates to next pages automatically | |
| // - Recovers from stuck modals, empty popups, loading states | |
| // - Floating status panel with Stop/Resume controls | |
| // | |
| // Console commands while running: | |
| // window._autoApply.stop() β Pause | |
| // window._autoApply.resume() β Resume | |
| // window._autoApply.count() β Check total applied | |
| (function () { | |
| let count = 0, | |
| running = true; | |
| const MAX = 500; | |
| let emptyTicks = 0; | |
| // --- Floating Status Panel --- | |
| const panel = document.createElement("div"); | |
| panel.id = "auto-apply-panel"; | |
| panel.style.cssText = | |
| "position:fixed;top:10px;right:10px;z-index:99999;background:#1a1a2e;color:#0f8;padding:15px 20px;border-radius:10px;font-family:monospace;font-size:14px;box-shadow:0 4px 20px rgba(0,0,0,.5);min-width:320px;border:1px solid #0f8;max-width:400px"; | |
| panel.innerHTML = ` | |
| <div style="font-size:16px;font-weight:bold;margin-bottom:8px">π Auto-Apply</div> | |
| <div>Applied: <span id="aa-c" style="color:#fff;font-size:22px">0</span></div> | |
| <div>Status: <span id="aa-s" style="color:gold">Starting...</span></div> | |
| <div id="aa-log" style="margin-top:8px;font-size:11px;color:#999;max-height:160px;overflow-y:auto"></div> | |
| <div style="margin-top:10px"> | |
| <button id="aa-stop" style="background:#f44;color:#fff;border:none;padding:8px 20px;border-radius:5px;cursor:pointer;font-size:13px">βΉ Stop</button> | |
| <button id="aa-go" style="background:#0a5;color:#fff;border:none;padding:8px 20px;border-radius:5px;cursor:pointer;font-size:13px;margin-left:8px;display:none">βΆ Resume</button> | |
| </div>`; | |
| document.body.appendChild(panel); | |
| const cEl = document.getElementById("aa-c"), | |
| sEl = document.getElementById("aa-s"), | |
| logEl = document.getElementById("aa-log"); | |
| // --- Stop / Resume Buttons --- | |
| document.getElementById("aa-stop").onclick = () => { | |
| running = false; | |
| sEl.textContent = "Stopped"; | |
| sEl.style.color = "#f44"; | |
| document.getElementById("aa-stop").style.display = "none"; | |
| document.getElementById("aa-go").style.display = "inline-block"; | |
| }; | |
| document.getElementById("aa-go").onclick = () => { | |
| running = true; | |
| sEl.textContent = "Resuming..."; | |
| sEl.style.color = "gold"; | |
| document.getElementById("aa-stop").style.display = "inline-block"; | |
| document.getElementById("aa-go").style.display = "none"; | |
| emptyTicks = 0; | |
| mainLoop(); | |
| }; | |
| // --- Helpers --- | |
| function log(m) { | |
| const d = document.createElement("div"); | |
| d.textContent = new Date().toLocaleTimeString().substring(0, 8) + " " + m; | |
| logEl.prepend(d); | |
| if (logEl.children.length > 60) logEl.lastChild.remove(); | |
| } | |
| function status(m, c) { | |
| sEl.textContent = m; | |
| if (c) sEl.style.color = c; | |
| } | |
| const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); | |
| // Wait for a condition to become true (with timeout) | |
| async function waitFor(fn, timeout = 5000) { | |
| const start = Date.now(); | |
| while (Date.now() - start < timeout) { | |
| const result = fn(); | |
| if (result) return result; | |
| await sleep(400); | |
| } | |
| return null; | |
| } | |
| // --- Page State Detectors --- | |
| // Find the "Apply" button inside the job detail modal (not the bulk popup) | |
| function findApplyBtn() { | |
| for (const b of document.querySelectorAll("button")) { | |
| if ( | |
| b.textContent.trim() === "Apply" && | |
| b.offsetParent !== null && | |
| getComputedStyle(b).display !== "none" && | |
| !b.closest(".candidate-apply-all-modal") | |
| ) { | |
| return b; | |
| } | |
| } | |
| return null; | |
| } | |
| // Check if the bulk-apply popup is visible | |
| function isBulkVisible() { | |
| const m = document.querySelector(".candidate-apply-all-modal"); | |
| return m && !m.classList.contains("ng-hide"); | |
| } | |
| // Check if the job detail modal is open | |
| function isModalOpen() { | |
| const bg = document.querySelector( | |
| ".candidate-apply-modal .application-modal-backdrop" | |
| ); | |
| return bg && getComputedStyle(bg).display !== "none"; | |
| } | |
| // Check if the page is in "Fetching" loading state | |
| function isPageFetching() { | |
| for (const h of document.querySelectorAll("h1, h2, h3, h4")) { | |
| if (h.textContent.trim().includes("Fetching") && h.offsetParent !== null) | |
| return true; | |
| } | |
| return false; | |
| } | |
| // Get the current job title from the detail modal | |
| function getJobTitle() { | |
| for (const s of [ | |
| ".candidate-apply-modal h3", | |
| ".candidate-apply-modal h4", | |
| ".candidate-apply-modal .heading", | |
| ]) { | |
| const el = document.querySelector(s); | |
| if (el && el.offsetParent !== null) { | |
| const txt = el.textContent.trim(); | |
| if (txt && txt !== "Hold on, loading..." && txt.length > 2) | |
| return txt.substring(0, 45); | |
| } | |
| } | |
| return "(loading...)"; | |
| } | |
| // --- Bulk Apply Popup Handler --- | |
| async function handleBulkPopup() { | |
| if (!isBulkVisible()) return; | |
| const m = document.querySelector(".candidate-apply-all-modal"); | |
| const cbs = m.querySelectorAll('input[type="checkbox"]'); | |
| if (cbs.length > 0) { | |
| // Select all checkboxes and click Apply | |
| cbs.forEach((cb) => { | |
| if (!cb.checked) cb.click(); | |
| }); | |
| await sleep(400); | |
| const btn = m.querySelector('button[ng-click="applyBulk()"]'); | |
| if (btn && !btn.disabled) { | |
| btn.click(); | |
| log("π Bulk applied (" + cbs.length + " extra)"); | |
| await sleep(2000); | |
| } else { | |
| m.querySelector('button[ng-click="applyBulkCancel()"]')?.click(); | |
| log("π Bulk: Apply disabled, cancelled"); | |
| await sleep(1000); | |
| } | |
| } else { | |
| // Empty bulk popup β just cancel | |
| m.querySelector('button[ng-click="applyBulkCancel()"]')?.click(); | |
| log("π Bulk: no extras, cancelled"); | |
| await sleep(1000); | |
| } | |
| // Safety: if still showing, force cancel | |
| if (isBulkVisible()) { | |
| document | |
| .querySelector( | |
| '.candidate-apply-all-modal button[ng-click="applyBulkCancel()"]' | |
| ) | |
| ?.click(); | |
| await sleep(1000); | |
| } | |
| } | |
| // --- Main Loop --- | |
| async function mainLoop() { | |
| while (running && count < MAX) { | |
| if (!running) return; | |
| // STEP 1: Handle bulk popup if visible | |
| if (isBulkVisible()) { | |
| await handleBulkPopup(); | |
| emptyTicks = 0; | |
| continue; | |
| } | |
| // STEP 2: If Apply button is visible in job detail, click it | |
| let applyBtn = findApplyBtn(); | |
| if (applyBtn) { | |
| const title = getJobTitle(); | |
| applyBtn.click(); | |
| count++; | |
| cEl.textContent = count; | |
| log("β #" + count + ": " + title); | |
| status("Applied #" + count + ": " + title, "#0f8"); | |
| emptyTicks = 0; | |
| await sleep(2000); // wait for bulk popup or auto-advance | |
| await handleBulkPopup(); | |
| await sleep(2000); // wait for next job to load | |
| continue; | |
| } | |
| // STEP 3: Modal is open but no Apply button (already applied / loading) | |
| if (isModalOpen()) { | |
| status("Waiting for Apply button...", "gold"); | |
| applyBtn = await waitFor(findApplyBtn, 4000); | |
| if (applyBtn) continue; // found it β loop will click it | |
| emptyTicks++; | |
| if (emptyTicks > 3) { | |
| log("β οΈ Closing stuck modal"); | |
| document | |
| .querySelectorAll(".application-modal-close") | |
| .forEach((c) => c.click()); | |
| emptyTicks = 0; | |
| await sleep(1500); | |
| } | |
| await sleep(1000); | |
| continue; | |
| } | |
| // STEP 4: We're on the job list β click first "View Β»" button | |
| emptyTicks = 0; | |
| if (isPageFetching()) { | |
| status("Page loading...", "gold"); | |
| await sleep(3000); | |
| continue; | |
| } | |
| const viewBtns = Array.from( | |
| document.querySelectorAll("button.button-interested") | |
| ).filter( | |
| (b) => b.offsetParent !== null && b.textContent.trim() === "View Β»" | |
| ); | |
| if (viewBtns.length > 0) { | |
| const row = viewBtns[0].closest("[ng-repeat], li, div"); | |
| const name = | |
| row | |
| ?.querySelector("a") | |
| ?.textContent?.trim() | |
| ?.substring(0, 45) || "?"; | |
| viewBtns[0].click(); | |
| status("Opening: " + name, "gold"); | |
| log("π Opening: " + name); | |
| await waitFor(findApplyBtn, 5000); | |
| continue; | |
| } | |
| // STEP 5: No View buttons β try next page | |
| let foundNext = false; | |
| for (const a of document.querySelectorAll(".pagination a, a")) { | |
| if ( | |
| a.textContent.trim().includes("Next") && | |
| a.offsetParent !== null | |
| ) { | |
| a.click(); | |
| log("π Next page"); | |
| status("Next page...", "gold"); | |
| await sleep(4000); | |
| foundNext = true; | |
| break; | |
| } | |
| } | |
| if (foundNext) continue; | |
| // STEP 6: Nothing found β retry a few times before finishing | |
| emptyTicks++; | |
| if (emptyTicks > 5) { | |
| status("π All done! " + count + " jobs applied", "#0f8"); | |
| log("π Finished! Total: " + count); | |
| return; | |
| } | |
| status("Waiting for jobs... (" + emptyTicks + "/5)", "gold"); | |
| await sleep(3000); | |
| } | |
| if (count >= MAX) | |
| status("π Max reached! " + count + " applied", "#0f8"); | |
| } | |
| // --- Start --- | |
| status("Starting in 2s...", "gold"); | |
| setTimeout(() => mainLoop(), 2000); | |
| // --- Console API --- | |
| window._autoApply = { | |
| stop: () => { | |
| running = false; | |
| status("Stopped", "#f44"); | |
| }, | |
| resume: () => { | |
| running = true; | |
| emptyTicks = 0; | |
| status("Resuming", "gold"); | |
| mainLoop(); | |
| }, | |
| count: () => count, | |
| }; | |
| console.log("=== INSTAHYRE AUTO-APPLY RUNNING ==="); | |
| console.log("Stop: window._autoApply.stop()"); | |
| console.log("Resume: window._autoApply.resume()"); | |
| console.log("Count: window._autoApply.count()"); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment