Greasy Fork 支持简体中文。

Declutter LinkedIn

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

// ==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();
})();