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