// ==UserScript==
// @name No Spam Results in Google Search
// @namespace http://tampermonkey.net/
// @version 0.8
// @description No more spam results in your google search!
// @author CY Fung
// @match https://www.google.com/search?*
// @match https://www.google.*.*/search?*
// @match http://*/*
// @match https://*/*
// @icon https://greasyfork.org/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBeGswQVE9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--d840d20219ec465d39691a24251f7affa09443a5/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2RkhKbGMybDZaVjkwYjE5c2FXMXBkRnNIYVFISWFRSEkiLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--e4f27e4605e5535222e2c2f9dcbe36f4bd1deb29/nospam.png
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.deleteValue
// @grant GM_getTab
// @grant GM_saveTab
// @grant GM_registerMenuCommand
// @license MIT
// @noframes
// ==/UserScript==
'use strict';
const cssTxt = `
a[href^="http"].gs-spam-link-disable, a[href^="http"].gs-spam-link-disable>h3 {
text-decoration: initial;
color: #d88a8a;
background: #605766;
cursor: no-drop;
}
`;
(function () {
'use strict';
const [window, document, hostname, location, history] = new Function('return [window, document, location.hostname, location, history];')(); // real window & document object
let isGoogleSearchPage = hostname.includes('.google')?/\w+\.(google|googleusercontent)\.com(\.\w{0,2}){0,2}/.test(hostname):false;
function getOrigin(url){
let nt=null;
url = `${url}`;
try{
nt=new URL(url);
}catch(e){
return url;
}
return `${nt.origin}`;
}
function purify(url){
let nt=null;
url = `${url}`;
try{
nt=new URL(url);
}catch(e){
return url;
}
return `${nt.origin}${nt.pathname}`;
}
function elementCount(document){
if(!document) return null;
try{
return document.all.length;
}catch(e){
return document.getElementsByTagName("*").length;
}
}
function consoleJSON(object){
try{
console.table(JSON.stringify(object,null,2))
}catch(e){
console.log('consoleJSON is not supported');
}
}
function getTopDomain(str){
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);
if(m) return m[1];
return str;
}
function checkSpam(tabObject){
if( tabObject.hitAt>1 && Date.now()- tabObject.hitAt<30*1000){
let filteredTimeline = tabObject.timeline.filter(entry=>entry.timeAt>=tabObject.hitAt);
consoleJSON(filteredTimeline);
let [entryA, entryB] = [filteredTimeline[0],filteredTimeline[1]]
if(filteredTimeline.length<10 && filteredTimeline.length>=2 && entryA.type=='click' && entryB.type=='start'){
let networkTime = entryB.timeAt - entryA.timeAt;
let [entryZ, entryY] = [filteredTimeline[filteredTimeline.length-1],filteredTimeline[filteredTimeline.length-2]]
function noPageContentGenerated(){
// not reliable.
return filteredTimeline.filter(entry=>entry.type=='complete').every(entry=>entry.elementCount<20);
}
function noRenderOfActualPage(){
// check whether there is any rendering of actual page
let renderOK= true;
filteredTimeline.reduce((a,b)=>{
if(a && b && a.type=='start' && b.type=='complete' && a.pageHref === b.pageHref && b.timeAt-a.timeAt>5000){
// assume >5000 rendering time is not spam page
renderOK = false;
}
return b;
},null);
return renderOK;
}
function jsHistCounter(){
if(entryA.type=='click' && entryB.type=='start' && entryA.histCount >= entryB.histCount) entryA.histCount = entryB.histCount-1;
let jsHistCountInOrder = true;
let endHist = -1;
filteredTimeline.map(entry=>entry.histCount).reduce((a,b)=>{
if(a>-1 && b>-1 && a<=b){
endHist = b
}else{
jsHistCountInOrder=false;
}
return b;
},0);
if(jsHistCountInOrder){
return endHist - entryA.histCount;
}
return 0;
}
function countDomains(filteredTimeline){
let domains = {}
for(const entry of filteredTimeline){
if(entry.type==='click') continue;
let domain = getTopDomain(new URL(entry.pageHref).hostname)
domains[domain]=(domains[domain]||0)+1
}
domains._arr=Object.keys(domains)
domains._count=domains._arr.length
return domains;
}
let redirectFlag = false;
if(entryA.type=='click' && entryB.type=='start' && getTopDomain(entryA.clickedHref) !== getTopDomain(entryB.pageHref) ){
// server side redirect
redirectFlag = true;
}else if(entryZ.type=='start' && (entryY.type=='complete' || entryY.type=='start')){
let [domainZ, domainY] =[ getTopDomain(new URL(entryZ.pageHref).hostname), getTopDomain(new URL(entryY.pageHref).hostname)]
if(domainZ!==domainY && entryZ.timeAt-entryY.timeAt<=1.25*networkTime && entryZ.timeAt-entryB.timeAt<=3*networkTime){
redirectFlag = true;
}
}
consoleJSON(filteredTimeline);
function startCount(){
return filteredTimeline.filter(entry=>entry.type=='start').length
}
if(redirectFlag || (noPageContentGenerated() && noRenderOfActualPage() && startCount()>=2)){
let jhc = jsHistCounter();
if(jhc>0){
// in case redirect on the same domain
// it is allowed
let domains = countDomains(filteredTimeline)
if(domains._count===1) return;
return {filteredTimeline, jhc};
}
}
}
}
}
GM_getTab(tabObject=>{
if(!tabObject.timeline)tabObject.timeline=[];
if(isGoogleSearchPage){
if(tabObject.hitAt<0){
history.pushState({},null);
}
tabObject.timeline.length=0;
tabObject.hitAt=0;
}else if(!tabObject.hitAt){
return;
}else if(Date.now()-tabObject.hitAt>30*1000){
tabObject.timeline.length=0;
tabObject.hitAt=0;
GM_saveTab(tabObject);
return;
}
// either isGoogleSearchPage or hitTime < 30s
tabObject.timeline.push({type:'start', pageHref:purify(location.href), timeAt:Date.now() , histCount: history.length});
let resCheckSpam = checkSpam(tabObject);
if(resCheckSpam){
let {filteredTimeline, jhc}=resCheckSpam;
tabObject.hitAt=-1;
GM_saveTab(tabObject);
let clickEntry = filteredTimeline[0]
// let entryZ = filteredTimeline[filteredTimeline.length-1];
;(async ()=>{
try{
let spamDomains = (await GM.getValue('spamDomains', "")).split(',');
// Note awaiting the set -- required so the next get sees this set.
await GM.setValue('spamDomains', spamDomains+','+ new URL(clickEntry.clickedHref).origin);
}catch(e){}
})();
alert(`Spam Website Detected.\n - Spam Domain: "${getOrigin(clickEntry.clickedHref)}"\n - Go back: "${getOrigin(clickEntry.pageHref)}"`);
history.go(-jhc);
}else{
GM_saveTab(tabObject);
}
function addStyle (styleText) {
const styleNode = document.createElement('style');
styleNode.type = 'text/css';
styleNode.textContent = styleText;
document.documentElement.appendChild(styleNode);
return styleNode;
}
function onReady(){
let emc = elementCount(document);
if(emc<40){ // including html, body, script, div, ... //7 16 33
emc-=document.querySelectorAll('script, style, meta, title, head, link, noscript').length; //remove count for non-visual elements //6 12
}
tabObject.timeline.push({type:'complete', pageHref:purify(location.href), timeAt:Date.now(), elementCount: emc , histCount: history.length });
GM_saveTab(tabObject);
let hElems = null
if(isGoogleSearchPage){
hElems = [...document.querySelectorAll('a[href^="http"]')].filter(elm=>!/^https?\:\/\/\w+\.(google|googleusercontent)\.com(\.\w{0,2}){0,2}\//.test(elm.href))
for(const hElem of hElems){
hElem.addEventListener('click',function(){
tabObject.hitAt = Date.now();
tabObject.timeline.push({type:'click', clickedHref:purify(hElem.href), pageHref:purify(location.href), timeAt:tabObject.hitAt , histCount: history.length});
GM_saveTab(tabObject);
})
}
;(async () => {
let spamDomains = (await GM.getValue('spamDomains', "")).split(',');
console.log(spamDomains)
let spamLinks = hElems.filter( elm=> spamDomains.includes(new URL(elm.href).origin) )
if(spamLinks.length>0){
let noClickFunc=function(evt){if(evt){evt.returnValue = false; evt.preventDefault();} return false;}
for(const spamLink of spamLinks) {
spamLink.classList.add('gs-spam-link-disable');
spamLink.onclick=noClickFunc
}
addStyle (cssTxt);
}
})();
async function clearSpamDomainList(){
await GM.deleteValue('spamDomains');
for(const spamLink of hElems) {
spamLink.classList.remove('gs-spam-link-disable');
spamLink.onclick=null
}
}
GM_registerMenuCommand('Clear Spam Domain List', clearSpamDomainList);
}else{
function fx(){
document.removeEventListener('mousedown',fx,true);
document.removeEventListener('keydown',fx,true);
tabObject.timeline.length=0;
tabObject.hitAt=0;
GM_saveTab(tabObject);
}
document.addEventListener('mousedown',fx,true);
document.addEventListener('keydown',fx,true);
}
}
;(function uiMain(){
if(document.documentElement == null) return window.requestAnimationFrame(uiMain)
if (document.readyState !== 'loading') {
onReady();
} else {
document.addEventListener('DOMContentLoaded', onReady);
}
})();
})
})();