// ==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 // @downloadURL none // ==/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("Downbooru") > 0 ) { maxAttempts = 0;//Give up status = "(Downbooru)"; } else if( responseDetails.responseText.indexOf("Failbooru") > 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 = '
Page '+nextPage.replace(/.*(\?|&)p=([0-9]+).*/,'$2')+''; //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.