Skip to content

Instantly share code, notes, and snippets.

@saviorxzero98
Forked from bijij/viewimage.user.js
Last active June 7, 2023 12:50
Show Gist options
  • Save saviorxzero98/88662a751b48cfeb90f22a61ca4567eb to your computer and use it in GitHub Desktop.
Save saviorxzero98/88662a751b48cfeb90f22a61ca4567eb to your computer and use it in GitHub Desktop.
Userscript version of the View Image chrome extension
// ==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);
@yourduskquibbles
Copy link

FYI for anyone else having trouble getting this to work on firefox with Violentmonkey.

You need to set Default injection mode: to auto under Violentmonkey Settings -> Advanced -> General and it should work again as google now sets a CSP script execution policy that blocks it from loading on the default Violentmonkey page injection mode.

@Mactastic1-5
Copy link

Mactastic1-5 commented Jun 3, 2023

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