Pixiv Image Searches and Stuff

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

目前为 2016-10-01 提交的版本。查看 最新版本

// ==UserScript==
// @name         Pixiv Image Searches and Stuff
// @namespace    https://gf.qytechs.cn/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      2016.10.01
// ==/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 )
	{
		function linkManga(stuff)
		{
			var thumbList = [], images = stuff.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 );
			return thumbList.length;
		}
		
		if( !linkManga( document ) )
		{
			//Normal manga images not found; set up a mutation observer in case a certain other script executes after this one and adds them.
			new MutationObserver( function(mutationSet)
			{
				mutationSet.forEach( function(mutation)
				{
					for( var x = 0; x < mutation.addedNodes.length; x++ )
						linkManga( mutation.addedNodes[x] );
				} );
			}).observe( document.body, { childList:true, subtree:true } );
		}
	}
}
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
	new MutationObserver( 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')]//img[not(@pisas)] | "+
										"descendant-or-self::li[@class='image-item']/a//img[not(@pisas)] | "+
										"descendant-or-self::section/a[contains(@href,'mode=medium')]//img[not(@pisas)] | "+//rankings
										"descendant-or-self::div/a[contains(@href,'mode=medium')]//img[not(@pisas)] | "+
										"descendant-or-self::div[@class='works_display']/a//img[not(@pisas)] | "+
										"descendant-or-self::div[@class='works_display']/div/img[not(@pisas)] | "+
										"descendant-or-self::div[@class='works_display']//a[contains(@href,'mode=ugoira_view') and not(@pisas)]",//ugoira 'full size' icon
										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 thumbCont, thumbPage = null, thumbImg = thumbSearch[i];
		
		for( thumbCont = thumbImg.parentNode; !thumbCont.classList.contains("works_display"); thumbCont = thumbCont.parentNode )
			if( thumbCont.tagName == "A" )
			{
				thumbPage = thumbCont;
				thumbCont = thumbPage.parentNode;
				break;
			}
		
		var bookmarkCount = 0, bookmarkLink = thumbCont.querySelector("a[href*='bookmark_detail.php']");
		var sourceContainer = thumbCont;
		
		if( thumbImg.tagName == "IMG" )
		{
			//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 && thumbPage && ( thumbImg.src.indexOf("_100.") > 0 || thumbPage.href.indexOf("_ranking") > 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
			thumbCont.appendChild( document.createElement("div") );
			thumbCont.appendChild( bookmarkLink );
		}
		else
		{
			//Dummy div to force new line when needed
			thumbCont.appendChild( document.createElement("div") );
		}
		
		if( anyBookmarks )
		{
			favList.push({ thumb: thumbCont, favcount: bookmarkCount });
			if( bookmarkCount < minFavs )
				thumbCont.style.display = "none";
		}
		
		if( iqdbURL )
		{
			bookmarkLink.href = iqdbURL+thumbImg.src+(thumbPage ? "&fullimage="+thumbPage.href : "");
			bookmarkLink.innerHTML = "(IQDB)";
		}
		
		if( addSourceSearch && ( !thumbImg.src || thumbImg.src.indexOf("/novel/") < 0 ) && pixivIllustID( thumbImg.src || thumbImg.href ) )
		{
			sourceContainer.appendChild( document.createTextNode(" ") );
			thumbList.push({ link: sourceContainer.appendChild( document.createElement("a") ), pixiv_id: pixivIllustID( thumbImg.src || thumbImg.href ), page: -1 });
		}
	}
	
	sourceSearch( thumbList );
}

function pixivIllustID(url) { var matcher = url.match(/\/(\d+)(_|\.)[^\/]+$/) || url.match(/illust_id=(\d+)/); return matcher && matcher[1]; }
function pixivPageNumber(url) { var matcher = url.match(/_p(\d+)(_master\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 );
	
	var idList = [];
	for( var i = 0; i < thumbList.length; i++ )
	{
		thumbList[i].status.textContent = " ["+attempt+"]";
		if( idList.indexOf( thumbList[i].pixiv_id ) < 0 )
			idList.push( thumbList[i].pixiv_id );
	}
	
	GM_xmlhttpRequest(
	{
		method: "GET",
		url: 'http://danbooru.donmai.us/posts.json?limit=100&tags=status:any+pixiv:'+idList.join()+'&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 = "(parse 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+id:"+thumbList[i].posts;
					//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.

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址