Skip to content

Instantly share code, notes, and snippets.

@ChrisMoney
Created February 15, 2012 15:11
Show Gist options
  • Select an option

  • Save ChrisMoney/1836534 to your computer and use it in GitHub Desktop.

Select an option

Save ChrisMoney/1836534 to your computer and use it in GitHub Desktop.
Javascript -Focus on the user script that returns google results without google+ data
(function(){
if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) {
alert(
"Unfortunately, this tool does not currently work in Internet Explorer. " +
"Please try another browser such as Firefox, Chrome, or Safari.");
return;
}
var anchor = document.getElementsByTagName('script')[0];
if (!anchor) { return; }
var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = '//focusontheuser.org/dontbeevil/style.css?3';
anchor.parentNode.insertBefore(link, anchor);
var script = document.createElement('script');
script.src = '//ajax.googleapis.com/ajax/libs/mootools/1.4.1/mootools-yui-compressed.js';
script.onload = main;
anchor.parentNode.insertBefore(script, anchor);
// Main Application
function main() {
// A custom pseudo selector for finding highlighted typeahead entries.
Slick.definePseudo('highlighted', function() {
return this.getStyle('background-color') != 'transparent';
});
// Debounce to prevent updatePage from being called too often.
var debouncedUpdatePage = debounce(updatePage);
// The main results div will serve as our flag. Store a reference to this
// node, and get it by id in a loop. Update the page whenever it changes.
var getFlag = function() { return $('rso'); };
var oldFlag = getFlag();
var compare = function() {
var newFlag = getFlag();
if (newFlag != oldFlag) {
oldFlag = newFlag;
debouncedUpdatePage();
}
};
setInterval(compare, 100);
setupTypeahead();
updatePage();
}
function updatePage() {
updateTypeahead();
updateRelatedBox();
updateResultStats();
updateContentLinks();
updateRelatedLinks();
}
// When the user searchs for a Twitter username including the leading @,
// redirect them directly to Twitter if their search shows up on the page.
function redirectIfTwitterUsername() {
var input = $$('input[name=q]')[0];
if (input && (/^@[A-Za-z0-9_]+$/).test(input.value)) {
var username = input.get('value').substr(1).toLowerCase();
$$('li.g div.s div.f cite').some(function(item) {
var path = item.get('text').toLowerCase();
if (path == ("twitter.com/" + username) ||
path == ("twitter.com/#!/" + username) ||
path == ("twitter.com/#!/@" + username)) {
document.location = "https://twitter.com/" + username;
}
});
}
}
// Attach some event listeners to the main text input, so we can navigate when
// the user uses enter to select a result from the typeahead.
function setupTypeahead() {
var href;
var input = $('gbqfq') || $$('input[name=q]')[0];
if (input) {
input.addEvents({
keyup: function() {
var span = $$('body > table tr:highlighted span:contains(".com")')[0];
href = span && span.retrieve('href') || '';
},
keydown: function(event) {
if (event.key != 'enter') { return; }
if (href) {
window.open(href);
event.stop();
} else {
redirectIfTwitterUsername();
}
}
});
}
}
// Look for any typeahead entries that contain '.com' in a span, since this is
// one differentiating feature of the Google+ entries. For each result, perform
// a full search for the text of that entry, and 'replace' the Google+ results
// with the highest ranked result as returned by the full search.
function updateTypeahead() {
var spans = $$('body > table tr span:contains(".com")');
spans.each(function(span) {
var row = span.getParent('tr');
var cell = row.getLast('td');
var span = cell.getLast('span');
// Some entries might contain .com, but not be the double height G+ entries.
if (!span) { return; }
var name = cell.getFirst('span').get('text');
getSearchResult(name, function(result) {
if (!result) { return; }
var img = row.getElement('img');
var item = row.getParent('tr');
// Attach a click listener, which will be used to navigate on select, to
// each entry in the typeahead. Results are reused, so only attach once.
if (item && !item.retrieve('attached')) {
item.store('attached', true);
item.addEvent('click', function(event) {
window.open(span.retrieve('href'));
});
}
// If we have a photo for this item, update the typeahead entry, otherwise
// memoize the existing photo in our result object for later use.
result.photo ? (img.src = result.photo) : (result.photo = img.src);
// Update the site that's displayed.
span.set('text', ' \xb7 ' + result.site);
// Store a reference to the href needed for navigation (used with 'enter').
span.store('href', result.href);
});
});
}
// This tweaks the text in the right hand column to say "People and Pages from
// the Social Web", rather than "People and Pages on Google+". The column gets
// completely replaced on every search, so we need to run this every time.
function updateRelatedBox() {
var fieldset = $('aerhs');
if (fieldset) {
var legend = fieldset.getFirst('legend');
legend && legend.set('text', 'People and Pages from the Social Web');
}
}
// Show a little blurb with a link in the result stats area. This also needs to
// be updated along with every query.
function updateResultStats() {
var stats = $$('#appbar :contains("seconds")').pop();
if (stats) {
// Probably only need to do this once, but it doesn't hurt.
stats.setStyle('overflow', 'visible');
stats.getParent('div').setStyle('overflow', 'visible');
stats.adopt([
new Element('span').adopt([
document.createTextNode('\xb7 '),
new Element('span', {'class': 'dbe_stats'}).adopt([
document.createTextNode('results improved by '),
new Element('a', {
href: 'http://www.focusontheuser.org',
text: 'FocusOnTheUser.org'
})
])
])
]);
}
}
// Find the special Google+ links attached directly to search results in the
// middle column, and replace them with the most relevant social result.
function updateContentLinks() {
var link = $$('#rso a[href*="plus.google.com"].l')[0];
if (!link) { return; }
var text = link.get('text').replace(' - Google+', '');
if (!text) { return; }
var item = link.getParent('li.g');
if (!item) { return; }
getSearchResult(text, function(result) {
if (!result) { return; }
// Memoize the node, and apply the style from the item we're replacing.
if (!result.plusNode) {
result.plusNode = result.node.set('style', item.get('style')).adopt(result.links);
}
// Plus links are indented.. this seems to be a pretty simple way to make
// sure we only replace the correct links.
if (parseInt(item.getStyle('margin-left'), 10) > 0) {
// Oh, why not use a little superfluous animation.
item.get('morph').start({opacity: [1, 0]}).chain(function() {
var morph = result.plusNode.get('morph').set({opacity: 0});
result.plusNode.replaces(item).highlight('#fea');
morph.start({opacity: [0, 1]});
});
}
});
}
// Find the Google+ links in the right hand column and replace them with the
// most relevant social results, including all links to social sites that show
// up in the top 100 results, ranked by Google.
function updateRelatedLinks() {
var links = $$('#rhs_block a[href*="profiles.google.com"]');
links.each(function(link) {
var text = (link.get('text') || '').replace(/\.com$/, '');
if (!text) { return; }
var item = link.getParent('table');
if (!item) { return; }
getSearchResult(text, function(result) {
if (!result) { return; }
if (!result.profileBlock) {
result.profileBlock = (result.site != 'plus.google.com') ?
buildProfileBlockMarkup(result, item.getElement('img').get('src')) :
item.clone();
}
item.get('morph').start({opacity: [1, 0]}).chain(function() {
var div = new Element('div', {'class': 'dbe_results'});
var morph = div.get('morph').set({opacity: 0});
div.adopt([result.profileBlock, result.links]).replaces(item).highlight('#fea');
morph.start({opacity: [0, 1]});
});
})
});
}
// For the most part, we're just going to use the markup as returned to us by
// Google's servers, and inject it into the various places on the page. We do
// want to modify it a little bit, adding photos, like / tweet buttons, etc,
// and then we'll memoize the node for later use.
function buildProfileBlockMarkup(result, defaultPhoto) {
var node = result.node;
var site = result.site;
var text = node.getElement('h3');
var link = text.getElement('a');
var href = link.get('href');
link.set('title', link.get('text')).set('text', result.text);
// Remove the 'Person Shared This' box.
var div = text.getNext().getNext();
div && div.dispose();
var src = '', html = '', span;
if (site == 'facebook.com') {
// The Facebook like button is dumb, and even shows up for profiles,
// where subscribe should be the default action - override for zuck.
var buttonType = href.indexOf('/zuck') > 0 ? 'subscribe' : 'like';
src = '//connect.facebook.net/en_US/all.js#xfbml=1';
html = '<div href="' + href + '" class="fb-' + buttonType + '" data-show-faces="false" data-layout="button_count" data-width="120" data-send="false"></div>';
} else if (site == 'twitter.com') {
src = '//platform.twitter.com/widgets.js';
html = '<a href="' + href + '" class="twitter-follow-button" data-show-screen-name="false"></a>';
// Remove the repetitive twitter default sign up text.
span = node.getElement('span:contains("Sign up for Twitter")');
span && span.set('text', span.get('text').replace(/(\s+|^)Sign up for Twitter[\s\w\-]+\(@\w+\)\.\s+/, ''));
} else if (site == 'linkedin.com') {
span = node.getElement('span:contains("professional profile on LinkedIn")');
if (span) {
var blurb = span.getPrevious();
span.set('text', blurb.get('text'));
blurb.dispose();
}
}
if (src) {
var script = new Element('script', {src: src, onload: function() { script.dispose(); }}).inject(anchor, 'top');
new Element('div', {'class': 'dbe_button', html: html}).inject(text, 'bottom');
}
new Element('a', {href: href})
.adopt(new Element('img', {src: result.photo || defaultPhoto, 'class': 'dbe_img'}))
.inject(text.getNext(), 'top');
return new Element('ul', {'class': 'dbe_first'}).adopt(node);
}
// This function builds the little div of &middot separated links that shows
// up below the most relevant search result in the middle and right columns.
function buildLinksDiv(results) {
var nodes = [];
results.each(function(result, index) {
nodes.push(new Element('a', {
href: result.href,
text: siteMap[result.site].name,
title: link.get('text')
}));
if (index < results.length - 1) {
nodes.push(document.createTextNode(' \xb7 '));
}
});
return new Element('div', {'class': 'dbe_extra'}).adopt(nodes);
}
// Functions for fetching data
// Given a query string and a callback, this function will perform an
// asynchronous Google search for the top 100 results, parse the response into
// HTMLLIElement DOM nodes, filter that set down to only meaningful socially
// relevent results, fetch photos for the linked profiles of those results,
// and invoke the callback provided with a sorted list of result objects.
var getSearchResult = (function() {
// Figure out where the search result passed in points to, by looking at the
// first anchor element inside the node.
var getHrefFromNode = function(node) {
var link = $(node).getElement('a');
return link ? link.get('href') : '';
}
// Retrieve a photo for the provided result object, based either on a
// specially crafted Google image query (for Twitter), or on a query to
// Google's own richsnippets web tool.
var getPhoto = function(result, callback) {
if (!siteMap[result.site].photo) {
return callback();
}
var href = result.href;
var url = '//www.google.com/';
if (result.site == 'twitter.com') {
// Get Twitter profile pic by google image searching for the twitter
// profile's url, e.g. "site:twitter.com/britneyspears", and then
// extracting the first result. This seems to usually get the right thing,
// though often the wrong size.
url += 'search?gbv=2&tbm=isch&fp=1&biw=1&bih=1&tch=1&q=' + encodeURIComponent('site:' + href);
xhr(url, function(text) {
var matches = unescape(text).match(/imgurl\\=(https?:\/\/[^.]+\.twimg\.com\/profile_images\/\d+\/[^\\&]+)/);
var match = matches ? matches[1].replace(/_(?:bigger|mini|normal|reasonably_small)/, '').replace(/\.jpg$/, '_normal.jpg') : '';
callback(match);
});
} else {
url += 'webmasters/tools/richsnippets?view=cse&url=' + encodeURIComponent(href);
xhr(url, function(text) {
var matches = text.match(/(?:image|photo)\s*=\s*(.+?)\s*<br\s*\/>/m);
var match = matches ? matches[1] : '';
callback(match);
});
}
};
// Out of all the results in the result set, find the socially meaningful
// ones, discarding all the rest. This function is used to return a sorted
// list of results for each query, and should aggressively filter out
// unrelated results.
var findMeaningfulResults = function(text, results) {
var hrefs = [];
var sources = [];
var matched = {};
return results.filter(function(result) {
// Filter out any results without an href.
var href = getHrefFromNode(result);
if (!href) { return false; }
// Filter down to only the sites we care about.
var matches = href.match(siteMatcher);
if (!matches) { return false; }
// Make sure we haven't already found a higher ranked site match.
var site = matches[1];
var alreadyMatched = matched[site];
if (alreadyMatched) { return false; }
// If we have a whitelist for this site, make sure it matches.
var whitelist = siteMap[site].whitelist;
if (whitelist && !whitelist.test(href)) { return false; }
// If we have a blacklist for this site, make sure it doesn't match.
var blacklist = siteMap[site].blacklist;
if (blacklist && blacklist.test(href)) { return false; }
// Store at most one match per site, including site name and href.
hrefs.push(href);
sources.push(site);
return (matched[site] = true);
}).map(function(node, index) {
return {
node: node,
text: text,
href: hrefs[index],
site: sources[index]
}
});
};
// This crazy function will parse a Google XHR response, pulling it apart
// into chunks, and turning each of those individual chunks into a DOM node
// that can then be analyzed / inserted into the DOM.
var parseResponseText = function(text) {
var offset = 0;
var openTag = "\\x3c!--m--\\x3e";
var closeTag = "\\x3c!--n--\\x3e";
var tagLength = 14;
var getNextResult = function() {
var result = null;
var open = text.indexOf(openTag, offset);
var close = text.indexOf(closeTag, offset);
var nextOpen = text.indexOf(openTag, open + tagLength);
var nextClose = text.indexOf(closeTag, close + tagLength);
if (nextOpen < close) {
close = nextOpen;
offset = nextClose + tagLength;
} else {
offset = close + tagLength;
}
if (open >= 0 && close >= 0) {
var html = unescape(text.substring(open + tagLength, close));
result = new Element('ul', {html: html}).getFirst();
}
return result;
};
var results = [];
var nextResult = null;
while (nextResult = getNextResult()) {
results.push(nextResult);
}
return results;
};
var cache = {};
return function(query, callback) {
if (query in cache) {
return callback(cache[query]);
}
var url = '//www.google.com/search?q=' + encodeURIComponent(query) + '&xhr=t&fp=1&psi=1&num=100&complete=0&pws=0';
xhr(url, function(text) {
var results = findMeaningfulResults(query, parseResponseText(text));
var result = results[0];
if (result) {
result.links = buildLinksDiv(results.slice(1));
getPhoto(result, function(photo) {
result.photo = photo || '';
cache[query] = result;
callback(result);
});
}
});
};
})();
// Generic Utilities
function debounce(func) {
var timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(func, 500);
}
}
var unescape = (function() {
var regex = /\\x22|\\x26|\\x27|\\x3c|\\x3d|\\x3e/g;
var callback = function(match) {
switch (match) {
case "\\x22": return '"';
case "\\x26": return "&";
case "\\x27": return "'";
case "\\x3c": return "<";
case "\\x3d": return "=";
case "\\x3e": return ">";
}
};
return function(str) {
return str.replace(regex, callback);
};
})();
function xhr(url, callback) {
var req = google.xhr();
req.open("GET", url, true);
req.onreadystatechange = function(evt) {
if (req.readyState === 4) {
callback(req.responseText);
}
};
req.send(null);
}
// Sites
var siteMap = {
'crunchbase.com': {
name: 'CrunchBase',
whitelist: /(?:^|[\/.])crunchbase\.com\/(person|company)\/(\w+)\/?$/i
},
'facebook.com': {
name: 'Facebook',
photo: true,
whitelist: /(?:^|[\/.])facebook\.com\/([a-zA-Z0-9.]+)\/?$/i
},
'flickr.com': {
name: 'Flickr',
whitelist: /(?:^|[\/.])flickr\.com\/people\/(\w+)\/?$/i
},
'foursquare.com': {
name: 'Foursquare',
whitelist: /(?:^|[\/.])foursquare\.com\/(\w+)\/?$/i
},
'friendfeed.com': {
name: 'FriendFeed',
whitelist: /(?:^|[\/.])friendfeed\.com\/(\w+)\/?$/i
},
'github.com': {
name: 'GitHub',
whitelist: /(?:^|[\/.])github\.com\/(\w+)\/?$/i
},
'linkedin.com': {
name: 'LinkedIn',
photo: true,
whitelist: /(?:^|[\/.])linkedin\.com\/in\/(\w+)\/?$/i
},
'myspace.com': {
name: 'MySpace',
photo: true,
whitelist: /(?:^|[\/.])myspace\.com\/(\w+)\/?$/i
},
'plus.google.com': {
name: 'Google+',
whitelist: /(?:^|[\/.])plus\.google\.com\/(\w+)\/?$/i
},
'quora.com': {
name: 'Quora',
photo: true,
whitelist: /(?:^|[\/.])quora\.com\/([\w\-]+)\/?$/i,
blacklist: /\/(who|what|when|where|why|how)\-/i
},
'stackoverflow.com': {
name: 'Stack Overflow',
whitelist: /(?:^|[\/.])stackoverflow\.com\/users\/(\w+)\/?/i
},
'tumblr.com': {
name: 'Tumblr',
whitelist: /(?:^|[\/.])(\w+)\.tumblr\.com\/?$/,
blacklist: /\/tagged\//
},
'twitter.com': {
photo: true,
name: 'Twitter',
whitelist: /(?:^|[\/.])twitter\.com\/(\w+)\/?$/i,
blacklist: /\/statuses\//
},
'youtube.com': {
name: 'YouTube',
whitelist: /(?:^|[\/.])youtube\.com\/user\/(\w+)(?:\/featured)?\/?$/
}
};
var siteMatcher = (function(){
var sites = [];
for (var site in siteMap) { sites.push(site); }
return new RegExp('[\/.](' + sites.join('|').replace('.', '\\.') + ')');
})();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment