Profanity Filter

Simple filtering for profanity from website text. Not limited to static text, while avoiding performance impact.

目前為 2017-03-22 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Profanity Filter
  3. // @author adisib
  4. // @namespace namespace_adisib
  5. // @description Simple filtering for profanity from website text. Not limited to static text, while avoiding performance impact.
  6. // @version 2017.03.22
  7. // @include http://*
  8. // @include https://*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12.  
  13. (function() {
  14.  
  15. "use strict";
  16.  
  17.  
  18. // --- GLOBALS --------
  19.  
  20.  
  21. // Display performance and debugging information to the console.
  22. const DEBUG = false;
  23.  
  24.  
  25. // set replacement string
  26. const replaceString = "*bleep*";
  27.  
  28. // words to be filtered list
  29. // This should be ordered by most common first for performance (still TODO, but not important)
  30. // (also should probably be sanitized before dropping into regex)
  31. const words = ['fuck','shit','ass','damn','asshole','bullshit','bitch','piss','goddamn','crap','sh!t','bastard','dumbass','fag','motherfuck','nig','cunt','douche','douchebag','jackass','mothafuck','pissoff','shitfull','fuk','fuckme','fvck','fcuk','b!tch','phuq','phuk','phuck','fatass','faggot','dipshit','fagot','faggit','fagget','assfuck','buttfuck','asswipe','asskiss','assclown'];
  32.  
  33. // filters the words and any versions with optional endings
  34. // shouldn't run into issues with optional endings; a whitelist would be trivial to implement should it be required
  35. const wordsFilter = new RegExp("\\b(?:" + words.join("|") + ")[tgkp]??(?=(?:ing?(:?ess)??|ed|i??er|a)??(?:e??[syz])??\\b)", "gi");
  36.  
  37.  
  38. // --------------------
  39.  
  40.  
  41. // Initial slow filter pass that handles static text
  42. function filterStaticText()
  43. {
  44. let startTime, endTime;
  45. if (DEBUG)
  46. {
  47. startTime = performance.now();
  48. }
  49.  
  50. // Do title first because it is always visible
  51. if (wordsFilter.test(document.title))
  52. {
  53. document.title = document.title.replace(wordsFilter, replaceString);
  54. }
  55.  
  56. filterNodeTree(document.body);
  57.  
  58. if (DEBUG)
  59. {
  60. endTime = performance.now();
  61. console.log("PF | Static Text Run-Time (ms): " + (endTime - startTime).toString());
  62. }
  63. }
  64.  
  65.  
  66. // --------------------
  67.  
  68.  
  69. // filters dynamic text, and handles things such as AJAX Youtube comments
  70. function filterDynamicText()
  71. {
  72. let textMutationObserver = new MutationObserver( function(mutations) { filterMutations(mutations); } );
  73. let TxMOInitOps = { characterData: true, childList: true, subtree: true };
  74. textMutationObserver.observe(document.body, TxMOInitOps);
  75.  
  76. let title = document.getElementsByTagName("title")[0];
  77. let titleMutationObserver = new MutationObserver( function(mutations) { filterNode(title); } );
  78. let TiMOInitOps = { characterData: true, subtree: true };
  79. titleMutationObserver.observe(title, TiMOInitOps);
  80. }
  81.  
  82.  
  83. // --------------------
  84.  
  85.  
  86. // Handler for mutation observer from filterDynamicText()
  87. function filterMutations(mutations)
  88. {
  89. let startTime, endTime;
  90. if (DEBUG)
  91. {
  92. startTime = performance.now();
  93. }
  94.  
  95. for (let i=0; i < mutations.length; ++i)
  96. {
  97. let mutation = mutations[i];
  98.  
  99. if (mutation.type === "childList")
  100. {
  101. let nodes = mutation.addedNodes;
  102. for (let i=0; i < nodes.length; ++i)
  103. {
  104. filterNodeTree(nodes[i]);
  105. }
  106. }
  107. else if (mutation.type === "characterData")
  108. {
  109. filterNode(mutation.target);
  110. }
  111. }
  112.  
  113. if (DEBUG)
  114. {
  115. endTime = performance.now();
  116. console.log("PF | Dynamic Text Run-Time (ms): " + (endTime - startTime).toString());
  117. }
  118. }
  119.  
  120.  
  121. // --------------------
  122.  
  123.  
  124. // Filters a textNode
  125. function filterNode(node)
  126. {
  127. if (wordsFilter.test(node.data))
  128. {
  129. node.data = node.data.replace(wordsFilter, replaceString);
  130. }
  131. }
  132.  
  133.  
  134. // --------------------
  135.  
  136.  
  137. // Filters all of the text from a node and its decendants
  138. function filterNodeTree(node)
  139. {
  140. if (!node || !(node.tagName))
  141. {
  142. return;
  143. }
  144.  
  145. if (node.data)
  146. {
  147. filterNode(node);
  148. }
  149.  
  150. let textNodes = document.evaluate(".//text()[string-length() > 2 and not(parent::script or parent::noscript or parent::code)]", node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  151.  
  152. const l = textNodes.snapshotLength;
  153. for (let i=0; i < l; ++i)
  154. {
  155. filterNode(textNodes.snapshotItem(i));
  156. }
  157. }
  158.  
  159.  
  160. // --------------------
  161.  
  162.  
  163. // Runs the different filter types
  164. function filterPage()
  165. {
  166. filterDynamicText();
  167. filterStaticText();
  168. }
  169.  
  170.  
  171. // --- MAIN -----------
  172.  
  173. filterPage();
  174.  
  175. })();