index-eaw / e621 Thumbnail Enhancer for Artist Watchlist

// ==UserScript==
// @name         e621 Thumbnail Enhancer for Artist Watchlist
// @version      0.74.2
// @description  Adaptation of 'e621_Thumbnail_Enhancer' by rooshoes for 'Artist_Watchlist'
// @author       rooshoes (modified by index)
// @license      MIT
// @homepageURL  http://twitter.com/rooshoos
// @match        *://*.e621.net/artist/watchlist*
// @match        *://*.e926.net/artist/watchlist*
// @match        *://*.e621.net/basis/*
// @match        *://*.e926.net/basis/*
// @match        *://*.e621.net/*basis=true*
// @match        *://*.e926.net/*basis=true*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_log
// @run-at       document-start
// @updateURL    https://openuserjs.org/meta/index-eaw/e621_Thumbnail_Enhancer_for_Artist_Watchlist.meta.js
// @downloadURL  https://openuserjs.org/install/index-eaw/e621_Thumbnail_Enhancer_for_Artist_Watchlist.user.js
// ==/UserScript==

GM_addStyle(`
	body .eab span.thumb {
		width: auto;
		height: 250px;
		margin: 0.75em 0.5em;
	}
	
	body .eab span.thumb > span {
		width: initial !important;
	}

	span.thumb .preview {
		display: block;
		height: 220px;
		width: auto;
	}

	#child-posts-expanded-thumbs span.thumb,
	#child-posts-expanded-thumbs span.thumb .preview {
		width: 180px;
		height: auto;
	}

	span.thumb .post-score {
		width: auto !important;
	}

	span.thumb .tooltip-thumb {
		display: block;
		position: relative;
	}

	span.thumb .gif,
	span.thumb .video {
		position: relative;
		display: block;
	}

	span.thumb .gif:not(:hover)::after,
	span.thumb .video:not(:hover)::after {
		content: '';
		display: block;
		position: absolute;
		top: 0; bottom: 0; left: 0; right: 0;
	}

	span.thumb .video:not(:hover)::after {
		background: transparent 
			url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjA2IiBoZWlnaHQ9IjIwNiIgdmlld0JveD0iMCAwIDIwNiAyMDYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxjaXJjbGUgaWQ9ImIiIGN4PSI5MCIgY3k9IjkwIiByPSI5MCIvPjxmaWx0ZXIgeD0iLTUwJSIgeT0iLTUwJSIgd2lkdGg9IjIwMCUiIGhlaWdodD0iMjAwJSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBpZD0iYSI+PGZlT2Zmc2V0IGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiLz48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSI2LjUiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIvPjxmZUNvbXBvc2l0ZSBpbj0ic2hhZG93Qmx1ck91dGVyMSIgaW4yPSJTb3VyY2VBbHBoYSIgb3BlcmF0b3I9Im91dCIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjI5NDE1NzYwOSAwIiBpbj0ic2hhZG93Qmx1ck91dGVyMSIvPjwvZmlsdGVyPjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzIDEzKSI+PHVzZSBmaWxsPSIjMDAwIiBmaWx0ZXI9InVybCgjYSkiIHhsaW5rOmhyZWY9IiNiIi8+PHVzZSBmaWxsLW9wYWNpdHk9Ii44ODEiIGZpbGw9IiNGRkYiIHhsaW5rOmhyZWY9IiNiIi8+PC9nPjxwYXRoIGZpbGwtb3BhY2l0eT0iLjQiIGZpbGw9IiMwMDAiIGQ9Ik04MS41IDE0Ny42NThWNTkuMzQybDY1IDQ0LjE1OCIvPjwvZz48L3N2Zz4=)
			no-repeat center/80px;
	}

	span.thumb .gif:not(:hover)::after {
		content: 'GIF';
		width: 60px;
		height: 30px;
		margin: auto;
		font-size: 16px;
		font-weight: bold;
		line-height: 30px;
		color: rgba(0,0,0,0.4);
		background-color: rgba(255,255,255,0.8);
		border-radius: 6px;
		box-shadow: 0 0 13px rgba(0,0,0,0.29);
	}

	span.thumb .gif:hover .preview {
		display: none !important;
	}

	span.thumb .gif:hover img.preview {
		display: block !important;
	}
`);

(async function(){

    await new Promise(resolve => {
        window.addEventListener('DOMContentLoaded', resolve, { once: true });
    });

	function tagEnhanced(node) {
		node.dataset.enhanced = 'true';
	}
	
	function checkEnhanced(node) {
		return (node.dataset && node.dataset.enhanced);
	}

	function is_gif(i) {
		return /^(?!data:).*\.gif/i.test(i.src);
	}

	function freeze_gif(i) {
		var c = document.createElement('canvas');
		var w = c.width = i.naturalWidth;
		var h = c.height = i.naturalHeight;
		var p = i.parentNode;
		c.getContext('2d').drawImage(i, 0, 0, w, h);
		p.className = ['gif', p.className].join(' ');
		try {
			i.src = c.toDataURL("image/gif"); // if possible, retain all css aspects
		} catch(e) { // cross-domain -- mimic original with all its tag attributes
			replaceImg(c, i);
			i.style.display = 'none';
			
			tagEnhanced(i);
			p.insertBefore(i, p.firstChild);
		}
	}

	function replaceImg(e, i) {
		e.className = i.className;
		var s = window.getComputedStyle(i);
		e.style.border = s.getPropertyValue('border');
		e.style.borderRadius = s.getPropertyValue('border-radius');
		
		tagEnhanced(e);
		i.parentNode.replaceChild(e, i);
	}

	function setVideo(thumb, videoUrl) {
		var video = document.createElement('video'),
			parent = thumb.parentNode;
		video.controls = false;
		video.loop = true;
		video.muted = true;
		video.preload = 'metadata';
		video.addEventListener('loadedmetadata', function(){
			parent.className = ['video', parent.className].join(' ');
			parent.addEventListener('mouseenter', function(){ video.play(); });
			parent.addEventListener('mouseleave', function(){ video.pause(); });
			replaceImg(this, thumb);
		}, false);
		video.src = videoUrl;
	}

	/* Replace video thumbnails with actual playable video */
	function videoThumb(thumb, parseHTML) {
		parseHTML = parseHTML || false;
		var parent = thumb.parentNode;

		GM_xmlhttpRequest({
			method: "GET",
			url: parent.href,
			headers: {
				"Accept": "text/xml"
			},
			onload: function(response) {
				var videoUrl = null;
				if (response.readyState !== response.DONE) return;
				if (!response.responseXML) return;

				var file_url = response.responseXML.getElementsByTagName('file_url')[0];
				if (!file_url) return;

				videoUrl = file_url.childNodes[0].nodeValue;
				setVideo(thumb, videoUrl);
			}
		});
	}

	/* Replace image thumbnails with higher resolution */
	function imageThumb(thumb) {
		var newThumb = new Image(),
			replace = function(thumb) {
				thumb.src = this.src;
				if (is_gif(thumb)) {
					freeze_gif(thumb);
				}
			},
			tryFullPng = function(thumb) {
				this.onerror = null;
				this.src = thumb.src.replace('/preview/', '/').replace('.jpg','.png');
			},
			tryFull = function(thumb) {
				this.onerror = tryFullPng.bind(this, thumb);
				this.src = thumb.src.replace('/preview/', '/');
			},
			tryGif = function(thumb) {
				this.onerror = tryFull.bind(this, thumb);
				this.src = thumb.src.replace('/preview/', '/').replace('.jpg','.gif');
			};
		
		if (thumb.src.includes('download-preview.png')) {
			thumb.src = thumb.src.replace('static1.e621.net/data/preview/static1', 'static1');
		} else {
			newThumb.onload = replace.bind(newThumb, thumb);
			newThumb.onerror = tryGif.bind(newThumb, thumb);
		}
		
		newThumb.src = thumb.src.replace('/preview/', '/sample/');
	}

	function imgError(image) {
		image.onerror = "";
		image.src = "/images/noimage.gif";
		return true;
	}


	function newThumb(thumb) {
		switch(true) {
			case /webm-preview.png/.test(thumb.src):
				// Slight delay for rate-limiting
				(function(thumb, i){
					setTimeout(function(){
						videoThumb(thumb);
					}, 100*i);
				})(thumb, i);
				break;
			default:
				imageThumb(thumb);
		}
	}

	function searchThumbs(target) {
	}

	//}-------------------//
	//-- Observer -------//{
	//--------------------//
	let content = document.getElementById('content');
	let config = { childList: true, subtree: true };

	let observer = new MutationObserver( (mutationsList, observer) => {
		for(let mutation of mutationsList) {
			if (mutation.type != 'childList') return;
			
			mutation.addedNodes.forEach(node => {
				if (!(node instanceof HTMLElement)) return;
				
				let thumbs = node.querySelectorAll('span.thumb img');

				thumbs.forEach( thumb => {
					if ( !checkEnhanced(thumb) ) newThumb(thumb);
				} );
			} );
		}
	} );

	observer.observe(content, config);

})();