-
Star
(104)
You must be signed in to star a gist -
Fork
(41)
You must be signed in to fork a gist
-
-
Save thealphadollar/7c0ee76664cbd28aecc1bd235f0202fd to your computer and use it in GitHub Desktop.
| // If the script does not work, you may need to allow same site scripting https://stackoverflow.com/a/50902950 | |
| Linkedin = { | |
| config: { | |
| scrollDelay: 3000, | |
| actionDelay: 5000, | |
| nextPageDelay: 5000, | |
| // set to -1 for no limit | |
| maxRequests: -1, | |
| totalRequestsSent: 0, | |
| // set to false to skip adding note in invites | |
| addNote: true, | |
| note: "Hey {{name}}, I'm looking forward to connecting with you!", | |
| }, | |
| init: function (data, config) { | |
| console.info("INFO: script initialized on the page..."); | |
| console.debug( | |
| "DEBUG: scrolling to bottom in " + config.scrollDelay + " ms" | |
| ); | |
| setTimeout(() => this.scrollBottom(data, config), config.actionDelay); | |
| }, | |
| scrollBottom: function (data, config) { | |
| window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" }); | |
| console.debug("DEBUG: scrolling to top in " + config.scrollDelay + " ms"); | |
| setTimeout(() => this.scrollTop(data, config), config.scrollDelay); | |
| }, | |
| scrollTop: function (data, config) { | |
| window.scrollTo({ top: 0, behavior: "smooth" }); | |
| console.debug( | |
| "DEBUG: inspecting elements in " + config.scrollDelay + " ms" | |
| ); | |
| setTimeout(() => this.inspect(data, config), config.scrollDelay); | |
| }, | |
| inspect: function (data, config) { | |
| var totalRows = this.totalRows(); | |
| console.debug("DEBUG: total search results found on page are " + totalRows); | |
| if (totalRows >= 0) { | |
| this.compile(data, config); | |
| } else { | |
| console.warn("WARN: end of search results!"); | |
| this.complete(config); | |
| } | |
| }, | |
| compile: function (data, config) { | |
| var elements = document.querySelectorAll("button"); | |
| data.pageButtons = [...elements].filter(function (element) { | |
| return element.textContent.trim() === "Connect"; | |
| }); | |
| if (!data.pageButtons || data.pageButtons.length === 0) { | |
| console.warn("ERROR: no connect buttons found on page!"); | |
| console.info("INFO: moving to next page..."); | |
| setTimeout(() => { | |
| this.nextPage(config); | |
| }, config.nextPageDelay); | |
| } else { | |
| data.pageButtonTotal = data.pageButtons.length; | |
| console.info("INFO: " + data.pageButtonTotal + " connect buttons found"); | |
| data.pageButtonIndex = 0; | |
| var names = document.getElementsByClassName("entity-result__title-text"); | |
| names = [...names].filter(function (element) { | |
| return element.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.textContent.includes( | |
| "Connect\n" | |
| ); | |
| }); | |
| data.connectNames = [...names].map(function (element) { | |
| return element.innerText.split(" ")[0]; | |
| }); | |
| console.debug( | |
| "DEBUG: starting to send invites in " + config.actionDelay + " ms" | |
| ); | |
| setTimeout(() => { | |
| this.sendInvites(data, config); | |
| }, config.actionDelay); | |
| } | |
| }, | |
| sendInvites: function (data, config) { | |
| console.debug("remaining requests " + config.maxRequests); | |
| if (config.maxRequests == 0) { | |
| console.info("INFO: max requests reached for the script run!"); | |
| this.complete(config); | |
| } else { | |
| console.debug( | |
| "DEBUG: sending invite to " + | |
| (data.pageButtonIndex + 1) + | |
| " out of " + | |
| data.pageButtonTotal | |
| ); | |
| var button = data.pageButtons[data.pageButtonIndex]; | |
| button.click(); | |
| if (config.addNote && config.note) { | |
| console.debug( | |
| "DEBUG: clicking Add a note in popup, if present, in " + | |
| config.actionDelay + | |
| " ms" | |
| ); | |
| setTimeout(() => this.clickAddNote(data, config), config.actionDelay); | |
| } else { | |
| console.debug( | |
| "DEBUG: clicking done in popup, if present, in " + | |
| config.actionDelay + | |
| " ms" | |
| ); | |
| setTimeout(() => this.clickDone(data, config), config.actionDelay); | |
| } | |
| } | |
| }, | |
| clickAddNote: function (data, config) { | |
| var buttons = document.querySelectorAll("button"); | |
| var addNoteButton = Array.prototype.filter.call(buttons, function (el) { | |
| return el.textContent.trim() === "Add a note"; | |
| }); | |
| // adding note if required | |
| if (addNoteButton && addNoteButton[0]) { | |
| console.debug("DEBUG: clicking add a note button to paste note"); | |
| addNoteButton[0].click(); | |
| console.debug("DEBUG: pasting note in " + config.actionDelay); | |
| setTimeout(() => this.pasteNote(data, config), config.actionDelay); | |
| } else { | |
| console.debug( | |
| "DEBUG: add note button not found, clicking send on the popup in " + | |
| config.actionDelay | |
| ); | |
| setTimeout(() => this.clickDone(data, config), config.actionDelay); | |
| } | |
| }, | |
| pasteNote: function (data, config) { | |
| noteTextBox = document.getElementById("custom-message"); | |
| noteTextBox.value = config.note.replace( | |
| "{{name}}", | |
| data.connectNames[data.pageButtonIndex] | |
| ); | |
| noteTextBox.dispatchEvent( | |
| new Event("input", { | |
| bubbles: true, | |
| }) | |
| ); | |
| console.debug( | |
| "DEBUG: clicking send in popup, if present, in " + | |
| config.actionDelay + | |
| " ms" | |
| ); | |
| setTimeout(() => this.clickDone(data, config), config.actionDelay); | |
| }, | |
| clickDone: function (data, config) { | |
| var buttons = document.querySelectorAll("button"); | |
| var doneButton = Array.prototype.filter.call(buttons, function (el) { | |
| return el.textContent.trim() === "Send"; | |
| }); | |
| // Click the first send button | |
| if (doneButton && doneButton[0]) { | |
| console.debug("DEBUG: clicking send button to close popup"); | |
| doneButton[0].click(); | |
| } else { | |
| console.debug( | |
| "DEBUG: send button not found, clicking close on the popup in " + | |
| config.actionDelay | |
| ); | |
| } | |
| setTimeout(() => this.clickClose(data, config), config.actionDelay); | |
| }, | |
| clickClose: function (data, config) { | |
| var closeButton = document.getElementsByClassName( | |
| "artdeco-modal__dismiss artdeco-button artdeco-button--circle artdeco-button--muted artdeco-button--2 artdeco-button--tertiary ember-view" | |
| ); | |
| if (closeButton && closeButton[0]) { | |
| closeButton[0].click(); | |
| } | |
| console.info( | |
| "INFO: invite sent to " + | |
| (data.pageButtonIndex + 1) + | |
| " out of " + | |
| data.pageButtonTotal | |
| ); | |
| config.maxRequests--; | |
| config.totalRequestsSent++; | |
| if (data.pageButtonIndex === data.pageButtonTotal - 1) { | |
| console.debug( | |
| "DEBUG: all connections for the page done, going to next page in " + | |
| config.actionDelay + | |
| " ms" | |
| ); | |
| setTimeout(() => this.nextPage(config), config.actionDelay); | |
| } else { | |
| data.pageButtonIndex++; | |
| console.debug( | |
| "DEBUG: sending next invite in " + config.actionDelay + " ms" | |
| ); | |
| setTimeout(() => this.sendInvites(data, config), config.actionDelay); | |
| } | |
| }, | |
| nextPage: function (config) { | |
| var pagerButton = document.getElementsByClassName( | |
| "artdeco-pagination__button--next" | |
| ); | |
| if ( | |
| !pagerButton || | |
| pagerButton.length === 0 || | |
| pagerButton[0].hasAttribute("disabled") | |
| ) { | |
| console.info("INFO: no next page button found!"); | |
| return this.complete(config); | |
| } | |
| console.info("INFO: Going to next page..."); | |
| pagerButton[0].click(); | |
| setTimeout(() => this.init({}, config), config.nextPageDelay); | |
| }, | |
| complete: function (config) { | |
| console.info( | |
| "INFO: script completed after sending " + | |
| config.totalRequestsSent + | |
| " connection requests" | |
| ); | |
| }, | |
| totalRows: function () { | |
| var search_results = document.getElementsByClassName("search-result"); | |
| if (search_results && search_results.length != 0) { | |
| return search_results.length; | |
| } else { | |
| return 0; | |
| } | |
| }, | |
| }; | |
| Linkedin.init({}, Linkedin.config); |
FIXED NEXT BUTTON ISSUE
// If the script does not work, you may need to allow same site scripting https://stackoverflow.com/a/50902950
Linkedin = {
config: {
scrollDelay: 1500, // Reduced from 3000
actionDelay: 2000, // Reduced from 5000
nextPageDelay: 2500, // Reduced from 5000
// set to -1 for no limit
maxRequests: -1,
totalRequestsSent: 0,
// set to false to skip adding note in invites
addNote: true,
note: "Hey {{name}}, I'm looking forward to connecting with you!",
},
init: function (data, config) {
console.info("INFO: script initialized on the page...");
console.debug(
"DEBUG: scrolling to bottom in " + config.scrollDelay + " ms"
);
setTimeout(() => this.scrollBottom(data, config), config.actionDelay);
},
scrollBottom: function (data, config) {
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
console.debug("DEBUG: scrolling to top in " + config.scrollDelay + " ms");
setTimeout(() => this.scrollTop(data, config), config.scrollDelay);
},
scrollTop: function (data, config) {
window.scrollTo({ top: 0, behavior: "smooth" });
console.debug(
"DEBUG: inspecting elements in " + config.scrollDelay + " ms"
);
setTimeout(() => this.inspect(data, config), config.scrollDelay);
},
inspect: function (data, config) {
var totalRows = this.totalRows();
console.debug("DEBUG: total search results found on page are " + totalRows);
if (totalRows >= 0) {
this.compile(data, config);
} else {
console.warn("WARN: end of search results!");
this.complete(config);
}
},
compile: function (data, config) {
var elements = document.querySelectorAll("button");
data.pageButtons = [...elements].filter(function (element) {
return element.textContent.trim() === "Connect";
});
if (!data.pageButtons || data.pageButtons.length === 0) {
console.warn("ERROR: no connect buttons found on page!");
console.info("INFO: moving to next page...");
setTimeout(() => {
this.nextPage(config);
}, config.nextPageDelay);
} else {
data.pageButtonTotal = data.pageButtons.length;
console.info("INFO: " + data.pageButtonTotal + " connect buttons found");
data.pageButtonIndex = 0;
var names = document.getElementsByClassName("entity-result__title-text");
names = [...names].filter(function (element) {
return element.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.textContent.includes(
"Connect\n"
);
});
data.connectNames = [...names].map(function (element) {
return element.innerText.split(" ")[0];
});
console.debug(
"DEBUG: starting to send invites in " + config.actionDelay + " ms"
);
setTimeout(() => {
this.sendInvites(data, config);
}, config.actionDelay);
}
},
sendInvites: function (data, config) {
console.debug("remaining requests " + config.maxRequests);
if (config.maxRequests == 0) {
console.info("INFO: max requests reached for the script run!");
this.complete(config);
} else {
console.debug(
"DEBUG: sending invite to " +
(data.pageButtonIndex + 1) +
" out of " +
data.pageButtonTotal
);
var button = data.pageButtons[data.pageButtonIndex];
button.click();
if (config.addNote && config.note) {
console.debug(
"DEBUG: clicking Add a note in popup, if present, in " +
config.actionDelay +
" ms"
);
setTimeout(() => this.clickAddNote(data, config), config.actionDelay);
} else {
console.debug(
"DEBUG: clicking done in popup, if present, in " +
config.actionDelay +
" ms"
);
setTimeout(() => this.clickDone(data, config), config.actionDelay);
}
}
},
clickAddNote: function (data, config) {
var buttons = document.querySelectorAll("button");
var addNoteButton = Array.prototype.filter.call(buttons, function (el) {
return el.textContent.trim() === "Add a note";
});
// adding note if required
if (addNoteButton && addNoteButton[0]) {
console.debug("DEBUG: clicking add a note button to paste note");
addNoteButton[0].click();
console.debug("DEBUG: pasting note in " + config.actionDelay);
setTimeout(() => this.pasteNote(data, config), config.actionDelay);
} else {
console.debug(
"DEBUG: add note button not found, clicking send on the popup in " +
config.actionDelay
);
setTimeout(() => this.clickDone(data, config), config.actionDelay);
}
},
pasteNote: function (data, config) {
noteTextBox = document.getElementById("custom-message");
noteTextBox.value = config.note.replace(
"{{name}}",
data.connectNames[data.pageButtonIndex]
);
noteTextBox.dispatchEvent(
new Event("input", {
bubbles: true,
})
);
console.debug(
"DEBUG: clicking send in popup, if present, in " +
config.actionDelay +
" ms"
);
setTimeout(() => this.clickDone(data, config), config.actionDelay);
},
clickDone: function (data, config) {
var buttons = document.querySelectorAll("button");
var doneButton = Array.prototype.filter.call(buttons, function (el) {
return el.textContent.trim() === "Send";
});
// Click the first send button
if (doneButton && doneButton[0]) {
console.debug("DEBUG: clicking send button to close popup");
doneButton[0].click();
} else {
console.debug(
"DEBUG: send button not found, clicking close on the popup in " +
config.actionDelay
);
}
setTimeout(() => this.clickClose(data, config), config.actionDelay);
},
clickClose: function (data, config) {
var closeButton = document.getElementsByClassName(
"artdeco-modal__dismiss artdeco-button artdeco-button--circle artdeco-button--muted artdeco-button--2 artdeco-button--tertiary ember-view"
);
if (closeButton && closeButton[0]) {
closeButton[0].click();
}
console.info(
"INFO: invite sent to " +
(data.pageButtonIndex + 1) +
" out of " +
data.pageButtonTotal
);
config.maxRequests--;
config.totalRequestsSent++;
if (data.pageButtonIndex === data.pageButtonTotal - 1) {
console.debug(
"DEBUG: all connections for the page done, going to next page in " +
config.actionDelay +
" ms"
);
setTimeout(() => this.nextPage(config), config.actionDelay);
} else {
data.pageButtonIndex++;
console.debug(
"DEBUG: sending next invite in " + config.actionDelay + " ms"
);
setTimeout(() => this.sendInvites(data, config), config.actionDelay);
}
},
nextPage: function (config) {
// --- FIX STARTS HERE ---
// Use a more reliable selector to find the 'Next' button
var pagerButton = document.querySelector('button[aria-label="Next"]');
// Check if the button doesn't exist or is disabled
if (!pagerButton || pagerButton.hasAttribute("disabled")) {
console.info("INFO: No 'Next' page button found or it is disabled. End of results.");
return this.complete(config);
}
// --- FIX ENDS HERE ---
console.info("INFO: Going to next page...");
pagerButton.click();
setTimeout(() => this.init({}, config), config.nextPageDelay);
},
complete: function (config) {
console.info(
"INFO: script completed after sending " +
config.totalRequestsSent +
" connection requests"
);
},
totalRows: function () {
var search_results = document.getElementsByClassName("search-result");
if (search_results && search_results.length != 0) {
return search_results.length;
} else {
return 0;
}
},
};
Linkedin.init({}, Linkedin.config);
// LinkedIn Connection Script - v4 (Definitive "Next Button" Fix)
(async () => {
// --- CONFIGURATION ---
const config = {
scrollDelay: 2000,
actionDelay: 2000,
nextPageDelay: 3000,
maxRequests: 25, // IMPORTANT: Keep this low to avoid detection.
addNote: true,
note: "Hi {{name}}, I came across your profile and would love to connect.",
};
// --- SCRIPT STATE ---
let totalRequestsSent = 0;
// --- HELPER FUNCTIONS ---
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const scrollToBottom = async () => {
console.log("📜 Scrolling to the bottom of the page...");
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
await sleep(config.scrollDelay);
};
// --- ⭐️ DEFINITIVE FIX FOR THE "NEXT" BUTTON ⭐️ ---
/**
- Tries multiple, updated selectors to reliably find the "Next" page button.
- @returns {HTMLElement|null} - The button element or null if not found.
*/
const findNextButton = () => {
// Method 1: Use the test ID (most stable and best practice)
let nextButton = document.querySelector('button[data-testid="pagination-control-next-btn"]');
if (nextButton && !nextButton.disabled) return nextButton;
// Method 2: Use the corrected aria-label from your screenshot
nextButton = document.querySelector('button[aria-label="Next page"]');
if (nextButton && !nextButton.disabled) return nextButton;
// Method 3: Fallback using the icon
const chevronIcon = document.querySelector('.artdeco-icon[type="chevron-right-small"]'); // Note: icon name might change
if (chevronIcon) {
const potentialButton = chevronIcon.closest("button");
if (potentialButton && !potentialButton.disabled) return potentialButton;
}
console.log("Could not find a valid 'Next' button via any method.");
return null; // Return null if no enabled button was found
};
const goToNextPage = async () => {
const nextButton = findNextButton(); // Use our new robust function
if (!nextButton) {
console.log("🏁 No enabled 'Next' button found. End of results.");
return false;
}
console.log("➡️ Clicking 'Next' to go to the next page...");
nextButton.click();
await sleep(config.nextPageDelay);
return true;
};
const processRequest = async (connectButton) => {
const resultContainer = connectButton.closest('.reusable-search__result-container');
let name = "there";
if (resultContainer) {
const nameElement = resultContainer.querySelector('.entity-result__title-text a .app-aware-link');
if (nameElement) name = nameElement.innerText.split(" ")[0].trim();
}
console.log(`📩 Preparing to send invite to ${name}...`);
connectButton.click();
await sleep(config.actionDelay);
if (config.addNote) {
const addNoteButton = document.querySelector('button[aria-label="Add a note"]');
if (addNoteButton) {
addNoteButton.click();
await sleep(config.actionDelay);
const noteTextarea = document.querySelector("textarea#custom-message");
if (noteTextarea) {
noteTextarea.value = config.note.replace("{{name}}", name);
noteTextarea.dispatchEvent(new Event("input", { bubbles: true }));
await sleep(config.actionDelay);
}
}
}
const sendButton = document.querySelector('button[aria-label="Send now"]');
if (sendButton) {
sendButton.click();
totalRequestsSent++;
console.log(`✅ Invite sent! Total sent: ${totalRequestsSent}`);
} else {
const closeButton = document.querySelector('button[aria-label="Dismiss"]');
if (closeButton) closeButton.click();
console.warn("⚠️ Could not find the 'Send' button. Closing modal and moving on.");
}
await sleep(config.actionDelay);
};
// --- MAIN LOGIC ---
const run = async () => {
console.log("🚀 LinkedIn Connection Script Initialized!");
let keepRunning = true;
while (keepRunning) {
await scrollToBottom();
const connectButtons = Array.from(document.querySelectorAll('button')).filter(button =>
button.textContent.trim() === "Connect" && !button.closest('.entity-result__actions')?.textContent.includes('Pending')
);
if (connectButtons.length === 0) {
console.log("🧐 No new 'Connect' buttons found on this page.");
keepRunning = await goToNextPage();
continue;
}
console.log(`🔎 Found ${connectButtons.length} 'Connect' buttons on the page.`);
for (const button of connectButtons) {
if (config.maxRequests !== -1 && totalRequestsSent >= config.maxRequests) {
console.log(`🎯 Maximum request limit of ${config.maxRequests} reached.`);
keepRunning = false;
break;
}
await processRequest(button);
}
if (keepRunning) {
keepRunning = await goToNextPage();
}
}
console.log(`🎉 Script finished. A total of ${totalRequestsSent} connection requests were sent.`);
};
run();
})();
Code working with next page option.
ClickDone "Send without a Note" Logic wasn't working for me, I resolved it using below code !
Hope it helps !
Just set addNote: false, in the configs and modify the clickDone function in the script
async clickDone(data, config) {
await new Promise((resolve) => setTimeout(resolve, 800));
const sendWithoutBtn = Array.from(
document.querySelectorAll(
"button, input[type='button'], input[type='submit']",
),
).find(
(btn) =>
(btn.innerText && btn.innerText.includes("Send without")) ||
(btn.value && btn.value.includes("Send without")),
);
if (sendWithoutBtn) {
console.debug("DEBUG: clicking 'Send without' button to close popup");
sendWithoutBtn.click();
} else {
console.debug(
"DEBUG: 'Send without' button not found, clicking close on the popup in " +
config.actionDelay,
);
}
setTimeout(() => this.clickClose(data, config), config.actionDelay);
},
Hello, the tag to refer to next page has been changed, in nextPage you can use: let nextButton = document.querySelector('button[aria-label="Next page"][data-testid="pagination-control-next-btn"]:not([disabled])');
last working script I use to add connections (no message is sent) is:
Linkedin = {
config: {
scrollDelay: 3000,
actionDelay: 4000,
nextPageDelay: 9000,
maxRequests: -1,
totalRequestsSent: 0,
addNote: false,
note: "Hello"
},
init: function () {
console.info("INFO: script initialized on the page...");
setTimeout(() => this.scrollBottom(), this.config.scrollDelay);
},
scrollBottom: function () {
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
setTimeout(() => this.scrollTop(), this.config.scrollDelay);
},
scrollTop: function () {
window.scrollTo({ top: 0, behavior: 'smooth' });
setTimeout(() => this.findConnectButtons(), this.config.scrollDelay);
},
findConnectButtons: function () {
let buttons = document.querySelectorAll("button");
let connectButtons = [...buttons].filter(btn => btn.textContent.trim() === "Connect");
if (connectButtons.length === 0) {
console.warn("No 'Connect' buttons found. Moving to next page...");
setTimeout(() => this.nextPage(), this.config.nextPageDelay);
} else {
console.info(`Found ${connectButtons.length} 'Connect' buttons`);
this.sendInvites(connectButtons, 0);
}
},
sendInvites: function (buttons, index) {
if (index >= buttons.length || this.config.maxRequests === 0) {
console.info("Finished sending invites on this page.");
setTimeout(() => this.nextPage(), this.config.nextPageDelay);
return;
}
buttons[index].click();
console.debug("Clicked 'Connect' button");
setTimeout(() => this.clickSendWithoutNote(buttons, index), this.config.actionDelay);
},
clickSendWithoutNote: function (buttons, index) {
let sendWithoutNoteButton = document.querySelector("button[aria-label='Send without a note']");
if (sendWithoutNoteButton) {
sendWithoutNoteButton.click();
console.info("Clicked 'Send without a note'");
} else {
console.warn("'Send without a note' button not found, closing popup.");
let closeButton = document.querySelector(".artdeco-modal__dismiss");
if (closeButton) closeButton.click();
}
this.config.totalRequestsSent++;
this.config.maxRequests--;
setTimeout(() => this.sendInvites(buttons, index + 1), this.config.actionDelay);
},
nextPage: function () {
let nextButton = document.querySelector('button[aria-label="Next page"][data-testid="pagination-control-next-btn"]:not([disabled])');
if (nextButton) {
nextButton.click();
console.info("Navigating to next page...");
setTimeout(() => this.init(), this.config.nextPageDelay);
} else {
console.info("No more pages to process.");
}
}
};
Linkedin.init();
Fixed Connect Button selection , its a link (a) now and not a Button, also selected the Next button selection, using a fixed attribute => Tested = OK
Linkedin = {
config: {
scrollDelay: 1000,
actionDelay: 250,
nextPageDelay: 250,
maxRequests: -1,
totalRequestsSent: 0,
},
init: function (data, config) {
console.info("INFO: script initialized on the page...");
console.debug(
"DEBUG: scrolling to bottom in " + config.scrollDelay + " ms"
);
setTimeout(() => this.scrollBottom(data, config), config.actionDelay);
},
scrollBottom: function (data, config) {
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
console.debug("DEBUG: scrolling to top in " + config.scrollDelay + " ms");
setTimeout(() => this.scrollTop(data, config), config.scrollDelay);
},
scrollTop: function (data, config) {
window.scrollTo({ top: 0, behavior: "smooth" });
console.debug(
"DEBUG: inspecting elements in " + config.scrollDelay + " ms"
);
setTimeout(() => this.inspect(data, config), config.scrollDelay);
},
inspect: function (data, config) {
var totalRows = this.totalRows();
console.debug("DEBUG: total search results found on page are " + totalRows);
if (totalRows >= 0) {
this.compile(data, config);
} else {
console.warn("WARN: end of search results!");
this.complete(config);
}
},
compile: function (data, config) {
var elements = document.querySelectorAll("a");
data.pageButtons = [...elements].filter(function (element) {
return element.textContent.trim() === "Connect";
});
if (!data.pageButtons || data.pageButtons.length === 0) {
console.warn("ERROR: no connect buttons found on page!");
console.info("INFO: moving to next page...");
setTimeout(() => {
this.nextPage(config);
}, config.nextPageDelay);
} else {
data.pageButtonTotal = data.pageButtons.length;
console.info("INFO: " + data.pageButtonTotal + " connect buttons found");
data.pageButtonIndex = 0;
console.debug(
"DEBUG: starting to send invites in " + config.actionDelay + " ms"
);
setTimeout(() => {
this.sendInvites(data, config);
}, config.actionDelay);
}
},
sendInvites: function (data, config) {
console.debug("remaining requests " + config.maxRequests);
if (config.maxRequests == 0) {
console.info("INFO: max requests reached for the script run!");
this.complete(config);
} else {
console.debug(
"DEBUG: sending invite to " +
(data.pageButtonIndex + 1) +
" out of " +
data.pageButtonTotal
);
var button = data.pageButtons[data.pageButtonIndex];
button.click();
console.debug(
"DEBUG: clicking send in popup, if present, in " +
config.actionDelay +
" ms"
);
setTimeout(() => this.clickSend(data, config), config.actionDelay);
}
},
clickSend: function (data, config) {
var buttons = document.querySelectorAll("button");
var sendButton = Array.prototype.filter.call(buttons, function (el) {
return el.textContent.trim() === "Send without a note";
});
// Click the first send button
if (sendButton && sendButton[0]) {
console.debug("DEBUG: clicking send button to close popup");
sendButton[0].click();
} else {
console.debug(
"DEBUG: send button not found, clicking close on the popup in " +
config.actionDelay
);
}
setTimeout(() => this.clickClose(data, config), config.actionDelay);
},
clickClose: function (data, config) {
var closeButton = document.getElementById("ember1683");
if (closeButton) {
closeButton.click();
}
console.info(
"INFO: invite sent to " +
(data.pageButtonIndex + 1) +
" out of " +
data.pageButtonTotal
);
config.maxRequests--;
config.totalRequestsSent++;
if (data.pageButtonIndex === data.pageButtonTotal - 1) {
console.debug(
"DEBUG: all connections for the page done, going to next page in " +
config.actionDelay +
" ms"
);
setTimeout(() => this.nextPage(config), config.actionDelay);
} else {
data.pageButtonIndex++;
console.debug(
"DEBUG: sending next invite in " + config.actionDelay + " ms"
);
setTimeout(() => this.sendInvites(data, config), config.actionDelay);
}
},
nextPage: function (config) {
/*
var pagerButton = document.getElementsByClassName(
"artdeco-pagination__button--next"
);
*/
var pagerButton = document.querySelectorAll('button[data-testid="pagination-controls-next-button-visible"]');
if (
!pagerButton ||
pagerButton.length === 0 ||
pagerButton[0].hasAttribute("disabled")
) {
console.info("INFO: no next page button found!");
return this.complete(config);
}
console.info("INFO: Going to next page...");
pagerButton[0].click();
setTimeout(() => this.init({}, config), config.nextPageDelay);
},
complete: function (config) {
console.info(
"INFO: script completed after sending " +
config.totalRequestsSent +
" connection requests"
);
},
totalRows: function () {
var search_results = document.getElementsByClassName("search-result");
if (search_results && search_results.length != 0) {
return search_results.length;
} else {
return 0;
}
},
};
Linkedin.init({}, Linkedin.config);
// Improved LinkedIn Connection Script
(async () => {
// --- CONFIGURATION ---
const config = {
scrollDelay: 2000, // Time in ms to wait after scrolling
actionDelay: 2000, // Time in ms between actions (clicks, typing)
nextPageDelay: 3000, // Time in ms to wait after clicking "Next"
maxRequests: 25, // Set to a low number to avoid detection. -1 for no limit.
addNote: true, // Set to false to send invites without a note.
note: "Hi {{name}}, I came across your profile and would love to connect.",
};
// --- SCRIPT STATE ---
let totalRequestsSent = 0;
// --- HELPER FUNCTIONS ---
/**
*/
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
/**
*/
const scrollToBottom = async () => {
console.log("📜 Scrolling to the bottom of the page...");
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
await sleep(config.scrollDelay);
};
/**
*/
const goToNextPage = async () => {
const nextButton = document.querySelector(".artdeco-pagination__button--next:not([disabled])");
if (!nextButton) {
console.log("🏁 No 'Next' button found or it is disabled. End of results.");
return false;
}
console.log("➡️ Clicking 'Next' to go to the next page...");
nextButton.click();
await sleep(config.nextPageDelay);
return true;
};
/**
*/
const processRequest = async (connectButton) => {
// Find the name associated with this connect button more reliably
const resultContainer = connectButton.closest('.reusable-search__result-container');
let name = "there"; // Default name
if (resultContainer) {
const nameElement = resultContainer.querySelector('.entity-result__title-text a .app-aware-link');
if (nameElement) {
name = nameElement.innerText.split(" ")[0].trim();
}
}
};
// --- MAIN LOGIC ---
const run = async () => {
console.log("🚀 LinkedIn Connection Script Initialized!");
};
// Start the script
run();
})();