Greasy Fork

来自缓存

Greasy Fork is available in English.

Pixiv Image Searches and Stuff

Searches Danbooru for pixiv IDs, adds IQDB image search links, and filters images based on pixiv favorites.

当前为 2014-08-18 提交的版本,查看 最新版本

// ==UserScript==
// @name           Pixiv Image Searches and Stuff
// @namespace      http://greasyfork.icu/users/3412
// @include        http://www.pixiv.net*
// @description    Searches Danbooru for pixiv IDs, adds IQDB image search links, and filters images based on pixiv favorites.
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_xmlhttpRequest
// @version        2014.07.12
// ==/UserScript==

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

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

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

//====================================== 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
	});
}

//Wow, "PISS"!  So edgy!  Much scare.