Last active
November 11, 2024 07:06
-
-
Save bijij/58cc8cfc859331e4cf80210528a7b255 to your computer and use it in GitHub Desktop.
Userscript version of the View Image chrome extension
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 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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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