Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save sanjayka1999/89a153c209be5e21dfa1650d516922d3 to your computer and use it in GitHub Desktop.

Select an option

Save sanjayka1999/89a153c209be5e21dfa1650d516922d3 to your computer and use it in GitHub Desktop.
LinkedIn Job Bot
/**
* LinkedIn Job Application Bot for Cloud Computing Roles
* Focus: Nashua, NH and 100-mile radius
* Targets: Entry-level cloud jobs and internships
*/
const LinkedInJobBot = {
config: {
// Timing controls (in milliseconds)
scrollDelay: 2000,
actionDelay: 3000,
nextPageDelay: 5000,
// Application limits
maxApplications: 30, // Safety limit
applicationsSent: 0,
// Job search parameters
jobTitles: [
"Cloud Engineer",
"AWS Engineer",
"Azure Administrator",
"Cloud Support",
"DevOps Engineer",
"Cloud Intern"
],
experienceLevel: "1", // 1=Entry level, 2=Associate, etc.
radius: "100", // miles
location: "Nashua, New Hampshire, United States",
// Personalization
addNote: true,
note: `I'm a recent Computer Science graduate with cloud certifications excited to apply for this {{jobTitle}} position. My skills in {{skills}} align well with your requirements.`,
// Skills to highlight (will be randomly selected for variation)
skills: [
"AWS services",
"Azure fundamentals",
"Terraform",
"Docker",
"Kubernetes",
"CI/CD pipelines",
"Cloud security"
]
},
// Initialize the bot
init: function() {
console.log("Starting LinkedIn Job Application Bot for Cloud Roles...");
this.navigateToJobsPage();
},
// Navigate to LinkedIn jobs search with our parameters
navigateToJobsPage: function() {
const titleParam = this.config.jobTitles.map(t => encodeURIComponent(t)).join("%20OR%20");
const url = `https://www.linkedin.com/jobs/search/?distance=${this.config.radius}&f_E=${this.config.experienceLevel}&geoId=102571732&keywords=${titleParam}&location=${encodeURIComponent(this.config.location)}`;
console.log(`Navigating to: ${url}`);
window.location.href = url;
// Start the process after page loads
setTimeout(() => this.preparePage(), 5000);
},
// Prepare the page by scrolling and loading all jobs
preparePage: function() {
console.log("Preparing page...");
this.scrollToBottom();
},
scrollToBottom: function() {
window.scrollTo(0, document.body.scrollHeight);
console.log("Scrolled to bottom, waiting for jobs to load...");
setTimeout(() => {
if (this.hasMoreJobs()) {
this.scrollToBottom();
} else {
this.processJobs();
}
}, this.config.scrollDelay);
},
// Check if more jobs are loading
hasMoreJobs: function() {
const loadingIndicator = document.querySelector('.jobs-search-two-pane__loading-indicator');
return loadingIndicator && loadingIndicator.style.display !== 'none';
},
// Main function to process and apply to jobs
processJobs: function() {
const jobListings = document.querySelectorAll('.jobs-search-results__list-item');
console.log(`Found ${jobListings.length} cloud-related jobs`);
if (jobListings.length === 0) {
console.log("No jobs found matching criteria");
return;
}
// Filter for Easy Apply jobs first
const easyApplyJobs = Array.from(jobListings).filter(job => {
return job.querySelector('.job-card-container__apply-method')?.textContent.includes('Easy Apply');
});
console.log(`Found ${easyApplyJobs.length} Easy Apply cloud jobs`);
if (easyApplyJobs.length > 0) {
this.applyToJobs(easyApplyJobs);
} else {
console.log("No Easy Apply jobs found. Consider manual applications.");
this.tryNextPage();
}
},
// Apply to a list of jobs
applyToJobs: function(jobs) {
if (this.config.applicationsSent >= this.config.maxApplications) {
console.log(`Reached maximum application limit of ${this.config.maxApplications}`);
return;
}
const job = jobs[0];
const jobTitle = job.querySelector('.job-card-list__title')?.textContent.trim() || 'cloud position';
const company = job.querySelector('.job-card-container__primary-description')?.textContent.trim() || '';
console.log(`Applying to: ${jobTitle} at ${company}`);
job.scrollIntoView();
job.click();
setTimeout(() => {
this.handleEasyApply(jobTitle);
// Remove this job from our list
jobs.shift();
// Continue with next job after delay
if (jobs.length > 0 && this.config.applicationsSent < this.config.maxApplications) {
setTimeout(() => this.applyToJobs(jobs), this.config.actionDelay * 2);
} else {
this.tryNextPage();
}
}, this.config.actionDelay);
},
// Handle the Easy Apply process
handleEasyApply: function(jobTitle) {
const easyApplyButton = document.querySelector('.jobs-apply-button');
if (!easyApplyButton) {
console.log("Easy Apply button not found");
return;
}
easyApplyButton.click();
setTimeout(() => {
this.completeApplicationForms(jobTitle);
}, this.config.actionDelay);
},
// Complete the application forms
completeApplicationForms: function(jobTitle) {
const forms = document.querySelectorAll('.jobs-easy-apply-form-section__grouping');
if (forms.length === 0) {
console.log("No application forms found");
this.closeApplication();
return;
}
console.log(`Completing ${forms.length} application sections...`);
// Randomly select 2-3 skills to mention
const shuffledSkills = [...this.config.skills].sort(() => 0.5 - Math.random());
const selectedSkills = shuffledSkills.slice(0, 2 + Math.floor(Math.random() * 2));
// Personalize the note
const personalizedNote = this.config.note
.replace('{{jobTitle}}', jobTitle)
.replace('{{skills}}', selectedSkills.join(', '));
// Find text areas and fill them
const textAreas = document.querySelectorAll('textarea');
textAreas.forEach(area => {
if (area.placeholder.includes('message') || area.placeholder.includes('note')) {
area.value = personalizedNote;
area.dispatchEvent(new Event('input', { bubbles: true }));
}
});
// Handle follow-up questions
const questions = document.querySelectorAll('.jobs-easy-apply-form-element');
questions.forEach(q => {
const questionText = q.textContent.toLowerCase();
// Sample question handling - would need to be expanded
if (questionText.includes('years of experience')) {
const options = q.querySelectorAll('input[type="radio"]');
if (options.length >= 1) options[0].click(); // Select lowest experience
}
else if (questionText.includes('sponsorship')) {
const noButton = [...q.querySelectorAll('button')].find(b =>
b.textContent.toLowerCase().includes('no') ||
b.textContent.toLowerCase().includes('not needed')
);
if (noButton) noButton.click();
}
});
// Submit the application
setTimeout(() => {
this.submitApplication();
}, this.config.actionDelay);
},
// Submit the application
submitApplication: function() {
const submitButton = [...document.querySelectorAll('button')].find(b =>
b.textContent.toLowerCase().includes('submit application')
);
if (submitButton) {
submitButton.click();
this.config.applicationsSent++;
console.log(`Application #${this.config.applicationsSent} submitted successfully`);
// Close any success modal
setTimeout(() => {
const doneButton = [...document.querySelectorAll('button')].find(b =>
b.textContent.toLowerCase().includes('done')
);
if (doneButton) doneButton.click();
}, 2000);
} else {
console.log("Submit button not found - application may not have been sent");
this.closeApplication();
}
},
// Close the application without submitting
closeApplication: function() {
const discardButton = [...document.querySelectorAll('button')].find(b =>
b.textContent.toLowerCase().includes('discard')
);
if (discardButton) discardButton.click();
},
// Try to navigate to next page of results
tryNextPage: function() {
if (this.config.applicationsSent >= this.config.maxApplications) {
console.log(`Reached maximum application limit of ${this.config.maxApplications}`);
return;
}
const nextButton = document.querySelector('button[aria-label="Next"]');
if (nextButton && !nextButton.disabled) {
console.log("Moving to next page of results...");
nextButton.click();
setTimeout(() => this.preparePage(), this.config.nextPageDelay);
} else {
console.log("No more pages of results found");
}
}
};
// Start the bot
LinkedInJobBot.init();
@MarwanShehata
Copy link

Hey there 💯 ,

I saw your LinkedIn Job Application Bot script gist – that's a really cool initiative and a great starting point for automating those applications!

It actually inspired me because I've been working on a similar Tampermonkey script for a different job board, and I've focused on adding things like a persistent UI, saved settings (so you don't have to edit the code each time), tracking which jobs have already been processed, and some more robust error handling.

I got curious and spent some time trying to adapt your LinkedIn bot's logic into that more feature-rich Tampermonkey framework. The idea was to see if it could be made a bit more user-friendly for day-to-day use and more resilient.

I've put together a version that includes:

  • A Tampermonkey UI panel to start/stop, see stats, and change settings.
  • Persistent settings (job titles, location, notes, etc.) saved using GM_setValue.
  • It remembers which jobs it has already processed to avoid duplicates.
  • More detailed stats on its activity.
  • Some utility functions like waitForElement to make it a bit more stable.

The most important thing to know before diving in is that LinkedIn's website structure changes very frequently. This means the CSS selectors in the script (for finding buttons, job listings, form fields, etc.) will almost certainly need to be verified and updated regularly to keep it working. That's the biggest maintenance point for any script like this.

No pressure at all to use this, of course, but I thought I'd share it in case you find it helpful or it gives you some ideas for your own script. You can take from it whatever you like!

Let me know what you think if you get a chance to look at it, I will show it for you if you accepted collaboration. and if you have any questions. Happy to discuss any part of it!

@MarwanShehata
Copy link

MarwanShehata commented May 17, 2025

Ah I forgot, I have done something similar for another job board, called Joblum, it seemed better to start with something simple like this because their website is ALMOST static pages.
https://gist.github.com/MarwanShehata/18fb82b8280edf3d2489878d004bf930

image

@sanjayka1999
Copy link
Author

sanjayka1999 commented May 19, 2025 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment