GreasyFork Bullshit Filter

Hides scripts for popular browser games and social networks as well as scripts that use "foreign" characters in descriptions. Applies to posts in Forum too.

当前为 2019-12-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GreasyFork Bullshit Filter
  3. // @namespace darkred
  4. // @version 2019.12.5
  5. // @description Hides scripts for popular browser games and social networks as well as scripts that use "foreign" characters in descriptions. Applies to posts in Forum too.
  6. // @author kuehlschrank, darkred, valacar, Graphen
  7. // @license MIT
  8. // @icon https://raw.githubusercontent.com/darkred/Userscripts/master/GreasyFork_Bullshit_Filter/large.png
  9. // @include /^https:\/\/(greasy|sleazy)fork\.org\/(.*\/)?(scripts|forum|users).*$/
  10. // @exclude /^https:\/\/(greasy|sleazy)fork\.org\/(.*\/)((scripts\/\d+)|forum\/(discussion\/|profile|messages)).*$/
  11. // @grant none
  12. // This is a modified version of this script (http://userscripts-mirror.org/scripts/show/97145) by kuehlschrank.
  13. // Thanks a lot to:
  14. // - kuehlschrank for making another great script,
  15. // - valacar for the refactoring,
  16. // - Graphen for the 'Non-Latin' regex.
  17. // ==/UserScript==
  18.  
  19.  
  20. (function() {
  21.  
  22. const DEBUGGING = 0;
  23.  
  24. const filters = {
  25. 'Non-ASCII': /[^\x00-\x7F\s]+/,
  26. 'Non-Latin': /[^\u0000-\u024F\u2000-\u214F\s]+/,
  27. 'Games': /Aimbot|AntiGame|Agar|agar\.io|alis\.io|angel\.io|ExtencionRipXChetoMalo|AposBot|DFxLite|ZTx-Lite|AposFeedingBot|AposLoader|Balz|Blah Blah|Orc Clan Script|Astro\s*Empires|^\s*Attack|^\s*Battle|BiteFight|Blood\s*Wars|Bloble|Bonk|Bots|Bots4|Brawler|\bBvS\b|Business\s*Tycoon|Castle\s*Age|City\s*Ville|chopcoin\.io|Comunio|Conquer\s*Club|CosmoPulse|cursors\.io|Dark\s*Orbit|Dead\s*Frontier|Diep\.io|\bDOA\b|doblons\.io|DotD|Dossergame|Dragons\s*of\s*Atlantis|driftin\.io|Dugout|\bDS[a-z]+\n|elites\.io|Empire\s*Board|eRep(ublik)?|Epicmafia|Epic.*War|ExoPlanet|Falcon Tools|Feuerwache|Farming|FarmVille|Fightinfo|Frontier\s*Ville|Ghost\s*Trapper|Gladiatus|Goalline|Gondal|gota\.io|Grepolis|Hobopolis|\bhwm(\b|_)|Ikariam|\bIT2\b|Jellyneo|Kapi\s*Hospital|Kings\s*Age|Kingdoms?\s*of|knastv(o|oe)gel|Knight\s*Fight|\b(Power)?KoC(Atta?ck)?\b|\bKOL\b|Kongregate|Krunker|Last\s*Emperor|Legends?\s*of|Light\s*Rising|lite\.ext\.io|Lockerz|\bLoU\b|Mafia\s*(Wars|Mofo)|Menelgame|Mob\s*Wars|Mouse\s*Hunt|Molehill\s*Empire|MooMoo|MyFreeFarm|narwhale\.io|Neopets|NeoQuest|Nemexia|\bOGame\b|Ogar(io)?|Pardus|Pennergame|Pigskin\s*Empire|PlayerScripts|pokeradar\.io|Popmundo|Po?we?r\s*(Bot|Tools)|PsicoTSI|Ravenwood|Schulterglatze|Skribbl|slither\.io|slitherplus\.io|slitheriogameplay|SpaceWars|splix\.io|Survivio|\bSW_[a-z]+\n|\bSnP\b|The\s*Crims|The\s*West|torto\.io|Travian|Treasure\s*Isl(and|e)|Tribal\s*Wars|TW.?PRO|Vampire\s*Wars|vertix\.io|War\s*of\s*Ninja|World\s*of\s*Tanks|West\s*Wars|wings\.io|\bWoD\b|World\s*of\s*Dungeons|wtf\s*battles|Wurzelimperium|Yohoho|Zombs/iu,
  28. 'Social Networks': /Face\s*book|Google(\+| Plus)|\bHabbo|Kaskus|\bLepra|Leprosorium|MySpace|meinVZ|odnoklassniki|Одноклассники|Orkut|sch(ue|ü)ler(VZ|\.cc)?|studiVZ|Unfriend|Valenth|VK|vkontakte|ВКонтакте|Qzone|Twitter|TweetDeck/iu,
  29. 'Clutter': /^\s*(.{1,3})\1+\n|^\s*(.+?)\n+\2\n*$|^\s*.{1,5}\n|do\s*n('|o)?t (install|download)|nicht installieren|(just )?(\ban? |\b)test(ing|s|\d|\b)|^\s*.{0,4}test.{0,4}\n|\ntest(ing)?\s*|^\s*(\{@|Smolka|Hacks)|\[\d{4,5}\]|free\s*download|theme|(night|dark) ?(mode)?/iu
  30. };
  31.  
  32. const commonCss = `
  33. .filter-status {
  34. margin-left: 6px;
  35. }
  36.  
  37. .filter-switches {
  38. display: none;
  39. }
  40.  
  41. :hover>.filter-switches {
  42. display: block;
  43. }
  44.  
  45. .filter-on,
  46. .filter-off {
  47. display: block;
  48. width: 97px;
  49. }
  50.  
  51. .filter-switches a {
  52. text-decoration: none;
  53. color: inherit;
  54. cursor: pointer;
  55. }
  56.  
  57. .filter-switches a {
  58. margin-left: 8px;
  59. padding: 0 4px;
  60. }
  61.  
  62. a.filter-on {
  63. background-color: #ffcccc;
  64. color: #333333;
  65. text-decoration: line-through;
  66. }
  67.  
  68. a.filter-off {
  69. background-color: #ccffcc;
  70. color: #333333;
  71. }
  72. `;
  73.  
  74. const isOnForum = window.location.href.includes('forum');
  75.  
  76. const site = {};
  77. if (isOnForum) {
  78. site.css = '.ItemDiscussion.filtered { display: none; } .filter-on, .filter-off { color: black; } ' + commonCss;
  79. site.cssDebug = '.ItemDiscussion.filtered { background-color: khaki; } ' + commonCss;
  80. site.filterStatusLocation = '.PanelColumn';
  81. site.itemsToCheck = '.ItemDiscussion';
  82. site.itemType = 'discussions';
  83. site.removeFilter = function(el) {
  84. el.classList.remove('filtered');
  85. };
  86. site.applyFilter = function(el, activeFilter) {
  87. const temp = el.children[1].firstElementChild.innerText;
  88. if (temp && temp.match(activeFilter)) {
  89. el.classList.add('filtered');
  90. return true;
  91. }
  92. return false;
  93. };
  94. } else {
  95. site.css = 'li.filtered { display: none; } ' + commonCss;
  96. site.cssDebug = 'li.filtered { background-color: khaki; } ' + commonCss;
  97. site.filterStatusLocation = '#script-list-option-groups';
  98. site.itemsToCheck = 'article > h2';
  99. site.itemType = 'scripts';
  100. site.removeFilter = function(el) {
  101. el.parentNode.parentNode.classList.remove('filtered');
  102. };
  103. site.applyFilter = function(el, activeFilter) {
  104. if (el.innerText.match(activeFilter)) {
  105. el.parentNode.parentNode.classList.add('filtered');
  106. return true;
  107. }
  108. return false;
  109. };
  110. }
  111.  
  112. insertStyle();
  113. insertStatus();
  114. filterScripts();
  115. insertSwitches();
  116.  
  117. function insertStyle() {
  118. const style = document.createElement('style');
  119. style.textContent = DEBUGGING ? site.cssDebug : site.css;
  120. style.type = 'text/css';
  121. document.head.appendChild(style);
  122. }
  123.  
  124. function insertStatus() {
  125. const p = document.querySelector(site.filterStatusLocation);
  126. if (p) {
  127. const status = document.createElement('span');
  128. status.className = 'filter-status';
  129. p.appendChild(status);
  130. }
  131. }
  132.  
  133. function filterScripts() {
  134. const activeFilters = [];
  135. for (let filterType of Object.keys(filters)) {
  136. if (configGetValue(filterType, 'on') === 'on') {
  137. activeFilters.push(filters[filterType]);
  138. }
  139. }
  140. const nodes = document.querySelectorAll(site.itemsToCheck);
  141. let numFiltered = 0;
  142. for (let node of nodes) {
  143. site.removeFilter(node);
  144. for (let activeFilter of activeFilters) {
  145. let filtered = site.applyFilter(node, activeFilter);
  146. if (filtered) {
  147. numFiltered++;
  148. break;
  149. }
  150. }
  151. }
  152. const filterStatus = document.querySelector('.filter-status');
  153. if (filterStatus) {
  154. const numUnfiltered = document.querySelectorAll(site.itemsToCheck).length - numFiltered;
  155. filterStatus.textContent = `${numUnfiltered} ${site.itemType} (${numFiltered} filtered)`;
  156. }
  157. }
  158.  
  159. function insertSwitches() {
  160. const span = document.createElement('span');
  161. span.className = 'filter-switches';
  162. for (let filterType of Object.keys(filters)) {
  163. span.appendChild(createSwitch(filterType, configGetValue(filterType, 'on') === 'on'));
  164. }
  165. const filterStatus = document.querySelector('.filter-status');
  166. if (filterStatus) {
  167. filterStatus.parentNode.appendChild(span);
  168. }
  169. }
  170.  
  171. function createSwitch(label, isOn) {
  172. const a = document.createElement('a');
  173. a.className = isOn ? 'filter-on' : 'filter-off';
  174. a.textContent = label;
  175. a.addEventListener('click', function(e) {
  176. if (this.className === 'filter-on') {
  177. this.className = 'filter-off';
  178. configSetValue(this.textContent, 'off');
  179. } else {
  180. this.className = 'filter-on';
  181. configSetValue(this.textContent, 'on');
  182. }
  183. filterScripts();
  184. e.preventDefault();
  185. }, false);
  186. return a;
  187. }
  188.  
  189. function configSetValue(name, value) {
  190. localStorage.setItem(name, value);
  191. }
  192.  
  193. function configGetValue(name, defaultValue) {
  194. const value = localStorage.getItem(name);
  195. return value ? value : defaultValue;
  196. }
  197.  
  198. })();