Created
April 17, 2025 17:44
-
-
Save sanjayka1999/89a153c209be5e21dfa1650d516922d3 to your computer and use it in GitHub Desktop.
LinkedIn Job Bot
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
| /** | |
| * 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(); |
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
Author
Hey There
Thanks for going through my LinkedIn Job got and giving me a good feedback.
Although it has some limitations as LinkedIn doesn't allow some code
features to go forward and apply for the desired positions. We have to
manually.
I have also gone through the tampermonkey code too it's looks very
interesting and I'll let you know the feedback about it.
Thank you once again
Warm Regards
Sanjay Kathula
…On Sat, May 17, 2025, 9:56 AM Marwan Shehata ***@***.***> wrote:
***@***.**** commented on this gist.
------------------------------
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!
—
Reply to this email directly, view it on GitHub
<https://gist.github.com/sanjayka1999/89a153c209be5e21dfa1650d516922d3#gistcomment-5583321>
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AXRGUOCCTRDEZZZ356ZXK7T26452RBFKMF2HI4TJMJ2XIZLTSKBKK5TBNR2WLJDUOJ2WLJDOMFWWLO3UNBZGKYLEL5YGC4TUNFRWS4DBNZ2F6YLDORUXM2LUPGBKK5TBNR2WLJDHNFZXJJDOMFWWLK3UNBZGKYLEL52HS4DFVRZXKYTKMVRXIX3UPFYGLK2HNFZXIQ3PNVWWK3TUUZ2G64DJMNZZDAVEOR4XAZNEM5UXG5FFOZQWY5LFVEYTGNZUG44DIOBTU52HE2LHM5SXFJTDOJSWC5DF>
.
You are receiving this email because you authored the thread.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

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:
GM_setValue.waitForElementto 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!