Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save carandraug/58505695bb234aa75456a9b93a7d37a5 to your computer and use it in GitHub Desktop.
Save carandraug/58505695bb234aa75456a9b93a7d37a5 to your computer and use it in GitHub Desktop.
Cleaning up the homefeed on Facebook. Removes Suggested Posts and Sponsored content from the homefeed.
// ==UserScript==
// @name Stig's Facebook Homefeed Cleanr
// @namespace dk.rockland.userscript.facebook.cleanr
// @description Cleaning up the homefeed on Facebook. Removes Suggested Posts and Sponsored content from the homefeed.
// @match *://*.facebook.com/*
// @version 2016.09.07.1
// @author Stig Nygaard, http://www.rockland.dk
// @homepageURL http://www.rockland.dk/userscript/facebook/cleanr/
// @supportURL http://www.rockland.dk/userscript/facebook/cleanr/
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_info
// @noframes
// ==/UserScript==
// Copyright 2016 Stig Nygaard
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// CHANGELOG - The most important updates/versions:
var changelog = [
{version: '2016.09.07.0', description: 'Language detection fix (Do a hard reload after changing language). More configuration options coming up soon...'},
{version: '2016.08.30.0', description: 'Optimizations...'},
{version: '2016.08.27.1', description: 'Ahem, though the script was actually kind of working, previous version was actually doing it internally in a different way than intended. That should be fixed now ;-)'},
{version: '2016.08.25.0', description: 'Fixes for some functionality-problems... And the script source is now available under Apache License 2.0 !'},
{version: '2016.07.03.0', description: 'Danish support added: Removing posts when "Foreslået opslag" or "Sponsoreret".'},
{version: '2016.06.24.1', description: '1st release. English Facebook supported.'}
]; // Coming-up/planned: Choose method observer/scroll-trigger/interval. Moving to GitHub repository. Further configuration options to tailor your homefeed.
var DEBUG = false;
var INFO = true; // Trace the hidden posts in log - even if debug=false
var cleaning_runcountINFO = false;
var setupObserverINFO = false;
function log(s, info) {
if ((info && window.console) || (DEBUG && window.console)) {
window.console.log('*Cleanr* '+s);
}
}
var cleanr = cleanr || {
list: [
{language: 'English', filter: ['Suggested Post', 'Sponsored']},
// {language: 'English', filter: ['Suggested Post', 'Sponsored', ' liked this.', 'Like Page', "'s Birthday", "'s birthday"]}, // my personnal settings
{language: 'Dansk', filter: ['Foreslået opslag', 'Sponsoreret']}
],
activefilter: null,
languageDetected: false,
cleaning_running: false,
cleaning_runcount: 0,
postlist: null,
startTime: Date.now(),
lazystart: 0,
insertStyle: function() { // transition: all 3s ease;
if (!document.getElementById('cleanrStyle')) {
var style = document.createElement('style');
style.type = 'text/css';
style.id = 'cleanrStyle';
if(DEBUG) {
style.innerHTML = 'div.cleaned > div {background-color:#FFB !important} #configBox {position:absolute;width:200px;right:-220px} .configBox {opacity:0.6}';
} else {
style.innerHTML = 'div.cleaned > div {display:none !important} #configBox {position:absolute;width:200px;right:-220px} .configBox {opacity:0.6}';
}
document.getElementsByTagName('head')[0].appendChild(style);
log('cleanrStyle has been ADDED');
}
},
configure: function() {
var lan = cleanr.list[0].language;
var languageselection = document.querySelector('._2cpb > div.fsm.fwn.fcg');
if (languageselection) {
languageselection = languageselection.textContent;
log('languageselection=[' + languageselection + ']');
for (var i = 0; i < cleanr.list.length; i++) {
if (languageselection.indexOf(cleanr.list[i].language) === 0) {
cleanr.activefilter = cleanr.list[i].filter;
lan = cleanr.list[i].language;
log('Language set to ' + lan, INFO);
}
}
cleanr.languageDetected = true; // well, at least we have checked
} else {
log('languageselection not found.')
}
var contentCol = document.querySelector('div#contentCol');
if (contentCol && !document.getElementById('cleanrsettings')) {
contentCol.insertAdjacentHTML('afterbegin',
'<div id="configBox" class="configBox">' +
'<div title="Facebook Homefeed Cleanr version ' + GM_info.script.version + '">' +
'<a href="javascript:if(document.getElementById(\'cleanrsettings\').style.display==\'none\')document.getElementById(\'cleanrsettings\').style.display=\'block\';else document.getElementById(\'cleanrsettings\').style.display=\'none\';void(0)">Cleanr settings</a>' +
'</div>' +
'<form id="cleanrsettings" name="cleanrsettings" style="display:none;padding:1em 0 1em 0">' +
'<fieldset><legend>Filter mode</legend>' +
'<div><label for="hideId"><input type="radio" name="mode" id="hideId" value="hide" '+(DEBUG?'':'checked="checked" ')+'/> Hide (Normal mode)</label></div>' +
'<div><label for="highlightId"><input type="radio" name="mode" id="highlightId" value="highlight" '+(DEBUG?'checked="checked" ':'')+'/> Highlight (Debug mode)</label></div>' +
'</fieldset>' +
'<fieldset id="filterlist"><legend>Filters (<span id="filterlanguage">' + lan + '</span>)</legend>' +
'</fieldset>' +
//'<fieldset><legend>Method</legend>' +
//'<div><label for="defaultId"><input type="radio" name="method" id="defaultId" value="default" /> Default/auto (Currently <em>Observer</em>) - Recommended method until it eventually stops working</label></div>' +
//'<div><label for="observerId"><input type="radio" name="method" id="observerId" value="observer" /> Force <em>Observer</em></label></div>' +
//'<div><label for="scrollId"><input type="radio" name="method" id="scrollId" value="scroll" /> Force <em>Scroll-triggered</em></label></div>' +
//'<div><label for="intervalId"><input type="radio" name="method" id="intervalId" value="interval" /> Force <em>Interval-driven</em></label></div>' +
//'</fieldset>' +
'<button type="button" id="updateSettings" style="margin-top:.5em">Update settings</button>' +
'<p>Most important and recent updates:</p>' +
'<div id="changelog">' +
'</div>' +
'</form>' +
'</div>');
var flist = document.getElementById('filterlist');
if (flist) {
for (i = 0; i < cleanr.activefilter.length; i++) {
flist.insertAdjacentHTML('beforeend', '<div><input type="checkbox" id="f' + i + '" value="' + cleanr.activefilter[i] + '" checked="checked" disabled="disabled" /><label for="f'+i+'"> ' + cleanr.activefilter[i] + '</label></div>');
}
flist.insertAdjacentHTML('beforeend', '<p style="margin-bottom:0;display:none">Filters are <em>case sensitive</em>.</p>');
}
var updateSettingsBtn = document.getElementById('updateSettings');
if (updateSettingsBtn) {
updateSettingsBtn.addEventListener('click', cleanr.saveSettings);
}
var clog = document.getElementById('changelog');
if (clog) {
for (i = 0; i < changelog.length; i++) {
clog.insertAdjacentHTML('beforeend', '<div><em>' + changelog[i].version + ':</em><br />' + changelog[i].description + '</div>');
}
}
}
},
loadSettings: function() {
DEBUG = (''+GM_getValue( 'debug', DEBUG)) === 'true';
},
saveSettings: function() {
GM_setValue('debug', (document.forms['cleanrsettings'].elements['mode'].value==='highlight') );
location.reload();
},
cleaning: function () {
cleanr.lazystart = 0;
if(cleanr.cleanr_running) return;
cleanr.cleanr_running = true;
cleanr.cleaning_runcount++;
log('Running cleaning() #'+cleanr.cleaning_runcount + ' at time='+cleanr.secondsSinceStart()+' sec. after start.', cleaning_runcountINFO);
if (!cleanr.postlist) cleanr.postlist = document.getElementsByClassName('_5jmm');
if (!cleanr.languageDetected) {
cleanr.configure();
}
log('Running cleaning() #'+cleanr.cleaning_runcount + '. Cleaning on a postlist of length=' + cleanr.postlist.length, cleaning_runcountINFO);
/*
for (var i = 0; i < cleanr.postlist.length; i++) {
if (!cleanr.postlist[i].classList.contains('cleaned')) {
for (var j = 0; j < cleanr.activefilter.length; j++) {
if (cleanr.postlist[i].textContent.indexOf(cleanr.activefilter[j]) > -1) {
cleanr.postlist[i].classList.add('cleaned');
log('Hiding or highlighting item because <' + cleanr.activefilter[j] + '> : [ ' + cleanr.postlist[i].textContent.substring(0, 500) + ' ]', INFO);
}
}
}
}
*/
for (var i = cleanr.postlist.length -1; i > 0; i--) {
var itemCleaned = cleanr.postlist[i].classList.contains('cleaned');
if (itemCleaned && (cleanr.cleaning_runcount % 3 < 2)) { // well, usually return, but not always because apparently there can be some left-overs...
cleanr.cleanr_running = false;
return; // quick exit cleaning
}
if (!itemCleaned) {
for (var j = 0; j < cleanr.activefilter.length; j++) {
if (cleanr.postlist[i].textContent.indexOf(cleanr.activefilter[j]) > -1) {
cleanr.postlist[i].classList.add('cleaned');
log('Hiding or highlighting item because <' + cleanr.activefilter[j] + '> : [ ' + cleanr.postlist[i].textContent.substring(0, 500) + ' ]', INFO);
break;
}
}
}
}
cleanr.cleanr_running = false;
},
lazystartCleaning: function(mutations) {
log('Running lazystartCleaning(), lazystart=' + cleanr.lazystart, cleaning_runcountINFO);
cleanr.lazystart++;
if (cleanr.lazystart > 1) {
log('Running lazystartCleaning(), but skipping cleaning() because a lazystart pending...', cleaning_runcountINFO);
} else {
log('Running lazystartCleaning() and scheduling cleaning() ' + (typeof mutations === 'undefined' ? ' without mutations.' : (' with ' + mutations.length + ' mutation records.')), cleaning_runcountINFO);
window.setTimeout(cleanr.cleaning, 100); // Let it breathe 100ms first......
}
},
setupObserver: function () {
log('Running setupObserver()...');
cleanr.insertStyle();
var observed = document.querySelector('div[id^="feed_stream_"]') || document.querySelector('div[id^="topnews_main_stream"]') || document.getElementById('stream_pagelet');
if (!observed) {
log('Object to observe NOT found - re-trying later...', setupObserverINFO);
} else if (observed.classList.contains('hasObserver')) {
log('Everything is okay! - But checking again later...', setupObserverINFO);
} else {
var oldObserved = document.getElementsByClassName('hasObserver').item(0); // Maybe we had an observer on another element?
if (oldObserved) {
oldObserved.disconnect();
log(' *** An old observer was removed from element with id='+oldObserved.id+'. ***', setupObserverINFO);
}
cleanr.cleaning();
log('Now adding Observer and starting...', setupObserverINFO);
// var observer = new MutationObserver(cleanr.cleaning);
var observer = new MutationObserver(cleanr.lazystartCleaning);
var config = {childList: true, attributes: false, characterData: false, subtree: true};
observer.observe(observed, config);
observed.classList.add('hasObserver');
log('Observer added and running on element with id='+observed.id+'...', setupObserverINFO);
}
},
secondsSinceStart: function() {
return ((Date.now()-cleanr.startTime)/1000).toFixed(3);
},
registerScroll: function () {
cleanr.hasScrolled = true;
},
scrollTick: function() {
if (cleanr.hasScrolled) {
cleanr.hasScrolled = false;
cleanr.cleaning();
}
},
init: function () {
log('Running init()');
cleanr.activefilter = cleanr.list[0].filter; // Default to English filters
cleanr.loadSettings();
cleanr.insertStyle();
// initial cleanups...
setTimeout(cleanr.cleaning,100);
setTimeout(cleanr.cleaning,300);
setTimeout(cleanr.cleaning,700);
setTimeout(cleanr.cleaning,1200);
// Methods:
if (true) { // observer
setInterval(cleanr.setupObserver, 2000); // Every twice second, check if observer is (still) running - and setup if not...
} else if (false) { // scroll trigger
window.addEventListener("scroll", cleanr.registerScroll);
setInterval(cleanr.scrollTick, 250);
} else if (false) { // simple interval check
setInterval(cleanr.cleaning, 250);
}
}
};
cleanr.init();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment