Endless Pixiv Pages

Makes Pixiv searches "bottomless", adds IQDB image search links, searches for existing posts on Danbooru, and filters out images on search pages with less than a set number of favorites

As of 2014-07-12. See the latest version.

// ==UserScript==
// @name           Endless Pixiv Pages
// @namespace      http://userscripts.org/scripts/show/57567
// @include        http://www.pixiv.net*
// @description    Makes Pixiv searches "bottomless", adds IQDB image search links, searches for existing posts on Danbooru, and filters out images on search pages with less than a set number of favorites
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_deleteValue
// @grant          GM_openInTab
// @grant          GM_xmlhttpRequest
// @version        2014.07.12
// ==/UserScript==

//Main features
var makeEndless = true;
var addIQDB = false;//Add links to IQDB image searches (Danbooru)
var addSourceSearch = false;//Danbooru post search (looks for matching source)

//Endless settings
var scrollBuffer = 500;//Minimum height remaining to scroll before loading the next page.
var timeToFailure = 30;//Seconds to wait for a response from the next page before attempting to fetch the page again.

//Default minimum number of favorites for a thumb to be displayed; only affects pages that display favorite counts.
var minFavs = 0;

//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

//////////////////////////////////////////////////////////////////////////////////////

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

//Remove Premium-only options
var premium = document.getElementsByClassName("require-premium");
for( var i = premium.length - 1; i >= 0; i-- )
	premium[i].parentNode.removeChild( premium[i] );
setTimeout( function()
{
	var filter = document.getElementById("thumbnail-filter-container");
	if( filter )
		filter.parentNode.removeChild(filter);
}, 300 );
	
if( typeof(GM_getValue) == "undefined" || !GM_getValue('a', 'b') )
{
	GM_getValue = function(name,defV){ var value = localStorage.getItem("endless_pixiv."+name); return( value ? value : defV ); };
	GM_setValue = function(name,value) { localStorage.setItem("endless_pixiv."+name, value ); }
}

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

//Source search requires GM_xmlhttpRequest()
addSourceSearch = ( addSourceSearch && typeof(GM_xmlhttpRequest) != "undefined" );

//Startup variables.
var nextPage = null, timeout, pending = false, anyBookmarks = false, sourceTimer;

//Manga images have to be handled specially
if( location.search.indexOf("mode=manga") >= 0 )
{
	if( addSourceSearch )
	{
		var thumbList = [];
		var images = document.evaluate("//div[contains(@class,'item-container')]/img", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	
		for( var i = 0; i < images.snapshotLength; i++ )
		{
			var thisImage = images.snapshotItem(i);
			thumbList.push({ link: thisImage.parentNode.appendChild( document.createElement("div") ).appendChild( document.createElement("a") ),
							 pixiv_id: pixivIllustID( thisImage.getAttribute("data-src") || thisImage.src ), page: i });
		}
		sourceSearch( thumbList );
	}
	return;
}

//Add ability to set minFavs inside Search Options
var addSearch = document.getElementById("word-and");	
if( addSearch )
{
	anyBookmarks = true;
	
	//Load "minFavs" setting
	if( GM_getValue("minFavs") )
		minFavs = parseInt( GM_getValue("minFavs") );
	
	//Set option
	addSearch = addSearch.parentNode.parentNode;
	var favTr = document.createElement("tr");
	favTr.appendChild( document.createElement("th") ).textContent = "Minimum favorites";
	favInput = favTr.appendChild( document.createElement("td") ).appendChild( document.createElement("input") );
	favInput.type = "text";
	favInput.value = ""+minFavs;
	favInput.addEventListener("input", function()
	{
		if( /^ *\d+ *$/.test(this.value) )
		{
			GM_setValue("minFavs", this.value.replace(/ +/g,''));
			minFavs = parseInt( this.value );
		}
	}, true);
	addSearch.parentNode.insertBefore( favTr, addSearch );
}

//Fix thumbs and add links if requested
processThumbs();
if( ( addIQDB || addSourceSearch ) && location.href.indexOf("mode=medium") > 0 )
	processThumbs(null,true);
window.addEventListener( "DOMNodeInserted", function(e) { setTimeout( function() { processThumbs(e) }, 1 ) }, true );

//Prevent page from being cut off after ~35 added pages
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '#pixiv { overflow:visible; ! important } ul.images li.image, li.image-item{ height:auto !important; min-height:250px !important; padding:5px 0px !important } ';
document.getElementsByTagName('head')[0].appendChild(style);

//Stop script if there are no thumbnails.
mainTable = makeEndless && getMainTable(document);
if( !mainTable )
	return;

var bottomDoc = getBottomPager(document);
if( bottomDoc == null )
	return;
bottomDoc.parentNode.removeChild(bottomDoc);
mainTable.parentNode.parentNode.appendChild(bottomDoc);

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

//Hide the showcase on search pages
var showcase = document.getElementsByClassName("user-ad-container")[0];
if( showcase )
	showcase.style.display = "none";

//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;

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

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

function processThumbs(e,modeMedium)
{
	if( !addIQDB && !addSourceSearch && !minFavs )
		return;
	
	var thumbSearch = null, thumbList = [];
	
	if( e && e.target.tagName != "LI" && e.target.tagName != "UL" && e.target.tagName != "DIV" )
		return;
	
	if( modeMedium )
		thumbSearch = document.evaluate("//div[@class = 'works_display']/a/img[not(@endless)]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	else
	{
		//Member profiles have both a/img and a/div/img
		thumbSearch = document.evaluate("descendant-or-self::li/a[contains(@href,'mode=medium') or contains(@href,'/novel/show.php')]/img[not(@endless)] | "+
										"descendant-or-self::div/a[contains(@href,'mode=medium') or contains(@href,'/novel/show.php')]/img[not(@endless)] | "+
										"descendant-or-self::li[@class='image-item']/a/node()/img[not(@endless)]",
										(e ? e.target : document), null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	}
	
	for( var i = 0; i < thumbSearch.snapshotLength; i++ )
	{
		var thumbImg = thumbSearch.snapshotItem(i);
		var thumbPage = ( thumbImg.parentNode.tagName == "A" ? thumbImg : thumbImg.parentNode ).parentNode;
		var thumbDiv = thumbPage.parentNode;
		var bookmarkCount = 0, bookmarkLink = document.evaluate( ".//a[contains(@href,'/bookmark_detail.php')]", thumbDiv, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue;
		var sourceContainer = thumbDiv;
		
		//Mark thumb so it gets skipped when the next page loads.
		thumbImg.setAttribute("endless","done");
		
		//Skip generic restricted thumbs
		if( thumbImg.src.indexOf("http://source.pixiv.net/") == 0 )
			continue;
		
		//Skip special thumbs except on image pages
		if( thumbImg.src.indexOf("_100.") > 0 && location.search.indexOf("mode=") < 0 )
			continue;
		
		if( bookmarkLink )
		{
			//Thumb has bookmark info
			bookmarkCount = parseInt( bookmarkLink.getAttribute("data-tooltip","x").replace(/([^\d]+)/g,'') ) || 1;
			sourceContainer = bookmarkLink.parentNode;
		}
		else if( addIQDB )
		{
			//Thumb doesn't have bookmark info.  Add a fake bookmark link to link with the IQDB.
			bookmarkLink = document.createElement("a");
			bookmarkLink.className = "bookmark-count";
			if( anyBookmarks )
			{
				bookmarkLink.className += " ui-tooltip";
				bookmarkLink.setAttribute("data-tooltip", "0 bookmarks");
			}
			
			//Dummy div to force new line when needed
			thumbDiv.appendChild( document.createElement("div") );
			thumbDiv.appendChild( bookmarkLink );
		}
		else
		{
			//Dummy div to force new line when needed
			thumbDiv.appendChild( document.createElement("div") );
		}
		
		if( anyBookmarks && bookmarkCount < minFavs )
		{
			thumbDiv.parentNode.removeChild(thumbDiv);
			continue;
		}
		
		if( addIQDB )
		{
			bookmarkLink.href = "http://danbooru.iqdb.org/?url="+thumbImg.src+"&fullimage="+thumbPage.href;
			bookmarkLink.innerHTML = "(IQDB)";
		}
		
		if( addSourceSearch )
		{
			sourceContainer.appendChild( document.createTextNode(" ") );
			thumbList.push({ link: sourceContainer.appendChild( document.createElement("a") ), pixiv_id: pixivIllustID(thumbImg.src), page: -1 });
		}
	}
	
	sourceSearch( thumbList );
}

function pixivIllustID(url) { return url.replace( /.*\/(\d+)(_|\.)[^\/]+$/g, '$1' ); }
function pixivPageNumber(url) { return /_p\d+\./.test(url) ? url.replace( /.*_p(\d+)\..*/g, '$1' ) : "x" }

function sourceSearch( thumbList, attempt, page )
{
	//Login info is required.
	if( danbooruPassHash.length == 0 || danbooruLogin.length == 0 )
		return;
	
	//thumbList[index] = { link, id, page? }
	
	if( page === undefined )
	{
		//First call.  Finish initialization
		attempt = page = 1;
		
		for( var i = 0; i < thumbList.length; i++ )
		{
			if( !thumbList[i].status )
				thumbList[i].status = thumbList[i].link.parentNode.appendChild( document.createElement("span") );
			thumbList[i].link.textContent = "Searching...";
			thumbList[i].posts = [];
		}
	}
	if( attempt >= maxAttempts )
	{
		//Too many failures (or Downbooru); give up. :(
		for( var i = 0; i < thumbList.length; i++ )
		{
			thumbList[i].status.style.display = "none";
			if( thumbList[i].link.textContent[0] != '(' )
				thumbList[i].link.textContent = "(error)";
			thumbList[i].link.setAttribute("style","color:blue; font-weight: bold;");
			
		}
		return;
	}
	
	//Is there actually anything to process?
	if( thumbList.length == 0 )
		return;
	
	//Retry this call if timeout
	var retry = (function(a,b,c){ return function(){ setTimeout( function(){ sourceSearch(a,b,c); }, 1000 ); }; })( thumbList, attempt + 1, page );
	//var retry = (function(a,b,c){ return function(){ sourceSearch(a,b,c); }; })( thumbList, attempt + 1, page );
	sourceTimer = setTimeout( retry, sourceTimeout*1000 );
	
	// Combine the IDs from the thumbList into a single search string
	var query = "status:any+pixiv:";
	for( var i = 0; i < thumbList.length; i++ )
	{
		thumbList[i].status.textContent = " ["+attempt+"]";
		query += thumbList[i].pixiv_id+",";
	}
	
	GM_xmlhttpRequest(
	{
		method: "GET",
		url: 'http://danbooru.donmai.us/posts.json?limit=100&tags='+query+'0&login='+danbooruLogin+'&password_hash='+danbooruPassHash+'&page='+page,
		onload: function(responseDetails)
		{
			clearTimeout(sourceTimer);
			
			//Check server response for errors
			var result = false, status = null;
			if( /^ *$/.test(responseDetails.responseText) )
				status = "(error)";//No content
			else if( responseDetails.responseText.indexOf("<title>Downbooru</title>") > 0 )
			{
				maxAttempts = 0;//Give up
				status = "(Downbooru)";
			}
			else if( responseDetails.responseText.indexOf("<title>Failbooru</title>") > 0 )
				status = "(Failbooru)";
			else try {
				result = JSON.parse(responseDetails.responseText);
				status = "Searching...";
			}
			catch(err) {
				result = false;
				status = "(error)";
			}
			
			//Update thumbnail messages
			for( var i = 0; i < thumbList.length; i++ )
				thumbList[i].link.textContent = status;
			
			if( result === false )
				return retry();//Hit an error; try again?
			
			for( var i = 0; i < thumbList.length; i++ )
			{
				//Collect the IDs of every post with the same pixiv_id/page as the pixiv image
				for( var j = 0; j < result.length; j++ )
					if( thumbList[i].pixiv_id == result[j].pixiv_id && thumbList[i].posts.indexOf( result[j].id ) < 0 && ( thumbList[i].page < 0 || thumbList[i].page == pixivPageNumber( result[j].source ) ) )
					{
						thumbList[i].link.title = result[j].tag_string+" user:"+result[j].uploader_name+" rating:"+result[j].rating+" score:"+result[j].score;
						thumbList[i].posts.push( result[j].id );
					}
				
				if( thumbList[i].posts.length == 1 )
				{
					//Found one post; link directly to it
					thumbList[i].link.textContent = "post #"+thumbList[i].posts[0];
					thumbList[i].link.href = "http://danbooru.donmai.us/posts/"+thumbList[i].posts[0];
					thumbList[i].link.setAttribute("style",styleSourceFound);
				}
				else if( thumbList[i].posts.length > 1 )
				{
					//Found multiple posts; link to tag search
					thumbList[i].link.textContent = "("+thumbList[i].posts.length+" sources)";
					thumbList[i].link.href = "http://danbooru.donmai.us/posts?tags=status:any+pixiv:"+thumbList[i].pixiv_id;
					thumbList[i].link.setAttribute("style",styleSourceFound);
					thumbList[i].link.removeAttribute("title");
				}
			}
			
			if( result.length == 100 )
				sourceSearch( thumbList, attempt + 1, page + 1 );//Max results returned, so fetch the next page
			else for( var i = 0; i < thumbList.length; i++ )
			{
				//No more results will be forthcoming; hide the status counter and set the links for the images without any posts
				thumbList[i].status.style.display = "none";
				if( thumbList[i].posts.length == 0 )
				{
					thumbList[i].link.textContent = "(no sources)";
					thumbList[i].link.setAttribute("style",styleSourceMissing);
				}
			}
		},
		onerror: retry,
		onabort: retry
	});
}

function getMainTable(source)
{
	var result = null, tableFun =
	[	
		//bookmarks: user
		function(src){ src = src.getElementById("search-result"); return src ? src.getElementsByTagName("ul")[0] : null; }
		,
		//search
		//new_illust
		//member_illust.php?id=####
		function(src){ src = src.getElementsByClassName("image-item")[0]; return src ? src.parentNode : null; }
		,
		//default
		function(src){ src = src.getElementsByClassName("linkStyleWorks")[0]; return src ? src.getElementsByTagName("ul")[0] : null; }
	];
	
	for( var i = 0; i < tableFun.length; i++ )
	{
		getMainTable = tableFun[i];
		if( (result = getMainTable(source)) != null )
			return result;
	}
	
	return null;
}

function getBottomPager(source)
{
	var result = null, pagerFun =
	[
		//search
		function(src){ src = src.getElementsByClassName("pager-container"); return src.length ? src[src.length - 1].parentNode : null; },
		
		//default
		function(src){ src = src.getElementsByClassName("pages"); return src.length ? src[src.length - 1] : null; }
	];
	
	for( var i = 0; i < pagerFun.length; i++ )
	{
		getBottomPager = pagerFun[i];
		if( (result = getBottomPager(source)) != null )
			return result;
	}
	
	return null;
}

function getNextPage(pager)
{
	var links = pager.getElementsByTagName("a");
	if( links.length == 0 || links[links.length-1].getAttribute("rel") != "next" )
		return null;//No more pages
	else if( links[links.length-1].href.indexOf("http://www.pixiv.net/") < 0 )
		return "http://www.pixiv.net/"+links[links.length-1].href;
	else
		return links[links.length-1].href;
}

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

function pullingMore(responseDetails)
{
	var bottomPage, newTable, nextElem, pageLink;
	
	if( timeout != undefined )
		clearTimeout(timeout);
	
	//Make sure page loaded properly
	if( (newTable = getMainTable(iframe.contentDocument)) == null )
	{
		pending = false;
		return;
	}
	
	//Add page link
	pageLink = mainTable.parentNode.appendChild( document.createElement("div") );
	pageLink.setAttribute("style","font-size:large; text-align:left; margin-left: 10px");
	pageLink.setAttribute("class","clear");
	pageLink.innerHTML = '<hr style="clear: both;"><a href="'+nextPage+'">Page '+nextPage.replace(/.*(\?|&)p=([0-9]+).*/,'$2')+'</a>';
	
	//Remove the paginator in case it's in mainTable, then refresh the visible bottom paginator.
	bottomPage = getBottomPager(iframe.contentDocument);
	bottomPage.parentNode.removeChild(bottomPage);
	bottomDoc.innerHTML = bottomPage.innerHTML;
	
	mainTable.parentNode.appendChild( document.adoptNode(newTable) );
	
	//Check for the next page, and disable the script if there isn't one.
	if( nextPage = getNextPage(bottomPage) )
	{
		pending = false;
		testScrollPosition();
	}
	else
	{
		pageLink = mainTable.parentNode.appendChild( document.createElement("div") );
		pageLink.setAttribute("class","clear");
		testScrollPosition = function() { }
	}
}

if( typeof GM_deleteValue != "undefined" )
	GM_deleteValue("last_check");

//So as I pray, Unlimited Pixiv Works.
长期地址
遇到问题?请前往 GitHub 提 Issues,或加Q群1031348184

赞助商

Fishcpy

广告

Rainyun

注册一下就行

Rainyun

一年攒够 12 元