LinkedIn Bulk Connection Remover

Remove LinkedIn connections in bulk by full name input

// ==UserScript==
// @name         LinkedIn Bulk Connection Remover
// @namespace    https://matthias-isler.com/
// @version      1.1
// @license      MIT
// @description  Remove LinkedIn connections in bulk by full name input
// @author       Matthias Isler
// @match        https://www.linkedin.com/mynetwork/invite-connect/connections/
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';

  // Inject floating button
  const button = document.createElement('button');
  button.textContent = '🗑️ Remove Connections';
  Object.assign(button.style, {
    position: 'fixed',
    top: '100px',
    right: '20px',
    padding: '10px 16px',
    backgroundColor: '#cb112d',
    color: 'white',
    border: 'none',
    borderRadius: '6px',
    cursor: 'pointer',
    zIndex: 9999,
    boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
  });
  document.body.appendChild(button);

  const sleep = (ms) => new Promise(r => setTimeout(r, ms));

  function normalizeName(name) {
    return name
      .toLowerCase()
      .normalize('NFKD')
      .replace(/[\p{P}\p{S}\u200B-\u200F\u202A-\u202E\u2060-\u206F\u00A0]/gu, '')
      .replace(/\s+/g, '')
      .trim();
  }

  async function typeLikeHuman(inputEl, text) {
    inputEl.focus();
    inputEl.value = '';
    inputEl.dispatchEvent(new InputEvent('input', { bubbles: true }));
    for (const char of text) {
      const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
      setter.call(inputEl, inputEl.value + char);
      inputEl.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true }));
      inputEl.dispatchEvent(new InputEvent('input', { bubbles: true }));
      inputEl.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));
      await sleep(100);
    }
  }

  async function removeConnection(fullNameRaw) {
    console.log(`🔍 Searching for: ${fullNameRaw}`);

    const input = document.querySelector('input[placeholder="Search by name"]');
    if (!input) return console.error('❌ Search input not found');

    await typeLikeHuman(input, fullNameRaw);
    await sleep(2500);

    const cards = [
      ...document.querySelectorAll('div[data-view-name="connections-list"] a[data-view-name="connections-profile"]')
    ].map(a => a.closest('div')).filter(Boolean);

    const card = cards.find(el => {
      const nameText = el.querySelector('a[data-view-name="connections-profile"] p > a')?.innerText?.trim() || '';
      return normalizeName(nameText) === normalizeName(fullNameRaw);
    });

    if (!card) {
      console.warn(`❌ No match found for "${fullNameRaw}"`);
      return;
    }

    const menuBtn = card.querySelector('button[aria-label="Show more actions"]');
    if (!menuBtn) {
      console.warn(`⚠️ Menu button not found for "${fullNameRaw}"`);
      return;
    }

    menuBtn.click();
    await sleep(1000);

    const removeBtn = [...document.querySelectorAll('div[role="button"]')]
      .find(el => el.textContent.trim().toLowerCase().includes('remove connection'));

    if (!removeBtn) {
      console.warn(`⚠️ Remove option not found for "${fullNameRaw}"`);
      return;
    }

    removeBtn.click();
    await sleep(1000);

    const confirmBtn = document.querySelector('button[data-view-name="connections-remove"]');
    if (confirmBtn) {
      confirmBtn.click();
      console.log(`✅ Removed: ${fullNameRaw}`);
    } else {
      console.warn(`⚠️ Confirm button missing for "${fullNameRaw}"`);
    }

    await sleep(2000);
  }

  button.onclick = async () => {
    const raw = prompt("Paste full list (tab-separated, one per line):\nFirstname[TAB]Lastname");
    if (!raw) return;

    const names = raw
      .split('\n')
      .map(line => line.trim().replace(/\s+/g, ' '))
      .filter(Boolean)
      .map(line => line.split('\t').join(' ').trim());

    for (const name of names) {
      await removeConnection(name);
    }
  };
})();