Endless Pixiv Pages

By Mango Last update Feb 12, 2012 — Installed 6,204 times.

There are 43 previous versions of this script.

// ==UserScript==
// @name           Endless Pixiv Pages
// @namespace      http://userscripts.org/scripts/show/57567
// @include        http://www.pixiv.net/*
// @description    Makes Pixiv searches "bottomless", removes bookmark links, and adds links to Danbooru and the IQDB image search.
// @updated        2012-02-11
// ==/UserScript==

//Minimum height remaining to scroll before loading the next page.
var scrollBuffer = 500;

//Seconds to wait for a response from the next page before attempting to fetch the page again.
var timeToFailure = 30;

//Options to add links below each thumb; set to false to disable
var addIQDB = true;//IQDB image search (Danbooru)
var addSourceSearch = false;//Danbooru post search (looks for matching source)

//Source search options; login info is required
var danbooruLogin = "";//e.g. "UserName"
var danbooruPassHash = "";//e.g. "ab3718d912849aff02482dbadf00d00ab391283a"
var styleSourceFound = "color:green; font-weight: bold;";
var styleSourceMissing = "color:red;";
var sourceTimeout = 20;//seconds to wait before retrying query
var maxAttempts = 20;//# of times to try a query before completely giving up on source searches


//////////////////////////////////////////////////////////////////////////////////////
//////////// Don't mess with the below unless you really know your stuff. ////////////
//////////////////////////////////////////////////////////////////////////////////////

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

//Stop script if this page is inside an iframe.
if( window != window.top ) return;

//Startup variables.
var pending = false, sourceSearchDone = addSourceSearch, thumbIndex = 0, illustList = {}, artistList = {}, thumbList = [], sourceTimer = -1, firstList = true;

//Fix hidden thumbs and add links if necessary
processThumbs();
if( location.href.indexOf("mode=medium") > 0 )
	processThumbs(true);

//Stop script if there are no thumbnails.
if( (mainTable = getMainTable(document)) == null ) return;
var bottomMarker = mainTable.nextSibling.nextSibling;

//Stop script if there are no more pages.
if( (nextPage = getNextPage(document)) == null ) return;

//iframe to hold new pages
var iframe = document.createElement("iframe");
	iframe.addEventListener("load", pullingMore, false);
	iframe.width = 0;
	iframe.height = 0;
	iframe.style.visibility = "hidden";
	document.body.appendChild(iframe);

//Adjust buffer height
scrollBuffer += window.innerHeight;

//Prevent page from being cut off after ~35 added pages
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '#pixiv { overflow:visible; ! important }';
document.getElementsByTagName('head')[0].appendChild(style);

//Clear bottom page list.
getBottomPager(document).innerHTML = "";

//Watch scrolling
window.addEventListener("scroll", testScrollPosition, false);
testScrollPosition();

//====================================== Functions ======================================

function processThumbs(separateQuery)
{
	var thumbSearch = null;
	
	if( separateQuery )
	{
		firstList = true;
		thumbSearch = document.evaluate("//div[@class = 'works_display']/a/img[not(@endless)]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	}
	else
	{
		if( firstList )
			thumbSearch = document.evaluate("//li/a[contains(@href,'mode=medium') or contains(@href,'/novel/show.php')]/img[not(@endless)] | //div/a[contains(@href,'mode=medium') or contains(@href,'/novel/show.php')]/img[not(@endless)]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		if( !thumbSearch || thumbSearch.snapshotLength == 0 )
		{
			firstList = false;
			thumbSearch = document.evaluate("//li[@class='image']/a/p/img[not(@endless)]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		}
	}
	
	for( var i = 0; i < thumbSearch.snapshotLength; i++ )
	{
		var thumbImg = thumbSearch.snapshotItem(i);
		var thumbPage = (firstList ? thumbImg.parentNode : thumbImg.parentNode.parentNode);
		var thumbDiv = thumbPage.parentNode;
		var bookmarkLink = document.evaluate( ".//a[contains(@href,'bookmark_detail.php')]", thumbDiv, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue;
		
		//Mark thumb so it gets skipped when the next page loads.
		thumbImg.setAttribute("endless","done");
		
		//Disable pixiv's "lazy loading" so that the thumbs on the added pages show.  This only seems to come up on /search.php now.
		if( thumbImg.getAttribute("data-src") )
		{
			thumbImg.src = thumbImg.getAttribute("data-src");
			thumbImg.removeAttribute("class");
		}

		//Skip generic restricted thumbs
		if( thumbImg.src.indexOf("http://source.pixiv.net/") == 0 )
			continue;
		
		if( !bookmarkLink )
		{
			if( thumbDiv.getElementsByTagName("ul").length == 0 )
				thumbDiv.appendChild( document.createElement("br") );
			bookmarkLink = thumbDiv.appendChild( document.createElement("a") );
		}	
		
		if( addIQDB )
		{
			bookmarkLink.setAttribute("class", "bookmark-count");
			bookmarkLink.href = "http://danbooru.iqdb.org/?url="+thumbImg.src+"&fullimage="+thumbPage.href;
			bookmarkLink.innerHTML = "(IQDB)";
		}
		
		if( addSourceSearch )
		{
			thumbList.push({ link: bookmarkLink.parentNode.appendChild( document.createElement("a") ), source: thumbImg.src.replace(/_(s|100)\..*/,'') });
		}
	}
	
	if( sourceSearchDone )
	{
		sourceSearchDone = false;
		sourceSearch();
	}
}

function sourceSearch(attempt)
{
	//thumbList[index] = { link, source }
	//artistList[name] = { fullSearchLen }
	//illustList[illustID] = [] list of post IDs
	
	clearTimeout(sourceTimer);
	
	//Login info is required.
	if( danbooruPassHash.length == 0 || danbooruLogin.length == 0 )
		return;
	
	//If attempt not specified, start at 0.
	if( attempt == undefined )
		attempt = 0;
	
	//Processed all existing thumbs
	if( thumbIndex >= thumbList.length )
	{
		sourceSearchDone = true;
		return;
	}
	
	//If this thumb has already been processed successfully, stop.
	if( thumbList[thumbIndex].link.textContent.length > 0 &&
		!/attempt/.test( thumbList[thumbIndex].link.textContent ) )
			return;
	
	//Keep a count of how many times we've tried to run this search
	if( attempt >= maxAttempts )
	{
		thumbList[thumbIndex].link.textContent = " (failed after "+attempt+" attempts)";
		return;
	}
	thumbList[thumbIndex].link.textContent = " (attempt "+(++attempt)+")";

	sourceTimer = setTimeout( function(){ sourceSearch(); }, sourceTimeout*1000 );
	
	//Function to extract illustID from pixiv URL
	function indexFromURL(url) { return "x"+url.replace(/(.*pixiv.net\/img\/[^\/]+\/|(\.|_).*)/g,''); }
	
	var artistName = thumbList[thumbIndex].source.replace(/(.*\/img\/|\/.*)/g,'');// ...pixiv.net/img/artistName/...
	var wantedIndex = indexFromURL( thumbList[thumbIndex].source );
	var searchURL = thumbList[thumbIndex].source+"*";
		
	if( illustList[wantedIndex] && illustList[wantedIndex].length > 0 )
	{
		if( illustList[wantedIndex].length == 1 )
		{
			//Found one post
			thumbList[thumbIndex].link.textContent = " post #"+illustList[wantedIndex][0];
			thumbList[thumbIndex].link.href = "http://danbooru.donmai.us/post/show/"+illustList[wantedIndex][0];
			thumbList[thumbIndex].link.setAttribute("style",styleSourceFound);
		}
		else
		{
			//Found multiple posts
			thumbList[thumbIndex].link.textContent = " ("+illustList[wantedIndex].length+" sources)";
			thumbList[thumbIndex].link.href = "http://danbooru.donmai.us/post/index?tags=status:any+source:"+searchURL;
			thumbList[thumbIndex].link.setAttribute("style",styleSourceFound);
		}
		thumbIndex++;
		return sourceSearch(0);
	}
	else if( !artistList[artistName] || artistList[artistName].fullSearchLen < 0 )
	{
		//First time searching for this artist; try to find everything by this artist
		searchURL = "*.pixiv.net/img/"+artistName+"/*";
		artistList[artistName] = { fullSearchLen: -1 };
	}		
	else if( artistList[artistName].fullSearchLen < 100 ||
			 ( illustList[wantedIndex] && illustList[wantedIndex].length == 0 ) )
	{
		//Either the full search didn't return the maximum amount or we did a specific search and still didn't find it.
		thumbList[thumbIndex].link.textContent = " (no sources)";
		thumbList[thumbIndex].link.setAttribute("style",styleSourceMissing);
		thumbIndex++;
		return sourceSearch(0);
	}
	
	if( typeof(GM_xmlhttpRequest) != "undefined" ) GM_xmlhttpRequest(
	{
		method: "GET",
		url: 'http://danbooru.donmai.us/post/index.json?limit=100&tags=status:any+source:'+searchURL+"&login="+danbooruLogin+"&password_hash="+danbooruPassHash,
		onload: function(responseDetails)
		{
			if( responseDetails.responseText.indexOf("<title>Downbooru</title>") > 0 )
			{
				//Danbooru is down; don't do any more queries.
				thumbList[thumbIndex].link.textContent = " (Downbooru)";
				thumbList[thumbIndex].link.setAttribute("style","color:blue; font-weight: bold;");
				thumbList[thumbIndex].link.href = 'http://twitter.com/#!/search/danbooru%20from:teruyo%20within:15mi?q=danbooru+from%3Ateruyo+within%3A15mi';
				return;
			}
			if( /^ *$/.test(responseDetails.responseText) )
				return sourceSearch( attempt );//Error, retry
			
			var result;
			
			try { result = JSON.parse(responseDetails.responseText); }
			catch(err) { return sourceSearch( attempt ); /*Error, retry*/ }
						
			//Remember the post IDs associated with each illustID
			for( var i = 0; i < result.length; i++ )
			{
				if( !illustList[indexFromURL(result[i]["source"])] )
					illustList[indexFromURL(result[i]["source"])] = [];
				illustList[indexFromURL(result[i]["source"])].push( result[i]["id"] );
			}
			
			//If we're doing a full search, record total results to compare to 100 later
			if( artistList[artistName].fullSearchLen < 0 )
				artistList[artistName].fullSearchLen = result.length;
			//If we're doing a specific search, note that no posts have that illust ID
			else if( !illustList[wantedIndex] )
				illustList[wantedIndex] = [];
			
			return sourceSearch( attempt );
		},
		onerror: function(x) { sourceSearch( attempt ); },
		onabort: function(x) { sourceSearch( attempt ); }
	});
}

function getMainTable(source)
{
	return new XPathEvaluator().evaluate(".//node()[contains(@class,'linkStyleWorks') or contains(@class,'list_box') or @id='search-result']", source, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

function getBottomPager(source)
{
	var pager = new XPathEvaluator().evaluate(".//node()[@class='pages' or @class='pager']", source, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	return pager.snapshotItem(pager.snapshotLength-1);
}

function getNextPage(source)
{
	var next = new XPathEvaluator().evaluate(".//node()[@class='pages' or @class='pager']/node()/li/a[@rel='next']", source, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
	
	if( !next )
		return null;//No (more) pages
	else if( next.href.indexOf("http://www.pixiv.net/") < 0 )
		return "http://www.pixiv.net/"+next.href;//relative URL --> absolute URL
	
	return next.href;
}

function testScrollPosition()
{
	if( !pending && window.pageYOffset + scrollBuffer > bottomMarker.offsetTop )
	{
		pending = true;
		timeout = setTimeout(function(){pending=false;testScrollPosition();},timeToFailure*1000);
		iframe.contentDocument.location.replace(nextPage);
	}
}

function pullingMore(responseDetails)
{
	var newTable, nextElem, pageLink = document.createElement("div");
	clearTimeout(timeout);
	
	//Make sure page loaded properly
	if( (newTable = getMainTable(iframe.contentDocument)) == null )
	{
		pending = false;
		return;
	}
	
	//Add page link
	pageLink.setAttribute("style","font-size:large; text-align:left;");
	pageLink.innerHTML = '<hr style="clear: both;"><a href="'+nextPage+'">Page '+nextPage.replace(/.*(\?|&)p=([0-9]+).*/,'$2')+'</a>';
	mainTable.appendChild(pageLink);
	
	//Copy content from retrieved page to current page.
	while( (nextElem = newTable.firstChild) )
		mainTable.appendChild( document.adoptNode(nextElem) );
	
	//Fix hidden thumbs and add links if necessary
	processThumbs();
	
	//If no more pages, append the proper page list and stop the script.
	if( nextPage = getNextPage(iframe.contentDocument) )
	{
		pending = false;
		testScrollPosition();
	}
	else
	{
		testScrollPosition = function() { }
		getBottomPager(document).innerHTML = getBottomPager(iframe.contentDocument).innerHTML;
	}
}

function updateCheck()
{
	var scriptNum = 57567;
	
	//Only check for update if using Greasemonkey and no check has been made in the last day.
	if( typeof(GM_getValue) != "undefined" &&
		GM_getValue('a', 'b') != undefined &&
		parseInt( GM_getValue('last_check', 0) ) + 24*3600*1000 < new Date().getTime() )
	{
		GM_setValue( 'last_check', ''+new Date().getTime() );
		GM_xmlhttpRequest(
		{
			method: 'GET',
			url: 'http://userscripts.org/scripts/source/'+scriptNum+'.meta.js?'+new Date().getTime(),
			headers: { 'Cache-Control': 'no-cache' },
			onload: function(response)
			{
				var localVersion = parseInt( GM_getValue( 'local_version', 0 ) );
				var remoteVersion = parseInt( /@uso:version\s*([0-9]+?)\s*$/m.exec(response.responseText)[1] );
				
				if( !localVersion || remoteVersion <= localVersion )
					GM_setValue( 'local_version', remoteVersion );
				else if( confirm( 'There is an update available for the Greasemonkey script "'+/@name\s*(.*?)\s*$/m.exec(response.responseText)[1]+'".\nWould you like to go to the install page now?' ) )
				{
					GM_openInTab( 'http://userscripts.org/scripts/show/'+scriptNum );
					GM_setValue( 'local_version', remoteVersion );
				}
			}
		});
	}
}

//So as I pray, Unlimited Pixiv Works.