AdBlock for Facebook

Block / Hide "Sponsored" posts on Facebook

// ==UserScript==
// @name         AdBlock for Facebook
// @namespace    fb-adblock
// @version      1.6
// @description  Block / Hide "Sponsored" posts on Facebook
// @author       Thor Lancaster
// @match        https://www.facebook.com/*
// @grant        none
// ==/UserScript==

// Add abDbg=true as a URL parameter to debug ad classification

(function() {
    'use strict';
    var initialWait = 1500;
    setTimeout(function(){
    if(document.getElementById("stream_pagelet") == null){
        console.log("AdBlock for Facebook currently only works on news feeds. Aborting.");
        return;
    }
    console.log("AdBlock for Facebook running");
    document.abDbg = window.location.href.includes("abDbg=true");
    document.abCheckAll = false; // If true, check every post every time. Comsumes more CPU
    document.abTestCheck = null; // Set to an element for debugging to determine if this script thinks it's an ad
    var el = (document.getElementById("stream_pagelet"));
    var feed = el.querySelectorAll('[role="feed"]')[0];
    setInterval(function(f){
        try{
            checkAds(f);
            if(document.abTestCheck != null){ // Check test element to determine whether it is an article and/or an and
                var el = document.abTestCheck;
                document.abTestCheck = null;
                if(!el.getAttribute("role") == "article"){
                    console.log("Element is not an article");
                }
                if(isAd(el)){
                    console.log("Element is an ad");
                } else {
                    console.log("Element is not an ad");
                }
            }
        } catch(e){ // Log error before it is swallowed by FB's obfusicated debugging maw
            console.error(e);
        }
    }, 2000, document);
    // TODO check for ads when feed updates rather than every 2 seconds
    }, initialWait);

    function checkAds(feed){
        document.lastABRun = new Date().getTime();
        var posts = feed.querySelectorAll('[role="article"]');
        for(var x = 0; x < posts.length; x++){
            var post = posts[x];
            // Calculate a quick checksum so that if text changes, the post will be re-scanned
            // I suspect that FB was creating non-ad posts and inserting them after a delay
            // To spoof the previous version of this script
            if(!post.abCheckSum || post.abCheckSum != abCheckSum(post) || document.abCheckAll){
                post.abCheckSum = abCheckSum(post);
                var isAdType = isAd(post);
                if(isAdType == 1){
                    if(document.abDbg){
                        markPost(post, "#F70");
                    } else {
                        removePost(post);
                    }
                }
                // Blank post, should never happen
                else if(isAdType == -1){
                    //console.log("ABCHECK blank post");
                }
                else if(isAdType == 0 && document.abDbg){
                    markPost(post, "#0F7"); // for debugging
                }
            }
        }
    }
    // Color a FB post for debugging purposes
    function markPost(post, color){
        var els = post.querySelectorAll("*");
        for(var x = 0; x < els.length; x++){
            els[x].style.background = color;
        }
    }
    function removePost(post){
        post.parentElement.removeChild(post);
        //post.style.height = "0px";
        //post.style.opacity = "0";
    }
    function isAd(post){
        if(post.getAttribute("aria-label") == "Comment"){ // Don't scan comments for ads
            return 0;
        }
        var scan = post.innerText.substring(0, 128);
        if(scan.length == 0){
            return -1; // Blank post
        }
        if(scan.includes("·")){
            var start = scan.substring(0, scan.indexOf("·"));
            if(isSpnsrd(start)){
                //console.log("IS AN AD");
                return 1;
            }
        }
        //console.log("IS NOT AN AD");
        return 0;
    }
    // Scan for a string that represents "Sponsored"
    var SPONSORED = "Sponsored";
    function isSpnsrd(str){
        var spPtr = 0;
        for(var x = 0; x < str.length; x++){
            var chr = str[x];
            if(chr == SPONSORED[spPtr]){
                spPtr++;
                if(spPtr == SPONSORED.length){
                    return true;
                }
            } else {
                var toReset = true;
                if(chr == "\n"){
                    toReset = false;
                } else{
                    for(var y = spPtr; y >= 0; y--){
                        if(chr == SPONSORED[y]){
                            toReset = false;
                        }
                    }
                }
                if(toReset){
                    spPtr = 0;
                }
            }
        }
        return false;
    }

    // Calculate the checksum of an element through it's innerText
    function abCheckSum(el){
        return checkSum(el.innerText);
    }

    // Fast Checksum algorithm. Works great, open-source
    // See https://stackoverflow.com/questions/811195/fast-open-source-checksum-for-small-strings
    function checkSum(s)
    {
        var chk = 0x12345678;
        var len = s.length;
        for (var i = 0; i < len; i++) {
            chk += (s.charCodeAt(i) * (i + 1));
        }
        return (chk & 0xffffffff).toString(16);
    }
})();