Greasy Fork is available in English.

Pixiv Direct Links

Turns thumbnail titles into direct links and disables lazy-loading on ranking pages.

// ==UserScript==
// @name           Pixiv Direct Links
// @namespace      https://greasyfork.org/scripts/4555
// @description    Turns thumbnail titles into direct links and disables lazy-loading on ranking pages.
// @include        *://www.pixiv.net/*
// @grant          none
// @version        2019.10.13
// ==/UserScript==

//Disable lazy loading images.  These appear on ranking pages and the "Recommended" section of the bookmarks page.
var dontSayLazy = true;

//----------------------------------------------------------------//

if( window == window.top )//not inside iframe
{
    //Link dem titles.
    linkThumbTitles(document);
    new MutationObserver( function(mutationSet)
    {
        mutationSet.forEach( function(mutation)
        {
            for( let i = 0; i < mutation.addedNodes.length; i++ )
                if( mutation.addedNodes[i].nodeType == Node.ELEMENT_NODE )
                    linkThumbTitles( mutation.addedNodes[i] );
        } );
    }).observe( document.body, { childList:true, subtree:true } );

    if( dontSayLazy && unlazyImage() )
    {
        //Initial page has lazy images; listen for more images added later
        new MutationObserver( function(mutationSet)
        {
            mutationSet.forEach( function(mutation)
            {
                for( let i = 0; i < mutation.addedNodes; i++ )
                    if( mutation.addedNodes[i].nodeType == Node.ELEMENT_NODE )
                        unlazyImage( mutation.addedNodes[i] );
            } );
        }).observe( document.body, { childList:true, subtree:true } );
    }
}

//----------------------------------------------------------------//

function unlazyImage(target)
{
	let images = ( target || document ).querySelectorAll("img[data-src]");
	for( let i = 0; i < images.length; i++ )
		images[i].src = images[i].getAttribute("data-src");
	return images.length;
}

function linkThumbTitles(target)
{
    //bookmark_new_illust.php, discovery, search.php
    let foundTitle = target.querySelectorAll("li a[href*='/artworks/'][title]");
    for( let j = 0; j < foundTitle.length; j++ )
        directLinkSingle( foundTitle[j] );
    
    //member.php, member_illust.php, new_illust.php, artworks (related works)
    foundTitle = target.querySelectorAll("li > div > a[href*='/artworks/']");
    for( let j = 0; j < foundTitle.length; j++ )
        directLinkSingle( foundTitle[j] );
    
    //main page
    foundTitle = target.querySelectorAll("li > a[href*='/artworks/'] > .title");
    for( let j = 0; j < foundTitle.length; j++ )
        directLinkSingle( foundTitle[j].parentNode );
    
    //bookmark.php
    foundTitle = target.querySelectorAll("li > a[href*='mode=medium'][href*='illust_id='] > .title");
    for( let j = 0; j < foundTitle.length; j++ )
        directLinkSingle( foundTitle[j].parentNode );

    //ranking.php
    foundTitle = target.querySelectorAll("a.title[href*='/artworks/']");
    for( let j = 0; j < foundTitle.length; j++ )
        directLinkSingle( foundTitle[j] );
    
    //response.php
    let image = target.querySelectorAll("li a[href*='mode=medium'][href*='illust_id='] img");
    for( let j = 0; j < image.length; j++ )
    {
        let page, title;
        for( page = image[j].parentNode; page.tagName != "A"; page = page.parentNode );

        //The prev/next thumbnails on mode=medium pages have text before/after the image.  Text also follows the image on image responses listings.
        if( !(title = page.getElementsByClassName("title")[0]) && (title = page.lastChild).nodeName != '#text' && (title = page.firstChild).nodeName != '#text' )
            continue;//Can't find title element

        //Start title link at mode=medium and change later.
        let titleLink = document.createElement("a");
        titleLink.href = page.href;
        titleLink.style.color = "#333333";//Style used on some pages

        //Move the title out of the thumbnail link
        page.removeChild(title);
        titleLink.appendChild(title);
        page.parentNode.insertBefore( titleLink, page.nextSibling );

        directLinkSingle( titleLink );
    }
}

function directLinkSingle(titleLink)
{
	let illustID;
	if( !titleLink || !titleLink.href )
		return;
    if( !(illustID = titleLink.href.match(/\/artworks\/(\d+)/)) && !(illustID = titleLink.href.match(/illust_id=(\d+)/)) )
        return;
    
	let req = new XMLHttpRequest();
	req.open( "GET", location.protocol+"//www.pixiv.net/artworks/"+illustID[1], true );
	req.onload = function()
	{
        let scripts = req.responseXML.head.getElementsByTagName("script");
        for( let i = 0; i < scripts.length; i++ )
        {
            //JSON requires double quotes around property names, forbids trailing commas, etc...  The illust info can't be simply parsed as a raw JSON object, so just grab the values we need.
            let originalURL = scripts[i].textContent.match(/"original":"(http[^"]+)"/);
            if( !originalURL )
                continue;
            
            //Do nothing for ugoira (animated) images
            if( originalURL.indexOf("ugoira") > 0 )
                return;
            
            //There are several pageCount properties; we just want the last one.
            let pageCount = 0, rCount = RegExp( '"pageCount": *(\\d+)', 'g' ), search;
            while( (search = rCount.exec( scripts[i].textContent )) !== null )
                pageCount = parseInt(search[1]);

            //Only link single images
            if( pageCount <= 1 )
                titleLink.href = originalURL[1].replace(/\\\//g,'/');

            return;
        }
        //console.log("Unable to find direct image link for illust #"+illustID[1]);
	};
	req.responseType = "document";
	req.send(null);
}