-
-
Save saviorxzero98/88662a751b48cfeb90f22a61ca4567eb 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 3.7.0 | |
// @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.github.com/saviorxzero98/88662a751b48cfeb90f22a61ca4567eb/raw/viewimage.user.js | |
// ==/UserScript== | |
'use strict'; | |
// i18n | |
const i18nTextMap = { | |
'en': { | |
searchByImage: 'Search by image', | |
viewImage: 'View Image' | |
}, | |
'zh-hans': { | |
searchByImage: '以图搜图', | |
viewImage: '查看图片' | |
}, | |
'zh-hant': { | |
searchByImage: '以圖搜尋', | |
viewImage: '檢視圖片' | |
} | |
} | |
function geti18nText() { | |
// Get navigator language | |
let lang = navigator.language || navigator.userLanguage || 'en'; | |
switch (lang.toLowerCase()) { | |
case 'zh-hans': | |
case 'zh-cn': | |
case 'zh-sg': | |
case 'zh-my': | |
return i18nTextMap["zh-hans"]; | |
case 'zh-hant': | |
case 'zh-tw': | |
case 'zh-hk': | |
case 'zh-mo': | |
return i18nTextMap["zh-hant"]; | |
default: | |
return i18nTextMap["en"]; | |
} | |
} | |
var i18nText = geti18nText(); | |
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'); | |
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'); | |
viewImageLink.setAttribute('rel', 'noreferrer'); | |
// 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 = i18nText.viewImage; | |
// 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'); | |
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 = i18nText.searchByImage; | |
// Set the search by image button url | |
//searchImageButton.href = '/searchbyimage?image_url=' + imageURL; | |
// Set additional options | |
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) { | |
// Find the container | |
var [container, version] = getContainer(node); | |
// Return if no container was found | |
if (!container) { | |
return; | |
} | |
// Clear any old extension elements | |
clearExtElements(container); | |
// Find the image url | |
var imageURL = findImageURL(container, version); | |
// Return if image was not found | |
if (!imageURL) { | |
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) { | |
} | |
} | |
} | |
function parseDataSource1() { | |
const start_search = /AF_initDataCallback\({key:\s'ds:1',\sisError:\s{2}false\s,\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) { | |
parseDataSource1(); | |
} else if (document.documentElement.innerHTML.indexOf('key: \'ds:2\'') != -1) { | |
parseDataSource2(); | |
} else { | |
throw 'Could not determine data source type.'; | |
} | |
} | |
catch (error) { | |
} | |
// Define the mutation observers | |
var observer = new MutationObserver(function (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); | |
} | |
} | |
} | |
}); | |
// Get options and start adding links | |
observer.observe(document.body, | |
{ | |
childList: true, | |
subtree: true, | |
attributes: true | |
} | |
); | |
// inject CSS into document | |
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); |
It's no longer working in Google Chrome.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FYI for anyone else having trouble getting this to work on firefox with Violentmonkey.
You need to set
Default injection mode:
toauto
under ViolentmonkeySettings
->Advanced
->General
and it should work again as google now sets a CSP script execution policy that blocks it from loading on the default Violentmonkeypage
injection mode.