Pixiv Direct Links

Turns thumbnail titles into direct or mode=manga links, adds direct image links on mode=manga pages, replaces the medium thumbnail on mode=medium pages with the full size, and disables lazy-loading images.

目前為 2018-01-29 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name           Pixiv Direct Links
// @namespace      https://greasyfork.org/scripts/4555
// @description    Turns thumbnail titles into direct or mode=manga links, adds direct image links on mode=manga pages, replaces the medium thumbnail on mode=medium pages with the full size, and disables lazy-loading images.
// @include        *://www.pixiv.net/*
// @grant          none
// @version        2018.01.28
// ==/UserScript==

//Turn thumbnail titles into direct links (single images) or mode=manga links.  Some kinds of thumbnails aren't covered, and an isolated few (like #17099702) don't work.
var directTitles = false;

//Append direct links below images on mode=manga pages
var directManga = true;

//Force pixiv's 'book view' style for manga sequences to something like the normal view.  Clicking a page won't scroll the window to the next page.
var breakBookView = false;

//Replace the medium thumbnail on mode=medium pages with the full size.  The image will be relinked to the full size regardless of this setting.
var fullSizeMedium = true;

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

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

var fullSizeWidth = "740px";

if( typeof(custom) != "undefined" )
	custom();

if( location.search.indexOf("mode=manga_big") > 0 || location.search.indexOf("mode=big") > 0 )
{
	//Make the 'big'/'manga_big' image link to itself instead of closing the window
	let image = document.getElementsByTagName("img")[0];
	if( image )
	{
		let link = document.createElement("a");
		link.href = image.src;
		link.appendChild( document.createElement("img") ).src = image.src;
		document.body.innerHTML = "";
		document.body.appendChild( link );
	}
}
else if( location.search.indexOf("mode=manga") > 0 )
{
	let container = document.getElementsByClassName("full-size-container");
	if( directManga && container.length )
	{
		//Check the mode=manga_big page for the first page, since the sample extension is always "jpg".
		let req = new XMLHttpRequest();
		req.open( "GET", location.href.replace(/page=\d+&?/,'').replace('mode=manga','mode=manga_big&page=0'), true );
		req.onload = function()
		{
			let firstImage = req.responseXML.querySelector("img[src*='_p0.']").src;
			for( let i = 0; i < container.length; i++ )
			{
				//Add direct link below each page
				let link = document.createElement("a");
				link.textContent = "direct link";
				link.style.display = "block";
				link.href = firstImage.replace( "_p0.", "_p"+i+"." );
				container[i].parentNode.appendChild( link );
			}
		};
		req.responseType = "document";
		req.send(null);
	}
	else if( breakBookView && document.head.innerHTML.indexOf("pixiv.context.images") > 0 )
	{
		//Book view (e.g. #54139174, #57045668)

		let mangaSection = document.createElement("section");
		mangaSection.className = "manga";
		
		let scripts = document.head.getElementsByTagName("script");
		let hits = 0;
		for( let i = 0; i < scripts.length; i++ )
		{
			let urls = scripts[i].innerHTML.match( /pixiv.context.images[^"]+"([^"]+)".*pixiv.context.originalImages[^"]+"([^"]+)"/ );
			if( urls )
			{
				let full = urls[2].replace( /\\\//g, "/");
				mangaSection.innerHTML += '<div class="item-container"><a href="'+full+'" class="full-size-container"><i class="_icon-20 _icon-full-size"></i></a><img style="width:auto;height:auto;max-width:1200px;max-height:1200px" src="'+full+'" class="image">'+( directManga ? '<a href="'+full+'" style="display:block">direct link</a>' : '' )+'</div>';
				hits++;
			}
		}
		
		if( hits > 0 )
		{
			let sheet = document.createElement("link");
			sheet.setAttribute("rel","stylesheet");
			sheet.setAttribute("href","http://source.pixiv.net/www/css/member_illust_manga.css");
			document.head.appendChild( sheet );
			document.getElementsByTagName("html")[0].className = "verticaltext no-textcombine no-ie";
			document.body.innerHTML = "";
			document.body.appendChild( mangaSection );
		}
	}
}
else if( window == window.top )//not inside iframe
{
	if( directTitles )
	{
		//Link dem titles.
		linkThumbTitles([document]);
		new MutationObserver( function(mutationSet)
		{
			mutationSet.forEach( function(mutation){ linkThumbTitles( mutation.addedNodes ); } );
		}).observe( document.body, { childList:true, subtree:true } );
	}
	
	let worksDisplay = document.getElementsByClassName("works_display")[0];
	if( worksDisplay )
	{
		let mainImage, fullsizeSrc = 0, mainLink = worksDisplay.querySelector("a[href*='mode=']");
		if( mainLink )
			mainLink.removeAttribute('target');//Make link open in same window
		
		let oClass = document.getElementsByClassName("original-image");
		if( oClass.length == 1 )//47235071
		{
			let worksDiv = worksDisplay.getElementsByTagName("div")[0];
			worksDisplay.removeChild( worksDiv );//Need to remove instead of hide to prevent double source search links in other script
			let link = worksDisplay.insertBefore( document.createElement("a"), worksDisplay.firstChild );
			mainImage = link.appendChild( fullSizeMedium ? document.createElement("img") : worksDiv.getElementsByTagName("img")[0] );
			fullsizeSrc = link.href = oClass[0].getAttribute("data-src");
		}
		else if( mainLink && mainLink.href.indexOf("mode=big") > 0 && (mainImage = mainLink.getElementsByTagName("img")[0]) !== null )//17099702
		{
			//New thumbnails are always jpg, need to query mode=big page to get the right file extension.
			let req = new XMLHttpRequest();
			req.open( "GET", mainLink.href, true );
			req.onload = function()
			{
				mainLink.href = req.responseXML.getElementsByTagName("img")[0].src;
				if( fullSizeMedium )
					mainImage.src = mainLink.href;
			};
			req.responseType = "document";
			req.send(null);
		}
		
		if( mainImage && fullSizeMedium )
		{
			if( fullsizeSrc )
				mainImage.src = fullsizeSrc;
			mainImage.setAttribute("style", "max-width: "+fullSizeWidth+"; height: auto; width: auto;");
			worksDisplay.style.width = fullSizeWidth;
		}
	}
}

if( dontSayLazy && unlazyImage() && window == window.top )
{
	//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++ )
				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 pushTitleLink(list, link)
{
	let matcher;
	if( link && link.href && (matcher = link.href.match(/illust_id=(\d+)/)) && matcher[1] > 0 )
		list.push({ "id": matcher[1], "link": link });
}

function linkThumbTitles(targets)
{
	let titleList = [];
	
	for( let i = 0; i < targets.length; i++ )
        if( targets[i] == document || targets[i].nodeType == Node.ELEMENT_NODE )
        {
            //search.php
            let foundTitle = targets[i].querySelectorAll("a[href*='mode=medium'][href*='illust_id='][title]");
            for( let j = 0; j < foundTitle.length; j++ )
                pushTitleLink( titleList, foundTitle[j] );

            //bookmark.php, member_illust.php, new_illust.php, member.php (uploads), mypage.php (new works)
            foundTitle = targets[i].querySelectorAll("a[href*='mode=medium'][href*='illust_id='] > .title");
            for( let j = 0; j < foundTitle.length; j++ )
                pushTitleLink( titleList, foundTitle[j].parentNode );

            //ranking.php
            foundTitle = targets[i].querySelectorAll(".ranking-item a.title[href*='mode=medium'][href*='illust_id=']");
            for( let j = 0; j < foundTitle.length; j++ )
                pushTitleLink( titleList, foundTitle[j] );

            //member_illust.php (what image was responding to)
            foundTitle = targets[i].querySelector(".worksImageresponseInfo a.response-out-work[href*='mode=medium'][href*='illust_id=']");
            if( foundTitle )
                pushTitleLink( titleList, foundTitle );

            //response.php, member_illust.php (before/after thumbnails), ?member.php (bookmarks)?
            let image = targets[i].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 );

                pushTitleLink( titleList, titleLink );
            }
	}
	
	for( let i = 0; i < titleList.length; i++ )
		directLinkSingle( titleList[i] );
}

//Query an image's mode=medium page.
function directLinkSingle(title)
{
	let req = new XMLHttpRequest();
	req.open( "GET", location.protocol+"//www.pixiv.net/member_illust.php?mode=medium&illust_id="+title.id, true );
	req.onload = function()
	{
		let select = req.responseXML.getElementsByClassName("original-image");
		if( select.length == 1 )
			title.link.href = select[0].getAttribute("data-src");
		else if( (select = req.responseXML.querySelector(".works_display a[href*='mode=manga']")) !== null )
		{
			title.link.href = select.href;
			let page = req.responseXML.querySelectorAll("ul.meta li")[1].textContent.match(/(\d+)P$/);
			if( page )
				( title.link.firstChild.nodeName == '#text' ? title.link : title.link.firstChild ).title += " ("+page[1]+" pages)";
		}
	};
	req.responseType = "document";
	req.send(null);
}