Back to Repository

Hide Non-Followed Facebook Junk

6 upvotes
By Default Upload
After page load (document_end)

Description

Hide that crap from people or pages you don't follow on facebook. Requires some scrolling before it works.

Match Pattern

https://*.facebook.com/*

Script Code

(function () {
  let clusterFound = false;

  function debounce(fn, delay) {
    let timer;
    return function (...args) {
      clearTimeout(timer);
      timer = setTimeout(() => fn.apply(this, args), delay);
    };
  }

  function isValidClusterCandidate(el) {
    if (!el || el.tagName !== "DIV") return false;
    const classList = Array.from(el.classList);
    return (
      classList.length >= 1 &&
      !Array.from(classList).some(c => c.startsWith("__fb-"))
    );
  }

  function findClusterClassFromHeader() {
    const headings = Array.from(document.querySelectorAll("h3"));
    const header = headings.find(h => h.textContent.trim().toLowerCase() === "news feed posts");
    if (!header) return null;

    const siblings = Array.from(header.parentElement?.children || []);
    const headerIndex = siblings.indexOf(header);
    const containersAfterHeader = siblings.slice(headerIndex + 1);
    const visibleDivs = containersAfterHeader.filter(el =>
      el.tagName === "DIV" && el.getAttribute("aria-hidden") !== "true"
    );

    const targetContainer = visibleDivs[0];
    if (!targetContainer) return null;

    const classMap = {};
    const children = Array.from(targetContainer.children);

    for (const child of children) {
      if (!isValidClusterCandidate(child)) continue;
      const classKey = Array.from(child.classList).sort().join(" ");
      classMap[classKey] = classMap[classKey] || [];
      classMap[classKey].push(child);
    }

    const sorted = Object.entries(classMap)
      .filter(([_, group]) => group.length > 1)
      .sort((a, b) => b[1].length - a[1].length);

    if (sorted.length === 0) return null;

    return {
      className: sorted[0][0],
      container: targetContainer
    };
  }

  function isRemovableItem(el) {
    const text = el.innerText?.toLowerCase() || "";
    return text.includes("follow") || text.includes("join");
  }

  function removeMatchingItems(classSelector) {
    const items = Array.from(document.querySelectorAll(`div.${classSelector.split(" ").join(".")}`));
    for (const item of items) {
      if (isRemovableItem(item)) item.remove();
    }
  }

  function observeNewItems(classSelector) {
    const observer = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (!(node instanceof HTMLElement)) continue;

          const selector = `div.${classSelector.split(" ").join(".")}`;

          if (
            node.matches?.(selector) &&
            isRemovableItem(node)
          ) {
            node.remove();
            continue;
          }

          const nestedMatches = node.querySelectorAll?.(selector) || [];
          for (const nested of nestedMatches) {
            if (isRemovableItem(nested)) {
              nested.remove();
            }
          }
        }
      }
    });

    observer.observe(document.body, { childList: true, subtree: true });
    console.log(`👀 Observing for new <div class="${classSelector}"> items`);
  }

  function attemptToFindClusterAndInit() {
    if (clusterFound) return;

    const cluster = findClusterClassFromHeader();
    if (cluster?.className) {
      clusterFound = true;
      console.log(`✅ Cluster found: "${cluster.className}". Cleaning and observing.`);

      removeMatchingItems(cluster.className);
      observeNewItems(cluster.className);

      // 🔁 Cleanup again shortly after observer starts, to catch race conditions
      setTimeout(() => removeMatchingItems(cluster.className), 100);
    } else {
      console.log("❌ No valid cluster found. Will retry on scroll.");
    }
  }

  // Initial run
  attemptToFindClusterAndInit();

  // Retry on scroll (debounced)
  const debouncedRetry = debounce(attemptToFindClusterAndInit, 300);
  window.addEventListener("scroll", debouncedRetry);
})();
Install requires the InjectJS Chrome extension. Scripts run only on sites matching the pattern above. Review code before installing any community script.