Facebook Adblocker

Block all ads in Facebook News Feed.

  1. // ==UserScript==
  2. // @name Facebook Adblocker
  3. // @name:vi Facebook Adblocker
  4. // @namespace https://lelinhtinh.github.io
  5. // @description Block all ads in Facebook News Feed.
  6. // @description:vi Chặn quảng cáo được tài trợ trên trang chủ Facebook.
  7. // @version 1.4.6
  8. // @icon https://i.imgur.com/F8ai0jB.png
  9. // @author lelinhtinh
  10. // @oujs:author baivong
  11. // @license MIT; https://baivong.mit-license.org/license.txt
  12. // @match https://facebook.com/*
  13. // @match https://*.facebook.com/*
  14. // @require https://unpkg.com/throttle-debounce@5.0.0/umd/index.js
  15. // @noframes
  16. // @supportURL https://github.com/lelinhtinh/Userscript/issues
  17. // @run-at document-idle
  18. // @grant none
  19. // ==/UserScript==
  20.  
  21. /**
  22. * Facebook sponsor labels
  23. */
  24. const sponsorLabelConfigs = {
  25. en: ['Sponsored'],
  26. vi: ['Được tài trợ'],
  27. };
  28.  
  29. /**
  30. * @type {'remove'|'fade'}
  31. */
  32. const sponsorHideMode = 'remove';
  33.  
  34. /* === DO NOT CHANGE ANYTHING BELOW THIS LINE === */
  35.  
  36. (function () {
  37. 'use strict';
  38.  
  39. /** @type Element */
  40. let labelHidden = null;
  41. /** @type string[]|null */
  42. let labelStore = null;
  43. /** @type MutationObserver */
  44. let observerLabel;
  45. /** @type MutationObserver */
  46. let observerStory;
  47. /** @type MutationObserver */
  48. let observerHead;
  49. /** @type IntersectionObserver */
  50. let observerScroll;
  51.  
  52. let sponsorLangConfigs =
  53. sponsorLabelConfigs[navigator.language.split('-')[0] || document.documentElement.lang || sponsorLabelConfigs.en];
  54. /**
  55. * @param {string} label
  56. * @param {boolean} removeSpaces
  57. * @returns {boolean}
  58. */
  59. const isSponsorLabel = (label, removeSpaces = false) => {
  60. if (!removeSpaces) return sponsorLangConfigs.includes(label);
  61. return sponsorLangConfigs.map((label) => label.replace(/\s/g, '')).includes(label);
  62. };
  63.  
  64. const feedSelector = () => (location.pathname.startsWith('/watch') ? '#watch_feed' : '[role="feed"]');
  65. const articleSelector = () => (location.pathname.startsWith('/watch') ? '._6x84' : '[role="article"]');
  66.  
  67. /**
  68. * @param {Element} sponsorLabel
  69. */
  70. const removeSponsor = (sponsorLabel) => {
  71. const sponsorWrapper = sponsorLabel.closest(articleSelector());
  72. if (sponsorWrapper === null) return;
  73. console.count('UserScript Facebook Adblocker');
  74.  
  75. if (sponsorHideMode === 'fade') {
  76. sponsorWrapper.style.opacity = 0.1;
  77. sponsorWrapper.style.transition = 'opacity 400ms ease-in-out 0s';
  78. sponsorWrapper.addEventListener('mouseenter', () => {
  79. sponsorWrapper.style.opacity = 1;
  80. });
  81. sponsorWrapper.addEventListener('mouseleave', () => {
  82. sponsorWrapper.style.opacity = 0.1;
  83. });
  84. } else {
  85. sponsorWrapper.remove();
  86. }
  87. };
  88.  
  89. /**
  90. * @param {Element} wrapper
  91. */
  92. const detectSponsor = (wrapper) => {
  93. let sponsorLabelSelector = ['a[href^="/ads/"]'];
  94. sponsorLabelSelector.push(...sponsorLangConfigs.map((label) => `a[aria-label="${label}"]`));
  95.  
  96. const sponsorLabels = wrapper.querySelectorAll(sponsorLabelSelector.join(','));
  97. if (sponsorLabels.length) sponsorLabels.forEach(removeSponsor);
  98.  
  99. const obfuscatedLabels = wrapper.querySelectorAll('[style="display: flex;"]');
  100. if (obfuscatedLabels.length) {
  101. obfuscatedLabels.forEach((obfuscatedLabel) => {
  102. const temp = [];
  103. obfuscatedLabel.querySelectorAll('div').forEach((span) => {
  104. if (
  105. getComputedStyle(span).getPropertyValue('display') === 'none' ||
  106. getComputedStyle(span).getPropertyValue('position') === 'absolute'
  107. )
  108. return;
  109.  
  110. const order = parseInt(getComputedStyle(span).getPropertyValue('order'), 10);
  111. temp[order] = span.textContent.trim();
  112. });
  113.  
  114. const label = temp.join('').replace(/\s/g, '');
  115. if (isSponsorLabel(label, true)) removeSponsor(obfuscatedLabel);
  116. });
  117. }
  118. };
  119.  
  120. /**
  121. * @param {Element} wrapper
  122. */
  123. const findSponsor = (wrapper = document) => {
  124. if (labelStore instanceof Array) {
  125. if (!labelStore.length) return;
  126. const labelId = labelStore.pop();
  127.  
  128. const sponsorLabel = wrapper.querySelector('span[aria-labelledby="' + labelId + '"][class]');
  129. if (sponsorLabel === null) return;
  130.  
  131. removeSponsor(sponsorLabel);
  132. findSponsor(wrapper);
  133. }
  134.  
  135. detectSponsor(wrapper);
  136. };
  137.  
  138. /**
  139. * @param {Element} labelHidden
  140. */
  141. const watchLabel = (labelHidden) => {
  142. if (observerLabel) return;
  143. observerLabel = new MutationObserver((mutationsList) => {
  144. for (let mutation of mutationsList) {
  145. if (
  146. mutation.type === 'attributes' &&
  147. mutation.attributeName === 'id' &&
  148. isSponsorLabel(mutation.target.textContent.trim())
  149. ) {
  150. labelStore.push(mutation.target.id);
  151. findSponsor();
  152. }
  153. }
  154. });
  155. observerLabel.observe(labelHidden, {
  156. attributes: true,
  157. attributeFilter: ['id'],
  158. subtree: true,
  159. });
  160. };
  161.  
  162. // Find while scrolling
  163. observerScroll = new IntersectionObserver((entries) => {
  164. entries.forEach((entry) => {
  165. if (!entry.intersectionRatio) return;
  166. detectSponsor(entry.target);
  167. });
  168. });
  169.  
  170. const init = throttleDebounce.debounce(300, () => {
  171. const newsFeed = document.querySelector(feedSelector());
  172. if (newsFeed === null) return;
  173.  
  174. newsFeed.querySelectorAll(articleSelector()).forEach((article) => {
  175. observerScroll.observe(article);
  176. });
  177.  
  178. // Find on DOM change
  179. if (observerStory) observerStory.disconnect();
  180. observerStory = new MutationObserver((mutationsList) => {
  181. for (let mutation of mutationsList) {
  182. findSponsor(mutation.target);
  183. mutation.target.querySelectorAll(articleSelector()).forEach((article) => {
  184. observerScroll.observe(article);
  185. });
  186. }
  187. });
  188. observerStory.observe(newsFeed, {
  189. attributes: false,
  190. childList: true,
  191. subtree: true,
  192. });
  193. findSponsor();
  194.  
  195. // Find on label list change
  196. if (labelHidden === null) {
  197. labelHidden = document.querySelector('[hidden="true"]');
  198. if (labelHidden === null) {
  199. labelStore = null;
  200. if (observerLabel) {
  201. observerLabel.disconnect();
  202. observerLabel = null;
  203. }
  204. } else {
  205. labelStore = [];
  206. labelHidden.querySelectorAll('span').forEach((target) => {
  207. if (isSponsorLabel(target.textContent.trim())) {
  208. labelStore.push(target.id);
  209. findSponsor();
  210. }
  211. });
  212. watchLabel(labelHidden);
  213. }
  214. }
  215. });
  216. init();
  217.  
  218. if (observerHead) observerHead.disconnect();
  219. observerHead = new MutationObserver(init);
  220. observerHead.observe(document.head, {
  221. attributes: true,
  222. childList: true,
  223. subtree: true,
  224. });
  225.  
  226. (function (old) {
  227. window.history.pushState = function () {
  228. old.apply(window.history, arguments);
  229. init();
  230. };
  231. })(window.history.pushState);
  232. })();