Skip to content

Instantly share code, notes, and snippets.

@leeroybrun
Last active May 6, 2023 02:15
Show Gist options
  • Save leeroybrun/f83c02016f97b91eb56c5892cdfcd5d1 to your computer and use it in GitHub Desktop.
Save leeroybrun/f83c02016f97b91eb56c5892cdfcd5d1 to your computer and use it in GitHub Desktop.
Tampermonkey / Greasemonkey script to check images of webpage with Pixelpeeper
// ==UserScript==
// @name Pixelpeeper
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// ==/UserScript==
(function() {
'use strict';
var defaultBtnStyle = {
position: 'static',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
color: 'white',
border: '0'
};
// https://davidwalsh.name/get-absolute-url
var getAbsoluteUrl = (function() {
var a;
return function(url) {
if(!a) a = document.createElement('a');
a.href = url;
return a.href;
};
})();
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// https://stackoverflow.com/a/26230989/1160800
function getCoords(elem) { // crossbrowser version
var box = elem.getBoundingClientRect();
var body = document.body;
var docEl = document.documentElement;
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
var clientTop = docEl.clientTop || body.clientTop || 0;
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return { top: Math.round(top), left: Math.round(left) };
}
/**
* Since the console.log doesn't respond to the `display` style,
* setting a width and height has no effect. In fact, the only styles
* I've found it responds to is font-size, background-image and color.
* To combat the image repeating, we have to get a create a font bounding
* box so to speak with the unicode box characters. EDIT: See Readme.md
*
* @param {int} width The height of the box
* @param {int} height The width of the box
* @return {object} {string, css}
*/
function getConsoleBox(width, height) {
return {
string: "+",
style: "font-size: 1px; padding: " + Math.floor(height/2) + "px " + Math.floor(width/2) + "px; line-height: " + height + "px;"
};
}
/**
* Display an image in the console.
* @param {string} url The url of the image.
* @param {int} scale Scale factor on the image
* @return {null}
*/
console.image = function(url, scale) {
scale = scale || 1;
var img = new Image();
img.onload = function() {
var dim = getConsoleBox(this.width * scale, this.height * scale);
console.log("%c" + dim.string, dim.style + "background: url(" + url + "); background-size: " + (this.width * scale) + "px " + (this.height * scale) + "px; color: transparent;");
};
img.src = url;
};
function series(arr, ready) {
var length = arr.length, orig;
if (!length) return setTimeout(ready, 1);
var handleItem = function(idx) {
setTimeout(function() {
arr[idx](function(err) {
if (err) return ready(err);
if (idx < length - 1) return handleItem(idx + 1);
return ready();
});
}, 1);
};
handleItem(0);
}
function addButton(text, onclick, cssObj, parent) {
cssObj = cssObj || {position: 'fixed', bottom: '2%', left:'2%', 'z-index': 300000};
parent = parent || document.body;
let button = document.createElement('button'), btnStyle = button.style;
parent.appendChild(button);
button.innerHTML = text;
button.onclick = onclick;
btnStyle.position = 'absolute';
Object.keys(cssObj).forEach(key => btnStyle[key] = cssObj[key]);
return button;
}
function addLink(text, href, cssObj, parent) {
cssObj = cssObj || {position: 'fixed', bottom: '2%', left:'2%', 'z-index': 300000};
parent = parent || document.body;
let link = document.createElement('a'), linkStyle = link.style;
parent.appendChild(link);
link.innerHTML = text;
link.href = href;
linkStyle.position = 'absolute';
Object.keys(cssObj).forEach(key => linkStyle[key] = cssObj[key]);
return link;
}
function addContextMenu() {
if (!("contextMenu" in document.documentElement &&
"HTMLMenuItemElement" in window)) return;
var body = document.body;
body.addEventListener("contextmenu", initMenu, false);
var menu = body.appendChild(document.createElement("menu"));
menu.outerHTML = '<menu id="userscript-pixelpeeper-by-image" type="context">\
<menuitem label="Send to Pixelpeeper"\
icon="data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\
AAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAAEl\
SURBVDiNY/z//z8DJYCRkIKsthv/kRX9Z2BgmFalARdiIcaGKZXqcH5O+01U+ay2G3MYGBiSiXUm\
mofnsBDSjEUTMkiBe2Eq1JnZ7TcZBHhZGNythBl0lLkZODmYGX7++sdw/sZnhl3H3zF8+voHwwsY\
FkR5ijNICLMzTF31hOHnr38MHGxMDJlhMgwv3vxkWL7jJYpaJmzu0lTigWtmYGBg+PHrH8P0VU8Y\
tJV5MNRiNYCfmxmuGQZ+/PrHwMmOqRyrAX///WfgYEOV4mBjwjAUpwHHL31iyA6XgRvCwcbEkBUm\
w3DuxmcMtVgDkYONicHLVoTBSJOXgYONieHHz38Ml+98Ydh88DXDtx//CBtACmBiYGCYS4H+OYyU\
5kasgUgKAADN8WLFzlj9rgAAAABJRU5ErkJggg=="></menuitem>\
</menu>';
document.querySelector("#userscript-pixelpeeper-by-image menuitem")
.addEventListener("click", onMenuClick, false);
var imgElmRightClicked = null;
function initMenu(aEvent) {
// Executed when user right click on web page body
// aEvent.target is the element you right click on
var node = aEvent.target;
var item = document.querySelector("#userscript-pixelpeeper-by-image menuitem");
console.log(node, item);
var imgElm = aEvent.target;
imgElmRightClicked = imgElm;
var imgUrl = null;
if(imgElm.src && imgElm.src.length > 5) {
imgUrl = imgElm.src;
} else if(imgElm.style && imgElm.style.backgroundImage && imgElm.style.backgroundImage.match('url')) {
imgUrl = imgElm.style.backgroundImage;
imgUrl = /url\(['"]?([^"')]+)/.exec(imgUrl) || [];
imgUrl = imgUrl[1];
}
console.log('imgUrl', imgUrl);
if (imgUrl) {
body.setAttribute("contextmenu", "userscript-pixelpeeper-by-image");
item.setAttribute("imageURL", imgUrl);
} else {
body.removeAttribute("contextmenu");
item.removeAttribute("imageURL");
}
/*if (node.localName == "img") {
body.setAttribute("contextmenu", "userscript-search-by-image");
item.setAttribute("imageURL", node.src);
} else {
body.removeAttribute("contextmenu");
item.removeAttribute("imageURL");
}*/
}
function onMenuClick(event) {
console.log(imgElmRightClicked);
var imgElm = imgElmRightClicked;
var imgUrl = event.target.getAttribute("imageURL");
if(!imgUrl) {
return alert('URL of image not found.');
}
returnOrGetToken(function(err, token) {
if(err) {
return alert(err);
}
processImage(token, imgUrl, function(err, res) {
if(err) {
return alert('Cannot get a result from Pixelpeeper.');
}
addResultToImgElm(imgElm, res);
});
});
}
}
function getCSRFToken(callback) {
GM_xmlhttpRequest({
method: 'GET',
url: 'https://pixelpeeper.io/',
onload: function(res) {
var csrfRegex = /name="csrf-token" content="(.+)"/g;
var match = csrfRegex.exec(res.responseText);
return callback(null, match[1]);
},
onerror: function(res) {
console.log(res);
return callback(new Error('Cannot get CSRF token.'));
}
});
}
function getImageResult(csrfToken, url, callback) {
GM_xmlhttpRequest({
method: 'POST',
url: 'https://pixelpeeper.io/urls.json',
headers: {
'Content-Type': 'application/json',
'Origin': 'https://pixelpeeper.io',
'Referer': 'https://pixelpeeper.io/',
'X-CSRF-Token': csrfToken
},
//overrideMimeType: 'application/json',
data: JSON.stringify({"url":{"url": url}}),
onload: function(res) {
return callback(null, {
status: res.status,
headers: res.responseHeaders,
response: JSON.parse(res.responseText)
});
},
onerror: function(res) {
console.log(res);
return callback(new Error('Cannot get image result.'));
}
});
}
function processImageUrl(url) {
// If it's an Unsplash image, remove parameters to get image with metadata
if(url.indexOf('images.unsplash.com') !== -1) {
url = url.substr(0, url.indexOf('?'));
}
return url;
}
function getDocumentImages() {
var imgs = [];
Array.from(document.images).forEach((img) => {
if(img.src && img.src.length > 5) {
imgs.push({
elm: img,
url: img.src
});
}
});
var tags = document.getElementsByTagName('*');
var numTags = tags.length;
for (var i = 0; i < numTags; i++) {
var tag = tags[i];
if (tag.style && tag.style.backgroundImage && tag.style.backgroundImage.match('url')) {
var url = tag.style.backgroundImage;
url = /url\(['"]?([^"')]+)/.exec(url) || [];
if(url.length > 1 && url[1].length > 5) {
imgs.push({
elm: tag,
url: getAbsoluteUrl(url[1])
});
}
}
}
return imgs;
}
function processImage(csrfToken, url, cb) {
url = processImageUrl(url);
getImageResult(csrfToken, url, function(err, res) {
if(err) {
return cb('Cannot get a result from Pixelpeeper.');
}
if((res.status === 302 || res.status === 200) && res.response.url) {
var hasPreset = res.response.html.indexOf('download-preset') !== -1;
if(hasPreset) {
return cb(null, {
imgUrl: url,
resultUrl: res.response.url,
presetUrl: res.response.url+'.lrtemplate'
});
} else {
return cb();
}
} else {
return cb();
}
});
}
function processPageImages(csrfToken) {
var imgs = getDocumentImages();
var imgResFunc = [];
imgs.forEach((img) => {
imgResFunc.push((function(url, imgElm) {
return function(cb) {
processImage(csrfToken, url, function(err, res) {
if(err) {
return alert('Cannot get a result from Pixelpeeper.');
}
addResultToImgElm(imgElm, res);
return cb();
});
};
})(img.url, img.elm));
});
series(imgResFunc, function(err) {
if(err) {
return console.log(err);
}
console.log('All done!');
});
}
var alreadyDefinedElm = [];
var wrprs = [];
function createImgElmWrp(imgElm) {
var elmWrp = document.createElement('div');
elmWrp.showOnHover = true;
var elmPos = getCoords(imgElm);
var elmStyle = {
position: 'absolute',
top: elmPos.top + 10+'px',
left: elmPos.left + 10+'px',
zIndex: 20000000,
display: 'none',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
color: 'white'
};
Object.keys(elmStyle).forEach((key) => {
elmWrp.style[key] = elmStyle[key];
});
document.body.appendChild(elmWrp);
imgElm.onmouseover = elmWrp.onmouseover = function(event) {
if(elmWrp.showOnHover) {
elmWrp.style.display = 'block';
}
};
imgElm.onmouseout = elmWrp.onmouseout = function(event) {
if(elmWrp.showOnHover) {
elmWrp.style.display = 'none';
}
};
return elmWrp;
}
function printResultToConsole(res) {
console.log('');
console.log('Image '+ res.imgUrl +' :');
console.log('Result URL '+ res.resultUrl);
console.log('Download preset: '+ res.presetUrl);
console.image(res.imgUrl, 0.25);
}
function addResultToElmWrp(elmWrp, res) {
elmWrp.showOnHover = false;
elmWrp.style.display = 'block';
if(res && res.presetUrl) {
printResultToConsole(res);
var resultLink = document.createElement('a');
resultLink.href = res.resultUrl;
resultLink.target = '_blank';
resultLink.style.color = 'white';
resultLink.style.padding = '0 10px';
resultLink.innerText = 'Result';
var presetLink = document.createElement('a');
presetLink.href = res.presetUrl;
presetLink.target = '_blank';
presetLink.style.color = 'white';
presetLink.style.padding = '0 10px';
presetLink.innerText = 'Preset';
elmWrp.appendChild(resultLink);
elmWrp.appendChild(presetLink);
} else {
var resultText = document.createElement('span');
resultText.innerText = 'No result';
elmWrp.appendChild(resultText);
}
}
function addResultToImgElm(imgElm, result) {
var imgElmPos = alreadyDefinedElm.indexOf(imgElm);
var elmWrp;
if(imgElmPos === -1) {
elmWrp = createImgElmWrp(imgElm);
alreadyDefinedElm.push(imgElm);
wrprs.push(elmWrp);
} else {
elmWrp = wrprs[imgElmPos];
}
addResultToElmWrp(elmWrp, result);
}
function addButtonsToImgElms() {
var imgs = getDocumentImages();
imgs.forEach(function(img) {
if(alreadyDefinedElm.indexOf(img.elm) !== -1) {
return;
}
var elm = img.elm;
var imgUrl = img.url;
var elmWrp = createImgElmWrp(img.elm);
alreadyDefinedElm.push(img.elm);
wrprs.push(elmWrp);
addButton('PP', function() {
returnOrGetToken(function(err, token) {
if(err) {
return alert(err);
}
processImage(token, img.url, function(err, res) {
if(err) {
return alert('Cannot get a result from Pixelpeeper.');
}
addResultToElmWrp(elmWrp, res);
});
});
}, defaultBtnStyle, elmWrp);
addLink('DL', processImageUrl(img.url), defaultBtnStyle, elmWrp);
});
}
var csrfToken = null;
function returnOrGetToken(cb) {
if(csrfToken) {
console.log('Token for PixelPeeper cached, returning it.');
return cb(null, csrfToken);
}
console.log('Getting new token for PixelPeeper.');
getCSRFToken(function(err, token) {
if(err) {
return alert('Cannot get a CSRF token for Pixelpeeper.');
}
csrfToken = token;
return cb(null, csrfToken);
});
}
function addStyleToHead() {
var css = '@media print { .pixelpeeperButtonsTM { display:none !important; }}';
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet){
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
}
var mainBtnWrp = document.createElement('div');
var mainWrpStyle = {
position: 'fixed',
bottom: '2%',
left: '2%',
zIndex: 20000000
};
mainBtnWrp.className = 'pixelpeeperButtonsTM';
Object.keys(mainWrpStyle).forEach((key) => {
mainBtnWrp.style[key] = mainWrpStyle[key];
});
document.body.appendChild(mainBtnWrp);
addButton('PP all', function() {
returnOrGetToken(function(err, token) {
if(err) {
return alert(err);
}
processPageImages(token);
});
}, defaultBtnStyle, mainBtnWrp);
addButton('PP btn', function() {
addButtonsToImgElms();
}, defaultBtnStyle, mainBtnWrp);
// Not working for now
//addContextMenu();
addStyleToHead();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment