-
-
Save bijij/58cc8cfc859331e4cf80210528a7b255 to your computer and use it in GitHub Desktop.
// ==UserScript== | |
// @name View Image | |
// @namespace https://github.com/bijij/ViewImage | |
// @version 4.1.1 | |
// @description This userscript re-implements the "View Image" and "Search by image" buttons into google images. | |
// @author Joshua B | |
// @run-at document-end | |
// @include http*://*.google.tld/search*tbm=isch* | |
// @include http*://*.google.tld/imgres* | |
// @updateURL https://gist.githubusercontent.com/bijij/58cc8cfc859331e4cf80210528a7b255/raw/viewimage.user.js | |
// ==/UserScript== | |
'use strict'; | |
const DEBUG = false; | |
const VERSIONS = { | |
FEB18: 'FEB18', | |
JUL19: 'JUL19', | |
OCT19: 'OCT19' | |
}; | |
var images = new Object(); | |
// Finds the div which contains all required elements | |
function getContainer(node) { | |
var container, version; | |
[ | |
['.irc_c[style*="visibility: visible;"][style*="transform: translate3d(0px, 0px, 0px);"]', VERSIONS.FEB18], | |
['.irc_c[data-ved]', VERSIONS.JUL19], | |
['.tvh9oe', VERSIONS.OCT19] | |
].forEach(element => { | |
if (node.closest(element[0])) { | |
[container, version] = [node.closest(element[0]), element[1]]; | |
} | |
}); | |
return [container, version]; | |
} | |
// Finds and deletes all extension related elements. | |
function clearExtElements(container) { | |
// Remove previously generated elements | |
var oldExtensionElements = container.querySelectorAll('.vi_ext_addon'); | |
for (var element of oldExtensionElements) { | |
element.remove(); | |
} | |
} | |
// Returns the image URL | |
function findImageURL(container, version) { | |
var image = null; | |
switch (version) { | |
case VERSIONS.FEB18: | |
image = container.querySelector('img[src]#irc_mi, img[alt^="Image result"][src]:not([src^="https://encrypted-tbn"]).irc_mut, img[src].irc_mi'); | |
break; | |
case VERSIONS.JUL19: | |
var iframe = container.querySelector('iframe.irc_ifr'); | |
if (!iframe) | |
return findImageURL(container, VERSIONS.FEB18); | |
image = iframe.contentDocument.querySelector('img#irc_mi'); | |
break; | |
case VERSIONS.OCT19: | |
image = container.querySelector('img[src].n3VNCb, img[src].r48jcc'); | |
if (image.src in images) { | |
return images[image.src]; | |
} | |
} | |
// Override url for images using base64 embeds | |
if (image === null || image.src === '' || image.src.startsWith('data')) { | |
var thumbnail = document.querySelector('img[name="' + container.dataset.itemId + '"]'); | |
if (thumbnail === null) { | |
// If no thumbnail found, try getting image from URL | |
var url = new URL(window.location); | |
var imgLink = url.searchParams.get('imgurl'); | |
if (imgLink) { | |
return imgLink; | |
} | |
} else { | |
var meta = thumbnail.closest('.rg_bx').querySelector('.rg_meta'); | |
var metadata = JSON.parse(meta.innerHTML); | |
return metadata.ou; | |
} | |
} | |
// If the above doesn't work, use the link in related images to find it | |
if (image === null || image.src === '' || image.src.startsWith('data')) { | |
var target_image = container.querySelector('img.target_image'); | |
if (target_image) { | |
var link = target_image.closest('a'); | |
if (link) { | |
// Some extensions replace google image links with their original links | |
if (link.href.match(/^[a-z]+:\/\/(?:www\.)?google\.[^/]*\/imgres\?/)) { | |
var link_url = new URL(link.href); | |
var new_imgLink = link_url.searchParams.get('imgurl'); | |
if (new_imgLink) { | |
return new_imgLink; | |
} | |
} else { | |
return link.href; | |
} | |
} | |
} | |
} | |
if (image) { | |
return image.src; | |
} | |
} | |
function addViewImageButton(container, imageURL, version) { | |
// get the visit buttonm | |
var visitButton; | |
switch (version) { | |
case VERSIONS.FEB18: | |
visitButton = container.querySelector('td > a.irc_vpl[href]').parentElement; | |
break; | |
case VERSIONS.JUL19: | |
visitButton = container.querySelector('a.irc_hol[href]'); | |
break; | |
case VERSIONS.OCT19: | |
visitButton = container.querySelector('.ZsbmCf[href], a.J2oL9c, a.jAklOc, a.uZ49bd, a.e0XTue, a.kWgFk, a.j7ZI7c'); | |
break; | |
} | |
// Create the view image button | |
var viewImageButton = visitButton.cloneNode(true); | |
viewImageButton.classList.add('vi_ext_addon'); | |
// Set the view image button url | |
var viewImageLink; | |
switch (version) { | |
case VERSIONS.FEB18: | |
viewImageLink = viewImageButton.querySelector('a'); | |
break; | |
default: | |
viewImageLink = viewImageButton; | |
} | |
viewImageLink.href = imageURL; | |
if (version == VERSIONS.OCT19) { | |
viewImageLink.removeAttribute('jsaction'); | |
} | |
// Set additional options | |
viewImageLink.setAttribute('target', '_blank'); | |
// Set the view image button text | |
var viewImageButtonText; | |
switch (version) { | |
case VERSIONS.FEB18: | |
viewImageButtonText = viewImageButton.querySelector('.Tl8XHc'); | |
break; | |
case VERSIONS.JUL19: | |
viewImageButtonText = viewImageButton.querySelector('.irc_ho'); | |
break; | |
case VERSIONS.OCT19: | |
viewImageButtonText = viewImageButton.querySelector('.pM4Snf, .KSvtLc, .Pw5kW, .q7UPLe, .K8E1Be, .pFBf7b, span'); | |
break; | |
} | |
viewImageButtonText.innerText = 'View Image'; | |
// Place the view image button | |
visitButton.parentElement.insertBefore(viewImageButton, visitButton); | |
visitButton.parentElement.insertBefore(visitButton, viewImageButton); | |
} | |
function addSearchImageButton(container, imageURL, version) { | |
var link; | |
switch (version) { | |
case VERSIONS.FEB18: | |
link = container.querySelector('.irc_dsh > a.irc_hol'); | |
break; | |
case VERSIONS.JUL19: | |
link = container.querySelector('.irc_ft > a.irc_help'); | |
break; | |
case VERSIONS.OCT19: | |
link = container.querySelector('.PvkmDc, .qnLx5b, .zSA7pe, .uZ49bd, .e0XTue, .kWgFk, .j7ZI7c'); | |
break; | |
} | |
// Create the search by image button | |
var searchImageButton = link.cloneNode(true); | |
searchImageButton.classList.add('vi_ext_addon'); | |
// Set the more sizes button text | |
var searchImageButtonText; | |
switch (version) { | |
case VERSIONS.FEB18: | |
searchImageButtonText = container.querySelector('.irc_ho'); | |
break; | |
case VERSIONS.JUL19: | |
searchImageButtonText = searchImageButton.querySelector('span'); | |
break; | |
case VERSIONS.OCT19: | |
searchImageButtonText = searchImageButton; | |
break; | |
} | |
searchImageButtonText.innerText = 'Search by Image'; | |
// Set the search by image button url | |
searchImageButton.href = '/searchbyimage?image_url=' + encodeURIComponent(imageURL); | |
// Set additional options | |
if (true) { | |
searchImageButton.setAttribute('target', '_blank'); | |
} | |
// Place the more sizes button | |
link.parentElement.insertBefore(searchImageButton, link); | |
link.parentElement.insertBefore(link, searchImageButton); | |
} | |
// Adds links to an object | |
function addLinks(node) { | |
if (DEBUG) | |
console.log('ViewImage: Trying to add links to node: ', node); | |
// Find the container | |
var [container, version] = getContainer(node); | |
// Return if no container was found | |
if (!container) { | |
if (DEBUG) | |
console.log('ViewImage: Adding links failed, container was not found.'); | |
return; | |
} | |
if (DEBUG) | |
console.log('ViewImage: Assuming site version: ', version); | |
// Clear any old extension elements | |
clearExtElements(container); | |
// Find the image url | |
var imageURL = findImageURL(container, version); | |
// Return if image was not found | |
if (!imageURL) { | |
if (DEBUG) | |
console.log('ViewImage: Adding links failed, image was not found.'); | |
return; | |
} | |
addViewImageButton(container, imageURL, version); | |
addSearchImageButton(container, imageURL, version); | |
} | |
function parseDataSource(array) { | |
var meta = array[31][0][12][2]; | |
for (var i = 0; i < meta.length; i++) { | |
try { | |
images[meta[i][1][2][0]] = meta[i][1][3][0]; | |
} catch (error) { | |
if (DEBUG) | |
console.log('ViewImage: Skipping image'); | |
} | |
} | |
} | |
function parseDataSource1() { | |
//const start_search = /AF_initDataCallback\({key:\s'ds:1',\sisError:\s{2}false\s,\shash:\s'\d+',\sdata:/; // Deprecated RegEx ('isError' not matched) | |
const start_search = /AF_initDataCallback\({key:\s'ds:1',\shash:\s'\d+',\sdata:/; | |
const end_search = ', sideChannel: {}});</script>'; | |
var match = document.documentElement.innerHTML.match(start_search); | |
var start_index = match.index + match[0].length; | |
var end_index = start_index + document.documentElement.innerHTML.slice(start_index).indexOf(end_search); | |
parseDataSource(JSON.parse(document.documentElement.innerHTML.slice(start_index, end_index))); | |
} | |
function parseDataSource2() { | |
const start_search = /AF_initDataCallback\({key:\s'ds:2',\sisError:\s{2}false\s,\shash:\s'\d+',\sdata:function(){return\s/; | |
const end_search = '}});</script>'; | |
var match = document.documentElement.innerHTML.match(start_search); | |
var start_index = match.index + match[0].length; | |
var end_index = start_index + document.documentElement.innerHTML.slice(start_index).indexOf(end_search); | |
parseDataSource(JSON.parse(document.documentElement.innerHTML.slice(start_index, end_index))); | |
} | |
// Check if source holds array of images | |
try { | |
if (document.documentElement.innerHTML.indexOf('key: \'ds:1\'') != -1) { | |
if (DEBUG) | |
console.log('ViewImage: Attempting to parse data source 1.'); | |
parseDataSource1(); | |
} else if (document.documentElement.innerHTML.indexOf('key: \'ds:2\'') != -1) { | |
if (DEBUG) | |
console.log('ViewImage: Attempting to parse data source 2.'); | |
parseDataSource2(); | |
} else { | |
throw 'Could not determine data source type.'; | |
} | |
if (DEBUG) | |
console.log('ViewImage: Successfully created source images array.'); | |
} catch (error) { | |
if (DEBUG) { | |
console.log('ViewImage: Failed to create source images array.'); | |
console.error(error); | |
} | |
} | |
// Define the mutation observers | |
var observer = new MutationObserver(function (mutations) { | |
if (DEBUG) | |
console.log('ViewImage: Mutations detected: ', mutations); | |
var node; | |
for (var mutation of mutations) { | |
if (mutation.addedNodes && mutation.addedNodes.length > 0) { | |
for (node of mutation.addedNodes) { | |
if (node.classList) { | |
// Check for new image nodes | |
if (['irc_mi', 'irc_mut', 'irc_ris', 'n3VNCb', 'r48jcc'].some(className => node.classList.contains(className))) { | |
addLinks(node); | |
} | |
} | |
} | |
} | |
if (mutation.target.classList && mutation.target.classList.contains('n3VNCb', 'r48jcc')) { | |
node = mutation.target.closest('.tvh9oe'); | |
if (!node.hasAttribute('aria-hidden')) { | |
addLinks(node); | |
} | |
} | |
} | |
}); | |
// Start adding links | |
if (DEBUG) | |
console.log('ViewImage: Initialising observer...'); | |
observer.observe(document.body, { | |
childList: true, | |
subtree: true, | |
attributes: true | |
}); | |
// inject CSS into document | |
if (DEBUG) | |
console.log('ViewImage: Injecting CSS...'); | |
var customStyle = document.createElement('style'); | |
customStyle.innerText = ` | |
.irc_dsh>.irc_hol.vi_ext_addon, | |
.irc_ft>.irc_help.vi_ext_addon, | |
.PvkmDc.vi_ext_addon, | |
.qnLx5b.vi_ext_addon | |
{ | |
margin: 0 4pt!important | |
} | |
.irc_hol.vi_ext_addon | |
{ | |
flex-grow:0!important | |
} | |
.zSA7pe[href^="/searchbyimage"] { | |
margin-left: 4px; | |
} | |
.ZsbmCf.vi_ext_addon{ | |
flex-grow:0 | |
}`; | |
document.head.appendChild(customStyle); |
The script doesn't work in iOS via the UserScripts safari extension, it says that the URLs don't match. I think that google could be using different set of classes/rules for mobile/iOS.
I guess Google keeps screwing up your awesome program... Weird how the Chrome extensions page actually has it listed, but the 'Add extension' button is disabled. I just now downloaded the zip file with the .js file in it. I have NO bloody idea what to do with it, can I add the folder with the .js file directly into my extensions page? I'm guessing not.
What now...? Is it time to give up : )
Thanks though for all your hard work Bro!
Ah! I guess maybe when Chrome last updated, your extension simply got removed. I just navigated to where I had unpacked your extension the last time and reloaded it! BAM! Works fine now :D
please update to 5.0.2
simply copying from here https://github.com/bijij/ViewImage/blob/master/js/content-script.js does not work on an older browser like a waterfox. and the userscript is the only option. because extensions are now manifest v3
It partially works. Kinda.
After making that change, I get the 3 buttons I was getting before, then to the right of those I get "Search by image" and a view image button that has the same icon as the visit button.
Clicking "Search by image" just opens this page: -
https://www.google.co.uk/imghp?sbi=1
However, clicking the view image button on the right does work as intended and opens the image in a new tab.
So, you're definitely on to something.