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