Last active
August 2, 2026 00:18
-
-
Save JayCuthrell/de951960b96b94d2afe11918b3ff02b6 to your computer and use it in GitHub Desktop.
LinkedIn Feed cleaner to unfollow everything that can be unfollowed
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
| javascript:(async () => { | |
| const MAX_UNFOLLOWS = 25; | |
| const MIN_DELAY_MS = 4000; | |
| const MAX_DELAY_MS = 8000; | |
| const DROPDOWN_WAIT_MS = 1000; | |
| const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); | |
| const getRandomDelay = () => Math.floor(Math.random() * (MAX_DELAY_MS - MIN_DELAY_MS + 1)) + MIN_DELAY_MS; | |
| const simulateFullClick = (el) => { | |
| ['pointerdown', 'mousedown', 'mouseup', 'click'].forEach((type) => { | |
| el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window })); | |
| }); | |
| }; | |
| const getUnprocessedPosts = () => { | |
| return Array.from(document.querySelectorAll('[role="listitem"], .feed-shared-update-v2, div[data-id*="urn:li:activity"]')) | |
| .filter((post) => post.dataset.scriptProcessed !== "true"); | |
| }; | |
| const getPostAuthor = (post, menuBtn) => { | |
| if (menuBtn) { | |
| const aria = menuBtn.getAttribute('aria-label') || ''; | |
| const match = aria.match(/control menu for post by (.+)/i); | |
| if (match && match[1]) return match[1].trim(); | |
| } | |
| const authorEl = post.querySelector('strong, span[dir="ltr"], a[href*="/in/"], a[href*="/company/"]'); | |
| return authorEl ? authorEl.innerText.split('\n')[0].trim() : 'Unknown Creator/Page'; | |
| }; | |
| console.log("π Starting Feed Unfollow Bookmarklet..."); | |
| let processedCount = 0; | |
| while (processedCount < MAX_UNFOLLOWS) { | |
| const posts = getUnprocessedPosts(); | |
| if (posts.length === 0) { | |
| console.log("π Scrolling feed..."); | |
| window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); | |
| await sleep(4000); | |
| if (getUnprocessedPosts().length === 0) break; | |
| continue; | |
| } | |
| const currentPost = posts[0]; | |
| currentPost.dataset.scriptProcessed = "true"; | |
| const menuBtn = currentPost.querySelector('button[aria-label*="control menu" i], button[aria-label*="Control menu" i], button.feed-shared-control-menu__trigger'); | |
| if (!menuBtn) continue; | |
| const author = getPostAuthor(currentPost, menuBtn); | |
| currentPost.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| await sleep(800); | |
| simulateFullClick(menuBtn); | |
| await sleep(DROPDOWN_WAIT_MS); | |
| const menuItems = Array.from(document.querySelectorAll('[role="menuitem"], .feed-shared-control-menu__content button, div[role="menu"] button')); | |
| const unfollowItem = menuItems.find((item) => { | |
| const text = (item.innerText || item.textContent || '').trim().toLowerCase(); | |
| return text.includes('unfollow') || text.includes('stop following'); | |
| }); | |
| if (unfollowItem && document.body.contains(unfollowItem)) { | |
| const itemText = unfollowItem.innerText.trim(); | |
| simulateFullClick(unfollowItem); | |
| processedCount++; | |
| console.log(`[${processedCount}/${MAX_UNFOLLOWS}] β ${itemText || `Unfollowed ${author}`}`); | |
| const delay = getRandomDelay(); | |
| console.log(` -> Waiting ${(delay / 1000).toFixed(1)}s...`); | |
| await sleep(delay); | |
| } else { | |
| simulateFullClick(menuBtn); | |
| console.log(`β© [Skip] Unfollow unavailable for: ${author}`); | |
| await sleep(1000); | |
| } | |
| } | |
| alert(`π Batch finished! Verified unfollows: ${processedCount}`); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment