Hides sponsored posts in FB's news-feed (Sept 2021)
当前为
// ==UserScript==
// @name facebook - ad block v3
// @version 3.08
// @description Hides sponsored posts in FB's news-feed (Sept 2021)
// @author zbluebugz
// @match https://*.facebook.com/*
// @run-at document-idle
// @namespace https://greasyfork.org/users/812551
// ==/UserScript==
/*
Comments:
original: https://pastebin.com/raw/vmaiA8jJ
05/09/2021: FB has changed the way sponsored posts are created - made harder to detect.
06/09/2021: Detected a couple of slight variations in how sponsored posts are created.
14/09/2021: Detected another slight variation in how sponsored posts are created.
14/09/2021: Fixed bug with getting textNode values.
18/09/2021: Detected another variation on how sponsored posts are created.
18/09/2021: Added code to detect 'Sponsored · Paid for by XYZ' (very rare - untested)
21/09/2021: Added Portuguese (requested by a user)
21/09/2021: Detected another variation on how sponsored posts are created.
22/09/2021: Detected another variation on how sponsored posts are created.
*/
(function () {
'use strict';
const title = 'facebook - ad block';
// -- START KEYWORDS
// Keyword: Sponsored posts'
// - multivalue object;
// - 'language code' : [ uses 'span' or 'b' elements, sponsored word ]
// - (language code located in the HTML element)
// - various non-sponsored keywords ...
const SPONSORED_LIST = {
// english
'en': ['Sponsored', 'People you may know', 'Suggested for you', 'Suggested Pages', 'Suggested Events', 'Paid partnership', 'Suggested groups'],
// portuguese
'pt': ['Patrocinado', 'Pessoas que talvez conheças', 'Sugerido para você', 'Páginas sugeridas', 'Eventos Sugeridos', 'Parceria paga', 'Grupos sugeridos']
}
// Hide certain promotional posts ...
const TOGGLE_PEOPLE_YOU_MAY_KNOW = true;
const TOGGLE_SUGGESTED_CONTENT = true;
const TOGGLE_SUGGESTED_PAGES = true ;
const TOGGLE_SUGGESTED_EVENTS = false;
const TOGGLE_PAID_PARTNERSHIP = true;
const TOGGLE_SUGGESTED_GROUPS = false;
// Create Room - uses the data-pagelet attribute for detection. no language text required.
const TOGGLE_CREATE_ROOM = true;
// Sponsored paid for ...
// *** untested as it is very rare ... ***
const TOGGLE_SPONSORED_PAID_FOR = false;
// - just match the first few words ...
const SPONSORED_PAID_FOR_WORDS = 'Sponsored · Paid for by';
// -- END KEYWORDS
// -- rest of code - no more keywords to adjust.
// which language entry to use?
const SPONSORED_OBJ = SPONSORED_LIST[document.querySelector('html').getAttribute('lang')] || SPONSORED_LIST.en;
const SPONSORED_WORD = SPONSORED_OBJ[0];
// Keywords: text to find for certain promotional elements
let suggestions = [];
if(TOGGLE_PEOPLE_YOU_MAY_KNOW) suggestions.push(SPONSORED_OBJ[1]);
if(TOGGLE_SUGGESTED_CONTENT) suggestions.push(SPONSORED_OBJ[2]);
if(TOGGLE_SUGGESTED_PAGES) suggestions.push(SPONSORED_OBJ[3]);
if(TOGGLE_SUGGESTED_EVENTS) suggestions.push(SPONSORED_OBJ[4]);
if(TOGGLE_PAID_PARTNERSHIP) suggestions.push(SPONSORED_OBJ[5]);
if(TOGGLE_SUGGESTED_GROUPS) suggestions.push(SPONSORED_OBJ[6]);
// hide or highlight the selected posts
let HIDE_STYLE = (true) ? 'display:none !important' : 'border:3px solid yellow !important';
// how often to run this script (milliseconds)
const CHECK_RATE_MS = 100;
// sponsored for posts - length of text
let SPONSORED_PAID_FOR_WORDS_LEN = SPONSORED_PAID_FOR_WORDS.length;
function hide(el) {
return el.setAttribute('style',HIDE_STYLE);
}
function doChanges() {
if(TOGGLE_CREATE_ROOM){
let create_room = document.querySelector('div[data-pagelet="VideoChatHomeUnitNoDDD"]');
if(create_room) hide(create_room);
}
function findSponsoredPosts() {
// get collection of posts, ignore those already read by this script.
let posts = Array.from(
document.querySelectorAll('div[data-pagelet*=FeedUnit]:not([adbpr])')
// next line for use in debugging - ignores the posts that have been already processed.
//document.querySelectorAll('div[data-pagelet*=FeedUnit]')
);
// loop through each post to see if it is a sponsored one or not
let hidePosts = [];
let csr ; // getComputedStyle results
let ss = 1; // sponsored structure (1 = uses aria-label, 2 = uses a tag.
let isFlagged = false; // has post been flagged as sponsored?
let sob = 'S'; // (S)pan or (B)old tags used for holding Sponsored letters/word
posts.forEach(
post => {
// flag this post as not to be read/processed again
post.setAttribute('adbpr', true);
// reset isHidden flag
isFlagged = false;
// within this unread post, find the SPAN element(s) having aria-label = Sponsored
// (usually only one is found)
let alSpans = Array.from(post.querySelectorAll('span[aria-label="' + SPONSORED_WORD + '"]'));
ss = 1;
// console.info('----:', SPONSORED_WORD, alSpans.length );
if (alSpans.length === 0) {
// sigh! uses the A tag structure; not language dependent.
alSpans = Array.from(post.querySelectorAll('a[href="#"][aria-label="label"]'));
ss = 2;
}
if(alSpans.length === 0) {
// sigh! uses the A tag structure; language dependent
alSpans = Array.from(post.querySelectorAll('a[aria-label="' + SPONSORED_WORD +'"]'));
ss = 3;
}
// is the word "Sponsored" visible?
// - there are several spans having single letters - all randomised, but will make up "sponsored" when certain span tags are "visible".
alSpans.forEach(sp => {
let daText = '';
// get the next sibling from the <span aria-label="Sponsored"></span>
let nsp ;
if (ss === 1) {
// uses the span[arial-label="sponsored] structure
nsp = sp.nextSibling;
// which set of TAGs used?
sob = 'S'; // SPAN
}
else {
// ss = 2 or 3
// ss 2 = uses the a[href=# aria-label=label] structure
// ss 3 = uses the a[aria-label=SPONSORED_WORD structure
// - A tag is nested with 2 SPANs then either B or SPAN tag wrapper with lots of B/SPAN tags.
// - grab the B/SPAN tag (wrapper)
nsp = sp.firstChild.firstChild.firstChild;
// which set of TAGS used?
sob = (nsp.tagName === 'B') ? 'B' : 'S' ;
}
// console.info("--- nsp:", nsp)
// note that this sibling is a "parent" ...
// .. sometimes it has a textNode (as firstChild) ...
if (sob === 'B') {
// uses the <b> tags structure
daText += nsp.innerText;
//console.info("--->", daText);
}
else {
// uses the <span> tags structure
if (nsp.tagName === "SPAN") {
if (nsp.firstChild.tagName === 'SPAN') {
// no immediate textNode
// - nothing to do.
}
else {
csr = window.getComputedStyle(nsp);
if (csr.position === 'relative' && csr.display === 'inline') {
// visible ... (need both styles)
// - ok, grab the textNode's value.
daText += nsp.firstChild.textContent ;
}
}
}
//console.info("--::", daText);
// the "parent" has childNodes (spans) ...
nsp = nsp.firstChild;
do {
if (nsp.tagName === 'SPAN') {
csr = window.getComputedStyle(nsp);
if (csr.position === 'relative' && csr.display === 'inline') {
// visible ... (need both styles)
if (nsp.innerText.length ===1) {
daText += nsp.innerText;
}
}
}
nsp = nsp.nextSibling;
} while (nsp);
}
// console.info("--is Sponsored post:", daText, (SPONSORED_WORD.indexOf(daText) > -1));
//console.info("-----sp: ", sp);
// do we hide this post?
if (SPONSORED_WORD.indexOf(daText) > -1 ) {
hidePosts.push(sp);
isFlagged = true;
}
});
if (!isFlagged) {
// post not yet marked to be hidden ...
// .. so check for suggestions.
if (suggestions) {
// scan the a tags
let els = Array.from(post.querySelectorAll('a'));
let skip = false;
for (let x = 0; x < els.length; x++) {
if (suggestions.includes(els[x].textContent)) {
hidePosts.push(els[x]);
skip = true;
break;
}
}
// scan the span tags
if (!skip) {
els = Array.from(post.querySelectorAll('span'));
for (let x = 0; x < els.length; x++) {
if (suggestions.includes(els[x].textContent)) {
hidePosts.push(els[x]);
break;
}
}
}
}
// sponsored + paid for ... posts (untested)
if (TOGGLE_SPONSORED_PAID_FOR) {
let tdivs = Array.from(post.querySelectorAll('div[role="button"'));
for (let x= 0; x < tdivs.length; x++) {
if (tdivs[x].textContent.substring(0,SPONSORED_PAID_FOR_WORDS_LEN) === SPONSORED_PAID_FOR_WORDS) {
hidePosts.push(tdivs[x]);
break;
}
}
}
}
}
);
return hidePosts
}
function kill(element) {
try {
if(element) {
let limit = 0;
while(limit++ < 100) {
if(typeof element.getAttribute('data-pagelet') == 'string')
{
if(element.getAttribute('data-pagelet').contains('FeedUnit'))
{
hide(element);
return;
}
}
element = element.parentNode;
}
}
} catch (e) {
}
}
findSponsoredPosts().forEach( e => kill(e) );
}
const callback = function () {
try {
doChanges();
} catch (e) {
console.warn(title, e);
}
};
setInterval(callback, CHECK_RATE_MS);
})();