Remove all ads, promoted posts, suggested people, and sidebar noise from LinkedIn. Requires a valid license key.
// ==UserScript==
// @name LinkedIn Cleaner
// @namespace https://linkedinfilter.com
// @version 2.1.0
// @description Remove all ads, promoted posts, suggested people, and sidebar noise from LinkedIn. Requires a valid license key.
// @author LinkedIn Cleaner
// @homepageURL https://linkedinfilter.com
// @supportURL https://linkedinfilter.com
// @license Proprietary
// @antifeature payment Requires a paid license key ($3.99 AUD/month or $29.99 AUD/year) from linkedinfilter.com. A free trial is not available.
// @match *://*.linkedin.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// @connect linkedincleaner.replit.app
// @connect linkedinfilter.com
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const VERIFY_URL = 'https://linkedincleaner.replit.app/api/licenses/verify';
/* ----------------------------------------------------------
LICENSE KEY STORAGE
Uses GM_getValue/GM_setValue if available (Tampermonkey),
falls back to localStorage for Safari Userscripts.
---------------------------------------------------------- */
const KEY_STORAGE_KEY = 'linkedin_cleaner_license_key';
const LAST_CHECK_KEY = 'linkedin_cleaner_last_check';
const LAST_VALID_KEY = 'linkedin_cleaner_last_valid';
const DEVICE_ID_KEY = 'linkedin_cleaner_device_id';
function getStoredKey() {
try { return GM_getValue(KEY_STORAGE_KEY, null); } catch (_) {}
try { return localStorage.getItem(KEY_STORAGE_KEY); } catch (_) {}
return null;
}
function setStoredKey(key) {
try { GM_setValue(KEY_STORAGE_KEY, key); return; } catch (_) {}
try { localStorage.setItem(KEY_STORAGE_KEY, key); } catch (_) {}
}
function deleteStoredKey() {
try { GM_deleteValue(KEY_STORAGE_KEY); } catch (_) {}
try { GM_deleteValue(LAST_CHECK_KEY); } catch (_) {}
try { GM_deleteValue(LAST_VALID_KEY); } catch (_) {}
try { localStorage.removeItem(KEY_STORAGE_KEY); } catch (_) {}
// Note: device ID is intentionally kept so the same device can re-register after a new key is entered
}
function getOrCreateDeviceId() {
let id;
try { id = GM_getValue(DEVICE_ID_KEY, null); } catch (_) {}
if (!id) { try { id = localStorage.getItem(DEVICE_ID_KEY); } catch (_) {} }
if (!id) {
id = crypto.randomUUID();
try { GM_setValue(DEVICE_ID_KEY, id); } catch (_) {}
try { localStorage.setItem(DEVICE_ID_KEY, id); } catch (_) {}
}
return id;
}
function getLastCheck() {
try { return Number(GM_getValue(LAST_CHECK_KEY, 0)); } catch (_) {}
try { return Number(localStorage.getItem(LAST_CHECK_KEY) || 0); } catch (_) {}
return 0;
}
function setLastCheck(ts, valid) {
try { GM_setValue(LAST_CHECK_KEY, String(ts)); GM_setValue(LAST_VALID_KEY, valid ? '1' : '0'); return; } catch (_) {}
try { localStorage.setItem(LAST_CHECK_KEY, String(ts)); localStorage.setItem(LAST_VALID_KEY, valid ? '1' : '0'); } catch (_) {}
}
function getLastValid() {
try { return GM_getValue(LAST_VALID_KEY, '0') === '1'; } catch (_) {}
try { return localStorage.getItem(LAST_VALID_KEY) === '1'; } catch (_) {}
return false;
}
/* ----------------------------------------------------------
VERIFY LICENSE KEY AGAINST THE SERVER
Verified once on first entry, then cached indefinitely.
The user can call linkedinCleanerSetKey() to reset and re-verify.
---------------------------------------------------------- */
const CHECK_INTERVAL_MS = Infinity; // verify once, trust forever
function verifyKey(key, deviceId) {
const body = JSON.stringify({ key, deviceId, platform: 'userscript' });
return new Promise((resolve) => {
const done = (valid, status) => resolve({ valid, status });
// Use GM_xmlhttpRequest if available (bypasses CORS in Tampermonkey)
if (typeof GM_xmlhttpRequest !== 'undefined') {
GM_xmlhttpRequest({
method: 'POST',
url: VERIFY_URL,
headers: { 'Content-Type': 'application/json' },
data: body,
timeout: 8000,
onload(r) {
try {
const data = JSON.parse(r.responseText);
done(data.valid === true, data.status);
} catch (_) { done(false, 'error'); }
},
onerror() { done(false, 'network_error'); },
ontimeout() { done(false, 'network_error'); },
});
return;
}
// Fallback: native fetch (works in Safari Userscripts)
fetch(VERIFY_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
signal: AbortSignal.timeout(8000),
})
.then((r) => r.json())
.then((data) => done(data.valid === true, data.status))
.catch(() => done(false, 'network_error'));
});
}
/* ----------------------------------------------------------
PROMPT FOR LICENSE KEY (shown when key is missing or invalid)
---------------------------------------------------------- */
function promptForKey(message) {
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.id = 'lc-overlay';
overlay.style.cssText = `
position:fixed;top:0;left:0;width:100%;height:100%;z-index:999999;
background:rgba(0,0,0,.55);display:flex;align-items:center;
justify-content:center;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
`;
overlay.innerHTML = `
<div style="background:#fff;border-radius:14px;padding:32px 28px;max-width:420px;width:90%;box-shadow:0 8px 40px rgba(0,0,0,.25)">
<div style="font-size:20px;font-weight:700;color:#1a1a1a;margin-bottom:6px">LinkedIn Cleaner</div>
<div style="font-size:13px;color:${message ? '#c0392b' : '#555'};margin-bottom:20px;line-height:1.5">
${message || 'Enter your license key to activate ad and suggestion removal.'}
</div>
<input id="lc-key-input" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
style="width:100%;padding:10px 12px;border:2px solid #ddd;border-radius:8px;font-size:13px;font-family:monospace;margin-bottom:16px;outline:none"
/>
<div style="display:flex;gap:10px">
<button id="lc-submit" style="flex:1;background:#0a66c2;color:#fff;border:none;border-radius:8px;padding:11px;font-size:14px;font-weight:600;cursor:pointer">
Activate
</button>
<button id="lc-cancel" style="padding:11px 16px;background:#f0f0f0;color:#444;border:none;border-radius:8px;font-size:14px;cursor:pointer">
Cancel
</button>
</div>
<div style="margin-top:14px;font-size:11px;color:#999;text-align:center">
No key yet? <a href="#" id="lc-buy" style="color:#0a66c2">Get a subscription</a>
</div>
</div>
`;
document.body.appendChild(overlay);
const input = overlay.querySelector('#lc-key-input');
input.focus();
overlay.querySelector('#lc-submit').addEventListener('click', () => {
const val = input.value.trim();
overlay.remove();
resolve(val || null);
});
overlay.querySelector('#lc-cancel').addEventListener('click', () => {
overlay.remove();
resolve(null);
});
overlay.querySelector('#lc-buy').addEventListener('click', (e) => {
e.preventDefault();
window.open('https://1cffa241-a031-4b1a-ac9a-07a7a32ed051-00-1la2kydxl1j2a.spock.replit.dev/', '_blank');
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') overlay.querySelector('#lc-submit').click();
if (e.key === 'Escape') overlay.querySelector('#lc-cancel').click();
});
});
}
/* ----------------------------------------------------------
SMALL STATUS BANNER (shown briefly after activation)
---------------------------------------------------------- */
function showBanner(message, type) {
const existing = document.getElementById('lc-banner');
if (existing) existing.remove();
const banner = document.createElement('div');
banner.id = 'lc-banner';
const bg = type === 'success' ? '#2e7d32' : type === 'error' ? '#c0392b' : '#0a66c2';
banner.style.cssText = `
position:fixed;bottom:24px;right:24px;z-index:999999;
background:${bg};color:#fff;padding:12px 18px;
border-radius:10px;font-size:13px;font-weight:500;
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
box-shadow:0 4px 16px rgba(0,0,0,.25);
animation:lc-slide-in .25s ease;
`;
banner.textContent = message;
document.body.appendChild(banner);
setTimeout(() => banner?.remove(), 3500);
}
/* ----------------------------------------------------------
LINKEDIN AD / SUGGESTION REMOVAL
---------------------------------------------------------- */
const STATIC_SELECTORS = [
'[data-ad-banner]', '.ad-banner-container', '.ad-banner', '.ad-slot', '.ads-container',
'section[aria-label="Add to your feed"]', 'section[aria-label="Grow your network"]',
'section[aria-label*="Learning"]', 'section[aria-label*="Pages you might like"]',
'section[aria-label*="Discover more"]', 'section[aria-label*="Newsletter"]',
'section[aria-label*="Events you might like"]',
'section[aria-label*="Jobs you might be interested in"]',
'.premium-upsell-link', '.premium-upsell-module', '[data-test-premium-module]',
'section[aria-label*="Premium"]', '.feed-follows-module',
'[data-finite-scroll-hotspot="pymk"]', '.discovery-modules',
/* ---- Messaging sponsored / InMail ads ---- */
'.msg-sponsored-card', '.msg-overlay-bubble-item--sponsored',
'.msg-conversation-listitem--sponsored', '.msg-s-message-list__event--sponsored',
'[data-control-name="sponsored_message"]',
];
const PROMOTED_PATTERNS = [/^promoted$/i, /^sponsored$/i, /^promoted\s*·/i, /^sponsored\s*·/i];
const SUGGESTION_LABEL_PATTERNS = [
/people you may know/i, /add to your feed/i, /grow your network/i,
/pages you might like/i, /events you might like/i, /learning/i, /newsletter/i,
/jobs you might be interested/i, /premium/i, /suggested/i, /discover more/i,
/you might also like/i,
];
function hide(el) {
if (el && el instanceof HTMLElement && el.style.display !== 'none')
el.style.setProperty('display', 'none', 'important');
}
function hideFeedItem(el) {
let node = el;
while (node && node !== document.body) {
if (
(node.tagName === 'LI' && node.classList.contains('occludable-update')) ||
node.classList.contains('feed-shared-update-v2') ||
(node.tagName === 'LI' && node.dataset.id)
) { hide(node); return; }
node = node.parentElement;
}
hide(el);
}
function cleanLinkedIn() {
STATIC_SELECTORS.forEach((s) => { try { document.querySelectorAll(s).forEach(hide); } catch (_) {} });
document.querySelectorAll(
'.update-components-actor__sub-description span[aria-hidden="true"],' +
'.feed-shared-actor__sub-description span[aria-hidden="true"],' +
'.update-components-actor__description span[aria-hidden="true"]'
).forEach((span) => {
if (PROMOTED_PATTERNS.some((re) => re.test(span.textContent.trim()))) hideFeedItem(span);
});
document.querySelectorAll(
'.feed-shared-actor__description--promoted,[data-control-name="promoted_entity"]'
).forEach((el) => hideFeedItem(el));
document.querySelectorAll('section[aria-label],div[aria-label]').forEach((el) => {
const label = el.getAttribute('aria-label') || '';
if (SUGGESTION_LABEL_PATTERNS.some((re) => re.test(label))) hide(el);
});
document.querySelectorAll(
'li.occludable-update,.scaffold-finite-scroll__content > div > ul > li'
).forEach((item) => {
if (
item.querySelector('.discover-entity-type-pill') ||
item.querySelector('[data-finite-scroll-hotspot="pymk"]') ||
item.querySelector('.feed-follows-module')
) { hide(item); return; }
const label = item.getAttribute('aria-label') || '';
if (SUGGESTION_LABEL_PATTERNS.some((re) => re.test(label))) hide(item);
});
document.querySelectorAll(
'.scaffold-layout__aside .artdeco-card,aside .artdeco-card'
).forEach((card) => {
const heading = card.querySelector('h2,h3,[aria-label]');
if (!heading) return;
const text = (heading.textContent || heading.getAttribute('aria-label') || '').trim();
if (SUGGESTION_LABEL_PATTERNS.some((re) => re.test(text))) hide(card);
});
// Messaging sponsored items — conversation list + message thread
['.msg-conversation-listitem', '.msg-selectable-entity',
'.msg-overlay-list-bubble-item', '.msg-s-message-list__event'].forEach((sel) => {
try {
document.querySelectorAll(sel).forEach((item) => {
const sponsored = Array.from(item.querySelectorAll('span,p,small,strong')).some(
(el) => el.children.length === 0 && /^sponsored$/i.test((el.textContent || '').trim()),
);
if (sponsored) hide(item);
});
} catch (_) {}
});
}
let debounceTimer = null;
const observer = new MutationObserver(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(cleanLinkedIn, 150);
});
function startCleaning() {
observer.observe(document.body, { childList: true, subtree: true });
cleanLinkedIn();
setTimeout(cleanLinkedIn, 1000);
setTimeout(cleanLinkedIn, 3000);
}
/* ----------------------------------------------------------
MAIN — LICENSE CHECK FLOW
---------------------------------------------------------- */
async function main() {
let key = getStoredKey();
const deviceId = getOrCreateDeviceId();
if (!key) {
key = await promptForKey(null);
if (!key) return;
setStoredKey(key);
}
const now = Date.now();
const lastCheck = getLastCheck();
const needsCheck = now - lastCheck > CHECK_INTERVAL_MS;
let valid;
if (!needsCheck) {
valid = getLastValid();
} else {
const result = await verifyKey(key, deviceId);
valid = result.valid;
setLastCheck(now, valid);
if (!valid && result.status === 'device_limit_reached') {
showBanner(
'This key is already activated on 2 devices. Purchase an additional license for more devices.',
'error'
);
return;
}
}
if (valid) {
startCleaning();
if (needsCheck) showBanner('LinkedIn Cleaner is active', 'success');
} else {
deleteStoredKey();
const newKey = await promptForKey(
'Your license key is invalid or your subscription has expired. ' +
'Please enter a valid key or renew your subscription.'
);
if (newKey) {
setStoredKey(newKey);
const { valid: recheck, status: recheckStatus } = await verifyKey(newKey, deviceId);
setLastCheck(Date.now(), recheck);
if (recheck) {
startCleaning();
showBanner('LinkedIn Cleaner is active', 'success');
} else if (recheckStatus === 'device_limit_reached') {
deleteStoredKey();
showBanner('This key is already activated on 2 devices. Purchase an additional license.', 'error');
} else {
deleteStoredKey();
showBanner('Key not recognised. Check your subscription.', 'error');
}
}
}
}
main();
/* ----------------------------------------------------------
GLOBAL HELPER — lets users re-enter their key from console
---------------------------------------------------------- */
window.linkedinCleanerSetKey = function () {
deleteStoredKey();
main();
};
})();