NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Eza's Image Glutton // @version 1.42.7 // @description Redirects to high-resolution images on gallery sites, skipping past descriptions & comments. // @author Ezalias (https://greasyfork.org/en/users/4876-ezalias) // @copyright 2021, Bruce Bentley (https://github.com/brucebentley) // @license MIT; https://opensource.org/licenses/MIT // @homepage https://greasyfork.org/en/users/4876-ezalias // @namespace https://github.com/brucebentley // @match *://*.deviantart.com/*/art/* // @match *://*.deviantart.com/art/* // @match *://*.media.tumblr.com/*/s* // @match *://*.tumblr.com/* // @match *://*.tumblr.com/image/* // @match *://gfycat.com/* // @match *://*imgur.com/* // @match *://i.imgur.com/*.gifv // @match *://luscious.net/c/* // @match *://luscious.net/pictures/c/* // @match *://pbs.twimg.com/media/* // @match *://r34hub.com/en/show/* // @match *://www.gifdeliverynetwork.com/* // @match *://www.redgifs.com/watch/* // @exclude *://pbs.twimg.com/media/*=orig* // @exclude *://www.deviantart.com/users/outgoing?* // @exclude *?comment* // @exclude *&pid=* // @exclude *#c* // @exclude *#comment* // @exclude *#dnr // @run-at document-idle // @noframes // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?updated=20180103 // @downloadURL https://raw.githubusercontent.com/brucebentley/userscripts/main/src/media/images/ezas-image-glutton.user.js // @updateURL https://raw.githubusercontent.com/brucebentley/userscripts/main/src/media/images/ezas-image-glutton.user.js // @grant none // ==/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 etc. - Site-specific cases where redirecting to the image is not desirable. E.g. when linked to individuals comments / comment pages. // global variables, for simplicity let image_url = ''; // location of the full-size image to redirect to let wait_for_dnr = false; // some site URLs use '#' liberally, so if this const isn't empty, only '#dnr' will stop a redirect let simple_redirect = false; // some domains are kicking back my JS redirect (for native referral), so do naive location=url instead let page_failed = false; // If the page 503s or otherwise forces us to reload, wait a moment, then reload. let 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. let 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 / get_link_with_text / querySelector sites 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; case 'r34hub.com': image_url = document.querySelector('.media-wrapper img, .media-wrapper source').src; break; // ------ Slightly complicated extract_image_url_after sites case 'luscious.net': image_url = document.getElementsByClassName('icon-download')[0].href; wait_for_dnr = true; break; case 'i.imgur.com': image_url = document.querySelector('source').src; break; // ------ Simple custom sites 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; break; } // ------ Holdovers from the previous method; domains that don't neatly conform to document.domain switch selection. if (domain('tumblr.com')) { // 2020 Tumblr is some bullshit. You cannot get a bare image. It always redirects to a page. But it's no longer a page with the large-sized image, because go fuck yourself. But: the general /image page we're still somehow redirecting from does have a large image... which you cannot right-click and save. I have fucking tried. Just bullshit of the highest order, instead of using CORS to deny hotlinks and just delivering a goddamn JPG. extract_image_url_after('"og:image"', 'http'); simple_redirect = true; // https://66.media.tumblr.com/99be3174c6066bb219cca0008f351ce5/4692a02d2fc92812-d6/s500x750/bd6f4ec02cc4a9aa036f3274b9c8af447e285a59.png if (document.querySelector('a')) { image_url = image_url.replace(/\/s\d*x\d*\//, '/s3072x3072/'); // E.g. s400x600, s640x960. image_url = image_url + '#dnr'; // Prevent infinite loops // Adding do-not-redirect sometimes breaks this, but I'd rather publish missing functionality than an infinite redirect trap. // I guess I should detect whether we're on an HTML page or a bare image? } } if (domain('deviantart.com')) { scrape_deviantart(); wait_for_dnr = true; } if (page_failed) { image_url = ''; } // If the page refused to load properly, do not redirect. (The delayed automatic reload is now in reload_if.) // 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. const ext = image_url.substring(image_url.lastIndexOf('.') + 1, image_url.length); // e.g. 'png' const not_images = ['mp3', 'swf', 'txt', 'docx', 'pdf', 'doc', 'rtf', 'midi', 'mid', 'wav', 'flv', 'cab', 'zip', 'rar']; for (const 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 as a function. // Slightly clunky way to trigger this from website-specific functions. 'Don't repeat yourself.' // Conveniently - any delay should (should) prevent Chrome from eating #dnr pages. // The minimum value for setTimeout is probably 4ms or 10ms, but aiming lower can't hurt. setTimeout(redirect, 1); // 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. let 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 const 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 const and begins with second var const html_elements = document.getElementsByTagName('html'); // this avoids doing getElementsEtc every time, while accessing the whole page HTML by reference const string_index = html_elements[0].innerHTML.indexOf(string_before_url); // find a unique string somewhere before the image URL if (string_index > -1) { const image_index = html_elements[0].innerHTML.indexOf(url_begins_with, string_index); // find where the image URL starts after the unique string const 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) { // Put reload on a delayed thread so that errors in the get-an-image-URL part can fail safely. if (document.body.innerHTML.match(error_string)) { // Look for a string indicating the page failed to load page_failed = true; // Prevent spurious redirection. setTimeout(function () { location.reload(); }, Math.floor(Math.random() * 10 + 1) * 1000); // 1s-10s pause. Most errors will be 503s, so let's avoid hammering the site like a naive spider. . } } function get_link_with_text(string_in_text) { // image_url = Array.from( document.querySelectorAll( 'a' ) ).find( a => a.textContent.match( string_in_text ) ).href; // Goddamn TypeErrors. let link = Array.from(document.querySelectorAll('a')).find((a) => a.textContent.match(string_in_text)); if (link) { image_url = link.href; } } // Sensible 'are we on this site or not?!' function. function domain(ending) { let want = ending.split('.').reverse(); // Reverse order, from TLD to domain to subdomain(s). let have = document.domain.split('.').reverse(); for (let n = 0; n < want.length; n++) { if (want[n] !== have[n]) { return false; } } // Implicit else return true; } // ----- // Functions for individual websites (separated for being especially long) // Keeping old notes about Deviantart is pointless because there is nothing constant or reliable about this entire goddamn website. function scrape_deviantart() { image_url = (document.querySelector('link[href*="token="]') || { href: '' }).href; // Can't wait for object?.property syntax. 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. // Pretty sure the meta-fullview thing doesn't even work anymore. } /* Test suite of random URLs from the relevant sites: http://rule34.xxx/index.php?page=post&s=view&id=1399731 http://rule34.xxx/index.php?page=post&s=view&id=1415193 */