// ==UserScript==
// @name Google Images All Sizes
// @version 1.9.0
// @description Adds 'All Sizes' and 'Full Size' buttons to google images.
// @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
// @license MIT
// @author Nour Nasser
// @namespace https://github.com/Nourz1234
// @match *://www.google.com/search*tbm=isch*
// @match *://www.google.com.eg/search*tbm=isch*
// @run-at document-end
// @supportURL https://github.com/Nourz1234/google-images-all-sizes/issues
// @grant none
// ==/UserScript==
// extensions.ts
var NourzUtils;
(function (NourzUtils) {
// an initializer for the extension methods.
// to use extension methods they must be explicitly initialized.
// this is done so that in the case that extension methods are not needed
// you can avoid the trouble that comes with modifying prototypes.
function extensions() {
arrayExtensions();
objectExtensions();
stringExtensions();
}
NourzUtils.extensions = extensions;
function arrayExtensions() {
Array.prototype._nuFirst = function () {
return this[0];
};
Array.prototype._nuFirstOr = function (defaultValue) {
return this.length ? this[0] : defaultValue;
};
Array.prototype._nuLast = function () {
return this[this.length - 1];
};
Array.prototype._nuLastOr = function (defaultValue) {
return this.length ? this[this.length - 1] : defaultValue;
};
Array.prototype._nuRemoveAt = function (index) {
return this.splice(index, 1)[0];
};
Array.prototype._nuInsertAt = function (index, ...items) {
this.splice(index, 0, ...items);
};
Array.prototype._nuRemove = function (item) {
const index = this.indexOf(item);
if (index !== -1) {
this.splice(index, 1);
}
};
}
NourzUtils.arrayExtensions = arrayExtensions;
function objectExtensions() {
// 'Object.prototype' seems to be special.
// can only add non-enumerable properties using 'Object.defineProperty'
// otherwise pages that use jQuery break. ¯\_(ツ)_/¯
Object.defineProperty(Object.prototype, "_nuEntries", {
enumerable: false, configurable: true, writable: true,
value: function () {
return Object.entries(this);
}
});
Object.defineProperty(Object.prototype, "_nuAssign", {
enumerable: false, configurable: true, writable: true,
value: function (...sources) {
return Object.assign(this, ...sources);
}
});
}
NourzUtils.objectExtensions = objectExtensions;
function stringExtensions() {
String.prototype._nuRemovePrefix = function (prefix) {
let str = this;
while (str.startsWith(prefix))
str = str.substring(prefix.length);
return str;
};
String.prototype._nuRemoveSuffix = function (suffix) {
let str = this;
while (str.endsWith(suffix))
str = str.substring(0, str.length - suffix.length);
return str;
};
String.prototype._nuPadd = function (char, length) {
return (char.repeat(length) + this).slice(-Math.max(this.length, length));
};
}
NourzUtils.stringExtensions = stringExtensions;
})(NourzUtils || (NourzUtils = {}));
// Utils.ts
var NourzUtils;
(function (NourzUtils) {
// url and url params manipulation
function getQueryParams(url) {
return Array.from(new URL(url).searchParams.entries());
}
NourzUtils.getQueryParams = getQueryParams;
function setQueryParams(url, params) {
let oUrl = new URL(url);
let searchParams = new URLSearchParams();
for (let [name, value] of params) {
searchParams.append(name, value);
}
oUrl.search = searchParams.toString();
return oUrl.toString();
}
NourzUtils.setQueryParams = setQueryParams;
function getQueryParam(url, param, defValue = null) {
let params = new URL(url).searchParams;
return params.has(param) ? params.get(param) : defValue;
}
NourzUtils.getQueryParam = getQueryParam;
function setQueryParam(url, param, value) {
let oUrl = new URL(url);
oUrl.searchParams.set(param, value);
return oUrl.toString();
}
NourzUtils.setQueryParam = setQueryParam;
function getCurrentQueryParams() {
return getQueryParams(window.location.href);
}
NourzUtils.getCurrentQueryParams = getCurrentQueryParams;
// DOM stuff
function isTopFrame(win = window) {
return win === win.parent;
}
NourzUtils.isTopFrame = isTopFrame;
function isElementVisible(elem) {
return elem.offsetParent !== null;
}
NourzUtils.isElementVisible = isElementVisible;
function getElementOwnText(elem) {
let text = '';
for (let node of elem.childNodes) {
if (node.nodeName === '#text') {
text += node.nodeValue;
}
}
return text;
}
NourzUtils.getElementOwnText = getElementOwnText;
function createElementFromHTML(html) {
let temp = document.createElement("div");
temp.innerHTML = html;
let elem = temp.firstElementChild;
if (elem)
temp.removeChild(elem);
return elem;
}
NourzUtils.createElementFromHTML = createElementFromHTML;
// react helper
class ReactHelper {
static setInputValue(input, value) {
let proto = Object.getPrototypeOf(input);
let valuePD = Object.getOwnPropertyDescriptor(proto, 'value');
valuePD?.set?.call(input, value);
input.dispatchEvent(new Event('change', { bubbles: true }));
}
}
NourzUtils.ReactHelper = ReactHelper;
// time
function dateSubDays(date, days) {
let newDate = new Date();
newDate.setTime(date.getTime() - (days * (24 * 60 * 60 * 1000)));
return newDate;
}
NourzUtils.dateSubDays = dateSubDays;
// async stuff
function sleep(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
NourzUtils.sleep = sleep;
async function waitUntil(condition, timeoutMs = 0, intervalMs = 100) {
let startTime = new Date();
while (!condition()) {
if (timeoutMs > 0) {
let elapsed = Date.now() - startTime.getTime();
if (elapsed > timeoutMs)
return false;
}
await sleep(intervalMs);
}
return true;
}
NourzUtils.waitUntil = waitUntil;
async function poll(getter, condition, timeoutMs = 0, intervalMs = 100) {
let startTime = new Date();
let value;
while (!condition(value = getter())) {
if (timeoutMs > 0) {
let elapsed = Date.now() - startTime.getTime();
if (elapsed > timeoutMs)
return null;
}
await sleep(intervalMs);
}
return value;
}
NourzUtils.poll = poll;
// csv
function csvEscape(string, always_quote = false) {
const escape_chars = [',', '"', '\r', '\n'];
let wrap_in_quotes = always_quote ? true : escape_chars.some(x => string.includes(x));
string = string.replace('"', '""'); // escape double qoutes
return wrap_in_quotes ? `"${string}"` : string;
}
NourzUtils.csvEscape = csvEscape;
function csvFromArray(array, eol = '\r\n', always_quote = false) {
return array.map(row => row.map(cell => csvEscape(cell, always_quote)).join(",")).join(eol);
}
NourzUtils.csvFromArray = csvFromArray;
function csvToArray(string, eol = '\r\n') {
let escape = (string) => string.replaceAll(',', '<COMMA>')
.replaceAll('\r', '<CR>')
.replaceAll('\n', '<LF>');
let unescape = (string) => string.replaceAll('<COMMA>', ',')
.replaceAll('<CR>', '\r')
.replaceAll('<LF>', '\n');
string = string.replaceAll(/"((?:[^"]|"")*)(?:"|$)/gs, (_match, group1) => group1 !== undefined ? escape(group1) : '').replaceAll('""', '"'); // unescape double qoutes
return string.split(eol).map(row => row.split(',').map(unescape));
}
NourzUtils.csvToArray = csvToArray;
// misc
function hasKey(obj, key) {
return key in obj;
}
NourzUtils.hasKey = hasKey;
function rndInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
NourzUtils.rndInt = rndInt;
// an alternative to the deprecated "unescape()" function
// apparently it can be dropped anytime
function _unescape(string) {
return string.replace(/(?<=%)[0-9a-f]{2}/gi, (match) => {
return String.fromCharCode(parseInt(match, 16));
});
}
NourzUtils._unescape = _unescape;
function base64Encode(string) {
return window.btoa(_unescape(encodeURIComponent(string)));
}
NourzUtils.base64Encode = base64Encode;
function downloadFile(filename, textContent, mimeType) {
let elem = document.createElement('a');
elem.href = `data:${mimeType};base64;,` + base64Encode(textContent);
elem.download = filename;
elem.style.display = 'none';
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
}
NourzUtils.downloadFile = downloadFile;
})(NourzUtils || (NourzUtils = {}));
let FILES = {};
(() => {
const { hasKey, isElementVisible } = NourzUtils;
const LocalizedStrings = {
'en': {
allSizes: 'All sizes',
btnAllSizes: 'All Sizes',
btnFullSize: 'Full Size'
},
'ar': {
allSizes: '\u062C\u0645\u064A\u0639 \u0627\u0644\u0623\u062D\u062C\u0627\u0645'
}
};
const DefaultStrings = LocalizedStrings.en;
let strings;
let fail = false;
let error = (...data) => {
console.error("Google Images All Sizes:", ...data);
fail = true;
alert("Google Images All Sizes:\nan error occurred. try refreshing, if this doesn't work then all you can do is wait for an update to fix this. (which might not happen so soon xD)");
};
function getPreviewImageUrl() {
for (let img of document.querySelectorAll("img[jsname='HiaYvf']")) {
if (isElementVisible(img)) {
return img.src;
}
}
return null;
}
function openImageInFullSize() {
let imgUrl = getPreviewImageUrl();
if (imgUrl !== null)
window.open(imgUrl, '_blank');
}
async function viewAllSizes() {
let btnViewAllSizes = this;
let timerID = setInterval(() => {
if (btnViewAllSizes.textContent?.endsWith('...')) {
btnViewAllSizes.textContent = btnViewAllSizes.textContent.slice(0, -3);
}
else {
btnViewAllSizes.textContent += '.';
}
}, 300);
let imageUrl = getPreviewImageUrl();
if (imageUrl === null)
return;
let responseHTML;
if (imageUrl.startsWith("data:")) {
let url = "/searchbyimage/upload";
let imgBlob = await fetch(imageUrl).then(x => x.blob());
let formData = new FormData();
formData.append("image_url", "");
formData.append("encoded_image", imgBlob);
formData.append("image_content", "");
formData.append("filename", "");
responseHTML = await fetch(url, {
method: "POST",
body: formData
}).then(x => x.text());
}
else {
let url = new URL('/searchbyimage', window.location.href);
url.searchParams.append("image_url", imageUrl);
url.searchParams.append("encoded_image", "");
url.searchParams.append("image_content", "");
url.searchParams.append("filename", "");
responseHTML = await fetch(url.href).then(x => x.text());
}
let doc = document.implementation.createHTMLDocument();
doc.open();
doc.write(responseHTML);
doc.close();
let allSizes = strings.allSizes || DefaultStrings.allSizes;
let allSizesLink = Array.from(doc.getElementsByTagName('a'))
.find(x => x.textContent == allSizes)?.href;
if (allSizesLink)
window.open(allSizesLink, '_blank');
clearInterval(timerID);
while (btnViewAllSizes.textContent?.endsWith('.')) {
btnViewAllSizes.textContent = btnViewAllSizes.textContent.slice(0, -1);
}
}
// because google likes to be an ass we need to catch errors occurring inside event handlers
// and log them manually otherwise the errors go poof!
// that is the whole purpose of this function
function evtHandler(func) {
return function (...args) {
try {
func.bind(this)(...args);
}
catch (e) {
error(e);
}
};
}
function addButtons() {
if (fail)
return;
if (!strings) {
let lang = WIZ_global_data?.GWsdKe;
if (!lang) {
error("couldn't identify the language.");
return;
}
if (!hasKey(LocalizedStrings, lang)) {
fail = true;
alert("Google Images All Sizes:\nlanguage not supported.");
return;
}
strings = LocalizedStrings[lang];
}
let btnBars = document.querySelectorAll('div[jsname="ZE6Ufb"]:not(:empty)');
for (let btnBar of btnBars) {
let btnOpenImageInFullSize = btnBar.querySelector('#MyCustomLink_OpenImageInFullSize');
let btnViewAllSizes = btnBar.querySelector('#MyCustomLink_ViewAllSizes');
if (btnOpenImageInFullSize !== null || btnViewAllSizes !== null)
return;
// create a button template
let btnTemplate = document.createElement('a');
// mimic google's button style
btnTemplate.className = 'CqeZic ZAxeoe';
btnTemplate.style.textDecoration = 'none';
btnTemplate.style.color = 'white';
btnTemplate.style.width = 'auto';
btnTemplate.style.maxHeight = '36px';
btnTemplate.style.margin = '6px';
btnTemplate.style.padding = '0 8px';
btnOpenImageInFullSize = btnTemplate.cloneNode(true);
btnViewAllSizes = btnTemplate.cloneNode(true);
btnOpenImageInFullSize.id = 'MyCustomLink_OpenImageInFullSize';
btnOpenImageInFullSize.textContent = strings.btnFullSize || DefaultStrings.btnFullSize;
btnViewAllSizes.id = 'MyCustomLink_ViewAllSizes';
btnViewAllSizes.textContent = strings.btnAllSizes || DefaultStrings.btnAllSizes;
btnOpenImageInFullSize.addEventListener("click", evtHandler(openImageInFullSize));
btnViewAllSizes.addEventListener("click", evtHandler(viewAllSizes));
btnBar.insertAdjacentElement('afterbegin', btnOpenImageInFullSize);
btnOpenImageInFullSize.insertAdjacentElement('afterend', btnViewAllSizes);
}
}
addButtons();
let observer;
observer = new MutationObserver(addButtons);
observer.observe(document.body, { attributes: true, subtree: true });
})();