// ==UserScript==
// @name Easy Compare
// @description Compare images
// @version 0.1
// @author Secant(TYT@NexusHD)
// @license GPL-3.0-or-later
// @supportURL [email protected]
// @contributionURL https://i.loli.net/2020/02/28/JPGgHc3UMwXedhv.jpg
// @contributionAmount 10
// @include *
// @require https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js
// @namespace https://gf.qytechs.cn/users/152136
// @icon data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23008000'%3E%3Cpath id='ld' d='M20 6H10c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h10v4h4V2h-4v4zm0 30H10l10-12v12zM38 6H28v4h10v26L28 24v18h10c2.21 0 4-1.79 4-4V10c0-2.21-1.79-4-4-4z'/%3E%3C/svg%3E
// @grant GM_xmlhttpRequest
// @grant unsafewindow
// ==/UserScript==
// TODO List
// - image diff
// - more sites support
// - guess images
// - redirect url chopper
(function ($, Mousetrap) {
'use strict';
// Title: Mousetrap Pause Plugin
// Reference: https://github.com/ccampbell/mousetrap/tree/master/plugins/pause
if (Mousetrap) {
let target = Mousetrap.prototype || Mousetrap;
const _originalStopCallback = target.stopCallback;
target.stopCallback = function (e, element, combo) {
var self = this;
if (self.paused) {
return true;
}
return _originalStopCallback.call(self, e, element, combo);
};
target.pause = function () {
var self = this;
self.paused = true;
};
target.unpause = function () {
var self = this;
self.paused = false;
};
}
// A global timeout ID holder
let timeout;
// Regex replacement array that converts thumbs to originals
const replaceLib = [
[/\.thumb\.jpe?g$/, ''], // nexusphp
[/\.th\.png$/, '.png'], // pterclub
[/_thumb\.png$/, '.png'], // totheglory
[/img\.awesome\-hd\.me\/t(\/\d+)?\//, 'img.awesome-hd.me/images/'], // awesome-hd
[/thumbs((?:\d+)?\.imgbox\.com\/.+_)t\.png$/, 'images$1o.png'], // imgbox
[/t((?:\d+)?\.pixhost\.to\/)thumbs\//, 'img$1images/'], // pixhost
[/t(\.hdbits\.org\/.+)\.jpg$/, 'i$1.png'], // hdbits
];
// Guess original image selectors on a view page
const guessSelectorLib = [
'img#img',
'.image-container>img'
];
// virtual DOM for selection without fetching images
function $$(htmlString) {
return $(htmlString, document.implementation.createHTMLDocument('virtual'));
}
//<svg height="20" width="20"><text x="0" y="15" fill="white">sample text</text></svg>
function guessOriginalImage(url, src) {
return new Promise((resolve) => {
GM_xmlhttpRequest({
url: url,
method: 'GET',
timeout: 6000,
onload: (x) => {
if (x.status === 200) {
try {
const $e = $$(x.responseText);
resolve($e.find(guessSelectorLib.join(','))[0].src);
}
catch (e) {
console.warn(e);
resolve(src);
}
}
else {
console.warn(x);
resolve(src);
}
},
ontimeout: (e) => {
console.warn(e);
resolve(src);
}
});
});
}
// Function to make an <img/> element
function makeImage(src, outlineColor = 'red') {
return $(`<img src="${src}"/>`).css({
'display': 'none',
'top': '50%',
'left': '50%',
'position': 'fixed',
'transform': 'translate(-50%, -50%)',
'opacity': '1',
'outline': '3px solid ' + outlineColor,
'vertical-align': 'middle'
});
}
// Function fired when compare button is activated
function activateCompare($target) {
$target.attr({
'fill': '#008000'
}).css({
'cursor': 'pointer',
'opacity': '1'
})[0].state = true;
}
// Function fired when compare button is clicked and toggled on
function enterCompare($overlay, $images) {
if (Mousetrap) {
Mousetrap.pause();
}
$overlay.show()[0].state = true;
let colors = ['red', 'blue'];
let step = 1;
$images.on('mouseenter.compare', (e) => {
const target = e.currentTarget;
clearTimeout(timeout);
$overlay.find('img:visible').hide();
if (!target.originalImage) {
const targetSrc = target.src;
let realSrc = targetSrc;
for (let pairs of replaceLib) {
realSrc = realSrc.replace(pairs[0], pairs[1]);
if (realSrc !== targetSrc) {
break;
}
}
/*
if (realSrc === targetSrc) {
const $parent = $(target.parentElement);
}
*/
const $originalImage = makeImage(realSrc, colors[0]);
$originalImage[0].targetImage = target;
$overlay.append($originalImage);
target.originalImage = $originalImage[0];
}
else {
$(target.originalImage).css({
'outline-color': colors[0]
});
}
$(target.originalImage).show();
colors.push(colors.shift());
}).on('mouseleave.compare', (e) => {
const target = e.currentTarget;
timeout = setTimeout(() => {
$overlay.find('img:visible').hide();
}, 200);
});
$(document).on('scroll.compare', (e) => {
const temp = $overlay.find('img:visible')[0];
if (temp) {
const $prev = $(temp.targetImage);
if (!$prev.is(':hover')) {
$prev.trigger('mouseleave');
$images.find('img:hover').trigger('mousenter');
}
}
}).on('keydown.compare', (e) => {
e.preventDefault();
e.stopImmediatePropagation();
switch (e.key) {
case 'Escape':
exitCompare($overlay, $images);
break;
case 'Q':
case 'q':
$overlay.css('opacity', 0.5);
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
step = parseInt(e.key);
break;
case '0':
step = 1;
break;
case 'E':
case 'e':
{
const index = $images.index($overlay.find('img:visible')[0].targetImage);
$($images[index]).trigger('mouseleave');
const nextElem = $images[index + step] || $images[index];
$(nextElem).trigger('mouseenter');
}
break;
case 'W':
case 'w':
{
const index = $images.index($overlay.find('img:visible')[0].targetImage);
$($images[index]).trigger('mouseleave');
const nextElem = $images[index - step] || $images[index];
$(nextElem).trigger('mouseenter');
}
break;
}
return false;
}).on('keyup.compare', (e) => {
e.preventDefault();
e.stopImmediatePropagation();
switch (e.key) {
case 'Q':
case 'q':
$overlay.css('opacity', '');
break;
}
return false;
});
}
// Function fired when compare button is clicked and toggled off
// or quit via keyboard 'esc'
function exitCompare($overlay, $images) {
if (Mousetrap) {
Mousetrap.unpause();
}
$overlay.find('img').hide();
$overlay.hide()[0].state = false;
$images
.off('mouseenter.compare')
.off('mouseleave.compare');
$(document)
.off('scroll.compare')
.off('keydown.compare');
}
// An overlay on the whole page
const $overlay = $('<div/>').css({
'position': 'fixed',
'top': 0,
'right': 0,
'bottom': 0,
'left': 0,
'z-index': 2147483647 - 1,
'background-color': 'rgba(0, 0, 0, 0.75)',
'pointer-events': 'none',
'display': 'none'
});
// The compare button
const $compareButton = $(`<svg xmlns="http://www.w3.org/2000/svg">
<path id="ld" d="M20 6H10c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h10v4h4V2h-4v4zm0 30H10l10-12v12zM38 6H28v4h10v26L28 24v18h10c2.21 0 4-1.79 4-4V10c0-2.21-1.79-4-4-4z"/>
</svg>`).attr({
'width': '30',
'height': '30',
'viewBox': '0 0 48 48',
'stroke': 'white',
'stroke-width': '5px',
'fill': 'gray'
}).css({
'position': 'fixed',
'top': '15px',
'right': '15px',
'z-index': 2147483647,
'paint-order': 'stroke',
'opacity': 0,
'transition': 'all 0.2s',
'cursor': 'auto'
}).on('mouseenter', (e) => {
$(e.currentTarget).attr({
'fill': 'gray'
}).css({
'opacity': 0.2
})
timeout = setTimeout(() => activateCompare($(e.currentTarget)), $overlay[0].state ? 0 : 1000);
}).on('mouseleave', (e) => {
clearTimeout(timeout);
$(e.currentTarget).attr({
'fill': 'gray'
}).css({
'cursor': 'auto',
'opacity': 0
})[0].state = false;
}).click((e) => {
if (e.currentTarget.state) {
switch ($overlay[0].state) {
case false:
enterCompare($overlay, $('img'));
break;
case true:
exitCompare($overlay, $('img'));
break;
}
}
else {
let x = e.clientX;
let y = e.clientY;
const lowerElement = document
.elementsFromPoint(x, y)
.find(e => !['svg', 'path'].includes(e.tagName));
lowerElement.click();
}
}).mousedown((e) => {
if (e.currentTarget.state) {
$(e.currentTarget).attr({
'fill': '#006000'
});
}
}).mouseup((e) => {
if (e.currentTarget.state) {
$(e.currentTarget).attr({
'fill': '#008000'
});
}
})
$overlay[0].state = false;
$compareButton[0].state = false;
$('body').append($compareButton).append($overlay);
})(window.$.noConflict(true), unsafeWindow.Mousetrap);