Skip to content

Instantly share code, notes, and snippets.

@benjaminjackson
Created November 13, 2025 17:39
Show Gist options
  • Select an option

  • Save benjaminjackson/60713b37dd9eb4eea00d567f4f98a43a to your computer and use it in GitHub Desktop.

Select an option

Save benjaminjackson/60713b37dd9eb4eea00d567f4f98a43a to your computer and use it in GitHub Desktop.
Unfollow Everyone on LinkedIn (Manual Confirmation)
// LinkedIn Semi-Automated Unfollow Script
// Opens unfollow modals one at a time and waits for manual confirmation
// You stay in control - manually click "Unfollow" or "Cancel" for each person
(() => {
let count = 0;
const processedPeople = new Set(); // Track who we've successfully unfollowed
const skippedPeople = new Set(); // Track who we've cancelled on
function getAllFollowingButtons() {
return Array.from(document.querySelectorAll('button.artdeco-button--muted.artdeco-button--secondary'))
.filter(btn => {
const buttonText = btn.querySelector('.artdeco-button__text')?.innerText;
const ariaLabel = btn.getAttribute('aria-label');
// Only include buttons we haven't processed or skipped yet
return buttonText === "Following" && !processedPeople.has(ariaLabel) && !skippedPeople.has(ariaLabel);
});
}
function waitForModalToClose() {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
const modal = document.querySelector('.artdeco-modal');
if (!modal) {
clearInterval(checkInterval);
resolve();
}
}, 500);
});
}
async function unfollowNext() {
const buttons = getAllFollowingButtons();
console.log(`Found ${buttons.length} unprocessed "Following" buttons`);
if (buttons.length === 0) {
// Try multiple times to load more content
let loadAttempts = 0;
const maxAttempts = 3;
while (loadAttempts < maxAttempts) {
loadAttempts++;
console.log(`Load attempt ${loadAttempts}/${maxAttempts}...`);
// Click "Show more results" button if it exists
const showMoreButton = document.querySelector('.scaffold-finite-scroll__load-button');
if (showMoreButton && !showMoreButton.disabled) {
console.log("Clicking 'Show more results' button...");
showMoreButton.click();
await new Promise((resolve) => setTimeout(resolve, 5000));
} else {
// Otherwise try scrolling
console.log("Scrolling to bottom...");
window.scrollTo(0, document.body.scrollHeight);
await new Promise((resolve) => setTimeout(resolve, 5000));
}
// Check for new buttons
const newButtons = getAllFollowingButtons();
console.log(`After attempt ${loadAttempts}: found ${newButtons.length} new buttons`);
if (newButtons.length > 0) {
console.log("Found new buttons, continuing...");
return unfollowNext();
}
if (loadAttempts < maxAttempts) {
console.log("No new buttons yet, waiting before next attempt...");
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
// After all attempts, we're done
console.log("Could not find more content after multiple attempts - reached the end");
console.log(`All done! Processed ${count} Following buttons total.`);
console.log(`Successfully unfollowed: ${processedPeople.size}`);
console.log(`Skipped/Cancelled: ${skippedPeople.size}`);
if (skippedPeople.size > 0) {
console.log(`Skipped people:`, Array.from(skippedPeople));
}
return;
}
// Get the first unprocessed button
const button = buttons[0];
const ariaLabel = button.getAttribute("aria-label");
// Extract person's name from aria-label (format: "Click to stop following [Name]")
const personName = ariaLabel.replace('Click to stop following ', '');
count++;
console.log(`${count}. Opening modal: ${ariaLabel}`);
// Scroll button into view more reliably
button.scrollIntoView({ behavior: 'smooth', block: 'center' });
await new Promise((resolve) => setTimeout(resolve, 500));
// Click to open modal
button.click();
// Wait for you to manually confirm/cancel the modal
await waitForModalToClose();
// Delay to let the button update (LinkedIn needs time to re-render)
await new Promise((resolve) => setTimeout(resolve, 1000));
// Re-query for buttons with this person's name (aria-label changes after unfollowing)
const updatedButton = Array.from(document.querySelectorAll('button.artdeco-button--secondary'))
.find(btn => {
const label = btn.getAttribute('aria-label');
return label && (
label === ariaLabel || // Still has old label (cancelled)
label === `Click to follow ${personName}` // Changed to new label (unfollowed)
);
});
if (!updatedButton) {
// Button disappeared completely, count it as success
processedPeople.add(ariaLabel);
console.log(`✓ Unfollowed (button removed from page)`);
} else {
// Check if the button text changed
const buttonText = updatedButton.querySelector('.artdeco-button__text')?.innerText;
const isMuted = updatedButton.classList.contains('artdeco-button--muted');
if (buttonText === "Follow" || !isMuted) {
// Successfully unfollowed (button changed to "Follow" or lost muted class)
processedPeople.add(ariaLabel);
console.log(`✓ Unfollowed`);
} else if (buttonText === "Following" && isMuted) {
// User cancelled, skip this person for now
skippedPeople.add(ariaLabel);
console.log(`✗ Cancelled - skipping for now`);
}
}
// Continue to next
unfollowNext();
}
console.log("Starting semi-automated unfollow. Click 'Unfollow' in each modal to proceed to the next person.");
unfollowNext();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment