Sleazy Fork is available in English.

Eza's Gallery Swallower

Turn a page of thumbnails into high-res images

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

// ==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     http://www.hentai-foundry.com/users/FaveUsersRecentPictures?username=*
// @include     http://www.hentai-foundry.com/user/*/faves/pictures/*

// @include     https://baraag.net/@*

// @exclude    *#dnr
// @noframes
// @version     2.6.23
// @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/40652351@300-1613453026.jpg
	// https://d.furaffinity.net/art/ratcha/1613453026/1613453026.ratcha_party.jpg
	// Nope, needs a fetch. 

// Paheal? Direct image links... but many images per page. Bandwidth and memory, whatever, but it might piss off the site. 

// 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. 

// Next goal: sort out which URLs should and shouldn't have the button. 

// Should the image-loading loop also use querySelector? It's already an interval; it doesn't need a list. 
	// And Array.from() prevents race conditions. And we could slice() that to do e.g. five at once. 
	// Dunno if a live HTMLCollection really evens out the browser's workload if we're triggering intervals five times per second. 
	// https://stackoverflow.com/questions/14377590/queryselector-and-queryselectorall-vs-getelementsbyclassname-and-getelementbyid - 
		// claims getElement methods are O(1). But it's not like O(n) is terrible. 
	// https://esbench.com/bench/57bdc462db965b9a00965c70 - 2x difference on a page with 500 elements. Basically: getElement is fine. 
	// http://ryanmorr.com/abstract-away-the-performance-faults-of-queryselectorall/ - ah, live collections are lazy. 
		// querySelectorAll has to collect the whole list up-front... which wouldn't matter if we just did one per interval, but it's still slower than getElementById.
		// Buuut the comparison is actually getElementsByClassName. Which would be slower overall. Except we're just grabbing the first element. Hmm. 
		// Just modify that ESBench example. el.className=i. Actually let's add some duplicates, like parseInt( i / 10 ). 
		// Aaaand they're basically identical. Not shocking. querySelector is marginally faster, but not enough to care. 
		// If we wanted to grab a slice, querySelectorAll[0] is like five times slower than getElements. 
		// All efforts to cooerce ESBench into grabbing several elements from the start of getElements fail with "element is undefined,"
			// despite aggressive consideration for all sensible obstacles to that basic-ass operation, 
			// buuut Array.from( querySelector ).slice(0,5) absolutely -chugs- at 250 ops/s, compared with ~6000 for a single query. 
		// Long story short: either way is fine for single elements. querySelector is a hair faster, especially with fewer elements on the page, but meh. 
	// Really I should be checking the impact of a very short interval and e = HTMLCollection[0]. 
		// First whack: querySelector is nearly twice as fast, when finding a class matching no elements. Fewer elements: smaller difference. 
		// But if I put the getElements in the setup, so I'm just checking liveCollection[0]... that's more than twice as fast as querySelector. 
		// And it's like 18,000 ops/s for no matches, versus 5,000-6,000 for matches and 8,000 for no matches. 
		// So I've accidentally done things in an incredibly efficient manner. Neat. 
		// Now I can set the interval super low and not give a damn. 

// Spent a while agonizing over performance vis-a-vis live HTMLCollections vs. querySelector, and found out it super doesn't matter. 
	// Setting up getElements and then only checking the first one has approximately zero impact on a modern PC. 
	// If I wanted to be absolutely paranoid and pursue order-of-magnitude gains, I'd make this all event-driven...
		// but having buttons in the page context trigger functions in the script might get fucky. I dunno, a lot has changed since Tumblr Scrape. 
	// And the impact of setInterval seems to be approximately dick-all, so long as the timeout isn't zero or effectively zero. 
	// The upshot is that I set the timer to 10 ms and pages just -fly.- The "swallow gallery" button works immediately. Images load instantly. 
		// The latter is a mild concern, because sites might notice. An ideal server interaction looks just like opening a bunch of pages.

// Might move image count to the controls area. 
	// Its text already implies "image size" and "image order." But mostly I'm bugged that it doesn't line up with other text.



	// 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:
	// (internal) added controls / thumbnails spans. 
	// Added "reverse image order" button. (Bit clunky.) 
		// No longer clunky. 
	// Added image-size controls. 
		// Fancied those up. 
		// (internal) made controls work with force_style. 
	// Add thumbnails.
		// Unfucked thumbnails by making them a grid. 
		// (internal) refactored Pixiv "thumbs" to "probes."
		// (internal) refactored all sites to use thumbnail without format-guessing. 
	// (internal) moved add_button inside show_images, because Pixiv demands it. IDFK.
	// (internal) functionified force_styles loop into fake_css(). 




// ------------------------------------ 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;";
style_rules[ ".short img" ] = "max-width: 90vw; max-height: 60vh; z-index: 10; vertical-align: middle;";  
style_rules[ ".full img" ] = "max-width: initial; max-height: initial; z-index: 10; vertical-align: middle;"; 			// "initial" for fake_css(). 
style_rules[ ".fit_width img" ] = "max-width: 90vw; max-height: initial; z-index: 10; vertical-align: middle;";  	// Leaving space for big X.
style_rules[ ".fit_height img" ] = "max-width: initial; max-height: 95vh; z-index: 10; vertical-align: middle;";  
style_rules[ ".fit_window img" ] = "max-width: 90vw; max-height: 95vh; 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;';

// Remove / reload 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;';

// Image-size controls:
style_rules[ '#controls' ] = 'float: right; font-size: 33px;';
style_rules[ '.control_button' ] = 'font-size: 33px; border-radius: 50%; width: 60px; height: 60px; text-align: center; display: inline-block; background-color:#363; color:#FFF;'; 

// Previous / next links, at the bottom:
style_rules[ '.page_links' ] = 'font-size: 33px;';

// Thumbnail container and fixed-size thumbnails:
style_rules[ '.thumbnails_container' ] = 'display: grid; grid-gap: 5px;';  		// Spans can't have fixed size, divs can't flow sensibly. Argh. 
style_rules[ '.thumb_box' ] = 'width:100px; height:100px; display: inline-block; overflow: hidden;';
style_rules[ '.thumbnail_image' ] = 'width:100px;';

// 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



// Floaty stuff:
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. */

// Structure:
html += '<br><span id="controls" class=""></span><br><br><br><br>';
html += '<span id="thumbnails_container"></span><br><br>';
html += '<br><br><center><span id="centered" class="short"></span></center>';  		/* Where most 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 probe_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 = 50; 		// 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. 
var centered, controls, thumbnails_container; 		// Spans that are already accessed globably, but were local "let" variables? Eh. 




	// ----- //			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; 
} 

function fake_css( parent ) { 
	if( force_style ) { 		// Not ideal practice? But it beats repeating "if( force_style ) { fake_css(); }" when that's the only time it'd happen. 
		if( parent == null ) { parent = document; } 		// Apply everywhere by default
		for( selector in style_rules ) { 		// Global
			Array.from( parent.querySelectorAll( selector ) )
				.forEach( element => { 
					element.style = element.style.cssText + style_rules[ selector ]; 
				} )
		} 
	}
} 


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



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

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

	case 'pixiv.net':
		probe_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
		// Pixiv's fake links mess this up sometimes. Nothing I can do - the browser is confused about the URL. 
		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: 	v.querySelector( 'img' ).src, 
					probe: 		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:		v.querySelector( 'div[style]' ).style.backgroundImage
						.replace( 'url("', '' )
						.replace( '")', '' ),
					probe: 		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; 

	// https://gelbooru.com/index.php?page=post&s=view&id=2135704 - bad submission crashes gather_items. 
	// https://gelbooru.com/index.php?page=post&s=list&tags=saberfish+aftersex+hug 
	// Huh. Only causes problems on the Comments page, not post list. 
	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: 	v.querySelector( 'img' ).src,
					image: 		scrub_extensions( v.querySelector( 'img' ).src )
						.replace( '/thumbnails', '/images' )
						.replace( 'thumbnail_', '' ) 
				} ) )

		}
	break;

	// 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: 	v.querySelector( 'img' ).src,
					image: 		scrub_extensions( v.querySelector( 'img' ).src )
						.replace( '/preview', '' )
				} ) )

		}
	break;

	// http://www.hentai-foundry.com/users/FaveUsersRecentPictures?username=Example
	// http://www.hentai-foundry.com/pictures/user/AmaZima
	// http://www.hentai-foundry.com/pictures/user/AmaZima/scraps
	// Not http://www.hentai-foundry.com/pictures/user/AmaZima/875960/Spirit-lantern
	// Not http://www.hentai-foundry.com/user/AmaZima/profile 
	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: 	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. 
		page_number = 2;		// Value as flag. previous_link doesn't display on page 1, even if it's defined. 
		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
		} 

		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, .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 )[0]; 	// One thumbnail 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: 	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. 
// ... or we could show the button, but not replace the page until thumbnails appear. 
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(); 
		} 
	}, 50 );  		// Doherty threshold for frustration is 400ms. But: "a couple times a second" doesn't bother modern machines. 

// 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:
	if( ! items ) { 
		items = gather_items(); 		// Array of objects, listing page link, thumbnail, presumed fullsize image, etc. 
		if( top_to_bottom == false ) { items.reverse(); } 
	} 
	// A "reverse order" button could just call show_images again, but check if items exists and re-use it. 

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

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

	controls = document.getElementById( 'controls' ); 		// Has to go after document = html, duh.
	thumbnails_container = document.getElementById( 'thumbnails_container' ); 
	centered = document.getElementById( 'centered' ); 
	let links_element = document.getElementById( 'links' ); 

	// Pixiv doesn't work if this function is in the main scope. Every other site is fine. I have no goddamn idea why. 
	// Making the root node an argument and passing #controls doesn't fix the problem. 
	// I've read "programming is a puzzle game where all the puzzles are caused by your own stupidity," but web design comes with its own stupidity DLC. 
	function add_button( text, onclick ) {  		// DRY. Specific to #controls, for now. 
		let button = document.createElement( 'button' ); 
		button.innerText = text; 
		button.onclick = onclick
		button.className = "control_button"; 
		controls.appendChild( button ); 
		controls.appendChild( document.createTextNode( " " ) ); 		// Asinine way to force spacing. 
	} 

	// Controls - e.g. image size and order.
	controls.appendChild( document.createTextNode( "Size: " ) );  		// Pixiv crashes after here. 
	add_button( "▣", function() { centered.className = "short"; fake_css(); } ); 	// This and "full" could use better glyphs.
	add_button( "↔", function() { centered.className = "fit_width"; fake_css(); } ); 
	add_button( "↕", function() { centered.className = "fit_height"; fake_css(); } ); 
	add_button( "✢", function() { centered.className = "fit_window"; fake_css(); } ); 
	add_button( "■", function() { centered.className = "full"; fake_css(); } ); 		// Unicode has no four-way arrow glyph. No emoji either. Weird. 
	controls.appendChild( document.createTextNode( " Order: " ) ); 
	add_button( "⇅", function() { 
		for( node of [ centered, thumbnails_container ] ) { 
			node.childNodes.forEach( (v,i,a) => node.insertBefore( node.childNodes[i], node.firstChild ) ); 
		}
	} )

	// Navigation links, at the bottom
	let link_html = ""; 		// "Previous" or "Previous - Next" or "Next"
	if( page_number <= 1 ) { previous_page = ''; } 		// No previous link on first pages. (Page 0 is valid.)
	// encodeURI breaks links. We're generating text from parseInt page numbers, or grabbing links already in the page - it's fine. 
	if( previous_page ) { link_html += "<a href='" + ( previous_page ) + "'>Previous page</a>"; }  
	if( previous_page && next_page ) { link_html += " - "; }
	if( next_page ) { link_html += "<a href='" + ( next_page ) + "'>Next page</a>"; } 
	links_element.className = 'page_links'; 
	links_element.innerHTML = link_html; 





	// ----- //			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. 

		let bottom_submission_remover = submission_remover.cloneNode(); 		// Why doesn't this include innerText? 
		bottom_submission_remover.innerText = '❌';  		// ⬆️❌? ↑⇑↥⤒⤊? Eh, nothing color scales well. Obvious mismatch. 
		// 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 ); 

		// Thumbails at the top: 
		let thumbnail_image = document.createElement( 'img' );
		thumbnail_image.className = "thumbnail_image";  	// Fixed-width image.
		thumbnail_image.src = item.thumb;
		thumbnail_image.onerror = function() { this.remove(); } 

		let thumb_box = document.createElement( 'span' );
		thumb_box.className = "thumb_box"; 						// Fixed-size grid box, overflow hidden. Crops tall images.
		thumb_box.onclick = function() { document.getElementById( item_key + 'container' ).scrollIntoView(); } 
		thumb_box.appendChild( thumbnail_image ); 

		thumbnails_container.appendChild( thumb_box ); 

		fake_css(); 		// Function itself checks force_style boolean. 

		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. 
	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 = "full_image 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 ); 	
				inner_span.appendChild( document.createElement( 'br' ) ); 
				inner_span.appendChild( document.createElement( 'br' ) ); 



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

					forms = new Array; 
					for( format of formats ) { 
						png = document.createElement( 'img' ); 		// Reuse 
						png.src = items[ item_id ].probe + (manga_page+1) + probe_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" ); 
					} 
				}

				fake_css( outer_span ); 		// Just for this image and any probes. 

			} 

		}, 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( 'full_image' ); 
		// 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 ); 

}