// ==UserScript== // @name FB Mobile - Clean my feeds // @namespace Violentmonkey Scripts // @match https://m.facebook.com/* // @version 0.21 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAbwAAAG8B8aLcQwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHZSURBVDiNnZFLSFRxFMa/c1/jjIzYpGEjxFQUCC5a9BKJIAtRzEXEFaJFZXRrIQMtk3a1lWo3iwqkTS0kZyGCA4VNFNEmWwU9MIoiscZp7jzuvf9zWogXogS9Z3fO4fv4feeQiCBKjY8M9Nca3lUtkhqAUnwNoPcUheC63b+z5qm3nmelIxGwkMMir+/MzJSNzYodZ7/ZolKXADoDAJsmSJXahpXiXxPThdlIBlCSFUh+rd1wBNvuttLu1sOGae7zYjy4Nt8QgXpoXbzf9/HVYNfi3O+KK5XP5V3rEti2rde3pHvyuVtFAMB8/JjWJLlEU0M7nlnE0e1fjGVqPgVg4b8E0rHnHoSeDY1mx/CCUiIyiVZdQ8YE7bVgdpCWCqrj6xIQ0Rtm/qlB3okXywHoDJcxAnWa0OPtpb8M8nPP06V6tVD3/Mqj2zcOApjA0/g5AU6HYl7llcAANP4WHnH6SfEQ65hPJuJdvh8cuDs165y8nO1bqiZb4KoyVhhYVoDLqxEDAwT+EBqwwAGwm4jQmmyGF/g3Y3pi+MLU2U9UCjKUwCga/BUmAT8CiDIAnRfCyI8LxSNCeABgh1uro+zWlq7YQ9v++WXe7GWDziu/bcS0+AQGvr8EgD/aK7uaswjePgAAAABJRU5ErkJggg== // @run-at document-end // @author https://github.com/webdevsk // @description Removed Unwanted Posts from FB Mobile Newsfeeds // @license MIT // @downloadURL none // ==/UserScript== // Some Things to note here // This is a React site. Only #screen-root is shipped with the HTML. Everything inside is populated using JS. // That makes it the perfect element to "observe". // In order to reduce device memory usage, they remove/compress/disable posts that are far from the current scroll position. // As they lose their organic Height, facebook uses (2) filler elements to make up for that empty space. // As posts get constantly added/removed by themselves, you see some jitters while scrolling. // We are removing posts ourselves. So the jitter happens way more often **SORRY** // As the posts get removed, the filler elements height need to be adjusted as well. Thats where the jitter happens. // As filler height goes from say 5000px to 500px in a second when we update it ourselves. // After scrolling for a while, they just keep spamming suggested posts and ads. So you will often see the "Loading more posts" element. const devMode = false const showPlaceholder = true // Make sure this is the React-Mobile version of facebook if (!document.documentElement.classList.contains("ssr")) return // React root const root = document.querySelector('#screen-root') if (!root) return //Show counter on top if (devMode) { var whiteCount = 0 var blackCount = 0 const devPanel = document.createElement("div") Object.assign(devPanel.style, { position: "fixed", top: 0, right: 0, padding: ".5rem 1rem", background: "#323436", borderRadius: ".2rem", display: "flex", flexFlow: "row wrap", zIndex: 99, color: "#ddd", gap: ".5rem", fontSize: ".8rem", pointerEvents: "none", }) const whiteList = document.createElement("p") whiteList.innerHTML = `Whitelisted: 0` const blackList = document.createElement("p") blackList.innerHTML = `Blacklisted: 0` devPanel.appendChild(whiteList) devPanel.appendChild(blackList) document.body.appendChild(devPanel) } //////////////////////////////////////////////////////////////////////////////// //////////////////// Labels //////////////////////// //////////////////////////////////////////////////////////////////////////////// // this version of fb does not update navigator.lang on language change // navigator.langs contain all of your preset languages. So we need to loop through it const getLabels = obj => navigator.languages.map(lang => obj[lang]) console.log(navigator.languages) // Placeholder Message const placeholderMsg = getLabels({ 'en-US': 'Removed', 'en': 'Removed', 'bn': 'বাতিল' })[0] // To be fixed later // Suggested const suggested = getLabels({ 'en-US': 'Suggested for you', 'en': 'Suggested for you', 'bn': 'আপনার জন্য প্রস্তাবিত' }) // Sponsored const sponsored = getLabels({ 'en-US': 'Sponsored', 'en': 'Sponsored', 'bn': 'স্পনসর্ড' }) //Whatever we wanna do with the convicts findConvicts((convicts) => { console.table(convicts) for (const { element, reason, author } of convicts) { element.tabIndex = "-1" element.dataset.purged = "true" // Sponsored posts get removed in an "out of order" fashion automatically. // Having placeholder inside them results in a scroll jump if (showPlaceholder && reason !== "Sponsored") { element.dataset.actualHeight = "32" Object.assign(element.style, { height: "32px", overflowY: "hidden", pointerEvents: "none", position: "relative" }) const overlay = document.createElement("div") Object.assign(overlay.style, { position: "absolute", inset: 0, background: "#242526", color: "#e4e6eb", display: "grid", pointerEvents: "auto", placeItems: "center", paddingInline: ".5rem" }) overlay.innerHTML = `

${placeholderMsg}: ${author} (${reason})

` element.appendChild(overlay) } else { // Hide elements by resizing to 0px // Removing from DOM or display:none causes issues loading newer posts element.dataset.actualHeight = "0" Object.assign(element.style, { height: "0px", overflowY: "hidden", pointerEvents: "none" }) //Hiding divider element preceding convicted element const { previousElementSibling: prevElm } = element if (prevElm.dataset.actualHeight !== "1") continue prevElm.style.marginTop = "0px" prevElm.style.height = "0px" prevElm.dataset.actualHeight = "0" } // Removing image links to restrict downloading unnecessary content for (const image of element.querySelectorAll("img")) { image.dataset.src = image.src //Clearing out src doesn't work as it gets populated again automatically image.removeAttribute("src") image.dataset.nulled = true } } }) //////////////////////////////////////////////////////////////////////////////// //////////////////// function definitions //////////////////////// //////////////////////////////////////////////////////////////////////////////// function findConvicts(callback) { const observer = new MutationObserver((mutationList, observer) => { if (location.pathname !== '/') return const convicts = [] for (const mutation of mutationList) { if (!(mutation.type === "childList" && mutation.target.matches("[data-type='vscroller']") && mutation.addedNodes.length !== 0)) continue // console.log(mutation) // console.table([...mutation.addedNodes].map(item => ({elm:item ,id: item.dataset.trackingDurationId, height: item.dataset.actualHeight}))) for (const element of mutation.addedNodes) { // Check if element is an actual facebook post if (!(element.hasAttribute("data-tracking-duration-id"))) continue let suspect = false let reason let raw let author for (const span of element.querySelectorAll("span.f5")) { if (![...suggested, ...sponsored].some(str => span.innerHTML.includes(str))) continue suspect = true reason = span.innerHTML.split("󰞋")[0] raw = span.innerHTML break } if (suspect) { author = element.querySelector("span.f2").innerHTML if (author.includes("Sponsored")) console.log(element) } if (suspect) { convicts.push({ element, reason, raw, id: element.dataset.trackingDurationId, author }) updateBlackCount(1) } else { updateWhiteCount(1) } } } if (convicts.length !== 0) callback(convicts) // Set new calculated height to the bottom ".filler" element // We need to calculate it after all the convicts are taken care of setFillerHeight(mutationList) }) observer.observe(root, { childList: true, subtree: true, }) } function setFillerHeight(mutationList) { const fillerNode = document.querySelectorAll('.filler')[1] if (!fillerNode) return let newHeight = 0 for (const mutation of mutationList) { if (!(mutation.type === "childList" && mutation.target.matches("[data-type='vscroller']") && mutation.addedNodes.length !== 0)) continue newHeight += [...mutation.addedNodes].reduce((accumulator, element) => ( accumulator += element.classList.contains('displayed') || element.classList.contains('filler') ? 0 : element.clientHeight ), 0) } fillerNode.style.height = newHeight } function updateWhiteCount(amount) { if (!devMode) return whiteCount += amount document.querySelector('#whitelist-count').innerHTML = whiteCount } function updateBlackCount(amount) { if (!devMode) return blackCount += amount document.querySelector('#blacklist-count').innerHTML = blackCount }