Strict separation of concerns, minimalist UI, modular automation
// ==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();
}
})();