NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Google SERP Keyword Highlighter // @author Jefferson "jscher2000" Scher // @namespace JeffersonScher // @copyright Copyright 2016 Jefferson Scher // @license BSD 3-clause // @include http*://www.google.*/* // @include http*://encrypted.google.*/* // @include http*://yandex.ru/* // @include http*://nigma.ru/* // @include http*://www.nigma.ru/* // @description Highlight your query terms on the Google, Yandex.ru and Nigma.ru results page. BETA v0.7.1 2016-12-11 // @version 0.7.1 // @grant none // @updateURL https://openuserjs.org/meta/jscher2000/Google_SERP_Keyword_Highlighter.meta.js // ==/UserScript== /* Contains code from Text Highlight and Seek by erosman and Jefferson "jscher2000" Scher DISCLAIMER: Use at your own risk. Functionality and harmlessness cannot be guaranteed. */ (function() { // anonymous function wrapper, used for error checking & limiting scope 'use strict'; // highlight colors -- up to 25 keywords -- INITIALLY SET TO YELLOW ONLY var yellowonly = true; // override all highlight colors to bright yellow var backcolors = ["rgb(255,255,128)", "rgb(255,128,255)", "rgb(128,255,255)", "rgb(255,128,128)", "rgb(128,255,128)", "rgb(128,128,255)", "rgb(128,255,0)", "rgb(255,128,0)", "rgb(0,128,255)", "rgb(0,255,128)", "rgb(255,0,128)", "rgb(128,0,255)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,255,0)", "rgb(255,0,0)"]; if (yellowonly){ for (var y=0; y<backcolors.length; y++) backcolors[y] = "rgb(255,255,0)"; } // special query features var parsephrases = true; // when quoted, only highlight "potato chips" as a phrase, not potato and chips separately var hyphenoptions = true; // for user-script: match user-script, userscript, and user script // TODO: stemming, Google modified your query, many other special cases... // do not run in any framed pages in this version var hlframe = "none"; if ((window.self !== window.top) && (hlframe != "any")) { // framed page if (hlframe == "none") return; if (hlframe == "same") { console.log(window.self.location.hostname + " vs " + window.top.location.hostname); } } // store query function getQuery(){ // Extract query terms, starting from end of results page URL var qa = window.location.href.substr(window.location.href.indexOf("?")+1).split("&"); for (var j=qa.length-1; j>=0; j--){ if (qa[j].split("=")[0] == "q" && location.hostname.indexOf("google") > -1){ // usually the initial query return decodeURIComponent(qa[j].substr(2)).trim(); } if (qa[j].indexOf("#q=") > -1 && location.hostname.indexOf("google") > -1){ // usually a subsequent query return decodeURIComponent(qa[j].substr(qa[j].indexOf("#q=")+3)).trim(); } if (qa[j].split("=")[0] == "text" && location.hostname == "yandex.ru"){ // Yandex return decodeURIComponent(qa[j].split("=")[1]).trim(); } if (qa[j].split("=")[0] == "s" && location.hostname.indexOf("nigma.ru") > -1){ // Nigma return decodeURIComponent(qa[j].split("=")[1]).trim(); } } } var currentquery = getQuery(), lastquery = currentquery; function parseQuery(qry){ if (parsephrases === false || qry.indexOf('"') < 0){ // highlight individual words var arrayTemp = qry.replace(/"/g, "").replace(/\+{1,}/g, " ").trim().split(" "); if (hyphenoptions){ for (var i=0; i<arrayTemp.length; i++){ if (arrayTemp[i].indexOf("-") > 0){ arrayTemp[i] += "|" + arrayTemp[i].replace("-", " ") + "|" + arrayTemp[i].replace("-", ""); } } } } else { // preserve phrases var qrysegments = qry.split('"'), phraserun = false, kwstring = "", kwtemp; for (var k=0; k<qrysegments.length; k++){ if (qrysegments[k].length > 0){ if (phraserun){ kwstring += "\\\\" + qrysegments[k].replace(/\+{1,}/g, " ").trim(); } else { kwtemp = qrysegments[k].replace(/\+{1,}/g, " ").trim().split(" "); if (hyphenoptions){ for (var i=0; i<kwtemp.length; i++){ if (kwtemp[i].indexOf("-") > 0){ kwtemp[i] += "|" + kwtemp[i].replace("-", " ") + "|" + kwtemp[i].replace("-", ""); } } } kwstring += "\\\\" + kwtemp.join("\\\\"); } } if (phraserun === true) phraserun = false; else phraserun = true; } var arrayTemp = kwstring.substr(2).split("\\\\"); } /* This should never be necessary and may not really make it much more efficient... for (i=arrayTemp.length; i>0; i--){ if(arrayTemp[i-1].substr(0,1) == '-'){ // this is an exclusion so remove from the set arrayTemp.splice(i-1, 1); } } */ return arrayTemp; } var qTerms = parseQuery(currentquery); // Main workhorse routine function THmo_doHighlight(el){ // Update query parsing if necessary currentquery = getQuery(); if (currentquery.length === 0) return; if (currentquery != lastquery){ lastquery = currentquery; qTerms = parseQuery(currentquery); } // Apply highlighting for (var j = 0; j < qTerms.length; ++j) { if (j >= 25) break; // not enough highlight colors! var hlkeywords = qTerms[j]; var rQuantifiers = /[-\/\\^$*+?.()[\]{}]/g; hlkeywords = hlkeywords.replace(rQuantifiers, '\\$&'); var pat = new RegExp('(' + hlkeywords + ')', 'gi'); var span = document.createElement('thdfrag'); span.setAttribute("thdcontain","true"); // getting all text nodes with a few exceptions var snapElements = document.evaluate( './/text()[normalize-space() != "" ' + 'and not(ancestor::style) ' + 'and not(ancestor::script) ' + 'and not(ancestor::textarea) ' + 'and not(ancestor::pre) ' + 'and not(ancestor::code) ' + 'and not(ancestor::div[@id="extrares"]) ' + 'and not(parent::thdfrag[@gskh])]', el, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); if (!snapElements.snapshotItem(0)) { break; } for (var i = 0, len = snapElements.snapshotLength; i < len; i++) { var node = snapElements.snapshotItem(i); // check if it contains the keywords if (pat.test(node.nodeValue)) { // create an element, replace the text node with an element var sp = span.cloneNode(true); sp.innerHTML = node.nodeValue.replace(/</g, '<').replace(/>/g, '>').replace(pat, '<thdfrag gskh="' + j + '" style="background-color:'+backcolors[j]+'">$1</thdfrag>'); node.parentNode.replaceChild(sp, node); // try to un-nest containers if (sp.parentNode.hasAttribute("thdcontain")) sp.outerHTML = sp.innerHTML; } } } } // first run if (document.getElementById('search')) THmo_doHighlight(document.getElementById('search')); else THmo_doHighlight(document.body); // Add MutationObserver to catch content added dynamically var THmo_MutOb = (window.MutationObserver) ? window.MutationObserver : window.WebKitMutationObserver; if (THmo_MutOb){ var THmo_chgMon = new THmo_MutOb(function(mutationSet){ mutationSet.forEach(function(mutation){ for (var i=0; i<mutation.addedNodes.length; i++){ if (mutation.addedNodes[i].nodeType == 1){ THmo_doHighlight(mutation.addedNodes[i]); } } }); }); // attach chgMon to document.body var opts = {childList: true, subtree: true}; THmo_chgMon.observe(document.body, opts); } })(); // end of anonymous function