No Spam Results in Google Search

No more spam results in your google search!

  1. // ==UserScript==
  2. // @name No Spam Results in Google Search
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.8
  5. // @description No more spam results in your google search!
  6. // @author CY Fung
  7. // @match https://www.google.com/search?*
  8. // @match https://www.google.*.*/search?*
  9. // @match http://*/*
  10. // @match https://*/*
  11. // @icon https://greatest.deepsurf.us/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBeGswQVE9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--d840d20219ec465d39691a24251f7affa09443a5/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2RkhKbGMybDZaVjkwYjE5c2FXMXBkRnNIYVFISWFRSEkiLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--e4f27e4605e5535222e2c2f9dcbe36f4bd1deb29/nospam.png
  12. // @grant GM.setValue
  13. // @grant GM.getValue
  14. // @grant GM.deleteValue
  15. // @grant GM_getTab
  16. // @grant GM_saveTab
  17. // @grant GM_registerMenuCommand
  18. // @license MIT
  19. // @noframes
  20.  
  21. // ==/UserScript==
  22.  
  23. 'use strict';
  24.  
  25. const cssTxt = `
  26. a[href^="http"].gs-spam-link-disable, a[href^="http"].gs-spam-link-disable>h3 {
  27. text-decoration: initial;
  28. color: #d88a8a;
  29. background: #605766;
  30. cursor: no-drop;
  31. }
  32. `;
  33.  
  34. (function () {
  35. 'use strict';
  36.  
  37. const [window, document, hostname, location, history] = new Function('return [window, document, location.hostname, location, history];')(); // real window & document object
  38. let isGoogleSearchPage = hostname.includes('.google')?/\w+\.(google|googleusercontent)\.com(\.\w{0,2}){0,2}/.test(hostname):false;
  39.  
  40. function getOrigin(url){
  41. let nt=null;
  42. url = `${url}`;
  43. try{
  44. nt=new URL(url);
  45. }catch(e){
  46. return url;
  47. }
  48. return `${nt.origin}`;
  49. }
  50.  
  51. function purify(url){
  52. let nt=null;
  53. url = `${url}`;
  54. try{
  55. nt=new URL(url);
  56. }catch(e){
  57. return url;
  58. }
  59. return `${nt.origin}${nt.pathname}`;
  60. }
  61. function elementCount(document){
  62. if(!document) return null;
  63. try{
  64. return document.all.length;
  65. }catch(e){
  66. return document.getElementsByTagName("*").length;
  67. }
  68. }
  69. function consoleJSON(object){
  70. try{
  71. console.table(JSON.stringify(object,null,2))
  72. }catch(e){
  73. console.log('consoleJSON is not supported');
  74. }
  75. }
  76. function getTopDomain(str){
  77. let m = /((?<![^\/]\/)\b[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.\b[a-zA-Z]{2,3}(?:\.\b[a-zA-Z]{2})?)(?:$|\/)/.exec(str);
  78. if(m) return m[1];
  79. return str;
  80. }
  81. function checkSpam(tabObject){
  82.  
  83. if( tabObject.hitAt>1 && Date.now()- tabObject.hitAt<30*1000){
  84. let filteredTimeline = tabObject.timeline.filter(entry=>entry.timeAt>=tabObject.hitAt);
  85. consoleJSON(filteredTimeline);
  86. let [entryA, entryB] = [filteredTimeline[0],filteredTimeline[1]]
  87. if(filteredTimeline.length<10 && filteredTimeline.length>=2 && entryA.type=='click' && entryB.type=='start'){
  88.  
  89. let networkTime = entryB.timeAt - entryA.timeAt;
  90. let [entryZ, entryY] = [filteredTimeline[filteredTimeline.length-1],filteredTimeline[filteredTimeline.length-2]]
  91.  
  92. function noPageContentGenerated(){
  93. // not reliable.
  94. return filteredTimeline.filter(entry=>entry.type=='complete').every(entry=>entry.elementCount<20);
  95. }
  96.  
  97. function noRenderOfActualPage(){
  98. // check whether there is any rendering of actual page
  99. let renderOK= true;
  100. filteredTimeline.reduce((a,b)=>{
  101. if(a && b && a.type=='start' && b.type=='complete' && a.pageHref === b.pageHref && b.timeAt-a.timeAt>5000){
  102. // assume >5000 rendering time is not spam page
  103. renderOK = false;
  104. }
  105. return b;
  106. },null);
  107. return renderOK;
  108. }
  109.  
  110.  
  111. function jsHistCounter(){
  112.  
  113. if(entryA.type=='click' && entryB.type=='start' && entryA.histCount >= entryB.histCount) entryA.histCount = entryB.histCount-1;
  114.  
  115. let jsHistCountInOrder = true;
  116. let endHist = -1;
  117. filteredTimeline.map(entry=>entry.histCount).reduce((a,b)=>{
  118. if(a>-1 && b>-1 && a<=b){
  119. endHist = b
  120. }else{
  121. jsHistCountInOrder=false;
  122. }
  123. return b;
  124. },0);
  125.  
  126. if(jsHistCountInOrder){
  127. return endHist - entryA.histCount;
  128. }
  129.  
  130. return 0;
  131.  
  132. }
  133.  
  134.  
  135. function countDomains(filteredTimeline){
  136. let domains = {}
  137. for(const entry of filteredTimeline){
  138. if(entry.type==='click') continue;
  139. let domain = getTopDomain(new URL(entry.pageHref).hostname)
  140. domains[domain]=(domains[domain]||0)+1
  141. }
  142. domains._arr=Object.keys(domains)
  143. domains._count=domains._arr.length
  144. return domains;
  145. }
  146.  
  147.  
  148. let redirectFlag = false;
  149.  
  150. if(entryA.type=='click' && entryB.type=='start' && getTopDomain(entryA.clickedHref) !== getTopDomain(entryB.pageHref) ){
  151. // server side redirect
  152. redirectFlag = true;
  153.  
  154. }else if(entryZ.type=='start' && (entryY.type=='complete' || entryY.type=='start')){
  155. let [domainZ, domainY] =[ getTopDomain(new URL(entryZ.pageHref).hostname), getTopDomain(new URL(entryY.pageHref).hostname)]
  156. if(domainZ!==domainY && entryZ.timeAt-entryY.timeAt<=1.25*networkTime && entryZ.timeAt-entryB.timeAt<=3*networkTime){
  157. redirectFlag = true;
  158. }
  159. }
  160.  
  161. consoleJSON(filteredTimeline);
  162.  
  163. function startCount(){
  164. return filteredTimeline.filter(entry=>entry.type=='start').length
  165. }
  166.  
  167. if(redirectFlag || (noPageContentGenerated() && noRenderOfActualPage() && startCount()>=2)){
  168.  
  169. let jhc = jsHistCounter();
  170. if(jhc>0){
  171.  
  172. // in case redirect on the same domain
  173. // it is allowed
  174. let domains = countDomains(filteredTimeline)
  175. if(domains._count===1) return;
  176.  
  177. return {filteredTimeline, jhc};
  178. }
  179.  
  180. }
  181. }
  182. }
  183. }
  184.  
  185. GM_getTab(tabObject=>{
  186.  
  187. if(!tabObject.timeline)tabObject.timeline=[];
  188.  
  189. if(isGoogleSearchPage){
  190. if(tabObject.hitAt<0){
  191. history.pushState({},null);
  192. }
  193. tabObject.timeline.length=0;
  194. tabObject.hitAt=0;
  195. }else if(!tabObject.hitAt){
  196. return;
  197. }else if(Date.now()-tabObject.hitAt>30*1000){
  198. tabObject.timeline.length=0;
  199. tabObject.hitAt=0;
  200. GM_saveTab(tabObject);
  201. return;
  202. }
  203.  
  204. // either isGoogleSearchPage or hitTime < 30s
  205.  
  206. tabObject.timeline.push({type:'start', pageHref:purify(location.href), timeAt:Date.now() , histCount: history.length});
  207.  
  208. let resCheckSpam = checkSpam(tabObject);
  209. if(resCheckSpam){
  210. let {filteredTimeline, jhc}=resCheckSpam;
  211. tabObject.hitAt=-1;
  212. GM_saveTab(tabObject);
  213.  
  214. let clickEntry = filteredTimeline[0]
  215. // let entryZ = filteredTimeline[filteredTimeline.length-1];
  216.  
  217. ;(async ()=>{
  218. try{
  219. let spamDomains = (await GM.getValue('spamDomains', "")).split(',');
  220.  
  221. // Note awaiting the set -- required so the next get sees this set.
  222.  
  223. await GM.setValue('spamDomains', spamDomains+','+ new URL(clickEntry.clickedHref).origin);
  224.  
  225. }catch(e){}
  226. })();
  227.  
  228. alert(`Spam Website Detected.\n - Spam Domain: "${getOrigin(clickEntry.clickedHref)}"\n - Go back: "${getOrigin(clickEntry.pageHref)}"`);
  229. history.go(-jhc);
  230. }else{
  231. GM_saveTab(tabObject);
  232. }
  233.  
  234.  
  235.  
  236. function addStyle (styleText) {
  237. const styleNode = document.createElement('style');
  238. styleNode.type = 'text/css';
  239. styleNode.textContent = styleText;
  240. document.documentElement.appendChild(styleNode);
  241. return styleNode;
  242. }
  243.  
  244.  
  245. function onReady(){
  246.  
  247. let emc = elementCount(document);
  248. if(emc<40){ // including html, body, script, div, ... //7 16 33
  249. emc-=document.querySelectorAll('script, style, meta, title, head, link, noscript').length; //remove count for non-visual elements //6 12
  250. }
  251. tabObject.timeline.push({type:'complete', pageHref:purify(location.href), timeAt:Date.now(), elementCount: emc , histCount: history.length });
  252. GM_saveTab(tabObject);
  253.  
  254. let hElems = null
  255. if(isGoogleSearchPage){
  256. hElems = [...document.querySelectorAll('a[href^="http"]')].filter(elm=>!/^https?\:\/\/\w+\.(google|googleusercontent)\.com(\.\w{0,2}){0,2}\//.test(elm.href))
  257. for(const hElem of hElems){
  258. hElem.addEventListener('click',function(){
  259. tabObject.hitAt = Date.now();
  260. tabObject.timeline.push({type:'click', clickedHref:purify(hElem.href), pageHref:purify(location.href), timeAt:tabObject.hitAt , histCount: history.length});
  261. GM_saveTab(tabObject);
  262. })
  263. }
  264.  
  265. ;(async () => {
  266.  
  267. let spamDomains = (await GM.getValue('spamDomains', "")).split(',');
  268. console.log(spamDomains)
  269. let spamLinks = hElems.filter( elm=> spamDomains.includes(new URL(elm.href).origin) )
  270.  
  271. if(spamLinks.length>0){
  272.  
  273. let noClickFunc=function(evt){if(evt){evt.returnValue = false; evt.preventDefault();} return false;}
  274. for(const spamLink of spamLinks) {
  275. spamLink.classList.add('gs-spam-link-disable');
  276. spamLink.onclick=noClickFunc
  277. }
  278. addStyle (cssTxt);
  279.  
  280. }
  281.  
  282. })();
  283.  
  284. async function clearSpamDomainList(){
  285. await GM.deleteValue('spamDomains');
  286. for(const spamLink of hElems) {
  287. spamLink.classList.remove('gs-spam-link-disable');
  288. spamLink.onclick=null
  289. }
  290. }
  291. GM_registerMenuCommand('Clear Spam Domain List', clearSpamDomainList);
  292.  
  293. }else{
  294.  
  295. function fx(){
  296. document.removeEventListener('mousedown',fx,true);
  297. document.removeEventListener('keydown',fx,true);
  298. tabObject.timeline.length=0;
  299. tabObject.hitAt=0;
  300. GM_saveTab(tabObject);
  301. }
  302.  
  303. document.addEventListener('mousedown',fx,true);
  304. document.addEventListener('keydown',fx,true);
  305. }
  306.  
  307. }
  308.  
  309. ;(function uiMain(){
  310. if(document.documentElement == null) return window.requestAnimationFrame(uiMain)
  311. if (document.readyState !== 'loading') {
  312. onReady();
  313. } else {
  314. document.addEventListener('DOMContentLoaded', onReady);
  315. }
  316. })();
  317.  
  318. })
  319. })();