Skip to content

Instantly share code, notes, and snippets.

@bijij
Last active March 3, 2024 16:28
Show Gist options
  • Star 68 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save bijij/58cc8cfc859331e4cf80210528a7b255 to your computer and use it in GitHub Desktop.
Save bijij/58cc8cfc859331e4cf80210528a7b255 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 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);
@FarisHijazi
Copy link

missing definition:

please add this to the top of the script (after the header)

function parseDataSource(array) {
    var meta = array[31][0][12][2];
    console.log('meta', meta);
    console.group('Metas')
    for (var i = 0; i < meta.length; i++) {
        try {
            console.log(meta[i][1][2][0], meta[i][1][3][0])
            images[meta[i][1][2][0]] = meta[i][1][3][0];
        } catch (error) {
            if (DEBUG)
                console.log('ViewImage: Skipping image');
        }
    }
    console.groupEnd();
}

@e-t-l
Copy link

e-t-l commented Jun 29, 2021

@bijij A question I've had in other cases, which you may be able to help with: Do you know if there's any difference in browser performance or system resource usage for a userscript vs its webextension version? I currently have this as a Firefox Add-on but I'm debating whether to delete that and use the userscript instead, since I already have FireMonkey for other scripts...

@benyaminl
Copy link

Code is broken now. Not showing view Image button anymore. Is that intended?

@Lathe-of-Heaven
Copy link

The Chrome extension no longer works, and going by the comment directly above posted only 4 days ago, I would assume this code no longer works either.

So, what do we do in order to 'View Images' again in Chrome Images?

BTW, I'm sorry to be so bloody NEWBIE, but I've searched until I've gone sterile, and I cannot see how to run the unzipped file for this program. Thanks!

@bijij
Copy link
Author

bijij commented Aug 3, 2021

The Chrome extension no longer works, and going by the comment directly above posted only 4 days ago, I would assume this code no longer works either.

So, what do we do in order to 'View Images' again in Chrome Images?

BTW, I'm sorry to be so bloody NEWBIE, but I've searched until I've gone sterile, and I cannot see how to run the unzipped file for this program. Thanks!

The Userscript should be functional now,
I have released an update for the chrome version however I need to wait for approval from google first, which may take a day or two.

@Lathe-of-Heaven
Copy link

Lathe-of-Heaven commented Aug 3, 2021 via email

@bijij
Copy link
Author

bijij commented Aug 3, 2021

Hi there, you simply install an extension such as Tampermonkey or Greasemonkey and add this script to your scripts.

@Lathe-of-Heaven
Copy link

Lathe-of-Heaven commented Aug 3, 2021 via email

@Shuunen
Copy link

Shuunen commented Sep 26, 2021

Works great, thanks @bijij ❤️

@bfgh012
Copy link

bfgh012 commented Jun 11, 2022

It seems a new update from Google broke the script.

@Shuunen
Copy link

Shuunen commented Jun 11, 2022

@bfgh012 same here 😭

@Lathe-of-Heaven
Copy link

Lathe-of-Heaven commented Jun 11, 2022 via email

@Shuunen
Copy link

Shuunen commented Jun 11, 2022

Ok just used this fork by @saviorxzero98 and it fix the issue : https://gist.github.com/saviorxzero98/88662a751b48cfeb90f22a61ca4567eb

Thanks to the dev ! <3

@Lathe-of-Heaven
Copy link

Lathe-of-Heaven commented Jun 11, 2022 via email

@Mactastic1-5
Copy link

This no longer works.

@bfgh012
Copy link

bfgh012 commented Jun 7, 2023

@bijij Hey, sorry for @ you, is this Userscript version outdated?

@Mactastic1-5
Copy link

@bijij Hey, sorry for @ you, is this Userscript version outdated?

Use https://gist.github.com/ner00/ec9ae47e202b8e99f19be44a5af6baf3 instead.

@TS6ix2GaH3q679h5
Copy link

Use https://gist.github.com/ner00/ec9ae47e202b8e99f19be44a5af6baf3 instead.

Not working for me, either.

@ner00
Copy link

ner00 commented Sep 2, 2023

@TS6ix2GaH3q679h5 You may have a new class name in your case, please follow the instruction in this post.

@SigmaTheDJ
Copy link

SigmaTheDJ commented Sep 4, 2023

This doesn't work for me, nor does v3.7.0.5.

These are the classes I get when inspecting the image and the visit button respectively: -

class=r48jcc
class=j7ZI7c

Both are those are mentioned in your script, but I get no buttons. The layout has changed, not the class names I think.

@ner00
Copy link

ner00 commented Sep 4, 2023

@SigmaTheDJ That's weird, v3.7.0.5 from my gist where I used a dynamic approach to finding the class names should be working.

Regardless, even this version (4.1.1) has the class names that you mentioned declared statically. Do you have the View Image extension installed and enabled? You can't have both running at the same time, either the extension or the user script (this one or mine).

Make sure you don't have multiple scripts running and conflicting with each other because your issue seems to be something else entirely.

@SigmaTheDJ
Copy link

Do you have the View Image extension installed and enabled? You can't have both running at the same time, either the extension or the user script (this one or mine).

Make sure you don't have multiple scripts running and conflicting with each other because your issue seems to be something else entirely.

I only have your script enabled in ViolentMonkey. I have the extension installed, but disabled.

The script version has worked fine for me before, because I switched to it temporarily about 4 months ago when the extension stopped working.

View Image stopped working for me when Google changed the layout of how the buttons are displayed in Firefox.

For example, in FF, I see this: -

1

But in Chrome, for the same image, I see this: -

2

Previously, FF looked the same as Chrome with a button that had "Visit" written on it. Now it doesn't. When that changed is when View Image stopped working.

@ner00
Copy link

ner00 commented Sep 4, 2023

Given the visual changes, that may indicate deeper structural changes that break how View Image iterates through the elements. I cannot reproduce this, I'm on a desktop and I don't have that button layout in FF, mine is still showing classic like your second screenshot.

But still, your Chrome browser also doesn't seem to have View Image working either, are you using extension or userscript in it too? What OS do you have? What's your screen resolution?

I don't see a viable way to help in this situation... could you maybe post the exact HTML code that you get from FF's new visit button layout through inspection?

@SigmaTheDJ
Copy link

SigmaTheDJ commented Sep 4, 2023

But still, your Chrome browser also doesn't seem to have View Image working either, are you using extension or userscript in it too? What OS do you have? What's your screen resolution?

I didn't have it installed in Chrome, as I just use that as a backup in case a site has an issue in FF.

I did try installing it just now though, and it does not work. Likewise with MS Edge. EDIT - I just tried the script version in Chrome and that doesn't work either.

I'm using Windows 10 and my screen resolution is 4K, but nothing has changed there.

could you maybe post the exact HTML code that you get from FF's new visit button layout through inspection?

4

@ner00
Copy link

ner00 commented Sep 5, 2023

I can see that it no longer uses the <span> element, which would typically be inside that div, before the svg:

<span class="iLgTbf CzIFEe cS4Vcb-pGL6qe-lfQAOe">Visit</span>

And also the fact that you can't get it working with other browsers, like Edge and Chrome, where the buttons have the classic visual seems to hint at further and deeper changes that need closer analysis and adjustments.

Try this, but if it doesn't work then I'm out of ideas due to not being able to reproduce and analyze:
Go to your user script in Violent Monkey and edit line 163, where you should add a comma and an svg tag, like so:

            viewImageButtonText = viewImageButton.querySelector('.pM4Snf, .KSvtLc, .Pw5kW, .q7UPLe, .K8E1Be, .pFBf7b, span, svg');

Probably won't work, but that's all I got at the moment. I wonder if there's a way to force Google Images to change its visual by changing any particular setting, most times it seems they just push stuff at random to a few users and not others. Bummer.

@SigmaTheDJ
Copy link

SigmaTheDJ commented Sep 5, 2023

Probably won't work, but that's all I got at the moment. I wonder if there's a way to force Google Images to change its visual by changing any particular setting, most times it seems they just push stuff at random to a few users and not others. Bummer.

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.

23

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.

@newkind
Copy link

newkind commented Nov 10, 2023

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.

@Lathe-of-Heaven
Copy link

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!

@Lathe-of-Heaven
Copy link

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

@ruboard
Copy link

ruboard commented Jan 9, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment