Last active
May 6, 2023 02:15
-
-
Save leeroybrun/f83c02016f97b91eb56c5892cdfcd5d1 to your computer and use it in GitHub Desktop.
Tampermonkey / Greasemonkey script to check images of webpage with Pixelpeeper
This file contains hidden or 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 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