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.

Version vom 24.08.2014. Aktuellste Version

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

var iqdbURL = "";//Replace with "http://danbooru.iqdb.org/?url=" (Danbooru) or "http://iqdb.org/?url=" (multi-service) to add IQDB search links (replaces bookmark counts)
var addSourceSearch = false;//Danbooru post search (looks for matching pixiv IDs); **Requires GM_xmlhttpRequest**

//Source search options
var danbooruLogin = "";//username
var danbooruApiKey = "";//api key as listed on your profile
var styleSourceFound = "color:green; font-weight: bold;";
var styleSourceMissing = "color:red;";
var sourceTimeout = 20;//seconds to wait before retrying query
var maxAttempts = 10;//# of times to try a query before completely giving up on source searches

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

var minFavs = 0, anyBookmarks = false, favList = [];

if( typeof(GM_getValue) == "undefined" || !GM_getValue('a', 'b') )
{
	GM_getValue = function(name,defV){ var value = localStorage.getItem("pisas."+name); return( value === null ? defV : value ); };
	GM_setValue = function(name,value) { localStorage.setItem("pisas."+name, value ); }
	GM_deleteValue = function(name){ localStorage.removeItem("pisas."+name); }
}

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

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

//Manga images have to be handled specially
if( location.search.indexOf("mode=manga") >= 0 )
{
	var searchID = addSourceSearch && location.search.match(/illust_id=(\d+)/);
	if( searchID )
	{
		var thumbList = [], images = document.querySelectorAll(".item-container img");
		for( var i = 0; i < images.length; i++ )
			thumbList.push({ link: images[i].parentNode.appendChild( document.createElement("div") ).appendChild( document.createElement("a") ), pixiv_id: searchID[1], page: i });
		sourceSearch( thumbList );
	}
}
else if( window == window.top )//Don't run if inside an iframe
{
	//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 (script)";
		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) && (minFavs = parseInt( this.value, 10 )) > 0 )
				GM_setValue("minFavs", ""+minFavs);
			else
			{
				GM_deleteValue("minFavs");
				minFavs = 0;	
			}
			
			for( var i = 0; i < favList.length; i++ )
			{
				if( favList[i].favcount < minFavs )
					favList[i].thumb.style.display = "none";
				else
					favList[i].thumb.style.removeProperty("display");
			}
		}, true);
		addSearch.parentNode.insertBefore( favTr, addSearch );
	}

	//Prevent added links sometimes being hidden for thumbnails with long titles
	var style = document.createElement('style');
	style.type = 'text/css';
	style.innerHTML = 'li.image-item{ height:auto !important; overflow:visible !important; padding:5px 0px !important } ';
	document.getElementsByTagName('head')[0].appendChild(style);

	processThumbs( [document] );

	//Monitor for changes caused by other scripts
	var MutObj = window.MutationObserver || window.WebKitMutationObserver;
	if( !MutObj )
		window.addEventListener( "DOMNodeInserted", function(e){ processThumbs( [e.target] ); }, true );
	else new MutObj( function(mutationSet)
	{
		mutationSet.forEach( function(mutation){ processThumbs( mutation.addedNodes ); } );
	}).observe( document.body, { childList:true, subtree:true } );
}

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

function processThumbs(target)
{
	var thumbSearch = [], thumbList = [];
	
	//Combine the results over all targets to minimize queries by source search
	for( var i = 0; i < target.length; i++ )
	{
		//Take care not to match on profile images, like those shown in the "Following" box on user profiles...
		
		var xSearch = document.evaluate("descendant-or-self::li/a[contains(@href,'mode=medium') or contains(@href,'/novel/show.php')]//img[not(@pisas)] | "+
										"descendant-or-self::li[@class='image-item']/a//img[not(@pisas)] | "+
										"descendant-or-self::section/a[contains(@href,'mode=medium') or contains(@href,'/novel/show.php')]//img[not(@pisas)] | "+//rankings
										"descendant-or-self::div/a[contains(@href,'mode=medium') or contains(@href,'/novel/show.php')]//img[not(@pisas)] | "+
										"descendant-or-self::div[@class='works_display']/a/img[not(@pisas)]",
										target[i], null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		for( var j = 0; j < xSearch.snapshotLength; j++ )
			(thumbSearch[thumbSearch.length] = xSearch.snapshotItem(j)).setAttribute("pisas","done");
	}
	
	for( var i = 0; i < thumbSearch.length; i++ )
	{
		var thumbImg = thumbSearch[i];
		var thumbPage = thumbImg.parentNode;
		while( thumbPage.tagName != "A" )
			thumbPage = thumbPage.parentNode;
		var thumbDiv = thumbPage.parentNode;
		var bookmarkCount = 0, bookmarkLink = thumbDiv.querySelector("a[href*='bookmark_detail.php']");
		var sourceContainer = thumbDiv;
		
		//Disable lazy loading
		if( thumbImg.getAttribute("data-src") )
			thumbImg.src = thumbImg.getAttribute("data-src");
		
		//Skip generic restricted thumbs
		if( thumbImg.src.indexOf("http://source.pixiv.net/") == 0 )
			continue;

		//Skip special thumbs except on image pages (daily rankings on main page, ...)
		if( location.search.indexOf("mode=") < 0 && ( thumbImg.src.indexOf("_100.") > 0 || thumbImg.src.indexOf("/img-master/") > 0 ) )
			continue;
		
		if( bookmarkLink )
		{
			//Thumb has bookmark info
			bookmarkCount = parseInt( bookmarkLink.getAttribute("data-tooltip","x").replace(/([^\d]+)/g,'') ) || 1;
			sourceContainer = bookmarkLink.parentNode;
		}
		else if( iqdbURL )
		{
			//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 )
		{
			favList.push({ thumb: thumbDiv, favcount: bookmarkCount });
			if( bookmarkCount < minFavs )
				thumbDiv.style.display = "none";
		}
		
		if( iqdbURL )
		{
			bookmarkLink.href = iqdbURL+thumbImg.src+"&fullimage="+thumbPage.href;
			bookmarkLink.innerHTML = "(IQDB)";
		}
		
		if( addSourceSearch && thumbPage.href.indexOf("novel/show.php") < 0 && pixivIllustID( thumbImg.src ) )
		{
			sourceContainer.appendChild( document.createTextNode(" ") );
			thumbList.push({ link: sourceContainer.appendChild( document.createElement("a") ), pixiv_id: pixivIllustID( thumbImg.src ), page: -1 });
		}
	}
	
	sourceSearch( thumbList );
}

function pixivIllustID(url) { var matcher = url.match(/\/(\d+)(_|\.)[^\/]+$/); return matcher && matcher[1]; }
function pixivPageNumber(url) { var matcher = url.match(/_p(\d+)\./); return matcher ? matcher[1] : "x"; }

function sourceSearch( thumbList, attempt, page )
{
	//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); }, maxAttempts == 0 ? 0 : 1000 ); }; })( thumbList, attempt + 1, page );
	var 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+'&api_key='+danbooruApiKey+'&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 )
			{
				addSourceSearch = maxAttempts = 0;//Give up
				status = "(Downbooru)";
			}
			else if( responseDetails.responseText.indexOf("<title>Failbooru</title>") > 0 )
				status = "(Failbooru)";
			else try
			{
				result = JSON.parse(responseDetails.responseText);
				if( result.success !== false )
					status = "Searching...";
				else
				{
					status = "(" + ( result.message || "error" ) + ")";
					addSourceSearch = maxAttempts = 0;//Give up
					result = false;
				}	
			}
			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.