NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Facebook Adblocker // @name:vi Facebook Adblocker // @namespace https://lelinhtinh.github.io // @description Block all ads in Facebook News Feed. // @description:vi Chặn quảng cáo được tài trợ trên trang chủ Facebook. // @version 1.4.6 // @icon https://i.imgur.com/F8ai0jB.png // @author lelinhtinh // @oujs:author baivong // @license MIT; https://baivong.mit-license.org/license.txt // @match https://facebook.com/* // @match https://*.facebook.com/* // @require https://unpkg.com/throttle-debounce@5.0.0/umd/index.js // @noframes // @supportURL https://github.com/lelinhtinh/Userscript/issues // @run-at document-idle // @grant none // ==/UserScript== /** * Facebook sponsor labels */ const sponsorLabelConfigs = { en: ['Sponsored'], vi: ['Được tài trợ'], }; /** * @type {'remove'|'fade'} */ const sponsorHideMode = 'remove'; /* === DO NOT CHANGE ANYTHING BELOW THIS LINE === */ (function () { 'use strict'; /** @type Element */ let labelHidden = null; /** @type string[]|null */ let labelStore = null; /** @type MutationObserver */ let observerLabel; /** @type MutationObserver */ let observerStory; /** @type MutationObserver */ let observerHead; /** @type IntersectionObserver */ let observerScroll; let sponsorLangConfigs = sponsorLabelConfigs[navigator.language.split('-')[0] || document.documentElement.lang || sponsorLabelConfigs.en]; /** * @param {string} label * @param {boolean} removeSpaces * @returns {boolean} */ const isSponsorLabel = (label, removeSpaces = false) => { if (!removeSpaces) return sponsorLangConfigs.includes(label); return sponsorLangConfigs.map((label) => label.replace(/\s/g, '')).includes(label); }; const feedSelector = () => (location.pathname.startsWith('/watch') ? '#watch_feed' : '[role="feed"]'); const articleSelector = () => (location.pathname.startsWith('/watch') ? '._6x84' : '[role="article"]'); /** * @param {Element} sponsorLabel */ const removeSponsor = (sponsorLabel) => { const sponsorWrapper = sponsorLabel.closest(articleSelector()); if (sponsorWrapper === null) return; console.count('UserScript Facebook Adblocker'); if (sponsorHideMode === 'fade') { sponsorWrapper.style.opacity = 0.1; sponsorWrapper.style.transition = 'opacity 400ms ease-in-out 0s'; sponsorWrapper.addEventListener('mouseenter', () => { sponsorWrapper.style.opacity = 1; }); sponsorWrapper.addEventListener('mouseleave', () => { sponsorWrapper.style.opacity = 0.1; }); } else { sponsorWrapper.remove(); } }; /** * @param {Element} wrapper */ const detectSponsor = (wrapper) => { let sponsorLabelSelector = ['a[href^="/ads/"]']; sponsorLabelSelector.push(...sponsorLangConfigs.map((label) => `a[aria-label="${label}"]`)); const sponsorLabels = wrapper.querySelectorAll(sponsorLabelSelector.join(',')); if (sponsorLabels.length) sponsorLabels.forEach(removeSponsor); const obfuscatedLabels = wrapper.querySelectorAll('[style="display: flex;"]'); if (obfuscatedLabels.length) { obfuscatedLabels.forEach((obfuscatedLabel) => { const temp = []; obfuscatedLabel.querySelectorAll('div').forEach((span) => { if ( getComputedStyle(span).getPropertyValue('display') === 'none' || getComputedStyle(span).getPropertyValue('position') === 'absolute' ) return; const order = parseInt(getComputedStyle(span).getPropertyValue('order'), 10); temp[order] = span.textContent.trim(); }); const label = temp.join('').replace(/\s/g, ''); if (isSponsorLabel(label, true)) removeSponsor(obfuscatedLabel); }); } }; /** * @param {Element} wrapper */ const findSponsor = (wrapper = document) => { if (labelStore instanceof Array) { if (!labelStore.length) return; const labelId = labelStore.pop(); const sponsorLabel = wrapper.querySelector('span[aria-labelledby="' + labelId + '"][class]'); if (sponsorLabel === null) return; removeSponsor(sponsorLabel); findSponsor(wrapper); } detectSponsor(wrapper); }; /** * @param {Element} labelHidden */ const watchLabel = (labelHidden) => { if (observerLabel) return; observerLabel = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { if ( mutation.type === 'attributes' && mutation.attributeName === 'id' && isSponsorLabel(mutation.target.textContent.trim()) ) { labelStore.push(mutation.target.id); findSponsor(); } } }); observerLabel.observe(labelHidden, { attributes: true, attributeFilter: ['id'], subtree: true, }); }; // Find while scrolling observerScroll = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (!entry.intersectionRatio) return; detectSponsor(entry.target); }); }); const init = throttleDebounce.debounce(300, () => { const newsFeed = document.querySelector(feedSelector()); if (newsFeed === null) return; newsFeed.querySelectorAll(articleSelector()).forEach((article) => { observerScroll.observe(article); }); // Find on DOM change if (observerStory) observerStory.disconnect(); observerStory = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { findSponsor(mutation.target); mutation.target.querySelectorAll(articleSelector()).forEach((article) => { observerScroll.observe(article); }); } }); observerStory.observe(newsFeed, { attributes: false, childList: true, subtree: true, }); findSponsor(); // Find on label list change if (labelHidden === null) { labelHidden = document.querySelector('[hidden="true"]'); if (labelHidden === null) { labelStore = null; if (observerLabel) { observerLabel.disconnect(); observerLabel = null; } } else { labelStore = []; labelHidden.querySelectorAll('span').forEach((target) => { if (isSponsorLabel(target.textContent.trim())) { labelStore.push(target.id); findSponsor(); } }); watchLabel(labelHidden); } } }); init(); if (observerHead) observerHead.disconnect(); observerHead = new MutationObserver(init); observerHead.observe(document.head, { attributes: true, childList: true, subtree: true, }); (function (old) { window.history.pushState = function () { old.apply(window.history, arguments); init(); }; })(window.history.pushState); })();