Pixiv Image Searches and Stuff

Searches Danbooru for pixiv IDs, adds IQDB image search links

  1. // ==UserScript==
  2. // @name Pixiv Image Searches and Stuff
  3. // @namespace https://gf.qytechs.cn/scripts/4296
  4. // @description Searches Danbooru for pixiv IDs, adds IQDB image search links
  5. // @include *://www.pixiv.net/*
  6. // @grant GM_deleteValue
  7. // @grant GM_xmlhttpRequest
  8. // @version 2018.02.01
  9. // ==/UserScript==
  10.  
  11. 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)
  12. var addSourceSearch = true;//Danbooru post search (looks for matching pixiv IDs); **Requires GM_xmlhttpRequest**
  13. var danbooruBase = "http://danbooru.donmai.us";//Base URL to use when linking to or querying Danbooru
  14.  
  15. //Source search options
  16. var danbooruLogin = "";//username
  17. var danbooruApiKey = "";//api key as listed on your profile
  18. var styleSourceFound = "color:green; font-weight: bold;";
  19. var styleSourceMissing = "color:red;";
  20. var sourceTimeout = 20;//seconds to wait before retrying query
  21. var maxAttempts = 10;//# of times to try a query before completely giving up on source searches
  22.  
  23. //////////////////////////////////////////////////////////////////////////////////////
  24.  
  25. var anyBookmarks = false, favList = [], jsonData = [];
  26.  
  27. if( typeof(custom) != "undefined" )
  28. custom();
  29.  
  30. if( iqdbURL )
  31. try{ jsonData = JSON.parse( document.querySelector("[id*='js-'][data-items]").getAttribute("data-items") ); }catch(e) {}
  32.  
  33. //Source search requires GM_xmlhttpRequest()
  34. if( addSourceSearch && typeof(GM_xmlhttpRequest) == "undefined" )
  35. addSourceSearch = false;
  36.  
  37. //Manga images have to be handled specially
  38. if( location.search.indexOf("mode=manga") >= 0 )
  39. {
  40. let searchID = addSourceSearch && location.search.match(/illust_id=(\d+)/);
  41. if( searchID )
  42. {
  43. function linkManga(stuff)
  44. {
  45. let thumbList = [], images = stuff.querySelectorAll(".item-container img");
  46. for( let i = 0; i < images.length; i++ )
  47. thumbList.push({ link: images[i].parentNode.appendChild( document.createElement("div") ).appendChild( document.createElement("a") ), pixiv_id: searchID[1], page: i });
  48. sourceSearch( thumbList );
  49. return thumbList.length;
  50. }
  51. if( !linkManga( document ) )
  52. {
  53. //Normal manga images not found; set up a mutation observer in case a certain other script executes after this one and adds them.
  54. new MutationObserver( function(mutationSet)
  55. {
  56. mutationSet.forEach( function(mutation)
  57. {
  58. for( let x = 0; x < mutation.addedNodes.length; x++ )
  59. linkManga( mutation.addedNodes[x] );
  60. } );
  61. }).observe( document.body, { childList:true, subtree:true } );
  62. }
  63. }
  64. }
  65. else if( window == window.top )//Don't run if inside an iframe
  66. {
  67. if( typeof(GM_deleteValue) != "undefined" )
  68. GM_deleteValue("minFavs");
  69. //Prevent added links sometimes being hidden for thumbnails with long titles
  70. let style = document.createElement('style');
  71. style.type = 'text/css';
  72. style.innerHTML = 'li.image-item{ height:auto !important; overflow:visible !important; padding:5px 0px !important } ';
  73. document.getElementsByTagName('head')[0].appendChild(style);
  74.  
  75. processThumbs( [document] );
  76.  
  77. //Monitor for changes caused by other scripts
  78. new MutationObserver( function(mutationSet)
  79. {
  80. mutationSet.forEach( function(mutation){ processThumbs( mutation.addedNodes ); } );
  81. }).observe( document.body, { childList:true, subtree:true } );
  82. }
  83.  
  84. //====================================== Functions ======================================
  85.  
  86. function processThumbs(target)
  87. {
  88. //Take care not to match on profile images, like those shown in the "Following" box on user profiles...
  89. var xPattern = [ "descendant-or-self::li/a[contains(@href,'mode=medium')]//img[not(@pisas)]",//member_illust.php, response.php, bookmark.php, new_illust.php, member.php...
  90. "descendant-or-self::div/figure/div/a/div[contains(@class,'lazy') and not(span) and not(@pisas)]",//search.php, bookmark_new_illust.php -- "contains(@class,'lazy')" prevents double matching on ugoira
  91. "descendant-or-self::div/a[contains(@href,'mode=medium')]//img[not(@pisas)]",//response.php (image being responded to), front page, ranking.php
  92. "descendant-or-self::div[@class='works_display']//a[contains(@href,'mode=ugoira_view') and not(@pisas)]", //member_illust.php (ugoira image)
  93. "descendant-or-self::div[@class='works_display']/a//img[not(@pisas)]"//member_illust.php (single image)
  94.  
  95. //"descendant-or-self::div[@class='works_display']/div/img[not(@pisas)]"
  96. //"descendant-or-self::li[@class='image-item']/a//img[not(@pisas)]",//bad hits on front page
  97. //"descendant-or-self::section/a[contains(@href,'mode=medium')]//img[not(@pisas)]"
  98. ];
  99.  
  100. let thumbSearch = [], thumbList = [];
  101. //Combine the results over all targets to minimize queries by source search
  102. for( let i = 0; i < target.length; i++ )
  103. {
  104. for( let j = 0; j < xPattern.length; j++ )
  105. {
  106. let xSearch = document.evaluate( xPattern[j], target[i], null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  107. if( xSearch.snapshotLength > 0 )
  108. {
  109. for( let k = 0; k < xSearch.snapshotLength; k++ )
  110. {
  111. (thumbSearch[thumbSearch.length] = xSearch.snapshotItem(k)).setAttribute("pisas","done");
  112. //console.debug( pixivIllustID( xSearch.snapshotItem(k).src ) );
  113. }
  114. //console.debug( "PISAS - "+xSearch.snapshotLength+" hit(s) for xPattern["+j+"]: "+xPattern[j] );
  115. }
  116. }
  117. }
  118. for( let i = 0; i < thumbSearch.length; i++ )
  119. {
  120. let thumbCont, thumbPage = null, thumbImg = thumbSearch[i];
  121. for( thumbCont = thumbImg.parentNode; !thumbCont.classList.contains("works_display"); thumbCont = thumbCont.parentNode )
  122. if( thumbCont.tagName == "A" )
  123. {
  124. thumbPage = thumbCont;
  125. thumbCont = thumbPage.parentNode;
  126. if( thumbCont.parentNode.tagName == "FIGURE" )
  127. thumbCont = thumbCont.parentNode.parentNode;//search.php
  128. break;
  129. }
  130. let bookmarkCount = 0, bookmarkLink = thumbCont.querySelector("a[href*='bookmark_detail.php']");
  131. let sourceContainer = thumbCont;
  132.  
  133. if( thumbImg.tagName == "IMG" )
  134. {
  135. //Disable lazy loading
  136. if( thumbImg.getAttribute("data-src") )
  137. thumbImg.src = thumbImg.getAttribute("data-src");
  138. //Skip generic restricted thumbs
  139. if( thumbImg.src.indexOf("//source.pixiv.net/") >= 0 )
  140. continue;
  141. //Skip special thumbs except on image pages (daily rankings on main page, ...)
  142. if( location.search.indexOf("mode=") < 0 && thumbPage && ( thumbImg.src.indexOf("_100.") > 0 || thumbPage.href.indexOf("_ranking") > 0 ) )
  143. continue;
  144. }
  145.  
  146. if( bookmarkLink )
  147. {
  148. //Thumb has bookmark info
  149. bookmarkCount = parseInt( bookmarkLink.getAttribute("data-tooltip","x").replace(/([^\d]+)/g,'') ) || 1;
  150. sourceContainer = bookmarkLink.parentNode;
  151. }
  152. else if( iqdbURL )
  153. {
  154. //Thumb doesn't have bookmark info. Add a fake bookmark link to link with the IQDB.
  155. bookmarkLink = document.createElement("a");
  156. bookmarkLink.className = "bookmark-count";
  157. if( anyBookmarks )
  158. {
  159. bookmarkLink.className += " ui-tooltip";
  160. bookmarkLink.setAttribute("data-tooltip", "0 bookmarks");
  161. }
  162. //Dummy div to force new line when needed
  163. thumbCont.appendChild( document.createElement("div") );
  164. thumbCont.appendChild( bookmarkLink );
  165. }
  166. else
  167. {
  168. //Dummy div to force new line when needed
  169. thumbCont.appendChild( document.createElement("div") );
  170. }
  171.  
  172. if( anyBookmarks )
  173. {
  174. favList.push({ thumb: thumbCont, favcount: bookmarkCount });
  175. }
  176.  
  177. if( iqdbURL )
  178. {
  179. //For search.php: Check the JSON data to get the thumb URLs.
  180. let directURL = thumbImg.src || thumbImg.style.backgroundImage.replace(/.*(http[^"]+).*/,'$1');
  181. if( !directURL )
  182. {
  183. let pid = pixivIllustID( thumbPage.href );
  184. for( let j = 0; j < jsonData.length; j++ )
  185. if( pid == jsonData[j].illustId )
  186. {
  187. directURL = jsonData[j].url;
  188. break;
  189. }
  190. }
  191. if( directURL )
  192. {
  193. bookmarkLink.href = iqdbURL+directURL+(thumbPage ? "&fullimage="+thumbPage.href : "");
  194. bookmarkLink.innerHTML = "(IQDB)";
  195. }
  196. }
  197.  
  198. if( addSourceSearch && ( !thumbImg.src || thumbImg.src.indexOf("/novel/") < 0 ) && pixivIllustID( thumbImg.src || thumbImg.href || thumbPage.href ) )
  199. {
  200. sourceContainer.appendChild( document.createTextNode(" ") );
  201. thumbList.push({ link: sourceContainer.appendChild( document.createElement("a") ), pixiv_id: pixivIllustID( thumbImg.src || thumbImg.href || thumbPage.href ), page: -1 });
  202. }
  203. }
  204. sourceSearch( thumbList );
  205. }
  206.  
  207. function pixivIllustID(url)
  208. {
  209. try{
  210. return ( url.match(/\/(\d+)(_|\.)[^\/]+$/) || url.match(/illust_id=(\d+)/) )[1];
  211. }catch(e){
  212. console.warn("PISAS - pixivIllustID(): Unable to parse ID from URL - " + url);
  213. return 0;
  214. }
  215. }
  216.  
  217. function pixivPageNumber(url)
  218. {
  219. try{
  220. return url.match(/_p(\d+)(_master\d+)?\./)[1];
  221. }catch(e){
  222. return "x";
  223. }
  224. }
  225.  
  226. function sourceSearch( thumbList, attempt, page )
  227. {
  228. //thumbList[index] = { link, id, page? }
  229. if( page === undefined )
  230. {
  231. //First call. Finish initialization
  232. attempt = page = 1;
  233. for( let i = 0; i < thumbList.length; i++ )
  234. {
  235. if( !thumbList[i].status )
  236. thumbList[i].status = thumbList[i].link.parentNode.appendChild( document.createElement("span") );
  237. thumbList[i].link.textContent = "Searching...";
  238. thumbList[i].posts = [];
  239. }
  240. }
  241. if( attempt >= maxAttempts )
  242. {
  243. //Too many failures (or Downbooru); give up. :(
  244. for( let i = 0; i < thumbList.length; i++ )
  245. {
  246. thumbList[i].status.style.display = "none";
  247. if( thumbList[i].link.textContent[0] != '(' )
  248. thumbList[i].link.textContent = "(error)";
  249. thumbList[i].link.setAttribute("style","color:blue; font-weight: bold;");
  250. }
  251. return;
  252. }
  253. //Is there actually anything to process?
  254. if( thumbList.length == 0 )
  255. return;
  256. //Retry this call if timeout
  257. var retry = (function(a,b,c){ return function(){ setTimeout( function(){ sourceSearch(a,b,c); }, maxAttempts == 0 ? 0 : 1000 ); }; })( thumbList, attempt + 1, page );
  258. var sourceTimer = setTimeout( retry, sourceTimeout*1000 );
  259. var idList = [];
  260. for( let i = 0; i < thumbList.length; i++ )
  261. {
  262. thumbList[i].status.textContent = " ["+attempt+"]";
  263. if( idList.indexOf( thumbList[i].pixiv_id ) < 0 )
  264. idList.push( thumbList[i].pixiv_id );
  265. }
  266. GM_xmlhttpRequest(
  267. {
  268. method: "GET",
  269. url: danbooruBase+'/posts.json?limit=100&tags=status:any+pixiv:'+idList.join()+'&login='+danbooruLogin+'&api_key='+danbooruApiKey+'&page='+page,
  270. onload: function(responseDetails)
  271. {
  272. clearTimeout(sourceTimer);
  273. //Check server response for errors
  274. let result = false, status = null;
  275. if( /^ *$/.test(responseDetails.responseText) )
  276. status = "(error)";//No content
  277. else if( responseDetails.responseText.indexOf("<title>Downbooru</title>") > 0 )
  278. {
  279. addSourceSearch = maxAttempts = 0;//Give up
  280. status = "(Downbooru)";
  281. }
  282. else if( responseDetails.responseText.indexOf("<title>Failbooru</title>") > 0 )
  283. status = "(Failbooru)";
  284. else try
  285. {
  286. result = JSON.parse(responseDetails.responseText);
  287. if( result.success !== false )
  288. status = "Searching...";
  289. else
  290. {
  291. status = "(" + ( result.message || "error" ) + ")";
  292. addSourceSearch = maxAttempts = 0;//Give up
  293. result = false;
  294. }
  295. }
  296. catch(err) {
  297. result = false;
  298. status = "(parse error)";
  299. }
  300. //Update thumbnail messages
  301. for( let i = 0; i < thumbList.length; i++ )
  302. thumbList[i].link.textContent = status;
  303. if( result === false )
  304. return retry();//Hit an error; try again?
  305. for( let i = 0; i < thumbList.length; i++ )
  306. {
  307. //Collect the IDs of every post with the same pixiv_id/page as the pixiv image
  308. for( let j = 0; j < result.length; j++ )
  309. 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 ) ) )
  310. {
  311. thumbList[i].link.title = result[j].tag_string+" user:"+result[j].uploader_name+" rating:"+result[j].rating+" score:"+result[j].score;
  312. thumbList[i].posts.push( result[j].id );
  313. }
  314. if( thumbList[i].posts.length == 1 )
  315. {
  316. //Found one post; link directly to it
  317. thumbList[i].link.textContent = "post #"+thumbList[i].posts[0];
  318. thumbList[i].link.href = danbooruBase+"/posts/"+thumbList[i].posts[0];
  319. thumbList[i].link.setAttribute("style",styleSourceFound);
  320. }
  321. else if( thumbList[i].posts.length > 1 )
  322. {
  323. //Found multiple posts; link to tag search
  324. thumbList[i].link.textContent = "("+thumbList[i].posts.length+" sources)";
  325. if( location.href.indexOf("mode=manga") > 0 )
  326. thumbList[i].link.href = danbooruBase+"/posts?tags=status:any+id:"+thumbList[i].posts;
  327. else
  328. thumbList[i].link.href = danbooruBase+"/posts?tags=status:any+pixiv:"+thumbList[i].pixiv_id;
  329. thumbList[i].link.setAttribute("style",styleSourceFound);
  330. thumbList[i].link.removeAttribute("title");
  331. }
  332. }
  333. if( result.length == 100 )
  334. sourceSearch( thumbList, attempt + 1, page + 1 );//Max results returned, so fetch the next page
  335. else for( let i = 0; i < thumbList.length; i++ )
  336. {
  337. //No more results will be forthcoming; hide the status counter and set the links for the images without any posts
  338. thumbList[i].status.style.display = "none";
  339. if( thumbList[i].posts.length == 0 )
  340. {
  341. thumbList[i].link.textContent = "(no sources)";
  342. thumbList[i].link.setAttribute("style",styleSourceMissing);
  343. }
  344. }
  345. },
  346. onerror: retry,
  347. onabort: retry
  348. });
  349. }

QingJ © 2025

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