LinkedIn Auto-Apply

Strict separation of concerns, minimalist UI, modular automation

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         LinkedIn Auto-Apply
// @namespace    http://tampermonkey.net/
// @version      21.0
// @license      MIT
// @description  Strict separation of concerns, minimalist UI, modular automation
// @author       Roshan Kumar
// @match        *://*.linkedin.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    const State = {
        url: GM_getValue('url', 'https://www.linkedin.com/jobs/search/?f_AL=true&keywords=Full%20Stack'),
        workplaceType: GM_getValue('workplaceType', '2'),
        email: GM_getValue('email', ''),
        phone: GM_getValue('phone', ''),
        country: GM_getValue('country', ''),
        exp: GM_getValue('exp', '3'),
        currentCtc: GM_getValue('currentCtc', '12.0'),
        expectedCtc: GM_getValue('expectedCtc', '18.0'),
        onsite: GM_getValue('onsite', 'Yes'),
        immediateStart: GM_getValue('immediateStart', 'Yes'),
        bachelors: GM_getValue('bachelors', 'Yes'),
        academicProject: GM_getValue('academicProject', 'No'),
        skillRating: GM_getValue('skillRating', '8'),
        include: GM_getValue('include', ''),
        exclude: GM_getValue('exclude', ''),
        running: GM_getValue('running', false)
    };

    const Storage = {
        save: (data) => {
            Object.keys(data).forEach(k => GM_setValue(k, data[k]));
            Object.assign(State, data);
        },
        setRunning: (val) => {
            GM_setValue('running', val);
            State.running = val;
        }
    };

    const UI = {
        container: null,
        panel: null,
        tabHeader: null,
        tabBody: null,
        toggleBtn: null,
        startBtn: null,
        stopBtn: null,
        inputs: {},

        init: () => {
            UI.container = document.createElement('div');
            UI.container.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:2147483647;font-family:monospace;font-size:12px;display:flex;flex-direction:column;align-items:flex-end;gap:10px;pointer-events:none;';
            document.documentElement.appendChild(UI.container);

            UI.panel = document.createElement('div');
            UI.panel.style.cssText = 'background:#111;color:#eee;padding:15px;width:400px;display:none;flex-direction:column;gap:10px;pointer-events:auto;box-shadow:0 4px 12px rgba(0,0,0,0.5);max-height:85vh;overflow:hidden;border:1px solid #333;';

            const style = document.createElement('style');
            style.textContent = `
                .rk-scroll::-webkit-scrollbar { width: 4px; }
                .rk-scroll::-webkit-scrollbar-track { background: transparent; }
                .rk-scroll::-webkit-scrollbar-thumb { background: #444; }
                .rk-tab-btn { background: transparent; color: #666; border: none; padding: 8px 12px; cursor: pointer; font-family: monospace; font-size: 11px; font-weight: bold; text-transform: uppercase; border-bottom: 2px solid transparent; transition: all 0.2s; white-space: nowrap; }
                .rk-tab-btn.active { color: #00ff00; border-bottom: 2px solid #00ff00; }
                .rk-tab-btn:hover:not(.active) { color: #aaa; }
                .rk-tab-content { display: none; flex-direction: column; gap: 12px; padding: 10px 5px; }
                .rk-tab-content.active { display: flex; }
                .rk-field-wrapper { display: flex; flex-direction: column; gap: 4px; }
                .rk-input { background: #1a1a1a !important; border: 1px solid #333 !important; color: #eee !important; padding: 8px !important; outline: none !important; font-family: monospace !important; width: 100% !important; transition: border 0.2s !important; box-sizing: border-box !important; appearance: auto !important; }
                .rk-input:focus { border-color: #00ff00 !important; }
                .rk-label { color: #888; font-size: 10px; text-transform: uppercase; letter-spacing: 1px; }
                option { background: #111; color: #eee; }
            `;
            document.head.appendChild(style);
            UI.container.appendChild(UI.panel);

            UI.tabHeader = document.createElement('div');
            UI.tabHeader.style.cssText = 'display:flex;gap:5px;border-bottom:1px solid #333;overflow-x:auto;padding-bottom:5px;scrollbar-width: none;';
            UI.tabHeader.className = 'rk-scroll';
            UI.panel.appendChild(UI.tabHeader);

            UI.tabBody = document.createElement('div');
            UI.tabBody.style.cssText = 'overflow-y:auto;flex:1;min-height: 300px;';
            UI.tabBody.className = 'rk-scroll';
            UI.panel.appendChild(UI.tabBody);

            const tabSearch = UI.createTab('search', 'Engine', true);
            UI.createField('url', 'Target Search URL', State.url, tabSearch);
            UI.createSelect('workplaceType', 'Workplace Type', [{label:'Remote',value:'2'},{label:'Onsite',value:'1'},{label:'Hybrid',value:'3'},{label:'Any',value:''}], State.workplaceType, tabSearch);
            UI.createField('include', 'Must Include Keywords', State.include, tabSearch);
            UI.createField('exclude', 'Strict Exclude Keywords', State.exclude, tabSearch);

            const tabProfile = UI.createTab('profile', 'Identity', false);
            UI.createField('email', 'Email Address', State.email, tabProfile);
            UI.createField('phone', 'Mobile (+Code)', State.phone, tabProfile);
            UI.createField('country', 'Country Name', State.country, tabProfile);
            UI.createField('exp', 'General Experience (Yrs)', State.exp, tabProfile);

            const tabJob = UI.createTab('job', 'Salary', false);
            UI.createField('currentCtc', 'Current CTC', State.currentCtc, tabJob);
            UI.createField('expectedCtc', 'Expected CTC', State.expectedCtc, tabJob);
            UI.createField('skillRating', 'Skill Rating (Scale 1-10)', State.skillRating, tabJob);

            const tabQs = UI.createTab('questions', 'Logic', false);
            UI.createField('onsite', 'Comfortable Onsite?', State.onsite, tabQs);
            UI.createField('immediateStart', 'Immediate Start?', State.immediateStart, tabQs);
            UI.createField('bachelors', 'Degree Completed?', State.bachelors, tabQs);
            UI.createField('academicProject', 'Internship/College Req?', State.academicProject, tabQs);

            const btnWrapper = document.createElement('div');
            btnWrapper.style.cssText = 'display:flex;gap:10px;margin-top:10px;border-top:1px solid #333;padding-top:10px;';
            UI.panel.appendChild(btnWrapper);

            UI.startBtn = UI.createButton('INITIATE', '#eee', '#111');
            UI.stopBtn = UI.createButton('TERMINATE', '#ff4444', '#111');
            UI.stopBtn.style.display = 'none';
            btnWrapper.appendChild(UI.startBtn);
            btnWrapper.appendChild(UI.stopBtn);

            UI.toggleBtn = document.createElement('button');
            UI.toggleBtn.innerText = 'CMD';
            UI.toggleBtn.style.cssText = 'background:#111;color:#eee;border:1px solid #333;padding:12px 24px;cursor:pointer;pointer-events:auto;font-family:monospace;font-weight:bold;letter-spacing:2px;';
            UI.container.appendChild(UI.toggleBtn);

            UI.bindEvents();

            if (State.running) {
                UI.setRunningState(true);
                Engine.start(false);
            }
        },

        createTab: (id, title, isActive) => {
            const btn = document.createElement('button');
            btn.innerText = title;
            btn.className = `rk-tab-btn ${isActive ? 'active' : ''}`;

            const content = document.createElement('div');
            content.className = `rk-tab-content ${isActive ? 'active' : ''}`;

            btn.addEventListener('click', () => {
                UI.tabHeader.querySelectorAll('.rk-tab-btn').forEach(b => b.classList.remove('active'));
                UI.tabBody.querySelectorAll('.rk-tab-content').forEach(c => c.classList.remove('active'));
                btn.classList.add('active');
                content.classList.add('active');
            });

            UI.tabHeader.appendChild(btn);
            UI.tabBody.appendChild(content);
            return content;
        },

        createField: (id, labelText, val, parent) => {
            const wrapper = document.createElement('div');
            wrapper.className = 'rk-field-wrapper';
            const label = document.createElement('label');
            label.innerText = labelText;
            label.className = 'rk-label';
            const input = document.createElement('input');
            input.value = val;
            input.className = 'rk-input';
            UI.inputs[id] = input;
            wrapper.appendChild(label);
            wrapper.appendChild(input);
            parent.appendChild(wrapper);
        },

        createSelect: (id, labelText, options, selectedValue, parent) => {
            const wrapper = document.createElement('div');
            wrapper.className = 'rk-field-wrapper';
            const label = document.createElement('label');
            label.innerText = labelText;
            label.className = 'rk-label';
            const select = document.createElement('select');
            select.className = 'rk-input';

            options.forEach(opt => {
                const optionEl = document.createElement('option');
                optionEl.value = opt.value;
                optionEl.innerText = opt.label;
                if (opt.value === selectedValue) optionEl.selected = true;
                select.appendChild(optionEl);
            });

            UI.inputs[id] = select;
            wrapper.appendChild(label);
            wrapper.appendChild(select);
            parent.appendChild(wrapper);
        },

        createButton: (text, bg, color) => {
            const btn = document.createElement('button');
            btn.innerText = text;
            btn.style.cssText = `background:${bg};color:${color};border:none;padding:12px;cursor:pointer;flex:1;font-weight:bold;font-family:monospace;letter-spacing:1px;`;
            return btn;
        },

        bindEvents: () => {
            UI.toggleBtn.addEventListener('click', () => {
                UI.panel.style.display = UI.panel.style.display === 'none' ? 'flex' : 'none';
            });

            UI.startBtn.addEventListener('click', () => {
                const data = {};
                for (const key in UI.inputs) {
                    data[key] = UI.inputs[key].value;
                }
                Storage.save(data);
                Storage.setRunning(true);
                UI.setRunningState(true);
                Engine.start(true);
            });

            UI.stopBtn.addEventListener('click', () => {
                Storage.setRunning(false);
                UI.setRunningState(false);
                Engine.stop();
            });
        },

        setRunningState: (isRunning) => {
            UI.startBtn.style.display = isRunning ? 'none' : 'block';
            UI.stopBtn.style.display = isRunning ? 'block' : 'none';
            UI.toggleBtn.style.borderColor = isRunning ? '#00ff00' : '#333';
            UI.toggleBtn.style.color = isRunning ? '#00ff00' : '#eee';
            UI.toggleBtn.innerText = isRunning ? 'RUN' : 'CMD';
        }
    };

    const Engine = {
        running: false,

        sleep: (ms) => new Promise(r => setTimeout(r, ms)),

        start: async (isManualClick) => {
            if (isManualClick) {
                let finalUrl = State.url;
                try {
                    const urlObj = new URL(finalUrl);
                    if (State.workplaceType) urlObj.searchParams.set('f_WT', State.workplaceType);
                    else urlObj.searchParams.delete('f_WT');
                    urlObj.searchParams.set('f_TPR', 'r604800');
                    finalUrl = urlObj.toString();
                } catch(e) {}

                window.location.href = finalUrl;
                return;
            }

            Engine.running = true;
            await Engine.sleep(2000);
            await Engine.loop();
        },

        stop: () => {
            Engine.running = false;
        },

        loop: async () => {
            let processedCount = 0;
            let stuckCounter = 0;

            while (Engine.running) {
                let cards = Helpers.getJobCards();

                if (!cards || cards.length === processedCount) {
                    const leftPane = document.querySelector('.jobs-search-results-list');
                    if (leftPane) {
                        leftPane.scrollTo(0, leftPane.scrollHeight);
                        await Engine.sleep(2000);
                        cards = Helpers.getJobCards();
                    }
                }

                if (!cards || cards.length === processedCount) {
                    stuckCounter++;
                    if (stuckCounter > 3) {
                        const nextBtn = Helpers.getPaginationNext();
                        if (nextBtn) {
                            nextBtn.click();
                            processedCount = 0;
                            stuckCounter = 0;
                            await Engine.sleep(4000);
                            continue;
                        } else {
                            UI.stopBtn.click();
                            break;
                        }
                    }
                } else {
                    stuckCounter = 0;
                }

                for (let i = processedCount; i < cards.length; i++) {
                    if (!Engine.running) break;

                    const card = cards[i];
                    const title = Helpers.getJobTitle(card);

                    processedCount++;

                    if (!Helpers.passesFilters(title, State.include, State.exclude)) continue;

                    Helpers.clickJobCard(card);
                    await Engine.sleep(2500);

                    const applyBtn = Helpers.getEasyApplyButton();
                    if (applyBtn) {
                        applyBtn.click();
                        await Engine.processModal();
                    }
                }
            }
        },

        processModal: async () => {
            let isModalActive = true;

            while (isModalActive && Engine.running) {
                await Engine.sleep(1500);
                Helpers.fillFormFields(State);

                const actions = Helpers.getModalActions();

                if (actions.submit) {
                    actions.submit.click();
                    await Engine.sleep(3000);
                    Helpers.closePostSubmitPopup();
                    isModalActive = false;
                } else if (actions.review) {
                    actions.review.click();
                } else if (actions.next) {
                    actions.next.click();
                } else {
                    if (!Helpers.isModalPresent()) {
                        isModalActive = false;
                    }
                }
            }
        }
    };

    const Helpers = {
        passesFilters: (title, includeStr, excludeStr) => {
            if (!title) return true;
            const t = title.toLowerCase();
            const inc = includeStr.split(',').map(w => w.trim().toLowerCase()).filter(Boolean);
            const exc = excludeStr.split(',').map(w => w.trim().toLowerCase()).filter(Boolean);

            for (let word of exc) {
                if (t.includes(word)) return false;
            }
            if (inc.length === 0) return true;
            for (let word of inc) {
                if (t.includes(word)) return true;
            }
            return false;
        },

        getJobCards: () => {
            return document.querySelectorAll('div[data-job-id], .job-card-container, .scaffold-layout__list-item');
        },

        getJobTitle: (cardElement) => {
            const titleEl = cardElement.querySelector('.job-card-list__title, .artdeco-entity-lockup__title, strong, a');
            return titleEl ? titleEl.innerText : '';
        },

        clickJobCard: (cardElement) => {
            cardElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
            const clickable = cardElement.querySelector('a, button, [role="button"]') || cardElement;
            clickable.click();
        },

        getEasyApplyButton: () => {
            return document.querySelector('.jobs-apply-button--top-card button, button[aria-label*="Easy Apply"], button[aria-label*="Apply to this job"]');
        },

        setSelect: (el, text) => {
            if (!text) return;
            Array.from(el.options).forEach((opt, i) => {
                if (opt.innerText.toLowerCase().includes(text.toLowerCase()) || opt.value.toLowerCase().includes(text.toLowerCase())) {
                    el.selectedIndex = i;
                    el.dispatchEvent(new Event('change', { bubbles: true }));
                }
            });
        },

        fillFormFields: (stateObj) => {
            const groups = document.querySelectorAll('.fb-dash-form-element, .jobs-easy-apply-form-element, .artdeco-text-input--container, fieldset');

            groups.forEach(g => {
                const labelEl = g.querySelector('label, legend');
                const label = labelEl ? labelEl.innerText.toLowerCase() : '';

                const input = g.querySelector('input[type="text"], input[type="tel"], input[type="number"]');
                if (input) {
                    if ((label.includes('phone') || label.includes('mobile')) && !input.value) {
                        input.value = stateObj.phone;
                        input.dispatchEvent(new Event('input', { bubbles: true }));
                    } else if (label.includes('current ctc') && !input.value) {
                        input.value = stateObj.currentCtc;
                        input.dispatchEvent(new Event('input', { bubbles: true }));
                    } else if (label.includes('expected ctc') && !input.value) {
                        input.value = stateObj.expectedCtc;
                        input.dispatchEvent(new Event('input', { bubbles: true }));
                    } else if ((label.includes('experience') || label.includes('years')) && !input.value) {
                        input.value = stateObj.exp;
                        input.dispatchEvent(new Event('input', { bubbles: true }));
                    } else if (label.includes('rate') || label.includes('scale')) {
                        if (!input.value) {
                            input.value = stateObj.skillRating;
                            input.dispatchEvent(new Event('input', { bubbles: true }));
                        }
                    }
                }

                const select = g.querySelector('select');
                if (select) {
                    if (label.includes('email')) Helpers.setSelect(select, stateObj.email);
                    if (label.includes('country code') || label.includes('country')) Helpers.setSelect(select, stateObj.country);
                    if (label.includes('internship') || label.includes('academic') || label.includes('college')) Helpers.setSelect(select, stateObj.academicProject);
                }

                if (label.includes('sponsorship') || label.includes('authorized') || label.includes('citizenship') || label.includes('clearance') || label.includes('onsite') || label.includes('immediately') || label.includes('urgently') || label.includes('bachelor') || label.includes('degree')) {
                    let target = 'Yes';

                    if (label.includes('require') || label.includes('sponsor')) target = 'No';
                    if (label.includes('onsite') && stateObj.onsite.toLowerCase() === 'no') target = 'No';
                    if ((label.includes('immediately') || label.includes('urgently')) && stateObj.immediateStart.toLowerCase() === 'no') target = 'No';
                    if ((label.includes('bachelor') || label.includes('degree')) && stateObj.bachelors.toLowerCase() === 'no') target = 'No';

                    const rad = g.querySelector(`input[value="${target}"], input[id*="${target}"], input[data-test-text-selectable-option__input="${target}"]`);
                    if (rad && !rad.checked) {
                        rad.click();
                        rad.dispatchEvent(new Event('change', { bubbles: true }));
                    }
                }
            });
        },

        getModalActions: () => {
            return {
                next: document.querySelector('button[data-easy-apply-next-button], button[aria-label="Continue to next step"]'),
                review: document.querySelector('button[aria-label="Review your application"]'),
                submit: document.querySelector('button[aria-label="Submit application"]')
            };
        },

        closePostSubmitPopup: () => {
            const closeBtn = document.querySelector('button[aria-label="Dismiss"], button[data-test-modal-close-btn]');
            if (closeBtn) closeBtn.click();
        },

        isModalPresent: () => {
            return !!document.querySelector('.jobs-easy-apply-modal, .artdeco-modal');
        },

        getPaginationNext: () => {
            return document.querySelector('button[aria-label="View next page"], button.artdeco-pagination__button--next, button[data-testid="pagination-controls-next-button-visible"]');
        }
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', UI.init);
    } else {
        UI.init();
    }

})();