Declutter LinkedIn

Remove the feed, LinkedIn Premium upsells, news, and other clutter from LinkedIn

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Declutter LinkedIn
// @namespace    August4067
// @version      0.2.0-alpha
// @description  Remove the feed, LinkedIn Premium upsells, news, and other clutter from LinkedIn
// @license      MIT
// @match        https://www.linkedin.com/*
// @icon         https://www.linkedin.com/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js
// ==/UserScript==

/*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
    that detects and handles AJAXed content.

    Usage example:

        waitForKeyElements (
            "div.comments"
            , commentCallbackFunction
        );

        //--- Page-specific function to do what we want when the node is found.
        function commentCallbackFunction (jNode) {
            jNode.text ("This comment changed by waitForKeyElements().");
        }

    IMPORTANT: This function requires your script to have loaded jQuery.
*/

// Pulled from: https://gist.github.com/raw/2625891/waitForKeyElements.js
function waitForKeyElements(
  selectorTxt /* Required: The jQuery selector string that
                        specifies the desired element(s).
                    */,
  actionFunction /* Required: The code to run when elements are
                        found. It is passed a jNode to the matched
                        element.
                    */,
  bWaitOnce /* Optional: If false, will continue to scan for
                        new elements even after the first match is
                        found.
                    */,
  iframeSelector /* Optional: If set, identifies the iframe to
                        search.
                    */,
) {
  var targetNodes, btargetsFound;

  if (typeof iframeSelector == "undefined") targetNodes = $(selectorTxt);
  else targetNodes = $(iframeSelector).contents().find(selectorTxt);

  if (targetNodes && targetNodes.length > 0) {
    btargetsFound = true;
    /*--- Found target node(s).  Go through each and act if they
            are new.
        */
    targetNodes.each(function () {
      var jThis = $(this);
      var alreadyFound = jThis.data("alreadyFound") || false;

      if (!alreadyFound) {
        //--- Call the payload function.
        var cancelFound = actionFunction(jThis);
        if (cancelFound) btargetsFound = false;
        else jThis.data("alreadyFound", true);
      }
    });
  } else {
    btargetsFound = false;
  }

  //--- Get the timer-control variable for this selector.
  var controlObj = waitForKeyElements.controlObj || {};
  var controlKey = selectorTxt.replace(/[^\w]/g, "_");
  var timeControl = controlObj[controlKey];

  //--- Now set or clear the timer as appropriate.
  if (btargetsFound && bWaitOnce && timeControl) {
    //--- The only condition where we need to clear the timer.
    clearInterval(timeControl);
    delete controlObj[controlKey];
  } else {
    //--- Set a timer, if needed.
    if (!timeControl) {
      timeControl = setInterval(function () {
        waitForKeyElements(
          selectorTxt,
          actionFunction,
          bWaitOnce,
          iframeSelector,
        );
      }, 100);
      controlObj[controlKey] = timeControl;
    }
  }
  waitForKeyElements.controlObj = controlObj;
}

// SETTINGS
const SETTING_REMOVE_FEED = "removeFeed";
const SETTING_REMOVE_NEWS = "removeNews";

class Setting {
  constructor(settingName, settingDisplayName, settingDefault) {
    this.settingName = settingName;
    this.settingDisplayName = settingDisplayName;
    this.settingDefault = settingDefault;
  }

  currentValue() {
    return GM_getValue(this.settingName, this.settingDefault);
  }

  toggleSetting() {
    var current = this.currentValue();
    GM_setValue(this.settingName, !current);
  }
}

class SettingRemoveFeed extends Setting {
  constructor() {
    super(SETTING_REMOVE_FEED, "Remove feed", true);
  }
}

class SettingRemoveNews extends Setting {
  constructor() {
    super(SETTING_REMOVE_NEWS, "Remove news", true);
  }
}

const SETTINGS = {
  [SETTING_REMOVE_FEED]: new SettingRemoveFeed(),
  [SETTING_REMOVE_NEWS]: new SettingRemoveNews(),
};

// MENU SETTINGS
function toggleMenuSetting(settingName) {
  var setting = SETTINGS[settingName];
  setting.toggleSetting();
  updateSettingsMenu();
  console.debug(`Setting ${settingName} set to: ${setting.currentValue()}}`);
  location.reload();
}

function updateSettingsMenu() {
  for (const [setting_name, setting] of Object.entries(SETTINGS)) {
    GM_registerMenuCommand(
      `${setting.settingDisplayName}: ${setting.currentValue() ? "Enabled" : "Disabled"}`,
      () => {
        toggleMenuSetting(setting_name);
      },
    );
  }
}

function waitAndRemove(selector, func) {
  if (func === undefined) {
    func = (node) => {
      node.style.display = "none";
    };
  }
  waitForKeyElements(selector, function (nodes) {
    if (nodes && nodes.length > 0 && nodes[0]) {
      func(nodes[0]);
    }
  });
}

function removeNotificationBadgeFromHomeIcon() {
  waitAndRemove(
    'li[class^="global-nav__primary-item"] span[class^="notification-badge"]',
  );
  waitAndRemove(
    'a[data-view-name="navigation-homepage"] span[class$="_1ptbkx61f4"]',
  );
}

function removeProfilePrompts() {
  waitAndRemove("#promo", (node) => node.parentElement.remove());
  waitAndRemove("#guidance", (node) => node.parentElement.remove());
  waitAndRemove("#resources", (node) => node.parentElement.remove());
}

function removeNewsFromRightSidebar() {
  if (!SETTINGS[SETTING_REMOVE_NEWS].currentValue()) {
    return;
  }

  waitAndRemove('aside[aria-label="LinkedIn News"]');
}

function removeUpsellLinks() {
  waitAndRemove(".premium-upsell-link--extra-long");
  waitAndRemove(".premium-upsell-link");
  waitAndRemove(
    'span[class="global-nav__secondary-premium-cta-text"]',
    (node) => node.parentElement.parentElement.parentElement.remove(),
  );
  waitAndRemove(
    'span[class="feed-identity-module__premium-cta-text"]',
    (node) => node.closest('div[class^="artdeco-card"]')?.remove(),
  );

  // Remove upsell section on mynetwork page
  waitAndRemove(
    'div[data-view-name="premium-upsell-card"]',
    (node) => (node.closest("section").style.display = "none"),
  );

  // Remove "try premium for $0" navbar menu item
  waitAndRemove(
    'a[data-view-name="premium-nav-upsell-text"]',
    (node) => (node.closest("li").style.display = "none"),
  );

  // Remove "try 1 month of premium for $0" in profile dropdown
  waitAndRemove('div[data-view-name="nav-me-my-premium"]', (node) =>
    node.parentElement.remove(),
  );

  // Remove premium upsell on contact info edit card
  waitAndRemove('h3[class$="card-upsell-v2__headline"]', (node) =>
    node.parentElement?.parentElement?.parentElement?.remove(),
  );
}

function removeRightSidebarOnProfile() {
  waitAndRemove(".scaffold-layout__aside");
}

function removeForBusinessDropDown() {
  document.querySelector('span[title="For Business"]')?.closest("li")?.remove();
}

function removeProfileEnhanceCTAs() {
  // Remove enhance button from top of profile
  waitForKeyElements(
    'button[class^="pvs-profile-actions__custom-action"]',
    function (nodes) {
      var customActionButtons = document.querySelectorAll(
        'button[class^="pvs-profile-actions__custom-action"]',
      );
      customActionButtons.forEach((button) => {
        if (
          button.innerText &&
          button.innerText.trim().toLowerCase() === "enhance profile"
        ) {
          button.remove();
        }
      });
    },
  );

  // Premium upsell with "enhance with AI" buttons on edit experience page
  waitForKeyElements(
    'svg[data-test-icon="premium-signal-ai-small"]',
    function (nodes) {
      var aiIcons = document.querySelectorAll(
        'svg[data-test-icon="premium-signal-ai-small"]',
      );
      aiIcons.forEach((icon) => {
        icon.closest("button").remove();
      });
    },
  );

  // Premium upsell with "enhance with AI" suggestion on edit about modal
  waitAndRemove('div[class^="ai-suggestions-bar"]');
}

function removeFeed() {
  if (!SETTINGS[SETTING_REMOVE_FEED].currentValue()) {
    return;
  }

  waitAndRemove('main[aria-label="Main Feed"]', (node) => {
    node.querySelector('div[class="relative"]')?.remove();
    var buttons = node.querySelectorAll("button");
    if (buttons.length > 0) {
      buttons[buttons.length - 1].remove();
    }
  });
}

function removeUpsellsFromEditIntroModal() {
  waitAndRemove('div[class$="pe-profile-top-card-form__upsell-wrapper"]');
}

function removeUpsellsFromOtherProfiles() {
  waitAndRemove('button[class="pv-top-card__premium-feature-banner"]');
}

function removeUpsellsFromSearchResults() {
  waitAndRemove('button[id="searchFilter_upsellFilter"]', (node) =>
    node.closest('li[class="search-reusables__primary-filter"]')?.remove(),
  );
}

function removeUpsellsFromSearchAppearances() {
  waitAndRemove(
    'div[class="member-analytics-addon-insight-v2  member-analytics-addon-insight-v2--bg-default"]',
  );
}

function removeSuggestedSearches() {
  waitAndRemove('ul[aria-label="Search suggestions"]');
  waitAndRemove(
    'h2[class="search-typeahead-v2__section-header pt3 pb2 t-black ph4 t-14 search-typeahead-v2__section-header--top-divide t-bold"]',
  );
}

// Shows up when you are on the /notifications page
function removeTurnNotificationsBackOnRequestBanner() {
  waitAndRemove('div[class="nt-setting-banner__container"]', (node) =>
    node.closest("article").remove(),
  );
}

function removeUpsellLinksFromJobsSearch() {
  // Remove the upsell from the job posting
  waitAndRemove(
    'a[class="ember-view job-details-how-you-fit__upsell-link"]',
    (node) => node.closest("div").remove(),
  );

  // Remove the upsell from the all filters sidebar on the right
  waitAndRemove('li[class="search-reusables__secondary-filters-upsell"]');
}

(function () {
  "use strict";

  updateSettingsMenu();

  removeNotificationBadgeFromHomeIcon();
  removeNewsFromRightSidebar();
  removeRightSidebarOnProfile();
  removeProfilePrompts();
  removeForBusinessDropDown();
  removeFeed();
  removeProfileEnhanceCTAs();
  removeUpsellLinks();
  removeUpsellLinksFromJobsSearch();
  removeUpsellsFromEditIntroModal();
  removeUpsellsFromOtherProfiles();
  removeUpsellsFromSearchResults();
  removeUpsellsFromSearchAppearances();
  removeSuggestedSearches();
  removeTurnNotificationsBackOnRequestBanner();
})();