Eza's Gallery Swallower

Turn a page of thumbnails into high-res images

Versión del día 19/02/2021. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name        Eza's Gallery Swallower
// @namespace   https://inkbunny.net/ezalias
// @description Turn a page of thumbnails into high-res images
// @license     MIT
// @license     Public domain / no rights reserved
// @include     https://www.pixiv.net/en/users/*
// @include     https://www.pixiv.net/bookmark_new_illust.php*
// @include     https://www.pixiv.net/en/tags/*/artworks*
// @include     https://www.pixiv.net/user/*/series/*
// @include     https://gelbooru.com/index.php?page*
// @include     https://e621.net/posts*
// @include     https://e926.net/posts*
// @include     https://*.booru.org/*s=list*
// @include     http://www.hentai-foundry.com/pictures/user/*
// @include     https://baraag.net/@*
// @exclude    *#dnr
// @noframes
// @version     2.6.14
// @grant        GM_registerMenuCommand
// ==/UserScript==



// Create a vertical view for high-res versions of all the image links you can see. 
// This includes all pages from multi-image submissions, where those exist.
// Individual images (or whole submissions) can be removed with one click. 
// Each image is also a link. 

// This script is no longer a mess. These notes, well, they're okay. 
// Oh, and the CSS is this way because I use a tallscreen monitor. Change img.short's max-height if that annoys you. 

// Scroll down past the wall of complaining to get to the code. 



// It might not even be possible to identify what error code onError caught. Jesus Christ, HTML. 
// Fetch still doesn't work on Pixiv. 
	// fetch( 'https://i.pximg.net/c/250x250_80_a2/img-master/img/2020/05/23/10/59/10/81782474_p0_square1200.jpg', { credentials: 'include' } ).then(response => response.json()).then(data => console.log(data)); - returns an empty object, but never, ever logs anything. Without credentials there's at least a CORS error. 
	// I literally just want to see the response. It should not be possible to not get that information - 'fine' is 200, 'fuck off' is 403, and even no response is 500. 
	// Oh for fuck's sake. I just tested it in mainstream Firefox to see if it was my old browser being shitty - FF still throws a CORS error.
	// It's i.pximg.net from pixiv.net. Aaaugh. 
	// I just want to know if a fucking image file exists! This is not an attack vector - you can do <img src onerror> and find the same damn information. Domain-agnostic. 
	// Do old-school  XMLHttpRequest()s work? I only want the head. Ugh, probably still stopped by CORS. 
	// Javascript: the language where reading a file is prohibited for safety, but executing it as a script is no-questions-asked. 
	// ... can we just run it as a script and see if it 404s? Obviously it won't work if it exists. 

// At this point I just need to style=visibility:hidden when things scroll offscreen. Same size, and god willing, no VRAM use. 
	// Maybe an interval that does this.src=this.src for all img elements - to fight when things half-load? Can't exactly press F5. 
	// Array.from( document.getElementsByTagName( 'img' ) ).forEach( i => i.src = i.src ) - inconsistently effective. 
	// I could add ?random-number to force a reload, but that's a complete re-download, so it's inadvisable. Can I check if it failed to-- oh right, onError. 
	// But I need to know what the error is, since we're still removing 404'd img elements with bad file extensions. 

// I should maybe move or copy the thumbnails into a top bit, like Pixiv Fixiv. 
	// Also, like Pixiv Fixiv, I should probably have the ability to repopulate existing sub-spans with images, 
		// both for "augh all PNGs slow loading" and "ah fuck loading broke." 

// What I have so far could easily replace Pixiv Fixiv's current code. The spans with this.parentElement.remove() stuff, anyway; we know the image count beforehand. 
	// For both, I'd like to stretch a thumbnail behind an image as it's loading. I'd need non-square thumbs. Or at least I'd really prefer them. 
	// I guess style=backgroundWhatever:that.src? I keep adding and removing this -> that for some damn reason. 

// Change the BG color to something dark. (Ego says #324.) 
// Testing whether loading shenanigans work: just make images visible on mouseover. 
// This accidentally ignores muted images - I'm calling that a feature. 
	// Ugoira animations get a spot, but don't load an image. Semi-feature. Deserves better handling and indication. 
// Clunky solution to memory problems: when an image loads, remove it, and put its URL in a list. 

// This script suggests a generic solution to Twitter nonsense: replace tweet divs with their media. Break classes and IDs so their infinite-scrolling JS can't remove them. No worries about their stupid random class names, since we'd just parent.parent.parent and then .replace(). 
	// Also I could probably just .remove() the sidebar bullshit. 

// Relying on onError suuucks. I categorically NEED a way to distinguish 'didn't finish loading' from 'file not found.' 
	// I can maybe get around this by assuming all the thumbnails load. They're tiny and quick. 
	// So if a thumbnail removes itself and produces a div where one file extension is expected to load, 
		// and that element has no children, we can infer the load failed, and start over with the three separate file extensions. 
	// Maybe ctrl+z instead of a per-image 're-show' button? Stick per-image-group IDs on a list, first in last out, as they're removed. 
	// Still might need per-submission do-over buttons for when loading is borked. Still want them, anyway. 
	// Be positive: have an onLoad for each image format. Signal success, not implicit failure. 
		// Or I guess do both: onError, check for success? 
		// Even just onError not removing an image if the other two are already gone would safely assume it timed out while the others 404'd. 

// Getting decent. Clone Pixiv Fixiv scaling modes, via changing image class and body class. Maybe go for unicode symbols instead of words. 
	// E.g. <-->, the vertical version of that, the cross version... '1:1' could just be text. Point is: not English. 
	// I can probably also do one-page-onscreen instead of as-it-comes vertical spacing, since I have divs around each image. 
// And use a damn dark mode already! 
// Buttons for forward/back? Floating over top, maybe. scrollTo stuff. Ech, but it has to update as you manually scroll down, so prev/next are at least consistently relative. 
	// Ideally the focus is somewhere in the middle of the screen, not like, one scanline of an image counts as being 'on' that image. 
// Per-image removal buttons could be larger and lower-contrast. Big easy target. Ignorable. 
// Spinner widget for individual images loading? I.e., keep spinning while the page is unfinished. That might be as simple as a DOM check. 
	// body.onload happens repeatedly, yeah? 
	// Style: thinking slow rotation, lower contrast, possibly inside the other spinner. 
	// I probably have to set/reset body.onError every time a new big-image starts loading. 
	// onstalled? onwaiting? onshow, for other things. Is oninvalid different from onerror? 
// Count beside spinners? 'X images, N loading.' 
// I could trivially turn this into a Pixiv Fixiv replacement - right? All I need is any image URL. No JSON shenanigans. 
// A stand-in for images that haven't loaded would be nice. Maybe their thumbnail, in thumbnail size. Just tell me when the top manga will affect scrolling. 
// Killing the submission reeeally needs to kill the underlying images. Maybe add another sub-sub-sub-span for next_image to target, and remove that. Then reloading the submission recreates that span before calling next_image on p0. 
// Come on, I can hack together previous / next page links. Only at the bottom. Doesn't trigger automatically. Just slap it in there. 
	// https://www.pixiv.net/en/users/1346633/artworks?p=2 -> p=1 and p=3. Needs to handle bare /artworks to infer ?p=1. 
	// Add buttons for scrolling to prev/next image/submission. Left-aligned, top of each image. 
// Long-term issue: this looks boring on a widescreen monitor. It's all tallscreen settings. Massive negative space otherwise. 
// Needs a big reload-all button at the top. 
// If I'm being clunky, I can probably grab image count from the counters in the corners. 
// Kinda want a "remove all first images" button. So many censored thumbnails. Japan - fuck off already. We're sorry we colonialized you. Stop ruining porn. 
// Ooh, Array.find(). Pass it a test function and it'll return the first element that matches. 

// This could trivially work on Baraag. (And other Mastodon sites.) 
// Oh, file-extension issues in Universal Scraper don't apply here. 
	// e621. 
	// Gelbooru:
		// https://thumbs.gelbooru.com/0e/dd/thumbnail_0edd1b5e5d7a1ba2488a135d60d70c78.jpg
		// https://img2.gelbooru.com//images/0e/dd/0edd1b5e5d7a1ba2488a135d60d70c78.jpg

// Image Toolbox on Pixiv shows the toolbox centered... with text... at the top of the screen. What is even the fuck. 
// This goes beyond "CSS is bullshit." This is getting into "CSS is haunted." 
// Removed the style making all spans position:relative, and that fixed the position, but there's still text. Only on Pixiv's bookmarks page. Why. 

// Gelbooru is inconsistent about WebMs. Some resolve to images. Others are blank. Dunno which is better, but pick one. 
	// https://gelbooru.com/index.php?page=post&s=list&tags=rosiekawaii 
// Gelbooru is inconsistent about images as well. https://gelbooru.com/index.php?page=post&s=view&id=5884654#dnr#&dnr 
	// https://gelbooru.com/index.php?page=post&s=list&tags=batako1812+manyuu_chifusa
	// https://img3.gelbooru.com//images/a5/de/a5de992f2e7cd27a79ffdc35e9ece3e1.jpeg
	// Oh, right, JPEG. Duh. I even anticipated that. 
// Still, address videos on Gelbooru. E.g. https://gelbooru.com/index.php?page=post&s=view&id=5614716&tags=rosiekawaii#dnr#&dnr
	// https://img3.gelbooru.com/images/3c/23/3c2376210444f7a7da737b412c722faa.jpg
	// https://img3.gelbooru.com//images/3c/23/3c2376210444f7a7da737b412c722faa.webm
	// Totally doable. So how do I check for a file without just embedding videos? Ugh, might be beyond onload / onerror behavior. 
	// Orrrr I could check tags. Not reliable. Could be wrong. But would allow a <video> with onerror to fall back to images. 
	// Better idea: indicate that it's probably a video, and link to the page without #dnr&dnr. Use Eza's Image Glutton, folks. 
		// Obviously that's the Ugoira solution on Pixiv: "don't." So it should be how I handle Ugoiras now that they're broken-ish. 
	// Embedding videos is undesirable because it implies linking to them for download. 
		// This script is already rude on bandwidth - videos would make it a DDOS attack. 
	// Solution: show thumbnail, not linked. (To avoid DownThemAll grabbing the thumbnail.) 

// FurAffinity? 
	// https://t.furaffinity.net/[email protected]
	// https://d.furaffinity.net/art/ratcha/1613453026/1613453026.ratcha_party.jpg
	// Nope, needs a fetch. 

// CSS-only? Absurd tangent, but GreasyFork now lists CSS userscripts. It's probably possible to replace thumbnails on gallery websites with RegEx'd full-size versions, and fallbacks for each file extension. And obviously I think you can insert a line-break ::after each image and make them full-size or max-width / max-height. But yeesh. 

// Visions of replacing filter-filter-filter chains with querySelectorAll fell through because A>B[C] always grabs B... not A. 
	// let things = Array.from( document.querySelectorAll( 'a > img[src*="thumbs"]' ) ); 
	// You can get all images contained by a link. You cannot get all links containing an image. 
	// ... though you could get the images and do .parentElement a bunch. 
	// .closest( 'a' ) seeeeems to work. I do not know if it prefers hierarchy or in which direction. 
	// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest 
	// Oh: it is explicitly going up the chain. Kickass, perfect. 

// Might use thumbnails of first image as scroll-into-view links at the top of the page. 
	// Maybe with Xs on each to remove things from there as well? Questionable. 

// Probing is possible without thumbnails - just onLoad( classList add "ready" ). Same results, but slower. Meh. 



	// Genuine bugs:

// https://www.pixiv.net/user/55117629/series/87453?p=2 - gets everything but the manga. Different page format. 

// Ugoira submissions display a big floating X, because those belong to the image-extension-trying block, not to each self-removing image. 
	// Ahh, it's because next_image would try _p0 and fail. This rewrite assumes the first image works. Maybe do cleanup in the interval?

// Dead images and possibly thumbnails take up horizontal space and cause slight movements while loading. 

// Spinners don't work on Baraag. 
	// And how could they? We never update per-element style. Even if @animation worked, it'd be stuck there. 



// Changes since last version:
	// Fixed Baraag.net previous / next controls. (PEBKAC.) 




// ------------------------------------ Custom replacement HTML ------------------------------------ //





// Replacement page. Not used immediately; it just makes more sense up here. 
var html = '';
var style_rules = new Object; 			// CSS "selector": "style" map. Blame Baraag. 



	// ----- //			CSS



// CSS rules as an associative array, so they can be applied per-element on uncooperative sites. 

// Image style(s):
style_rules[ "img.short" ] = "max-width: 90vw; max-height: 60vh; z-index: 10; vertical-align: middle;"; 

// Dead spinners:
style_rules[ '.spacer' ] = 'position: absolute; width: 0px; height: 0px;'; 
style_rules[ '.other_spacer' ] = 'position: absolute; width: 0px; height: 0px;'; 	/* Arbitrarily smaller. */
style_rules[ '#image_counter' ] = 'position: absolute; left: 70px; top: 5px; font-size: 33px;';

// Controls: 
style_rules[ '.remover' ] = 'background-color:#d7dbd8; border-radius: 50%; width: 60px; height: 60px; text-align: center; display: inline-block; border:1px solid #ab1919; cursor:pointer; line-height: 20px; color:#4d1919; font-family:Arial; font-size:17px; padding: 10px 10px; text-decoration:none;';
style_rules[ '.remover:hover' ] = 'background-color:#bd2a2a;';
style_rules[ '.floating' ] = 'z-index: 1; position: absolute; top: 50%; -ms-transform: translateY(-50%); transform: translateY(-50%); width: 120px; height: 120px; font-size:45px;';
style_rules[ '.reloader' ] = 'background-color:#dbd7d8; border-radius: 50%; width: 60px; height: 60px; text-align: center; display: inline-block; border:1px solid #19ab19; cursor:pointer; line-height: 20px; color:#194d19; font-family:Arial; font-size:33px; padding: 10px 10px; text-decoration:none;';
style_rules[ '.reloader:hover' ] = 'background-color:#2abd2a;';

// Hidden thumbnails, probing to check for next image in multi-image sets:
style_rules[ '.test, .thumb' ] = 'display:none'; 

// Push all of that into a <style> block:
html += '<style> ';
for( selector in style_rules ) { 
	html += selector + ' { ' + style_rules[ selector ] + ' } \n'; 
} 
html += '</style> ';

// Spinners:
html += '<style> .submissions_loader { position: absolute; left: 0px; top: 0px; border: 8px solid #3498db; border-top: 8px solid #111111; border-bottom: 8px solid #111111; border-radius: 50%; width: 48px; height: 48px; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style>'; 		
html += '<style> .images_loader { position: absolute; left: 8px; top: 8px; z-index: -1; border: 24px solid #db9834; border-top: 24px solid #aaaaaa; border-bottom: 24px solid #AAAAAA; border-radius: 50%; width: 0px; height: 0px; animation: images_spin 3s linear infinite; } @keyframes images_spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style>';  
// Either I bodged the implementation of style_rules to a <style> block, or there's some other obstacle to animations. So these are separate. 




	// ----- //			Page elements



html += '<div class="submissions_loader" id="submissions_spinner"></div>' 		/* Spinner for submissions, 'new images being found.' */
html += '<div class="images_loader" id="images_spinner"></div>' 		/* Spinner for images, 'images loading in high-res.' */
html += '<span id="image_counter"></span>'; 		/* This detaches. */

html += '<br><br><center><span id="centered"></span></center>';  		/* Where stuff goes. */
html += '<br><br><br>'; 		/* Spacing for prev/next links. */
html += '<center><span id="links"></span></center>'
html += '<br><br><br><br><br>'; 		/* Runout. */

// Additional HTML may be added by per-site functions. Once injected, we use the DOM. 





// ------------------------------------ General setup & Per-site functions ------------------------------------ //





	// ----- //			Global variables 



var trigger_size = [ 15, 70, 25, 10 ]; 		// Left, top, font-size, padding. Pixiv defaults. 
var button_delay_function = false; 			// Should maybe have separate boolean and function. 
var proper_fetch = false; 			// If true, we fetch pages instead of modifying thumbnail URLs. (Not yet implemented.) 
var top_to_bottom = false; 		// Chronological order is confusing to describe. 
var automatic_pagination = false; 		// Pixiv-style p0, p1, p2, etc. 
var gather_items;  		// Per-site function to scrape page contents. 
var items;  					// Scraped contents of page. 
var formats = [ ".png", ".jpg", ".gif", ".jpeg" ]; 		// File extensions. (Edit: no sense not having JPEG by default.)
var page_number = 1; 				// Default.
var next_page, previous_page;  	// URLs of obvious purpose. 
var thumb_followup = '';  		// Used to be part of "items," per-item, but only Pixiv uses them, and they're standard. 
var image_followup = ''; 		// Ditto. 
var new_image_rate = 200; 		// Milliseconds between checks for "ready" submissions. Lower is faster. 
var force_style = false; 			// If sites don't allow inline CSS, apply style_rules to each element. 




	// ----- //			Helper functions 



// Turn window.location.search into a sensible associative array. Should be standard, guys!
function parse_search( search_string ) { 
	let strings = search_string.split( /[\?\&]/ )
		.filter( v => v ) 		// Remove empty strings
	let associative = new Object;
	strings.forEach( v => associative[ v.split('=')[0] ] = v.split('=')[1] ); 
	return associative; 
}

function scrub_extensions( url, format_list ) { 
	if( format_list == null ) { format_list = formats; } 		// Shut up, it's global. 
	format_list.forEach( ext => 
		url = url.replace( ext, '' ) )
	return url; 
} 



	// ----- //			Per-site setup and gather functions 



var args = parse_search( window.location.search ); 		// DRY 

switch( document.domain.replace( 'www.', '' ) ) {

	case 'pixiv.net':
		thumb_followup = '_square1200'; 
		formats = [ ".png", ".jpg", ".gif" ]; 	// I have never seen a JPEG on Pixiv. I have 100,000 _p0 JPGs, and they're all ".jpg". 
		automatic_pagination = true; 

		// Ever-useful test profile: https://www.pixiv.net/en/users/53625793 
		button_delay_function = () => 
			document.querySelector( 'img[src*="250x"]' ) ||
			document.querySelector( 'a *[style*="240x"]' ); 

		// https://www.pixiv.net/bookmark_new_illust.php?p=2
		if( args["p"] ) { page_number = parseInt( args["p"] ); } 
		next_page = window.location.origin + window.location.pathname + "?p=" + (page_number + 1)
		previous_page = window.location.origin + window.location.pathname + "?p=" + (page_number - 1); 
		// And now per-image removers are at the bottom again. because CSS is punishment for mankind's hubris. 

		gather_items = function() { 
			// Not sure the Array-map approach clarified or simplified anything, in Pixiv's case...
			return Array.from( document.querySelectorAll( 'a[href*="/art"] img[src*="c/2"]' )  )
				.map( v => v.closest( 'a' ) )
				// https://www.pixiv.net/en/users/53625793
				.map( v => new Object( { 
					page: 		v.href,
					title: 			v.href.split('/').pop(),
					// https://i.pximg.net/c/250x250_80_a2/img-master/img/2020/06/22/05/58/05/82488314_p0_square1200.jpg
					// https://i.pximg.net/img-original/img/2020/06/22/05/58/05/82488314_p0.jpg
					thumb: 	scrub_extensions( v.querySelector( 'img' ).src )
						.replace( '_custom1200', '' )
						.replace( '_master1200', '' )
						.replace( '_square1200', '' )
						.replace( '_p0', '_p' ),
					image: 		scrub_extensions( v.querySelector( 'img' ).src )
						.replace( '_custom1200', '' )
						.replace( '_master1200', '' )
						.replace( '_square1200', '' )
						.replace( '_p0', '_p' )
						.replace( 'c/250x250_80_a2/', '' )
						.replace( 'c/240x240/', '' )
						.replace( 'img-master/', 'img-original/' )
						.replace( 'custom-thumb/', 'img-original/' ) 		// Some of these are redundant but it's not worth figuring out which. 
				} ) )

			.concat( Array.from( document.querySelectorAll( 'a *[style*="background-image"]' ) )
				.map( v => v.closest( 'a' ) )
				// https://www.pixiv.net/bookmark_new_illust.php?p=2 
				.map( v => new Object( { 
					page: 		v.href,
					title: 			v.href.split('/').pop(),
					// url("https://i.pximg.net/c/240x240/img-master/img/2021/02/17/04/05/41/87838098_p0_master1200.jpg")
					// https://i.pximg.net/img-original/img/2021/02/17/04/05/41/87838098_p0.jpg
					thumb: 	scrub_extensions( v.querySelector( 'div[style]' ).style.backgroundImage )
						.replace( 'url("', '' )
						.replace( '")', '' ) 
						.replace( '_custom1200', '' )
						.replace( '_master1200', '' )
						.replace( '_square1200', '' )
						.replace( '_p0', '_p' ),
					image: 		scrub_extensions( v.querySelector( 'div[style]' ).style.backgroundImage )
						.replace( 'url("', '' )
						.replace( '")', '' )  
						.replace( '_custom1200', '' )
						.replace( '_master1200', '' )
						.replace( '_square1200', '' )
						.replace( '_p0', '_p' )
						.replace( 'c/250x250_80_a2/', '' )
						.replace( 'c/240x240/', '' )
						.replace( 'img-master/', 'img-original/' )
						.replace( 'custom-thumb/', 'img-original/' ) 
				} ) )
			)

/*
			let things = Array.from( links ) 		// There's probably a less-verbose querySelectorAll for this.
				.filter( a => a.href )
				.filter( a => a.href.indexOf( '/artworks/' ) > 0 )
				.filter( a => a.querySelector( 'img' ) )
				.filter( a => a.querySelector( 'img' ).src )
				.filter( a => a.querySelector( 'img' ).src.match( 'c/2' ) );
			// document.querySelectorAll( 'a[href*="/art"] img[src*="c/2"]' ) 
			things = things.concat( Array.from( links ) 		// Sort and reverse: side-effectful. Concat: functional. Go fuck yourself, Javascript. 
				.filter( a => a.innerHTML.match( 'background-image' ) )
			) 
			// document.querySelectorAll( 'a *[style*="background-image"]' )
			let items = new Array;
			things.forEach( (v,i,a) => { 
				let item = new Object;
				item.page = v.href; 
				item.title = v.href.split('/').pop(); 		// Just the number - large print. 

				// https://i.pximg.net/c/250x250_80_a2/img-master/img/2020/06/22/05/58/05/82488314_p0_square1200.jpg
				// https://i.pximg.net/img-original/img/2020/06/22/05/58/05/82488314_p0.jpg
				if( v.querySelector( 'img' ) ) { 	// Would it kill you to return null for all properties of null? Does it have to be a TypeError? 
					item.thumb = v.querySelector( 'img' ).src
				} else { 
					item.thumb = v.querySelector( 'div[style]' ).style.backgroundImage
						.replace( 'url("', '' )
						.replace( '")', '' ); 
				}
				item.thumb = scrub_extensions( item.thumb )
				item.thumb = item.thumb
					.replace( '_p0', '_p' );   		// Remember to add a page number. 
				item.thumb = item.thumb
					.replace( '_custom1200', '' )
					.replace( '_master1200', '' )
					.replace( '_square1200', '' ); 
				item.image = item.thumb
					.replace( 'c/250x250_80_a2/', '' )
					.replace( 'c/240x240/', '' )
					.replace( 'img-master/', 'img-original/' )
					.replace( 'custom-thumb/', 'img-original/' );		// And still remember to add a page number. 

				items.push( item ); 
			} ); 
			return items; 
*/
		}
	break; 

	case 'gelbooru.com':
		// https://gelbooru.com/index.php?page=post&s=list&tags=shuujin_academy_uniform+chair+1girl+pink_background 
		trigger_size = [ 15, 35, 16, 5 ]; 		// Left, top, font-size, padding. 

		// https://gelbooru.com/index.php?page=post&s=list&tags=4girls
		// https://gelbooru.com/index.php?page=post&s=list&tags=4girls&pid=42
		// https://gelbooru.com/index.php?page=comment&s=list&pid=10 		// Whoops. 
		page_number = 0; 
		let pages_at_once = 42; 
		if( window.location.href.match( 'page=comment' ) ) { pages_at_once = 10; } 
		if( args["pid"] ) { page_number = parseInt( args["pid"] ); } 

		next_page = window.location.href + "&pid=" + (page_number + pages_at_once); 		// Flawed, but it works. 
		previous_page = window.location.href + "&pid=" + (page_number - pages_at_once); 

		gather_items = function() { 

			return Array.from( document.querySelectorAll( 'a[href*="s=view"]' ) )
				.map( v => new Object( { 
					// https://gelbooru.com/index.php?page=post&s=view&id=4179699&tags=pink_background
					page: 		v.href,
					title: 			v.href.split( '&id=' )[1].split( '&' )[0],
					// https://img3.gelbooru.com/thumbnails/f5/c7/thumbnail_f5c7826072943fd72076ba9121b473f0.jpg
					// https://img3.gelbooru.com/images/f5/c7/f5c7826072943fd72076ba9121b473f0.jpg
					thumb: 	scrub_extensions( v.querySelector( 'img' ).src ),
					image: 		scrub_extensions( v.querySelector( 'img' ).src )
						.replace( '/thumbnails', '/images' )
						.replace( 'thumbnail_', '' ) 
				} ) )

		}
	break;

	// Janky e621 issues seem to be because JS isn't allowed to run on the page? Somehow? It's not ScriptBlock. 
		// "The page’s settings blocked the loading of a resource at self." 
		// Aaaargh there's probably some horrible version of this where I fake on_error and on_load inside this script's interval function. 
	// Intermittent issue: just had e621's images all disappear. Second time this has happened. Not sure the first time was on this site. 
		// Both times, the page had been sitting open (second monitor) for like a minute. Fresh reload before that. Console almost certainly open.
		// img elements still show valid src. 
		// .post-preview, #image-container, #c-comments .post, .mod-queue-preview.post-preview, .post-thumbnail { visibility: hidden !important; }
		// ... so why did that get set? It's triggering on .post-preview. That's at the <article> level... for some reason. 
		// This code only touches article elements to check for blacklisting, it only does that if gather_items is called. Which it wasn't. 
	case 'e621.net':
	case 'e926.net':
		// https://e621.net/posts?tags=somik+mirror
		trigger_size = [ 15, 50, 16, 5 ]; 		// Left, top, font-size, padding. 

		// https://e621.net/posts
		// https://e621.net/posts?page=2
		// https://e621.net/posts?tags=asthexiancal++
		// https://e621.net/posts?page=2&tags=asthexiancal++ 
		if( args["page"] ) { page_number = parseInt( args["page"] ); } 
		next_page = window.location.href + "&page=" + (page_number + 1); 		// Insufficient on https://e621.net/posts
		if( next_page.indexOf( '?' ) < 0 ) { next_page = next_page.replace( '&', '?&' ); } 		// Klunk. 
		previous_page = window.location.href + "&page=" + (page_number - 1); 

		gather_items = function() { 

			return Array.from( document.querySelectorAll( 'a[href*="posts/"] img' ) )
				.map( v => v.closest('a') )
				.filter( v => ! v.closest('article').className.match( 'blacklisted-active' ) ) 		// Exclude hidden posts. 
				.map( v => new Object( { 
					// https://e621.net/posts/1333873?q=somik+mirror
					page: 		v.href,
					title: 			v.href.split( 'posts/' )[1].split( '?q=' )[0],
					// https://static1.e621.net/data/preview/37/75/3775cd8664c688f98a41780f6796ce86.jpg
					// https://static1.e621.net/data/37/75/3775cd8664c688f98a41780f6796ce86.png
					thumb: 	scrub_extensions( v.querySelector( 'img' ).src ),
					image: 		scrub_extensions( v.querySelector( 'img' ).src )
						.replace( '/preview', '' )
				} ) )

		}
	break;

	case 'hentai-foundry.com':
		var trigger_size = [ 15, 20, 25, 10 ]; 

		// button_delay_function might be the only way to separate gallery pages from submission pages. HF's URLs are duuumb. 

		// http://www.hentai-foundry.com/pictures/user/AmaZima/page/9
		if( window.location.href.indexOf( '/page/' ) > 0 ) { 
			page_number = parseInt( window.location.href.split( '/' ).pop() ); 
			previous_page = window.location.href.split( '/page' )[0] + '/page/' + (page_number - 1); 
		} 
		next_page = window.location.href.split( '/page' )[0] + '/page/' + (page_number + 1);  	// page_number defaults to 1. 

		gather_items = function() { 

			return Array.from( document.querySelectorAll( 'a.thumbLink' ) ) 
				.map( v => {
						// http://www.hentai-foundry.com/pictures/user/AmaZima/589016/Tired-but-happy-Lottie
						let username = v.href.split( '/' )[5]; 	// AmaZima
						let title = v.href.split( '/' )[6]; 			// 589016
						return new Object( { 
							page: 		v.href,
							title: 			title, 
							// url("//thumbs.hentai-foundry.com/thumb.php?pid=589016&size=350")
							// http://pictures.hentai-foundry.com/a/AmaZima/589016/AmaZima-589016-Tired_but_happy_Lottie.png
							// http://pictures.hentai-foundry.com/t/Tixnen/869342/Tixnen-869342-Vasilina.jpg
							thumb: 	scrub_extensions( v.querySelector( 'span[style]' ).style.backgroundImage )		// Why would it be easy.
								.replace( 'url("', '' )
								.replace( '")', '' ),
							image: 		window.location.protocol + "//pictures.hentai-foundry.com/" 
								+ username.slice( 0, 1 ).toLowerCase() + '/' + username + '/'
								+ title + '/' 
								+ username + '-' + title + '-'
								+ v.href.split( '/' ).pop().replace( /-/g, '_' )
						} )
				} )

		} 
	break; 

	// Baraag... and Mastodon in general, ideally. 
	// Oof. Baraag has multiple submissions per post, but they're not numerically related.
	// JS answer: let items.image be an array. Check typeOf and either show one or all. 
		// Better JS answer: check typeOf and show one at a time, like Pixiv. (Easier to handle individual Xs that way anyhow.) 
		// Bonus for Baraag, we can set formats to [""] and just show the correct image.
		// Also maybe delay when "ready" again, so we don't instantly re-ready this one submission. Avoid linearity. 
		// We don't need a thumbnail value. Buuut we might have one anyway, if we implement Pixiv Fixiv style shortcuts at the top. 
	// Instead of typeOf - item.thumbs vs item.thumb? Neither is especially elegant. Global boolean?
		// I am hard pressed not to just lean on typeof. Why keep track? If you pass an array of URLs, you get an array of images. 
		// Out-of-bounds signalling for page count -might- still be useful for Pixiv. That'd let us skip the thumbnail-try stuff. 
	// Argh, retweets ("boosts?") aren't of class h-entry. They're h-cite. Can I just querySelector with a comma? Sure can. 
		// Guess that puts a nail in whether to grab links and filter down, or one-shot with complex CSS and work up. 
	case 'baraag.net':
		formats = [""]
		force_style = true; 		// querySelectorAll, element.style = be round, dammit. 

		// https://baraag.net/@Applalt/media
		// https://baraag.net/@Applalt/media?max_id=105165058865018699 // Yeesh. 
		let navs = document.querySelectorAll( 'a[class*="load-more"]' ); 
		if( navs ) { 
			if( navs.length == 2 ) { previous_page = navs[0].href; next_page = navs[1].href; }  		// Both
			else if( window.location.href.indexOf( '?max_id=' ) > 0 ) { previous_page = navs[0].href; } 		// Last page
			else { next_page = navs[0].href; } 		// First page
		} 		// There is no concept of a page number. If previous_link is undefined, it simply won't display. 

		gather_items = function() { 

	 		// Is it better to get links and filter by children, or select precisely and map to ancestors? 
			// Certainly it's clearer to get links and filter. Dunno if performance matters. 
//			return Array.from( document.querySelectorAll( '.h-entry div[data-props*="media"]' ) ) 
//				.map( v => v.closest( '.h-entry' ) ) 
			return Array.from( document.querySelectorAll( '.h-entry, .h-cite' ) ) 
				.filter( v => v.querySelector(  'div[data-props*="media"]' ) ) 		// Text-only posts fuck us up. 
				.map( v => {
						let item = new Object; 
						item.page = v.querySelector( 'a[class*="time"][href]' ).href;
						item.title = item.page.split( '/' ).pop(); 

						// dataset.props example: Object { height: 343, sensitive: true, autoplay: true, media: Array[3] }
						// .media example: Array [ Object, Object, Object ]
						// .media[0]: Object { id: "105651575500861937", type: "image", 
							// url: "https://baraag.net/system/media_att…", preview_url: "https://baraag.net/system/media_att…", 
							// remote_url: null, preview_remote_url: null, text_url: "https://baraag.net/media/C62aWhAqWt…", 
							// meta: Object, description: null, blurhash: "UFCGJfX99@RP^%t7OoV@4ms:kpn+x[V@Rja#" }
						// ... but none of those URLs are the post URL, so keep original item.page code. 
						let data = JSON.parse( v
								.querySelector( 'div[data-props]' )
								.dataset.props )
							.media; 		// Array of objects

						item.thumb = data.map( v => v.preview_url ); 	// Arguably should be a single image? One thumb per submission? 
						item.image = data.map( v => v.url ); 

						return item; 
				} )

		} 
	break; 

}

// Subdomains always have to cause problems. 
if( document.domain.split( '.' ).slice( 1, 3 ).join( '.' ) == "booru.org" ) { 		// *://*.booru.org/* 
	trigger_size = [ 15, 55, 16, 5 ]; 		// Left, top, font-size, padding. 

	// https://svtfoe.booru.org/index.php?page=post&s=list&tags=crack_ship&pid=20
	// https://svtfoe.booru.org/index.php?page=post&s=list&tags=socks&pid=20
	page_number = 0; 
	if( args["pid"] ) { page_number = parseInt( args["pid"] ); } 
	next_page = window.location.href + "&pid=" + (page_number + 20); 		// Flawed, but it works. 
	previous_page = window.location.href + "&pid=" + (page_number - 20); 

	gather_items = function() { 

		return Array.from( document.querySelectorAll( 'a img[src*="thumbs"]' ) )
			.map( v => v.closest( 'a' ) )
			.filter( v => ! v.style.display ) 		// Exclude hidden posts. Should be !display=="none", but typeError says fuck you. 
			.map( v => new Object( { 
				// https://svtfoe.booru.org/index.php?page=post&s=view&id=29292
				page: 		v.href,
				title: 			v.href.split( '=' ).pop(), 
			 	// https://thumbs.booru.org/svtfoe/thumbnails//28/thumbnail_187209e3ca22a28fd1ce75a7fd4f54aee3cf1e62.jpg
				// https://img.booru.org/svtfoe//images/28/187209e3ca22a28fd1ce75a7fd4f54aee3cf1e62.jpg
				thumb: 	scrub_extensions( v.querySelector( 'img[src*="thumbs"]' ).src ), 
				image: 		scrub_extensions( v.querySelector( 'img[src*="thumbs"]' ).src ) 
					.replace( 'thumbs.', 'img.' )
					.replace( '/thumbnails', '/images' )
					.replace( 'thumbnail_', '' ) 
			} ) )

	} 
}



	// ----- //			Controls to invoke script



GM_registerMenuCommand( "Swallow entire gallery", show_images ); 		// GreaseMonkey dropped this feature years ago, but I am stubborn. 

// Put button on page, since there's no menu in "modern" Userscript plugins.
// Onclick, change class to some spinner, so it reacts instantly and looks like it's loading. Really the interval is waiting a second. 
var trigger = document.createElement( 'button' ); 
trigger.innerText = "Swallow gallery"; 
trigger.className = "unclicked_button"; 
trigger.onclick = function(){ this.innerText='Swallowing...'; this.className = 'clicked_button'; } 		// Immediate visible change, idempotent
trigger.style = "position: absolute; left: " + trigger_size[0] + "px; top: " + trigger_size[1] + "px; background-color:#dbd7d8; border-radius: 20px; text-align: center; display: inline-block; border:1px solid #19ab19; cursor:pointer; line-height: 20px; color:#194d19; font-family:Arial; font-size:" + trigger_size[2] + "px; padding: " + trigger_size[3] + "px " + trigger_size[3] + "px; text-decoration:none;"

// Pixiv doesn't work properly until after thumbnails load, so don't show the button until then. 
if( typeof( button_delay_function ) == "function" ) { 		// Note: checking for presence of function. 
	add_button = setInterval( function() { 
		if( button_delay_function() ) { 		// Note: function called, checking results. 
			clearInterval( add_button ); 
			document.body.appendChild( trigger ); 
		}  
	}, 1000 ); 		// Passive - no interaction concerns. 
} else { 
	document.body.appendChild( trigger ); 		// Most sites are ready-to-go. 
}

// Injecting code into the page is nontrivial - ironically because function.toString is fragile - so just look for a change in the page. 
var button_check = document.getElementsByClassName( 'clicked_button' ); 
var fake_event = setInterval( function() {  
		if( button_check.length > 0 ) { 
			clearInterval( fake_event ); 
			show_images(); 
		} 
	}, 400 );  		// Doherty threshold for frustration is 400ms. 

// function fetch_pages( page_links ) { }		// Still expecting to use this approach on some site, but so far, nnnope. 

// End of main execution. 





// ------------------------------------ Gallery Swallower ------------------------------------ //





function show_images() { 



	// ----- //			Replace page, set up furniture 



	// Grab links and/or thumbnails using per-site code:
	items = gather_items(); 		// Array of objects, listing page link, thumbnail, presumed fullsize image, etc. 

//	console.log( items ); 		// Debug. Be honest, this is staying here. 

	if( top_to_bottom == false ) { items.reverse(); } 

	// Erase existing page, use ours instead
//	document.head.innerHTML = ''; 		// Looks bad, accomplishes nothing. 
	document.body.innerHTML = html; 

	// Navigation links, at the bottom
	let links_element = document.getElementById( 'links' ); 		// Has to go after document = html, duh.

	// "Previous" or "Previous - Next" or "Next"
	let link_html = "";
	if( page_number <= 1 ) { previous_page = ''; } 		// No previous link on first pages. (Page 0 is valid.)
	if( previous_page ) { link_html += "<a href='" + encodeURI( previous_page ) + "'>Previous page</a>"; } 
	if( previous_page && next_page ) { link_html += " - "; }
	if( next_page ) { link_html += "<a href='" + encodeURI( next_page ) + "'>Next page</a>"; } 
	links_element.innerHTML = link_html; 

	let centered = document.getElementById( 'centered' ); 



	// ----- //			Per-submission links and controls 



	// Give each item its own set of spans, with basic onClick controls to remove images or reload a submission.
	for( let item_key = 0; item_key < items.length; item_key++ ) { 
		item = items[ item_key ]; 
		let container = document.createElement( 'span' ); 
		container.id = item_key + 'container'; 
		let basket = document.createElement( 'span' );		// Two hard problems. 
		basket.id = item_key; 

		let reloader = document.createElement( 'button' ); 
		reloader.innerText = '⟳'; 
		reloader.onclick = function() { 
			let e=document.getElementById( item_key ); 
			e.innerHTML = ""; 
			e.dataset.page_number=0; 
			e.className="ready"; 
		} 
		reloader.className = 'reloader'; 

		let link = document.createElement( 'a' ); 
		link.href = item.page + '#dnr#&dnr'; 
		link.innerText = ' ' + item.title + ' '; 
		link.setAttribute( "target", "_blank" ); 
		link.style = 'font-size:30px'; 

		let submission_remover = document.createElement( 'button' );  		// Erase whole submission. 
		submission_remover.innerText = '❌'; 
		submission_remover.onclick = function(){ document.getElementById( item_key ).innerHTML = ""; } 
		submission_remover.className = 'remover'; 		// Add some CSS to float left or whatever. 

		// Maybe don't do bottom remover on single-image sites. It's clutter.
			// Would arguably still be useful for very tall comic strips, except we don't have image-size controls yet. 
		let bottom_submission_remover = submission_remover.cloneNode(); 		// Why doesn't this include innerText? 
		bottom_submission_remover.innerText = '❌';  	
		// Scroll back up on removal, since a bunch of vertical content disappeared. 
		// The presumed use of this button is when you've scrolled past a long-ass manga and gone "meh," 
			// so you don't want to hunt for the root reload / remove buttons. That crap killed me in Tumblr Scrape. 
		bottom_submission_remover.onclick = function() {
			document.getElementById( item_key ).innerHTML = ""; 
			document.getElementById( item_key + 'container' ).scrollIntoView(); 
		} 

		container.appendChild( reloader ); 
		container.appendChild( link ); 
		container.appendChild( submission_remover ); 
		container.appendChild( document.createElement( 'br' ) ); 
		container.appendChild( basket ); 
		container.appendChild( bottom_submission_remover ); 
		container.appendChild( document.createElement( 'br' ) ); 

		centered.appendChild( container ); 

		if( force_style ) { 		// Apply CSS rules to every element individually. 
			for( selector in style_rules ) { 
				Array.from( document.querySelectorAll( selector ) )
					.forEach( element => { 		// Finding .cssText was so hard, you'd think it's a secret. 
						element.style = element.style.cssText + style_rules[ selector ]; 
					} )
			} 
		} 

		basket.dataset.page_number = 0; 
		basket.classList.add( "ready" ); 		// Signals interval function to load an image here. 
	}  



	// ----- //			Ongoing interaction and loading 



	// Load more images in "ready" submissions, when available. 
	// Sloppy speedup: "while ready_element = [0]." Service all ready spaces, every time. 
		// Cons: might 503, might lock up. 
		// Tried while( ready_element = ready_elements[0] ). Unscientific results: same time either way. Eh. 
	// Better kludge: forEach, setTimeout. It'll complete the for-each, then schedule each single-service action, in order. 
		// Cons: minor race condition where one "ready" state could get scheduled repeatedly. (If another interval occurs before that timeout.) 
		// Cons: still might 503. 
	var ready_elements = document.getElementsByClassName( 'ready' ); 
	var interval_object = setInterval( function() { 

			// Add an image to the ready basket, increment page number, conditionally ready-up for another image. 
			// Image list: just grab list[n]. Automatic pagination: probe for matching numbered thumbnail. 
			if( ready_element = ready_elements[0] ) { 		// "If ready_elements.length > 0," but with race condition paranoia. 
				ready_element.classList.remove( 'ready' ); 		// We fancy. 

				let item_id = parseInt( ready_element.id ); 
				let manga_page = parseInt( ready_element.dataset.page_number ); 		// Not web-page... comic-page. 
				ready_element.dataset.page_number = 1 + manga_page; 

				// Prepare filename, once:
				let image_url = items[ item_id ].image; 
				if( typeof( items[ item_id ].image ) == "object" )  		// typeof, not typeOf? "object", not "Array"? Fuck you, Javascript. 
					{ image_url = items[ item_id ].image[ manga_page ]; }
				if( automatic_pagination ) 		// Basically just Pixiv. 
					{ image_url +=  manga_page; } 
				image_url += image_followup; 		// Usually nothing. 

				// Try all plausible file extensions for an inline image. 
				let forms = new Array; 
				for( format of formats ) { 
					let png = document.createElement( 'img' );
					let apng = document.createElement( 'a' ); 
					apng.appendChild( png );

					png.className = "short loading"; 		// Debug-ish. Also, doesn't work for some goddamn reason. 
					png.src = image_url + format; 
//					console.log( image_url ); 
					png.onerror = function() { this.parentElement.remove(); } 		// Remove parent link (apng / ajpg / agif) as well. 
					png.onload = function() { this.classList.remove( "loading" ); }

					apng.href = png.src; 
					apng.setAttribute( "target", "_fuckgelbooru" ); 		// WHY IS THERE NO OPPOSITE TO DISPLAY:NONE?!

					forms.push( apng ); 
				}

				let remover = document.createElement( 'button' );  		// Big red X, to remove this image specifically. 
				remover.innerText = '❌'; 
				remover.onclick = function() { this.parentElement.remove(); } 
				remover.className = 'remover floating'; 

				// New span per-image, so whole-submission X prevents new images from loading:
				let outer_span = ready_element.querySelector( 'span' ); 
				if( outer_span == null ) { 
					outer_span = document.createElement( 'span' ); 
					ready_element.appendChild( outer_span ); 
				} 
				let inner_span = document.createElement( 'span' ); 
				inner_span.style = "position: relative;"; 		// This fixes the vertically-aligned Xs. Do NOT ask me how. 
				outer_span.appendChild( inner_span ); 

				for( form of forms ) { inner_span.appendChild( form ); } 		// Insert all image tries. 
				inner_span.appendChild( remover ); 		// Also insert delete-this button... afterward? Yeah, on the right-hand side. 
				inner_span.appendChild( document.createElement( 'br' ) ); 
				inner_span.appendChild( document.createElement( 'br' ) ); 



				// Thumb-try stuff:
				if( automatic_pagination ) { 

					forms = new Array; 
					for( format of formats ) { 
						png = document.createElement( 'img' ); 		// Reuse 
						png.src = items[ item_id ].thumb + (manga_page+1) + thumb_followup + format;
						png.className = 'test'; 
						png.onerror = function() { this.remove(); } 
						// Any truly hideous behavior is often attributable to the wrong number of .parentElements below: 
						png.onload = function() { this.parentElement.parentElement.parentElement.classList.add( "ready" ); this.remove(); }
						forms.push( png )
					} 

					for( form of forms ) { inner_span.appendChild( form ); } 
				}

				// Array-of-images stuff:
				if( typeof( items[ item_id ].image ) == "object" ) { 		// Not a global boolean, because direct testing beats keeping track. 
					if( manga_page + 1 < items[ item_id ].image.length ) { 
						ready_element.classList.add( "ready" ); 
					} 
				}

				// Force style, but only for children of this element. 
					// Should maybe only be for children of inner_span or outer_span? No; also applies to Pixiv thumbnail probes. 
					// ... though "+=" does mean 30-image submissions will have one element with style=size;size;size;size;, etc. 
					// Nevermind, text -> CSS2Properties eats all duplicates. 
				if( force_style ) { 
					for( selector in style_rules ) { 
						Array.from( outer_span.querySelectorAll( selector ) )
							.forEach( element => { 
								element.style = element.style.cssText + style_rules[ selector ]; 
							} )
					} 
				} 

			} 

		}, new_image_rate ); 



	// ----- // 		🍭 Spinners 🍭



	// Garish spinners that indicate "finding new images" and "files still loading." 
	// Easy answer for responsive loading / lazy spinners: use two intervals. Duh. 
	// I could simplify this to e.g. images_spinner.className = images_loading.length and do CSS bullshit like #id.class=spinning #id.0=not-spinning. 
		// I don't think there's any truly automatic way to spin based on the properties of children... but I wouldn't be surprised. 
		// Like I'm pretty sure per-element style properties can't go 'animate if( self.querySelector )'. But again: not ruling it out. 
		// It's only desirable for forced_style sites like Baraag, since lazy spinners are better, so pfffft. 
	var submissions_loading, submissions_spinner; 		// These are technically global? 
	var images_loading, images_spinner; 
	submissions_loading = document.getElementsByClassName( 'test' );
	submissions_spinner = document.getElementById( 'submissions_spinner' );
	images_loading = document.getElementsByClassName( 'loading' ); 
	images_spinner = document.getElementById( 'images_spinner' );
	image_count = document.getElementsByClassName( 'short' ); 		// No longer updates as images are removed. Just add another class. 
		// Tracking "short" instead counts the test images, leading to brief overcounting. But not every site uses thumbnails. 
	image_counter = document.getElementById( 'image_counter' ); 		// Two hard problems. 

	// Flickering on/off multiple times per second: bad.
	// Waiting an entire second between image loads: also bad. 
	// So... add another low-impact interval. Efficient? Not really. But eeeasy. 
	var spinner_interval = 	setInterval( function() { 
			if( submissions_loading.length == 0 ) { 		/* If we're done testing for new images in submissions */
				submissions_spinner.className = 'spacer'; 
			} else {  		/* If e.g. we reload a submission and it takes a while */
				submissions_spinner.className = 'submissions_loader'; 
			}

			if( images_loading.length == 0 ) { 		/* If we're done testing for new images in submissions */
				images_spinner.className = 'other_spacer'; 
			} else {  		/* If e.g. we reload a submission and it takes a while */
				images_spinner.className = 'images_loader'; 
			}

			image_counter.innerText = '' + image_count.length + ' images'; 
			/* Should arguably exclude loading images.  */
			/* Could be made accurate by counting calls to next_image. Right? Or incrementing some var when that succeeds. */ 
			/* Just count className="thumb". Right? Haha, I'm already double-counting because loaded thumbnails don't really go away. Yeah, count "thumb." */
	}, 1000 ); 

}