Greasy Fork is available in English.

Facebook - Hide Ads

Hides Facebook ads (best-effort)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         Facebook - Hide Ads
// @version      1.1
// @description  Hides Facebook ads (best-effort)
// @author       Selbi
// @match        https://www.facebook.com/*
// @require      http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @grant        none
// @namespace    selbi
// ==/UserScript==

// Fetch the main feed and fire it once on page load
var feed = document.querySelector("div[role*=main]");

// Create the observer
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    // Fire the ad-hiding method every time the observer detects something
    // (This is stupidly CPU-intensive and could probably be optimized but I cba, sry lel)
    hideAds();
  });
});

// Set up a mutation observer for the feed so ads keep getting blocked as you scroll down
// (Not a big fan of jQuery, but it has a pretty robust .ready() method, so whatever)
$(document).ready(function() {
  observer.observe(feed, {
    // I trial-and-error'd the observe settings until I found something that worked
    childList: true,
    subtree: true
  });
});

// Where the magic happens
function hideAds() {
  // First off, find all the POTENTIAL ads using the few available static selectors,
  // because Facebook is literally satan and obfuscated nearly every selector
  let ads = feed.querySelectorAll("div[id*=fbfeed_sub__header] a[role*=button] > span");
  
  // Iterate through all the found posts
  ads.forEach((e) => {
    // Skip this post if it was already hidden to save some CPU cycles
    if (!e.classList.contains("hiddenAd")) {
      // This is where it gets tricky:
      // There will be a few normal posts sneaking in using the above query selector,
      // which we gotta deal with in the following steps. Facebook is a gigantic cuckfest,
      // because not only do they obfuscate the crap out of their classes, they also put each
      // individual letter of the word "Sponsored" into its own span.
      // Fortunately, we can use the number in a real post (e.g. "9 hours ago") to differentiate
      // a sponsored post from a normal post. A hacky workaround, but it works.

      // Assume this is an ad
      let isAd = true;

      // Iterate through all the spans
      for (c of e.children) {
        // Check if the innerHTML or "data-content" attribute's content of any child is a number
        for (cc of c.children) {
          let content = cc.getAttribute("data-content");
          if (content.length > 0 && !isNaN(content)) {
            // If it is, this is NOT an ad. Mark the flag as such and break the loop.
            isAd = false;
            break;
          }
        }
      }

      // Check if this article is still considered an ad
      if (isAd) {
        // If yes, traverse up the ancestry to find the root node
        // (This is not the true root, but it does the job to hide the ad, which is the whole point)
        let article = e.closest("div[id*=hyperfeed_story_id");
        if (article !== null) {
          // Hide that dickhead
          article.style.display = "none";

          // Mark the original selector as already hidden for future fetches
          e.classList.add("hiddenAd");
        }
      }
    }
  });
}