Skip to content

Instantly share code, notes, and snippets.

@ankitjaininfo
Last active December 4, 2024 04:29
Show Gist options
  • Save ankitjaininfo/8360b4431ebc6684aec5b9e7224fbd55 to your computer and use it in GitHub Desktop.
Save ankitjaininfo/8360b4431ebc6684aec5b9e7224fbd55 to your computer and use it in GitHub Desktop.
LinkedIn Automation: Send bulk message to job applicants using browser/JavaScript automation
/* This automation script is designed to streamline the process of messaging applicants on LinkedIn by performing the following steps:
*********************************************************
* Iterate Through Applicants And Send A Message To All *
*********************************************************
Copy and paste the following script to the Console tab of Developer Tools. The script identifies each applicant listed on the page and sequentially processes them.
Works on the URls of likes: `https://www.linkedin.com/hiring/jobs/******/applicants/******/detail/?keyword=&r=UNRATED%2CGOOD_FIT%2CMAYBE&sort_by=APPLIED_DATE`
1. Click the Applicant:
2. Click the Message Button:
3. Paste a Custom Message:
4. Send the Message
5. Waiting for the message to be sent
6. Close the message tab.
Notes:
- After processing all applicants on the current page, the script navigates to the previous page in the pagination and repeats the process.
- You may see the script getting stuck of the LN's message rate limits kicks in. Some manual intervention required.
- You may need to teak a few classes for it to work.
*/
async function processPagination() {
// close all inactive bubbles.
closeInactiveBubble();
// Process applicants on the current page
await processApplicants(0);
// Find the current pagination element and click the previous sibling
const currentPage = document.querySelector(
'li.artdeco-pagination__indicator.artdeco-pagination__indicator--number.active.selected'
);
if (currentPage && currentPage.previousElementSibling) {
const previousPage = currentPage.previousElementSibling.querySelector('button');
if (previousPage) {
await clickElement(previousPage);
await delay(3000); // Wait for 3 seconds to ensure the page loads
console.log("STARTING NEW PAGE", (new Date()).toISOString());
await processPagination(); // Recursively process the previous page
}
}
}
async function processApplicants(startAt) {
let start = startAt || 0;
// Step 1: Find applicants
const applicants = Array.from(document.getElementsByClassName("hiring-applicants__list-item")).slice(start, 26);
let count = 0 + start;
for (const applicantElement of applicants) {
// Step 2.1: Click the first child element
await clickElement(applicantElement.firstElementChild);
// Step 2.2: Click the button inside the 'hiring-applicant-header-actions' class
const headerActions = document.querySelector('.hiring-applicant-header-actions');
if (headerActions) {
const actionButton = headerActions.querySelectorAll('button')[1];
if (actionButton) await clickElement(actionButton);
}
await delay(300);
// Step 2.3: Find the active conversation bubble and update the text
let allBubbles = document.querySelectorAll(
'.msg-overlay-conversation-bubble:not(.msg-overlay-conversation-bubble--is-minimized)'
);
for (const aBubble of allBubbles) {
sendMessageInBubble(aBubble);
}
// Add a small delay between processing each applicant for stability
await delay(2500);
console.log("Processed:", count);
count++;
}
}
async function sendMessageInBubble(activeBubble) {
if (activeBubble) {
const editableElement = activeBubble.querySelector('.msg-form__contenteditable');
if (editableElement) {
editableElement.innerHTML = `<p>Thank you for applying! As the next step, please complete one of the following exercises:</p><p>
</p><p>Last date for submission: 8th Dec, 2024
</p><p>Company Name</p>`;
// Dispatch an input event to trigger listeners
const inputEvent = new Event('input', {
bubbles: true,
cancelable: true,
});
editableElement.dispatchEvent(inputEvent);
// Dispatch a keyup event to simulate user interaction
const keyupEvent = new Event('keyup', {
bubbles: true,
cancelable: true,
});
editableElement.dispatchEvent(keyupEvent);
await delay(300);
// Step 2.4: Click the send button and wait for the 'Sending...' text to disappear
const sendButton = activeBubble.querySelector('.msg-form__send-button');
if (sendButton) {
await clickElement(sendButton);
await waitForThankYouMessage(activeBubble);
//await waitForSendingToDisappear(activeBubble);
}
await delay(1000);
// Step 2.5: Close the conversation bubble
const headerControls = activeBubble.querySelector('.msg-overlay-bubble-header__controls');
if (headerControls) {
const closeButton = headerControls.querySelector('button:last-child');
if (closeButton) await clickElement(closeButton);
}
await delay(500);
}
}
}
// Helper function to wait for 'Sending...' to disappear
function waitForSendingToDisappear(activeBubble) {
return new Promise(resolve => {
const interval = setInterval(() => {
const sendingElement = activeBubble.querySelector('*:contains("Sending...")');
if (!sendingElement) {
clearInterval(interval);
resolve();
}
}, 100); // Check every 100ms
});
}
// Helper function to simulate a synchronous click
function clickElement(element) {
return new Promise(resolve => {
if (element) {
element.click();
setTimeout(resolve, 800); // Allow some time for the click to take effect
} else {
resolve(); // Resolve immediately if no element
}
});
}
// Helper function for a delay
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function waitForSendingToDisappear(activeBubble) {
return new Promise(resolve => {
const interval = setInterval(() => {
// Find all elements inside the activeBubble
const elements = activeBubble.querySelectorAll('*');
// Check if any element contains the text 'Sending...'
const isSendingVisible = Array.from(elements).some(el => el.textContent.includes("Sending..."));
if (!isSendingVisible) {
clearInterval(interval);
resolve();
}
}, 100); // Check every 100ms
});
}
function waitForThankYouMessage(activeBubble) {
return new Promise(resolve => {
const interval = setInterval(() => {
// Find the element with class 'msg-s-event__content'
const thankYouElement = activeBubble.querySelector('.msg-s-event__content');
// Check if the text content includes "Thank you for applying!"
if (thankYouElement && thankYouElement.textContent.includes("Thank you for applying!")) {
clearInterval(interval);
resolve();
}
}, 100); // Check every 100ms
});
}
function closeInactiveBubble() {
// Select all elements with the specified class
const inActiveBubbles = document.querySelectorAll(".msg-overlay-conversation-bubble--default-inactive");
// Loop through each element
inActiveBubbles.forEach((bubble) => {
// Find the child element with the specified class inside each bubble
const controls = bubble.querySelector(".msg-overlay-bubble-header__controls");
// Check if the controls element exists
if (controls) {
// Find the button inside the controls element
const button = controls.querySelector("button");
// Check if the button exists and click it
if (button) {
button.click();
console.log("Clicked button in:", bubble);
} else {
console.warn("No button found in controls for bubble:", bubble);
}
} else {
console.warn("No controls found in bubble:", bubble);
}
});
}
// Run the process
processPagination();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment