Created
August 16, 2024 02:00
-
-
Save MustCodeAl/6b3bb149d1b5d82ea6d6e0a317cf7bb3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Google DevSearch Mode | |
// @description Google search improve for developers. | |
// @version 1.5 | |
// @include *://www.google.*/* | |
// @author yanorei32 (modified by mustcodeal) | |
// @supportURL https://github.com/yanorei32/GSI4D/issues | |
// @website http://yano.teamfruit.net/~rei/ | |
// @namespace http://yano.teamfruit.net/~rei/ | |
// @license MIT License | |
// @run-at document-end | |
// @grant none | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
function debounce(func, wait) { | |
let timeout; | |
return function() { | |
const context = this, args = arguments; | |
const later = function() { | |
timeout = null; | |
func.apply(context, args); | |
}; | |
clearTimeout(timeout); | |
timeout = setTimeout(later, wait); | |
}; | |
} | |
const site = { | |
reference: { | |
isBlackList: false, | |
color: '#0F01', | |
list: [ | |
// Microsoft | |
'windows.com', | |
'msdn.microsoft.com', | |
'docs.microsoft.com', | |
'learn.microsoft.com', | |
// Oracle | |
'docs.oracle.com', | |
// Mozilla | |
'developer.mozilla.org', | |
'cloud.google.com', | |
'developers.google.com', | |
// RedHat | |
'access.redhat.com/documentation', | |
// Docker | |
'hub.docker.com', | |
// GitHub | |
'github.com', | |
// OSDN | |
'osdn.net', | |
// sourceforge | |
'sourceforge.net', | |
// Golang | |
'golang.org', | |
'godoc.org', | |
// Ruby | |
'ruby-lang.org', | |
'ruby-doc.org', | |
'railsdoc.com', | |
'rubygems.org', | |
// Python | |
'postgresql.org', | |
'python.org', | |
'requests-docs-ja.readthedocs.io', | |
'python-requests.org', | |
// Raspberry Pi | |
'raspberrypi.org', | |
// IPA | |
'ipa.go.jp', | |
// w3school.com | |
'w3schools.com', | |
// PHP | |
'php.net', | |
// Apache | |
'apache.org', | |
// Unity 3D | |
'docs.unity3d.com', | |
// Rustlang | |
'doc.rust-lang.org', | |
'rust-lang.org', | |
'docs.rs', | |
'source.chromium.org', | |
'graphql.org', | |
'docs.diesel.rs', | |
'sm.alliedmods.net', | |
// Added links | |
'ultimatewindowssecurity.com', | |
'attack.mitre.org', | |
'vx-underground.org', | |
'gtfobins.github.io', | |
'anti-debug.checkpoint.com', | |
'evasions.checkpoint.com', | |
'wiki.osdev.org', | |
'thestarman.pcministry.com', | |
'corelan.be', | |
'guyinatuxedo.github.io', | |
'fuzzysecurity.com', | |
'malwareunicorn.org', | |
'connormcgarr.github.io', | |
'fumalwareanalysis.blogspot.com', | |
'persistence-info.github.io', | |
'websites.umich.edu', | |
'xdaforums.com', | |
], | |
}, | |
recommend: { | |
isBlacklist: false, | |
color: '#0FF1', | |
list: [ | |
'30secondsofcode.org', | |
'actix.rs', | |
'pypi.org', | |
'andreasbm.github.io', | |
'arewewebyet.org', | |
'arewegameyet.rs', | |
'arewedistributedyet.rs', | |
'arewefastyet.com', | |
'areweguiyet.com', | |
'gist.github.com', | |
'mysqltutorial.org', | |
'awesome-rust.com', | |
'awesome.red-badger.com', | |
'blog.cloudflare.com', | |
'blessed.rs', | |
'blog.logrocket.com', | |
'blog.trailofbits.com', | |
'cbyexample.com', | |
'cheat.sh', | |
'cheatography.com', | |
'cheatsheetseries.owasp.org', | |
'codechalleng.es', | |
'cppcheatsheet.com', | |
'cpppatterns.com', | |
'cppreference.com', | |
'crates.io', | |
'csharp-station.com', | |
'css-tricks.com', | |
'csslayout.io', | |
'ctf101.org', | |
'dev.to', | |
'devdocs.io', | |
'developer.amazon.com', | |
'developer.apple.com', | |
'developer.mozilla.org', | |
'developer.valvesoftware.com', | |
'doc.qt.io', | |
'doc.rust-lang.org', | |
'docs.near.org', | |
'digitalocean.com', | |
'freecodecamp.org', | |
'codeacademy.com', | |
'geeksforgeeks.org', | |
'getfrontend.tips', | |
'golang.org', | |
'grepper.com', | |
'htmq.com', | |
'hacktricks.xyz', | |
'learnxinyminutes.com', | |
'lib.rs', | |
'martin.kleppmann.com', | |
'mui.com', | |
'nodejs.dev', | |
'os.phil-opp.com', | |
'programming-idioms.org', | |
'realpython.com', | |
'refactoring.guru', | |
'roadmap.sh', | |
'rosettacode.org', | |
'rust-lang.github.io', | |
'rust-unofficial.github.io', | |
'rust-lang-nursery.github.io', | |
'rustwasm.github.io', | |
'searchcode.com', | |
'serde.rs', | |
'source.chromium.org', | |
'sourcegraph.com', | |
'sourcemod.net', | |
'systemdesign.one', | |
'tauri.app', | |
'tohoho-web.com', | |
'tokio.rs', | |
'tutorialspoint.com', | |
'wiki.osdev.org', | |
'windowscentral.com', | |
'webassemblyman.com', | |
'composingprograms.com', | |
'learncomputerscienceonline.com', | |
'exploit-db.com', | |
'infoq.com', | |
'yew.rs', | |
'zenrows.com', | |
], | |
}, | |
recommendForum: { | |
isBlacklist: false, | |
color: '#FFFFE0', | |
list: [ | |
'stackexchange.com', | |
'stackoverflow.com', | |
'superuser.com', | |
'teratail.com', | |
'askubuntu.com', | |
'reddit.com', | |
'answers.unity.com', | |
'indiehackers.com', | |
'medium.com', | |
'alliedmods.net', | |
], | |
}, | |
// 任意のユーザーが使えるサービス。比較的良質な物が多い。 | |
publicService: { | |
isBlacklist: false, | |
color: '#FF01', | |
list: [ | |
'qiita.com', | |
], | |
}, | |
blackList: { | |
isBlacklist: true, | |
color: null, | |
list: [ | |
// Wikipedia スクレイピング(?) | |
'wikiwand.com', | |
// 微妙な入門講座が引っかかった事があった。 | |
'employment.en-japan.com', | |
// 誤った情報を大々的に掲載している。 | |
'profession-net.com', | |
'symmetric.co.jp', | |
'mupon.net', | |
'marucoblog.com', | |
'suke-log.com', | |
'proengineer.internous.co.jp', | |
// ADBlockerが有効だとコンテンツを見せない | |
'server-setting.info', | |
// 侍エンジニア。画像が多くて嫌い。 | |
'sejuku.net', | |
// 画像や広告が多くて嫌い。 | |
'techacademy.jp', | |
'programming-study.com', | |
'codecamp.jp', | |
'tadaken3.hatenablog.jp', | |
'udemy.benesse.co.jp', | |
'techplay.jp', | |
'furien.jp', | |
'eng-entrance.com', | |
'unitopi.com', | |
'internetacademy.jp', | |
'kurashi-no.jp', | |
'kokinn.com', | |
// コピペ(翻訳)サイト | |
'bunnyinside.com', | |
'forsenergy.com', | |
'jp.mytory.net', | |
'phpspot.net/php/man/', | |
'code.i-harness.com', | |
'stackovernet.com', | |
'stackoverrun.com', | |
'codeday.me', | |
'code-examples.net', | |
'code-adviser.com', | |
'kotaeta.com', | |
'tutorialmore.com', | |
'living-sun.com', | |
'it-swarm.net', | |
'voidcc.com', | |
'qastack.jp', | |
'coder.work', | |
'it-swarm.dev', | |
'sobrelinux.info', | |
// AdSite | |
'solvusoft.com', | |
'reviversoft.com', | |
'dll-files.com', | |
'softonic.com', | |
'softonic.jp', | |
'systweak.com', | |
'chip.de', | |
'qpdownload.com', | |
'jaleco.com', | |
'findmysoft.com', | |
'akvatoriyal.ru', | |
'89139849001.ru', | |
'ukgorod37.ru', | |
'krasota-olimpia.ru', | |
'korobkaoo.ru', | |
'bkrolik.ru', | |
'hockorder.ru', | |
], | |
}, | |
}; | |
const SearchTypes = { | |
Default: '', | |
Image: 'isch', | |
Video: 'vid', | |
News: 'nws', | |
Shop: 'shop', | |
Books: 'bks', | |
Patent: 'ptd', | |
Unknown: null, | |
}; | |
const deleteElement = (e) => { | |
if (e.parentElement !== null) | |
e.parentElement.removeChild(e); | |
}; | |
const formatGSI4DLog = (log) => { | |
if (!log) { | |
return 'No log to report'; | |
} | |
if (!log.blockedCount && !log.trackedCount) { | |
return 'No blocked or tracked connections'; | |
} | |
return `GSI4D Blocked: ${log.blockedCount} Tracked: ${log.trackedCount}`; | |
}; | |
const pageStyle = { | |
PC: { | |
supportSearchTypes: [ | |
SearchTypes.Default, | |
SearchTypes.Video, | |
SearchTypes.Image, | |
], | |
getLinkElems: (searchType, parentElement) => { | |
let linkElements; | |
if (searchType === SearchTypes.Image) { | |
linkElements = parentElement.querySelectorAll('a.VFACy.kGQAp.sMi44c.lNHeqe'); | |
} else { | |
linkElements = parentElement.querySelectorAll('div.g a:not(.fl):not(.GHDvEf)'); | |
} | |
return linkElements || []; | |
}, | |
coloriseCandidateByLinkElem: (searchType, linkElem, color) => { | |
const isImageSearch = searchType === SearchTypes.Image; | |
const candidateElem = isImageSearch ? linkElem.parentElement : linkElem.parentElement.parentElement.parentElement; | |
if (!candidateElem) { | |
return; | |
} | |
candidateElem.style.backgroundColor = color; | |
}, | |
// Delete the element containing the candidate | |
// link, which is the grandparent of the link. | |
deleteCandidateByLinkElem: (searchType, linkElem) => { | |
switch (searchType) { | |
case SearchTypes.Image: | |
deleteElement(linkElem.parentElement); | |
return; | |
case SearchTypes.Video: | |
deleteElement( | |
linkElem.parentElement.parentElement | |
.parentElement | |
); | |
return; | |
case SearchTypes.Audio: | |
deleteElement( | |
linkElem.parentElement.parentElement | |
.parentElement | |
.parentElement | |
); | |
return; | |
default: | |
return; | |
} | |
}, | |
writeLog: (st, formatedLog) => { | |
if (st === SearchTypes.Image) | |
return; | |
document.getElementById('result-stats').innerHTML += formatedLog; | |
}, | |
after: (st) => { | |
if (st !== SearchTypes.Image) return; | |
const observerOptions = {childList: true, subtree: true}; // Added subtree to observe deeper changes | |
const processMutations = debounce((records, observer) => { | |
for (const record of records) { | |
for (const addedNode of record.addedNodes) { | |
if (addedNode.nodeType == Node.ELEMENT_NODE) { | |
linkProcess(st, addedNode); | |
} | |
} | |
} | |
}, 300); | |
const observer = new MutationObserver(processMutations); | |
// Start observing | |
observer.observe(document.querySelector('.islrc'), observerOptions); | |
}, | |
}, | |
Phone: { | |
supportSearchTypes: [], | |
// Get the links to the results | |
getLinkElems: (searchType, parentElement) => { | |
// If we're searching for images, get the image links | |
if (searchType === SearchTypes.Image) | |
return document.querySelectorAll('.iKjWAf'); | |
// Otherwise, get the web links | |
try { | |
return parentElement.querySelectorAll('div.kCrYT>a'); | |
} catch (e) { | |
return []; | |
} | |
}, | |
// The linkE parameter is the link element that was clicked on. | |
// The color parameter is the color to use to highlight the candidate | |
// link elements. | |
coloriseCandidateByLinkElem: (searchType, linkE, color) => { | |
if (!linkE) { | |
return; | |
} | |
if (searchType === SearchTypes.Image) { | |
linkE.style.backgroundColor = color; | |
return; | |
} | |
linkE.parentElement.parentElement.style.backgroundColor = color; | |
}, | |
deleteCandidateByLinkElem: (searchType, linkElem) => { | |
if (searchType === SearchTypes.Image) { | |
const imageContainer = linkElem.parentElement; | |
if (imageContainer) { | |
deleteElement(imageContainer); | |
} | |
return; | |
} | |
const articleContainer = linkElem.parentElement.parentElement.parentElement; | |
if (articleContainer) { | |
deleteElement(articleContainer); | |
} | |
}, | |
writeLog: (searchType, formattedLog) => { | |
if (searchType === SearchTypes.Image) | |
return; | |
const logCardContainer = document.createElement('div'); | |
const logCard = document.createElement('div'); | |
const logCardSpan = document.createElement('span'); | |
const logCardText = document.createElement('div'); | |
logCardContainer.classList.add('ZINbbc', 'xpd', 'O9g5cc', 'uUPGi', 'gsi4d'); | |
logCard.classList.add('kCrYT'); | |
logCardText.classList.add('BNeawe', 's3v9rd', 'AP7Wnd'); | |
logCardText.textContent = formattedLog; | |
logCardSpan.appendChild(logCardText); | |
logCard.appendChild(logCardSpan); | |
logCardContainer.appendChild(logCard); | |
const mainNode = document.getElementById('main'); | |
if (mainNode && mainNode.childNodes.length > 1) | |
mainNode.childNodes[1].after(logCardContainer); | |
else | |
document.body.appendChild(logCardContainer); | |
}, | |
after: (st) => { | |
if (st !== SearchTypes.Image) return; | |
const observerOptions = {childList: true, subtree: true}; // Added subtree to observe deeper changes | |
const observerCallback = (records, observer) => { | |
// Pause the observer | |
observer.disconnect(); | |
for (const record of records) { | |
if (record.type !== "childList") continue; // Only process childList mutations | |
for (const addedNode of record.addedNodes) { | |
if (addedNode.nodeType == Node.ELEMENT_NODE) { | |
linkProcess(st, addedNode); | |
} | |
} | |
} | |
// Restart the observer after a delay to reduce CPU usage | |
setTimeout(() => { | |
observer.observe(document.querySelector('#islrg'), observerOptions); | |
}, 300); // 300ms delay | |
}; | |
const observer = new MutationObserver(observerCallback); | |
// Start observing | |
observer.observe(document.querySelector('#islrg'), observerOptions); | |
}, | |
}, | |
}; | |
const getPageStyle = () => { | |
if (window.navigator.userAgent.toLowerCase().indexOf('mobile') !== -1) { | |
console.log('PageStyle: Phone'); | |
return pageStyle.Phone; | |
} | |
console.log('PageStyle: PC'); | |
return pageStyle.PC; | |
}; | |
const parseURL = (url) => { | |
if (!url.startsWith('/url?') && url.indexOf('://')) | |
return url; | |
if (!url.startsWith('/url?')) { | |
console.error('Unsupported URL: ' + url); | |
return ''; | |
} | |
const paramStrs = url.split('?')[1].split('&'); | |
for (const paramStr of paramStrs) { | |
const parsedParam = paramStr.split('='); | |
if (parsedParam[0] !== 'q') | |
continue; | |
if (parsedParam.length !== 2) | |
continue; | |
if (parsedParam[1].indexOf('://')) { | |
return parsedParam[1]; | |
} | |
return decodeURI(parsedParam[1]); | |
} | |
console.error('Failed to read URL from /url?: ' + url); | |
return ''; | |
}; | |
const parseSearchURL = (url) => { | |
const parameterPairs = url.split('?')[1].split('&'); | |
for (const parameterPair of parameterPairs) { | |
const parameter = parameterPair.split('='); | |
if (parameter[0] !== 'tbm') | |
continue; | |
if (parameter.length !== 2) | |
continue; | |
for (const [_, searchType] of Object.entries(SearchTypes)) | |
if (parameter[1] === searchType) | |
return searchType; | |
return SearchTypes.Unknown; | |
} | |
return SearchTypes.Default; | |
}; | |
const parseLinkElement = (st, link) => { | |
if (link.tagName === 'DIV') { | |
const parsedJSON = JSON.parse(link.innerHTML); | |
if (parsedJSON['ru'] == undefined) { | |
console.error('Link element has not ru.'); | |
console.error(link); | |
return ''; | |
} | |
return parsedJSON['ru']; | |
} | |
const rawHref = link.getAttribute('href'); | |
if (rawHref === null) { | |
console.error('Link element has not href.'); | |
console.error(link); | |
return ''; | |
} | |
return parseURL(rawHref); | |
}; | |
// Optimization: Use Set for blacklist checks for O(1) inclusion checks | |
const blacklistSet = new Set(site.blackList.list); | |
const linkProcess = (st, parentE) => { | |
const links = p.getLinkElems(st, parentE); | |
// Cache commonly used functions and values | |
const deleteCandidate = p.deleteCandidateByLinkElem; | |
const coloriseCandidate = p.coloriseCandidateByLinkElem; | |
for (const link of links) { | |
const url = parseLinkElement(st, link); | |
if (!url) continue; | |
const urlHostname = new URL(url).hostname; | |
// Optimization: Use Set for O(1) blacklist check | |
if (blacklistSet.has(urlHostname)) { | |
deleteCandidate(st, link); | |
log.blockedCount++; | |
continue; | |
} | |
// Original code to check against non-blacklisted categories | |
let isMatched = false; | |
for (const [category, siteType] of Object.entries(site)) { | |
if (siteType.isBlacklist) continue; // Skip blacklisted category | |
for (const domain of siteType.list) { | |
if (urlHostname === domain || urlHostname.endsWith('.' + domain)) { | |
coloriseCandidate(st, link, siteType.color); | |
log.trackedCount++; | |
isMatched = true; | |
break; // Break out of inner loop | |
} | |
} | |
if (isMatched) break; // Break out of outer loop | |
} | |
} | |
}; | |
const currentST = parseSearchURL(location.href); | |
if (currentST === SearchTypes.Unknown) { | |
console.debug('Unknown search type'); | |
return; | |
} | |
const p = getPageStyle(); | |
if (!p.supportSearchTypes.includes(currentST)) { | |
console.debug('Unsupported search type in this platform.'); | |
return; | |
} | |
const log = { | |
blockedCount: 0, | |
trackedCount: 0, | |
}; | |
linkProcess(currentST, document); | |
p.writeLog(currentST, formatGSI4DLog(log)); | |
p.after(currentST); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment