Eza's Image Glutton

Redirects to high-res images on gallery sites, skipping past descriptions and comments

// ==UserScript==
// @name        Eza's Image Glutton
// @namespace   https://inkbunny.net/ezalias
// @homepage     https://greasyfork.org/en/users/4876-ezalias
// @author			Ezalias
// @description Redirects to high-res images on gallery sites, skipping past descriptions and comments
// @license     MIT
// @license     Public domain / No rights reserved
// @include     *://www.furaffinity.net/view/*
// @include     *://www.furaffinity.net/full/*
// @include     https://inkbunny.net/submissionview.php*
// @include     https://inkbunny.net/s/*
// @include     http://gelbooru.com/*page=post&s=view*
// @include     http://youhate.us/*page=post&s=view*
// @include     https://youhate.us/*page=post&s=view*
// @include     *://www.gelbooru.com/*s=view*
// @include     *://gelbooru.com/*s=view*
// @include     *://danbooru.donmai.us/posts/*
// @include     *://*.tumblr.com/image/*
// @include     /^http(s|)://e(621|926)\.net/post/show*//
// @include     *://e621.net/posts/*
// @include     *://e926.net/posts/*
// @include     *://*.deviantart.com/art/*
// @include     *://*.deviantart.com/*/art/*
// @include     /^https?://w*\.*hentai-foundry\.com/pictures/user/.*/[0-9]*/.*/ 
// @include     /^https*://www\.sofurry\.com/view/*//
// @include     https://www.weasyl.com/*
// @include     http://www.y-gallery.net/view/*
// @include     *://rule34.paheal.net/post/view/*
// @include     *://rule34.xxx/index.php?page=post*
// @include     *://rule34hentai.net/post/view/*
// @include     /^https*://derpibooru.org/.*/
// @include     *://*.booru.org/*s=view*
// @include     *://mspabooru.com/*s=view*
// @include     *://safebooru.org/*s=view*
// @include     *://www.majhost.com/cgi-bin/gallery.cgi?i=*
// @include     *://e-hentai.org/s/*
// @include     *://nijie.info/view.php?id=*
// @include     *://www.pixiv.net/member_illust.php?mode=medium&illust_id=*
// @include     *://www.pixiv.net/jump.php?*
// @include     *://www.pixiv.net/*/artworks/*
// @include     http://*sleepymaid.com/*
// @include     *://*.sankakucomplex.com/post/*
// @include     *://*.bronibooru.com/posts/*
// @include     *://bronibooru.com/posts/*
// @include     *://luscious.net/c/*
// @include     *://luscious.net/pictures/c/*
// @include     *://imageboard.neko-sentai.com/post/*
// @include     *://uberbooru.com/posts/*
// @include     *://www.furiffic.com/*/view/*
// @include     *://beta.furrynetwork.com/artwork/*
// @include     *://hiccears.com/picture.php?pid=*
// @include     *://www.hiccears.com/picture.php?pid=*
// @include     *://www.jabarchives.com/main/post/*
// @include     *://aryion.com/g4/view/*
// @include     *://www.newgrounds.com/art/view/*/*
// @include     *://www.newgrounds.com/portal/view/*
// @include     *://lolibooru.moe/post/show/*
// @include     *://pbs.twimg.com/media/*
// @include     *://gfycat.com/*
// @include     *://tbib.org/index.php?page=post&s=view&id=*
// @include     *://myhentaigallery.com/gallery/show/*/*
// @include     *://www.megabooru.com/post/view/*
// @include     *://www.redgifs.com/watch/*
// @include     *://www.gifdeliverynetwork.com/*
// @exclude    *://pbs.twimg.com/media/*=orig*
// @exclude    *://www.deviantart.com/users/outgoing?*
// @exclude    *#comment*
// @exclude    *?comment*
// @exclude    *#c* 
// @exclude    *&pid=*
// @exclude    *#dnr
// @noframes
// @version     1.35.4
// ==/UserScript==

// Any single-image submission will redirect to the full-size image. On multi-image submissions, every page except the first will redirect to its full-size image. 
// If you go "back" to the normal gallery page (to favorite the image, read its description, leave a comment, etc.) then this script will not send you forward again. 
// https://greasyfork.org/scripts/4713-eza-s-image-glutton 
// https://sleazyfork.org/scripts/4713-eza-s-image-glutton 

// Commentary on the UserScript block:
// @exclude #dnr - This string is appened to a URL when redirecting, to prevent back-trapping. 
// @exclude http://www.deviantart.com/users/outgoing?* - Archaic misfire prevention; should probably be changed to automatically redirect through.
// @exclude ?comment_id or whatever - Site-specific cases where redirecting to the image is not desirable. E.g. when linked to individuals comments / comment pages. TBD. 

// TO DO: 
// for modify_tumblr: for photoset pages (but everywhere, to be safe) make unlinked images link to themselves. I want nice, clean, chronological tabs for multi-image comics. 
// modify_furaffinity to change prev/next/fav links with pre-appended #dnr. not raw html fiddling: use the DOM and getElementsByType or whatever. thingy.href=url_plus_dnr. 
// flickr? maybe separately. that whole site is a mess. also full-size images are sometimes gigantic, like dozens of megabytes. 
// Consider changing some @includes to @match. 
// http://thehentaiworld.com/hentai-doujinshi/theres-something-about-sakura-naruto/ ? I already do rule34; there's no pretending this is just about "art." 
	// Almost deserves a more Pixiv Fixiv-like fix. Maybe just a link dump like that DeviantArt gallery script?
// Greasyfork install page as options page? 
// This would work faster if I could delay or prevent the loading of images. E.g., execute script before loading page, define CSS that doesn't download embedded images, wait for page to load, scrape image_url, and then redirect as usual. Since the script wouldn't trigger on #dnr (which I should do as an @exclude, I guess) images would load as usual when you clicked 'back.' 
	// This thought is mostly driven by opening a bunch of e.g. Gelbooru links all at once. They spend long enough loading that the full-size images are usually half-done before the redirect happens. 
// Escape function in JS is encodeURI. Also use in Tumblr Scraper, where we need 'safe' URLs as tag IDs. 
// FurAffinity stories redirect to thumbnail, e.g. http://www.furaffinity.net/view/15903888/ - might need to break out a whole complex function here. 
// http://seiga.nicovideo.jp/seiga/im4507046 ? 
// Nijie.info support might be missing out on multipage submissions? I don't even have an account. 
	// My Nijie support is basically nonexistant because I didn't have an account. Turns out they're more like Pixiv now, including multi-image posts. This is problematic. (Animations work, though.) 
// Make undersized images link to themselves on imgur. 
// Eza's image glutton as described on http://cuddle.horse/post/109728993805/a-few-browser-extensions-that-make-furaffinity-a -
	// Eza’s Image Glutton: This affects websites beyond just FA but is a unique tool for powerbrowsing and such. When you open a page with a single image it skips all comments and descriptions and just shows the image in the highest quality possible. If you want to see all the items that are hidden all you have to do is go back a page.
	// good rundown from a third party. 'A page with a single image' is clearer than 'gallery submission page.' 
// It's impossible to find this script after Greasyfork fucked over "adult" scripts. SleazyFork does not appear in search engines, at all. The empty GreasyFork page doesn't show up. All that people will see if they don't already know where to go is userscripts-mirror.org, which was last updated in twenty-fucking-twelve. This is completely unacceptable. 
	// On the other hand, Yahoo also won't find 'greasyfork ezalias,' so what the fuck. Google finds it. Yahoo just sucks. 
// Apparently Chrome doesn't redirect properly - there's no 'back' functionality. Grand. 
	// ... there's back functionality if and only if the page finishes first. Fuck Chrome. 
// Fixing what ain't broken - I could "//@include *" and keep the switch-case business, if only to skirt GreasyFork's dumb "adult" rules. 
	// Ugh, I'd probably be marked "adult" just for mentioning that it works on "adult" sites. Like e621... but not tumblr or pixiv. What even. 
	// Could also generalize all extract_image_url_after guff to a for-loop over an array of search strings. 
	// GreasyFork is wise to my bullshit, and specifies that even mentioning adult sites makes the script "adult." Can't even host code without censorship.
	// @include * might still be a good idea, because I can check the browser-recognized domain instead of just the URL string.
		// Or I guess I could switch by domain anyway.
		// @including all sites would mean I need to check per-site URL formats in the switch-case block. E.g. example.com/art/1234 but not example.com/blog/2345. 
		// The deciding factor is whether JS makes it easy to get domains without subdomain - regexing just to figure out if we're on *.booru.org sucks. www alone would hurt. 
// view-source:https://inkbunny.net/submissionview.php?id=1325657 - inkbunny 'friends only' page - 
// Finally on Pawoo, let's add that. Ugh - Twitter-style presentation. Gimme a gallery grid. 
	// Might be more like that Tumblr bare-image resizer, which this script should also mimic. Bare images are linked. Are those max size?
	// https://curate.mastodon.art/gallery/media_attachments/files/000/088/717/original/525713c353f7db21.jpg
	// https://files.mastodon.social/media_attachments/files/000/963/359/original/ccfa625b17d2e1d0.png 
	// https://files.mastodon.social/media_attachments/files/000/963/345/original/d23781d67022b95f.png
	// Compare:
	// https://pawoo.net/media/r89DgtVaqQeydHyPb1g 
	// https://img.pawoo.net/media_attachments/files/001/246/940/original/b4a22a1ea4433a9f.jpg
	// Totally arbitrary, no way to figure one from another. Hmm. 
// https://www.hentai-foundry.com/pictures/user/teku/566798/Daisy-Darret-and-Penny#dnr loads an image from the description? 
// Fiddled with @includes to consolidate http/https under http*. Seems to work? Might open execution to e.g. http://maliciousdomain.com/?//gelbooru etc. 
	// Yeah, it's an attack vector worth worrying about. Admittedly the switch case is on document.domain - the attack site would have to end correctly.
	// Goddammit, this is possible anyway, since http://*.gelbooru.org matches http://maliciousdomain.com?.gelbooru.org as-is! 
	// Aaaugh Greasemonkey needs a goddamn domain inclusion method besides string-matching. 
// Twitter broke videos - even on mobile. Fuck that, give me the MP4. You can't show me a video and pretend I don't have it. 
	// <video preload="none" playsinline="" style="width: 100%; height: 100%; position: absolute; transform: rotate(0deg) scale(1);" poster="https://pbs.twimg.com/media/DdFx3A9VAAEEqA9.jpg"><source src="https://video.twimg.com/amplify_video/995698796209225728/pl/qWXZDSRj7npFBnoS.m3u8?tag=2" type="application/x-mpegURL"><source src="https://video.twimg.com/amplify_video/995698796209225728/vid/720x720/hjcLz5e56ojDYS8j.mp4?tag=2" type="video/mp4"></video>
	// And here's the "button" that steals clicks:
	// <div style="position: relative; width: 100%; height: 100%; background-color: black;"><video preload="none" playsinline="" style="width: 100%; height: 100%; position: absolute; transform: rotate(0deg) scale(1);" poster="https://pbs.twimg.com/media/DdFx3A9VAAEEqA9.jpg"><source src="https://video.twimg.com/amplify_video/995698796209225728/pl/qWXZDSRj7npFBnoS.m3u8?tag=2" type="application/x-mpegURL"><source src="https://video.twimg.com/amplify_video/995698796209225728/vid/720x720/hjcLz5e56ojDYS8j.mp4?tag=2" type="video/mp4"></video></div> 
// https://the-collection.booru.org/index.php?page=post&s=view&id=74939 ? 
// https://thehentaiworld.com/hentai-images/asami-and-korra-sunsetriders7-the-legend-of-korra/ ? 
// Twitter: auto-click click-to-view nonsense? 
// Image Glutton: https://yande.re/post/show?md5=936f8ff6b34aacd80fa0038505b0c61a 
// Oh, and Tumblr lost their goddamn minds following the Great Purge. What the fuck is a PNJ? GifV is not a real format. You embed WebMs for lossless sprite work? Whaaat? 
// https://baraag.net/@conoghi/101982558283665128
	// Not on multi-image submissions like https://baraag.net/@conoghi/102022419532733468 
// Nijie support is broken and someone finally noticed. I guess I'll do it properly. 
	// Should I extend Pixiv Fixiv support to Nijie? The initial motivation was Pixiv's awful lazy-loading. Nijie is high-quality, high-res, and low-nonsense. 
	// At least consider inserting some direct image links on Nijie. Maybe below each image for accessibility... maybe invisible, for DownThemAll. 
	// Oh, split the difference: add thumbnails to the top of the page. (Would they link to the image files, or #diff_n? Bluh.) 
// https://nijie.info/view.php?id=232449 - what is this nonsense? A single-image multi-image post? Fuck off, Nijie. 
	// Apparently it has multiple pages, but the second one is "guro" - so it doesn't link it? Qua? 
// You know, since I check document.domain and silently fail on unknown domains, I could probably change @includes to *// instead of http/https. 
	// First check if address_bar_contains can safely be reconfigured to domain_name_contains. We want to prevent maliciousexample.com/?.tumblr.com crap. 
	// @include     /^https*://www\.furaffinity\.net/(view|full)/.*/ 
	// One reason not to do this (or not to keep it) is if it breaks GreasyFork's domain detection. 
		// Listing the filters instead of plain domain names is fine - but people can browse by domain. This won't show up on 'scripts for Pixiv' without detecting pixiv.com. 
		// I probably can't know whether this "works" until I upload a version with these @includes. Bluh. 
// Should probably replace all indexOf > -1 stuff with match. Oops, no: just most of it. match( '?' ) silently fails because it tries interpreting that as a regex. 
// Double-check that Weasyl and SoFurry actually work.
// Random failures on Paheal. E.g. http://rule34.paheal.net/post/view/2381933#search=bluebreed#dnr - works intermittently. 
// Should fix twitter to at least link the displayed images. In this script? In Gallery Smoothener? 
// Nijie broke. 
// https://www.newgrounds.com/art/view/archviie/rose-pressed#dnr - some Newgrounds pages have multiple images. Dammit. 
	// Simple answer is, don't redirect if there's a second image in the submission. 
// Incidentally, string.match returns an array. I've just been using it as true/false. 
// https://www.pornhub.com/album/31567741 ? 
// https://myhentaigallery.com/gallery/show/7994/1 - "Pixiv Fixiv" approach might be more useful. 
// I should probably parse &amp; crap by default. 
// https://www.megabooru.com/post/view/223100?search=homestuck ? 
// Paheal has been fucky. Might be some CloudFlare bullshit, or my new wifi dongle, or quantum eddies in the spacetime continuum, I don't fucking know. 
// Tumblr now treats bare image URLs like the old /image/ pages. Right-click, view image - same URL, different results. I hate the modern web. 

// Since I'm just leafing through HTML (usually), can I jump to the image /before/ trying to load the page? GreaseMonkey has a wonky option for running the script before the page runs, but I don't think we get all the HTML first. Maybe... maybe AJAX the page we're on? Like, @RunAtStart or whatever, then create a little blank page, then grab the URL via XmlHTTPgetObject or whatever, then read the HTML as responseText. The trouble (I expect) would be going back to the normal page when someone hits 'back.' This script shouldn't run... but any browser will probably have cached the fake page. 

// Owyn Tyler has a ridiculously replete script with similar goals called Handy Just Image - http://userscripts.org/scripts/show/166494
// The supported-site list is waaay longer than mine, and/but his goals are more complex. Image Glutton exists only to deliver the image. 
// He's having trouble with back-trapping, though. His solution sounds absurdly complex even compared to mine. Test the script and recommend help if possible. 

// Changes since last upload:
// Fixed InkBunny for small images. 
// Added Newgrounds videos. 
// Added RedGIFs, which is basically GfyCat. 
// Added GifDeliveryNetwork, which is basically RedGIFs? 
// Added reload_if for Sankaku Complex. 
// Do-not-redirect on Gelbooru comment pages. Mostly. 
// Added @noframes for TamperMonkey. (GreaseMonkey works that way by default.) 

// global variables, for simplicity
var image_url = '';		// location of the full-size image to redirect to
var wait_for_dnr = false;		// some site URLs use "#" liberally, so if this var isn't empty, only "#dnr" will stop a redirect
var simple_redirect = false; 		// some domains are kicking back my JS redirect (for native referral), so do naive location=url instead
var page_failed = false; 		// If the page 503s or otherwise forces us to reload, wait a moment, then reload. 
var interval_handle; 		// In case we need to set an interval, this is the global handle to kill it. Because a simple "die" or "clearInterval( this )" would be too much to ask. 
var assume_extension = true; 		// Most sites should obviously point to an image, so if there's no file extension, guess ".jpg". This breaks DeviantArt. 

// detect site, extract image URL, then decide whether or not to redirect
switch( document.domain.replace( 'www.', '' ) ) { 		// Remove "www" to avoid cases where both example.com and www.example.com are supported. 
		////////// 		Simple extract_image_url_after sites
	case 'e621.net': image_url = document.getElementById( 'image-container' ).dataset.fileUrl; simple_redirect = true; break;    
	case 'e926.net': image_url = document.getElementById( 'image-container' ).dataset.fileUrl; simple_redirect = true; break;    
	case 'weasyl.com': extract_image_url_after( '<div id="detail-art">', '/' ); break; 		// also redirects to plaintext/HTML on stories, haha 
	case 'y-gallery.net': extract_image_url_after( 'id="idPreviewImage"', 'http://' ); break; 		// Fucked, but they say they're coming back eventually 
	case 'rule34.xxx':  extract_image_url_after( '>Edit</a></li>', '//' ); break;
	case 'derpibooru.org':  extract_image_url_after( ' View</a>', '//' ); simple_redirect = true; break;
	case 'chan.sankakucomplex.com':  extract_image_url_after( '<li>Original:', '//' ); image_url = image_url.replace( '&amp;', '&' ); reload_if( 'Please slow down or'); break; 
	case 'idol.sankakucomplex.com':  extract_image_url_after( '<li>Original:', '//' ); image_url = image_url.replace( '&amp;', '&' ); reload_if( 'Please slow down or'); break; 
	case 'furiffic.com': extract_image_url_after( 'onload="$', '//' ); break; 		// Not using og:image because different URL causes image to re-load if user hits Back 
	case 'jabarchives.com': extract_image_url_after( 'class="group1"', '/main' ); break;
	case 'gfycat.com': extract_image_url_after( 'og:video', '//' ); break; 
	case 'redgifs.com': extract_image_url_after( 'og:video', '//' ); break; 
	case 'gifdeliverynetwork.com': extract_image_url_after( 'og:video', '//' ); break; 
		////////// 		Slightly complicated extract_image_url_after sites
	case 'rule34hentai.net': extract_image_url_after( 'shm-zoomer', '/_images/' ); wait_for_dnr = true; reload_if( '<h2>Rate limit hit' ); break; 	// wtf? even 'view image' returns text nonsense. images save fine. I bet the site's lying about the mime type. google's not helping for other answers, and I can't fix that, so 'meh' for now. sorry. 
	case 'rule34.paheal.net': extract_image_url_after( 'Links</th>', 'http' ); wait_for_dnr = true; reload_if( '<h1>429 Too Many Requests' ); break; 
	case 'majhost.com':  image_url = document.getElementsByTagName( "img" )[0].src; break; 		// first and only <img> tag 
	case 'luscious.net':  image_url = document.getElementsByClassName( 'icon-download' )[0].href; wait_for_dnr = true; break;
	case 'gelbooru.com': 	extract_image_url_after( "og:image", '//' ); simple_redirect = true; break; 
	case 'youhate.us': 	extract_image_url_after( "og:image", '//' ); simple_redirect = true; break; 
	case 'aryion.com': extract_image_url_after( "item-box", '//' ); image_url = image_url.split("'")[0]; simple_redirect = true; break; 		// Singlequote terminator 
	case 'myhentaigallery.com': extract_image_url_after( 'class="gallery-slide">', '//' ); image_url = image_url.replace( '&amp;', '&' ); simple_redirect = true; break; 
	case 'megabooru.com': image_url = document.getElementById("main_image").src; image_url = image_url.split( '%' )[0]; simple_redirect = true; break; 
		////////// 		Simple custom sites
	case 'sofurry.com': 
		image_url = window.location.href.replace('sofurry.com/view/','sofurryfiles.com/std/content?page='); 
		if( document.body.outerHTML.indexOf( '<div id="sfContentImage' ) < 0 ) { image_url = ''; } 		// Do not redirect from stories
		if( document.body.outerHTML.indexOf( '<div class="sf-story"' ) > 0 ) { image_url = ''; }  		// Really do not redirect from stories 
	case 'danbooru.donmai.us':  
		extract_image_url_after( '% of original (', '/data/' );		// resized images will say "X% of original (view full)" or something like that
		if( image_url === '' ) { image_url = document.getElementById( "image" ).src; } 		// IMG and VIDEO both use SRC. Thank god. 
	case 'furaffinity.net':  		// This is a mess because I'm trying not to redirect from stories / music... but FA kindly links the thumbnail images for those. 
		reload_if( 'center;">Error 503' ); 
		extract_image_url_after( '<div class="alt1 actions', '//' );    // Works even when not signed in 
		if( image_url == "" ) { extract_image_url_after( '<img id="submissionImg"', '//' ); } 		// New UI 
		// Choosing not to redirect based on content type is impossible because FA's tags and categories are a complete joke. Nothing is reliable. 
	case 'e-hentai.org': image_url = document.getElementById( 'img' ).src; break;
	case 'nijie.info': 
		reload_if( 'title>429' ); 
		extract_image_url_after( '"thumbnailUrl":', 'http' ); 		// E.g. https://pic.nijie.net/03/__rs_cns30x30/nijie_picture/1126260_20190526173732_0.png
		let url_parts = image_url.split( '/' ); 		// Can't chain these because splice returns the spliced element instead of the array minus that element.
		url_parts.splice( 4,1 ); 
		image_url = url_parts.join( '/' ); 		// E.g. https://pic.nijie.net/03/nijie_picture/1126260_20190526173732_0.png
		if( document.body.outerHTML.indexOf( '#diff_' ) > 0 ) { image_url = window.location.href.replace( 'view.php', 'view_popup.php' ); } 		// Multi-page view, not single image. 
		// I probably want to insert links to the individual images, on the view_popup page. 
	case 'sleepymaid.com':  
	case 'yay.sleepymaid.com':  
		image_url = document.getElementById( 'the-image' ).src; 
		if( document.getElementById( 'next' ) ) { image_url = ''; }  		// Don't redirect on comic pages 
	case 'imageboard.neko-sentai.com':  image_url = document.getElementById( 'main_image' ).src; break;
	case 'uberbooru.com': 
		extract_image_url_after( 'Size: <a', '/data' ); 
		if( image_url.indexOf( '<' ) > -1 ) { image_url = ''; } 		// Uberbooru is having back-end problems with missing images. Don't redirect if we grabbed HTML instead. 
	case 'hiccears.com':  extract_image_url_after( 'href="./upl0ads', './' ); break; 		// Wow, long garbage names. Can we use Download titles? Apparently not. 
	case 'hentai-foundry.com': 
		extract_image_url_after( ' ', '//pictures.' ); 
		if( image_url.indexOf( "';" ) > 0 ) { image_url = image_url.substring( 0, image_url.indexOf( "';" ) ) } 		// Singlequote terminate, more or less - only on resizable images 
		reload_if( '<h1>An error occurred.' ); 
	case 'pbs.twimg.com':
		image_url = window.location.href.split( '&' ).filter( s => ! s.match( 'name=' ) ).join( '&' ) + '&name=orig'; 
		if( image_url.indexOf( '?' ) < 0 ) { image_url = image_url.replace( '&', '?' ); }  		// string.match( '?' ) reads the ? like a regex. In quotes. Ugh. 
		assume_extension = false;
	case 'lolibooru.moe': image_url = document.getElementById( 'highres' ).href; break; 
		////////// 		Sites complex enough to shove into a function down below 
	case 'inkbunny.net': scrape_inkbunny(); break;
	case 'pixiv.net': scrape_pixiv(); break;
	case 'mspabooru.com': scrape_booru(); break;
	case 'safebooru.org': scrape_booru(); break;
	case 'bronibooru.com': scrape_booru(); break; 
	case 'tbib.org': scrape_booru(); break; 
//	case 'megabooru.com': scrape_booru(); break; 
	case 'newgrounds.com': scrape_newgrounds(); break; 
////////// 		Holdovers from the previous method; domains that don't neatly conform to document.domain switch selection. 
if( document.domain.match( 'tumblr.com' ) ) { extract_image_url_after( '"og:image"', 'http' ); simple_redirect = true; } 
if( document.domain.match( 'deviantart.com' ) ) { scrape_deviantart(); wait_for_dnr = true; }  
if( document.domain.match( '.booru.org' ) ) { scrape_booru(); } 
if( document.domain.match( 'beta.furrynetwork.com' ) ) { interval_handle = setInterval( scrape_furrynetwork, 500 ); } 		// This site's designers are loons. 

// If the page didn't load properly, but could be fixed by reloading, then wait a moment and reload 
if( page_failed ) { 		// If we get a 503 or other 'please reload' error
	image_url = ''; 		// do not redirect this time
	setTimeout( function inline_reload() { location.reload(); }, Math.floor((Math.random() * 10) + 1) * 1000 ); 		// 1s-10s pause. Can't believe you have to name inline functions. 

// Don't redirect if the filetype is obviously not an image. SWF, TXT, MP3, etc. 
// It's tedious to detect flash, story, and music pages on every website supported, so instead let's just cancel redirection based on those file extensions.
// Added ZIP & RAR because apparently DeviantArt lets you host 3D models and stuff. Automatically downloading those is not what this script is for. 
var ext = image_url.substring( image_url.lastIndexOf( '.' ) + 1, image_url.length ); 		// e.g. "png"
var not_images = [ 'mp3', 'swf', 'txt', 'docx', 'pdf', 'doc', 'rtf', 'midi', 'mid', 'wav', 'flv', 'cab', 'zip', 'rar' ];  		
for( var n in not_images ) { if( ext == not_images[n] ) { image_url = ''; } } 		// If the extension is in our blacklist, don't redirect. 
// Oh right. Doesn't work on FA because FA points to the icon. Yaaayfuck. 

redirect(); 		// Slightly clunky way to trigger this from website-specific functions. "Don't repeat yourself." Execution continues:

function redirect() { 
	// Having defined image_url based on the page's HTML or DOM, modify the current URL to prevent back-traps, then redirect to that full image. 
	var do_we_redirect = true; 		// If we've come this far we'll probably go to an image. 
	if( image_url == '' ) { do_we_redirect = false; } 		// Don't redirect to an empty string. (Emptying this string is how some functions fail safe.) 
	if( !wait_for_dnr && window.location.href.match( '#' ) ) { do_we_redirect = false; } 		// Don't redirect if the wait_for_dnr flag is false and there's a hash. (E.g. FA comments.) 
	if( window.location.href.match( '#dnr' ) ) { do_we_redirect = false; } 		// Don't redirect if there's a #dnr in the URL. 
	if( do_we_redirect == true ) 		// So much clearer than a mess of &&s and ||s. 
		// some images don't redirect properly, even if you manually "view image" - so we append ".jpg" to URLs without extensions, forcing the browser to consider them images
		// even if this doesn't work, the new URL should just 404, which is better than the semi-modal "octet stream" dialog seen otherwise. 
		if( assume_extension ) { 
			if( image_url.lastIndexOf( '/' ) > image_url.lastIndexOf( '.' ) ) { image_url = image_url + '.jpg'; }		// if there's not a "." after the last "/" then slap a file extension on there 
			if( image_url[ image_url.length - 1 ] == '.' ) { image_url = image_url + 'jpg'; }		// if the URL ends with a dot, slap a file extension on there 

		// modify current location, so that when the user clicks "back," they aren't immediately sent forward again
		modified_url = window.location.href + '#dnr'; 		// add do-not-redirect tag to current URL
		history.replaceState( {foo:'bar'}, 'Do-not-redirect version', modified_url );		// modify URL without redirecting. {foo:'bar'} is a meaningless but necessary state object. 

		image_url = encodeURI( image_url ); 		// Executing code with strings from the page has always been a mildly horrifying attack surface - hopefully this defangs it. 
		if( simple_redirect ) { window.location.href = image_url; } 		// This has different referral properties than clicking a link or displaying an image, so some sites 403 
		else { location.assign("javascript:window.location.href=\""+image_url+"\";"); } 		// Pixiv-friendly redirect to full image: maintains referral, happens within document's scope.
}		// end of main execution

// ----- //			Functions for readability

function extract_image_url_after( string_before_url, url_begins_with ) {		// extract the first quote-delimited string that appears after unique first var and begins with second var
	var html_elements = document.getElementsByTagName('html'); 		// this avoids doing getElementsEtc every time, while accessing the whole page HTML by reference
	var string_index = html_elements[0].innerHTML.indexOf( string_before_url ); 		// find a unique string somewhere before the image URL

	if( string_index > -1 ) {
		var image_index = html_elements[0].innerHTML.indexOf( url_begins_with, string_index );  		// find where the image URL starts after the unique string
		var delimiter_index = html_elements[0].innerHTML.indexOf( '"', image_index ); 		// find first doublequote after the image URL starts
		image_url = html_elements[0].innerHTML.substring( image_index, delimiter_index ); 		// grab the image URL up to the next doublequote 
//	return image_url;		// Debug

function reload_if( error_string ) { 
//	var html_elements = document.getElementsByTagName('html'); 		// avoid doing getElementsEtc every time, while still accessing the whole page's HTML by reference
//	var string_index = html_elements[0].innerHTML.indexOf( error_string ); 		// look for a string indicating the page failed to load 
//	if( string_index > -1 ) { page_failed = true; } 
	if( document.body.innerHTML.match( error_string ) ) { page_failed = true; } 

// ----- //			Functions for individual websites (separated for being especially long)

// DeviantArt sometimes doesn't redirect until you F5. I suspect it's their fancy-pants not-actually-redirecting nonsense. 
// Websites - stop acting stupid and just /be documents./ You are not an app. 
// This used to be horrible, then it was simple, then it was horrible again. Images have some ridiculous "token," without which they 404, 401, or simply hang. 
// I really hate this website. 
// Now looking for an img src matching both 'fullview.' and '?token='. 
// Nope, now grabbing the full URL from the preload header. From wherever possible. Briefly, this was simple again. 
function scrape_deviantart() { 
	try{ image_url = Array.from( document.head.getElementsByTagName( 'link' ) ).filter( link => link.href.match( 'token=' ) )[0].href; } catch(e) { } 
	// Why isn't undefined falsy, Javascript? Who hurt you? 
	if( image_url == '' ) { image_url = Array.from( document.head.getElementsByTagName( 'meta' ) ).filter( meta => meta.content.match( 'fullview' ) )[0].content; } 
	// You'd think 'fullview' would always be preferable, but on e.g. https://www.deviantart.com/thecynicalhound/art/Tofauti-Sawa-169-781760760 it's a JPG of a PNG submission. 

/* 		// Tried to be clever, but some full-image URLs don't contain 'fullview'. Exceptions to exceptions are the rule on DeviantArt. I really hate this website. 
	image_url = Array.from( document.head.getElementsByTagName( '*' ) )
		.filter( child => child.href != null || child.content != null ) 
		.map( child => { if( child.href != null ) { return child.href; } else { return child.content; } } ) 
		.filter( url => url.match( 'fullview.' ) && url.match( 'token=' ) )

	// Pages like https://www.deviantart.com/ninjapunchy/art/kara-830828367 still redirect to "pre" filesizes. And higher resolutions are available. 
	// Ditto https://www.deviantart.com/bara-chan/art/jelly-milk-tea-829153133 
/*		// This only works sometimes - better to redirect to a mid-size image than to an error. 
		// E.g. https://www.deviantart.com/shiniez/art/sunstone-7-001-011-825087711 treats the token as invalid without /v1/ nonsense. 
		// Best I've figured out is to remove 'q_' and get an original-quality JPG. But the resolution is still wrong for e.g. the ninjapunchy example. Which is a PNG anyway. 
		// Nope, not original-quality - it doesn't match the "download" option. It just defaults to 90% quality, I guess. 
		// Can't be conditional on .match('fullview') because the shiniez image doesn't match 'fullview'. 
		// Download URL plus token? Nope. Totally different format. 
		// window.__INITIAL_I18N__ ? Nope, it's in window.__INITIAL_STATE__ which apparently gets removed. Christ. 
		// I can isolate and eval() that again - but it still doesn't appear to contain the proper full-size URL. Nevermind the horror of relying on eval(). 
	let token = '?' + image_url.split( '?' ).pop(); 
	image_url = image_url.split( '/v1/' )[0];  		// Break at /v1/fill/w_690,h_1159,q_70,strp nonsense, but maintain token 
	image_url = image_url.split( '?' )[0]; 		// Break on ?token= if there was no /v1/ nonsense to strip
	image_url = image_url + token; 

function scrape_inkbunny() {
	var image_index = document.body.outerHTML.indexOf( 'files/screen/' ); 		// Find the middle of a screen-sized image URL 
	image_index = document.body.outerHTML.lastIndexOf( 'https://', image_index ); 		// ... then back up to the start of it
	if( image_index !== -1 )		// if that URL is found
		var delimiter_index = document.body.outerHTML.indexOf( '"', image_index );		// find first doublequote delimiter after URL
		image_url = document.body.outerHTML.substring( image_index, delimiter_index );		// grab delimited URL 
		image_url = image_url.replace( '/screen/', '/full/' );		// turn screen URL into full URL - we don't care if /screen/ is already full-size, because /full/ will kindly redirect anyway

	try { 
		image_url = document.getElementById( 'submission-highdefURL' ).value; 
	} catch(e) {}; 		// Javascript, why can't you just fail gracefully when a function returns nothing? 

	if( image_url == '' ) { 		// Naturally my clean and simple modernization gets tripped up by images that aren't resized.
										// If you operate an art site, ID your goddamn img tags, and treat small images the same as big images. 
		image_url = Array.from( document.body.getElementsByTagName('img') )
			.map( img => img.src )
			.filter( src => src.match( 'files/screen' ) )
	wait_for_dnr = true; 
	simple_redirect = true; 

	// if this page is the landing page for a multi-image submission, do not redirect 
	// Look for 'show custom thumbnails' button (indicating multi-page submission) or #pictop (which doesn't appear on landing pages for multi-page submissions)
	if ( document.body.outerHTML.indexOf( '<form id="changethumboriginal_form"' ) !== -1 && !window.location.href.match( '#pictop' ) ) { 
		image_url = '';		// note: we do redirect on URLs for individual pages, including the first. 

// Furrynetwork is a joke because every single page has the same HTML. We have to use the DOM, but on an unknown delay, because these fools were too clever to just deliver a goddamn document. 
function scrape_furrynetwork() {
	let link_list = Array.from( document.getElementsByClassName( 't--reset-link' ) ); 
	if( link_list.length > 0 ) {
		clearInterval( interval_handle ); 		// Once we detect something - anything - stop looping. 
		image_url = link_list[0].href; 		// Safely handling an HTMLcollection, because Javascript is pain. 
		if( image_url !== window.location.href ) { 
			// Fuck it, copy-paste for now. This can't just 'return' because it's faux-parallel. 
			let modified_url = window.location.href + '#dnr'; 		// add do-not-redirect tag to current URL
			history.replaceState( {foo:'bar'}, 'Do-not-redirect version', modified_url );		// modify URL without redirecting. 
			window.location.href = image_url; 

// Miraculously, Ugoira stuff still works. 
function scrape_pixiv() { 
	// If this is a redirect page, just fucking redirect. Test case: 
	// https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72946956#dnr 
	if( window.location.href.match( 'jump.php?' ) ) { 
		extract_image_url_after( 'noopener noreferrer', 'http' ); 
		simple_redirect = true; 
		assume_extension = false; 
		return; 		// Skip all this other stuff

	// How does the non-simple redirect manage to put the code into the address but but then not work? The page displays the URL as text. 
	// But if you highlight the address bar and hit enter, it actually goes to that URL, like on any other dang website. 
	// I want some variation on history.replaceState that forces us to -go- to the same page. Maybe push to 'forward' and then 'go forward.' 

	let submission = window.location.href.split( '/' ).pop().split( '#' )[0]; 		// E.g. https://www.pixiv.net/en/artworks/12345#etc -> 12345 
	fetch( 'https://www.pixiv.net/ajax/illust/' + submission, { credentials: 'include' } ).then( response => response.text() ).then( text => { 
		// Thanks again to Pixiv Plus for identifying the relevant JSON at a predictable URL. 
		pixiv_data = JSON.parse( text ).body;  		// There's probably more direct way to 'JSON.parse( url )'. It's why JSON exists. 
		image_url = pixiv_data.urls.original; 
		if( pixiv_data.pageCount > 1 ) { image_url = ''; } 		// Don't redirect to a single image if this is a multi-image "manga" submission. 

		// Bringing back Ugoira / Ugoku animation links in the absence of the pixiv.context object required cleverness, research, and then giving up and reading Pixiv Plus's source.
		// So thanks and kudos to those guys for finding a page JSON file that points to an ugoira JSON file that names a URL that's basically just the image URL plus "ugoira."
		// E.g.  '/ajax/illust/65423021' and 'ajax/illust/65423021/ugoira_meta' are the JSON for id=65423021. The latter names several ZIP files.
		// So basically this takes 
			// https://i.pximg.net/img-original/img/2017/10/14/06/24/30/65423021_ugoira0.jpg and outputs 
			// https://i.pximg.net/img-zip-ugoira/img/2017/10/14/06/24/30/65423021_ugoira1920x1080.zip then links to that instead of redirecting.
			// I don't actually know if all sizes are universal. 600x600 seems to be the baseline. Do they ever top 1920x1080?
			// See if globalInitData contains any ugoira information. (Doesn't appear so.) 
		if( image_url.indexOf( 'ugoira' ) > -1 ) { 	
			var animation_url = image_url.split( 'ugoira0' )[0]; 		// E.g. 65423021_ugoira0.jpg -> 65423021_
			animation_url = animation_url + 'ugoira1920x1080.zip'; 		// E.g. 65423021_ -> 65423021_ugoira1920x1080.zip
			animation_url = animation_url.replace( 'img-original', 'img-zip-ugoira' ); 		// Middle bit in the URL. Same server, different directory. 
			animation_url = animation_url.split( '\\' ).join( '' ); 		// Remove all escaping backlashes. Should be decodeURI, but that sometimes leaves a trailing backslash. 

			var download_string = '<a style="text-decoration:none" href="' + animation_url + '">Download Ugoira animation frames (.zip)</a>';  
			// Can I give this a class such that it matches the original appearance? Pixiv is such a mess. 

			// All this frustrating bullshit just to insert a link:
			var download_interval = setInterval( function() {
				// Repeatedly check until the clientside nonsense page generates its "figcaption" element
				if( document.getElementsByTagName( 'figcaption' ) && !document.getElementById( 'download_link' ) ) {
					var temp_html = document.getElementsByTagName( 'figcaption' )[0].innerHTML;

					// Complete jank: insert the download link, as a string, immediately following the h1 tag. 
					// Would be "more correct" to do this with string.slice. 
					// I don't know where those commas come from. 
					var split_html = temp_html.split( '</h1>' ); 
					split_html.splice( 1, 0, '</h1>' + download_string ); 		// Surprise, this operation has a side effect instead of returning a string. Aristocrats.js. 
					document.getElementsByTagName( 'figcaption' )[0].innerHTML = split_html.join(); 		
					clearInterval( download_interval ); 		// Can I clear the interval variable, from inside the interval? Is JS that friendly about sloppy global variables? Apparently so! 
			}, 1000 )
			image_url = ''; 		// Don't redirect on animated images. 

		redirect();		// We have to do this manually now, since we're in a callback. 
	} )

// Maybe clean this up now that Gelbooru's stupid shit gets its own function. 
function scrape_booru() {		// this works on a wide variety of booru-style imageboards. 
//	reload_if( '<h1>503' );		// Beforehand?! Maybe??? Fucking Christ!  
//	location.reload(); 		// THIS IS AWFUL BUT SO IS THE SITUATION 
	extract_image_url_after( '>Resize image</a>', 'http://' );		// for booru's which have automatic resizing and images which require it
	// Gelbooru's anti-adblock shit might make the script fail the FIRST time you load a page, but not subsequent times. God dammit. 
	// Might be time for Gelbooru to get its own scrape function, because god damn. 
	extract_image_url_after( "$('edit_form')", '//' ); 		// For booru's with automatic resizing on, use the Original Image link, which appears after the Edit button
	if( image_url === '' ) { extract_image_url_after( "$('resized_notice')", '//' ); } 		// Hey guess what! Gelbooru now serves different sidebars for adblock. Fuck you!
	if( image_url === '' ) { extract_image_url_after( 'class="showEditBox">', '//' ); } 		// Hey guess what!!! Gelbooru now just tells you not to adblock! Fuuuck youuu! 
	if( image_url === '' ) {		// otherwise, use the image that's being displayed 
		var container = document.getElementById( 'image' ); 		// Instead of lurching through raw HTML, let's just grab the display image via the DOM. 
		image_url = container.src; 		// "You think it's cool that things don't always have to be a federal fucking issue." 
//	reload_if( '<h1>503' ); 		// Doesn't work for some reason.
//	reload_if( '503' ); 		// Same.
//	page_failed = true; 		// Debug. Booru pages will reload if this variable is set. So why isn't it being set? 
	// https://jamesab-art.booru.org/index.php?page=post&s=view&id=1130 - Except this one loops? What the fuck? There's not even the string '503' on the page. 
	// Ah - there's a 503 in the image URL. So son of a fuck, it's just not checking the page text when a 503 error occurs. 
	// document.body.innerHTML.match( '<h1>503' ) returns an array, so it's not a stupid regex thing. 
//	setTimeout( function fuckoff() { 
//		if( window.location.href.match( 'page=post' ) ) { setTimeout( function inline_reload() { location.reload(); }, Math.floor((Math.random() * 10) + 1) * 1000 ); } }
//	, 1000 )
//	setInterval( function fuckoff() { if( window.location.href.match( 'page=post' ) ) { location.reload(); }, 1000 ); 
//	setInterval( function fuckoff() { location.reload(); }, 1000 ); 
//	setInterval( function fuckoff() { window.location = window.location; }, 1000 ); 
	// Reload the goddamn page, you piece of shit! 
	// https://stackoverflow.com/questions/52613406/how-to-catch-status-503-in-tampermonkey - 'userscripts don't run outside 200.' Except every other site fucking does! 

// Finding images is pretty easy, but multi-image submissions need special handling. 
// Simply not redirecting on multi-image submissions is insufficient because the dang images ought to link to themselves. 
function scrape_newgrounds() { 
	extract_image_url_after( 'full_image_text', '\\/\\/' ); 
	image_url = image_url.split('\\').join(''); simple_redirect = true; 		// Un-escape URL 

	if( window.location.href.match( 'portal' ) ) { 		// If this is a video, grab the video. 
		image_url = embed_controller.getFileURL(); 		// Okay sure why not. 

	if( document.body.innerHTML.match( 'data-user-image' ) ) { 		// If the submission has multiple images:
		image_url = ''; 		// Do not redirect 
		// Link all subsequent images to themselves for easy middle-clicking. Oh, of course this requires a delay. Why make things easy? 
		// Naturally this causes problems, but only after I've uploaded the update. "Testing indicates the presence of bugs but not their absence." 
		// Images don't have a src defined until they're onscreen. Yeesh. No data either. Does Newgrounds just bodge a URL from the alt-text filename? 
		// New plan: avoid modifying images unless they're ready, mark images when they've been modified, and stop looping when modified count == relevant image count. 
		var interval_object = setInterval( function() { 
			Array.from( document.body.getElementsByTagName( 'img' ) ) 
				.filter( img => img.className == "media-block-center" && img.src != '' ) 		// Implicitly excludes glutton-linked. 
				.forEach( img => { 
					img.className += " glutton-linked"; 
					img.outerHTML = "<a href='" + img.src + "'>" + img.outerHTML + "</a>"; 
				} ); 
			if( document.body.getElementsByClassName( 'media-block-center' ).length == document.body.getElementsByClassName( 'glutton-linked' ).length ) { 
				clearInterval( interval_object ); 		// Relying on a global variable is sloppy design, but so is requiring a variable to let an interval unset itself. 
		}, 1000 ); 

Test suite of random URLs from the relevant sites: 
http://danbooru.donmai.us/posts/1162284?tags=dennou_coildata:text/html,<img src='http://example.com/image.jpg'>