pixiv_sort_by_popularity

non premium menber use "Sort by popularity"

当前为 2020-08-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name pixiv_sort_by_popularity
  3. // @name:zh-CN pixiv_sort_by_popularity
  4. // @name:zh-TW pixiv_sort_by_popularity
  5. // @name:ja pixiv_sort_by_popularity
  6. // @name:ru pixiv_sort_by_popularity
  7. // @name:kr pixiv_sort_by_popularity
  8. // @namespace pixiv_sort_by_popularity
  9. // @supportURL https://github.com/zhuzemin
  10. // @description non premium menber use "Sort by popularity"
  11. // @description:zh-CN non premium menber use "Sort by popularity"
  12. // @description:zh-TW non premium menber use "Sort by popularity"
  13. // @description:ja non premium menber use "Sort by popularity"
  14. // @description:ru non premium menber use "Sort by popularity"
  15. // @description:kr non premium menber use "Sort by popularity"
  16. // @include https://www.pixiv.net/*/tags/*
  17. // @include https://www.pixiv.net/tags/*
  18. // @version 1.21
  19. // @run-at document-end
  20. // @author zhuzemin
  21. // @license Mozilla Public License 2.0; http://www.mozilla.org/MPL/2.0/
  22. // @license CC Attribution-ShareAlike 4.0 International; http://creativecommons.org/licenses/by-sa/4.0/
  23. // @grant GM_xmlhttpRequest
  24. // @grant GM_registerMenuCommand
  25. // @grant GM_setValue
  26. // @grant GM_getValue
  27. // @connect-src workers.dev
  28. // @contributionAmount 0.5
  29. // @contributionURL https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=rzzm@hotmail.com&item_name=Greasy+Fork+donation
  30. // ==/UserScript==
  31. var config = {
  32. 'debug': false,
  33. 'bookmarkSupport': true //set this option to true, will enable bookmark cue(*red heart) in search result page, but page loading may slower.
  34. }
  35. var debug = config.debug ? console.log.bind(console) : function () {
  36. };
  37.  
  38. //this userscript desire for free member use "Sort by popularity"
  39.  
  40. //default
  41. //pixiv search request through this url will use my cookie.
  42. var cloudFlareUrl='https://proud-surf-e590.zhuzemin.workers.dev/ajax/';
  43.  
  44. //Obejct use for xmlHttpRequest
  45. class requestObject{
  46. constructor(originUrl,page,order) {
  47. this.method = 'GET';
  48. this.url = cloudFlareUrl+originUrl
  49. .replace(/(https:\/\/www\.pixiv\.net\/)(\w*\/)?tags\/([^\/]*)\/(\w*)\?([^\/\?]*)/,
  50. function($1,$2,$3,$4,$5){ return $1+'ajax/search/'+$4+'/'+$3+'?'+$5;})
  51. .replace(/p=\d*/,'').replace(/order=[_\w]*/,'')+'&p='+page+'&order='+order;
  52. this.data=null,
  53. this.responseType='json',
  54. this.headers = {
  55. 'User-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0',
  56. 'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'
  57. //'Accept': 'application/atom+xml,application/xml,text/xml',
  58. //'Referer': window.location.href,
  59. };
  60. this.charset = 'text/plain;charset=utf8';
  61. this.package=null;
  62. }
  63. }
  64.  
  65. var btn;
  66. let illustId_list=[];
  67.  
  68. // prepare UserPrefs
  69. setUserPref(
  70. 'cloudFlareUrl',
  71. cloudFlareUrl,
  72. 'Set cloudFlareUrl',
  73. `cloudFlareUrl only working on "Sort by popularity"`,
  74. ','
  75. );
  76.  
  77.  
  78. //for override fetch, I think override function sure insert to page, otherwise userscript don't have permission modified fetch in page?
  79. function addJS_Node (text)
  80. {
  81. var scriptNode = document.createElement ('script');
  82. scriptNode.type = "text/javascript";
  83. if (text) scriptNode.textContent = text;
  84.  
  85. var targ = document.getElementsByTagName('head')[0] || d.body || d.documentElement;
  86. targ.appendChild (scriptNode);
  87. }
  88.  
  89.  
  90.  
  91. //override fetch
  92. function intercept(){
  93. //insert override function to page
  94. addJS_Node(`
  95. var newData;
  96. var interceptEnable;
  97. var newUrl;
  98. var constantMock = window.fetch;
  99. window.fetch = function() {
  100. if(interceptEnable&&/https:\\/\\/www\\.pixiv\\.net\\/ajax\\/search\\//.test(arguments[0])){
  101. arguments[0]=newUrl;
  102. //console.log(arguments);
  103. }
  104. return new Promise((resolve, reject) => {
  105. constantMock.apply(this, arguments)
  106. .then((response) => {
  107. if(interceptEnable&&/https:\\/\\/www\\.pixiv\\.net\\/ajax\\/search\\//.test(response.url)){
  108. var blob = new Blob([newData], {type : 'application/json'});
  109. //console.log(newData);
  110.  
  111. var newResponse=new Response(
  112. blob, {
  113. status: response.status,
  114. statusText: response.statusText,
  115. headers: response.headers
  116. });
  117. //console.log(newResponse);
  118. response=newResponse;
  119. interceptEnable=false;
  120. }
  121. resolve(response);
  122. })
  123. .catch((error) => {
  124. reject(response);
  125. })
  126. });
  127. }
  128. `);
  129. //here is script end,
  130. //in console ,log show fetch response body has been changed <--- not very sure
  131. //and page have react ---> stay blank for ever
  132. //my confuse is: even comment "return data" (line:93), page still return blank,
  133. //that makes me wonder: maybe this override function miss something.
  134. //if my terrible code can be understanding somehow,
  135. //and knoa san have nothing else todo in leisure time,
  136. //knoa san can you take while, look my newbie problem?
  137. //of cource if too painful read my code, I totally understand!
  138. //knoa san can read to here already be my greatest honor, and I'm very happy!
  139. }
  140.  
  141. //userscript entry
  142. var init=function(){
  143. //create button
  144. if(window.self === window.top){
  145. debug("init");
  146. cloudFlareUrl=GM_getValue('cloudFlareUrl')||cloudFlareUrl;
  147. intercept();
  148. var interval=setInterval(function () {
  149. var navList=document.querySelectorAll('nav');
  150. debug('navList.length: '+navList.length)
  151. if(navList.length==2){
  152. nav=navList[1];
  153. debug('nav: '+nav.innerHTML)
  154. var nav=document.querySelector('nav');
  155. btn =document.createElement('button');
  156. btn.innerHTML='Sort by popularity';
  157. btn.addEventListener('click',sortByPopularity);
  158. nav.insertBefore(btn,null);
  159. select=document.createElement('select');
  160. select.id='sortByPopularity';
  161. var optionObj={
  162. 'Popular with all':'popular_d',
  163. 'Popular (male)':'popular_male_d',
  164. 'Popular (female)':'popular_female_d'
  165. }
  166. for(var key of Object.keys(optionObj)){
  167. var option=document.createElement('option');
  168. option.innerHTML=key;
  169. option.value=optionObj[key];
  170. select.insertBefore(option,null);
  171. }
  172. nav.insertBefore(select,null);
  173. let elm_Message=document.querySelector('button[title="Message"]');
  174. if(elm_Message==null){
  175. btn.disabled=true;
  176. let lebal=document.createElement('lebal');
  177. lebal.innerHTML='Login required';
  178. lebal.style.color='red';
  179. nav.insertBefore(lebal,btn);
  180. }
  181. clearInterval(interval);
  182.  
  183. }
  184. },4000);
  185. }
  186.  
  187. }
  188.  
  189. window.addEventListener('load', init);
  190.  
  191. //get current search word, then use xmlHttpRequest get response(from my server)
  192. function sortByPopularity(e) {
  193. btn.innerHTML='Searching...'
  194. try{
  195. var page;
  196. //var matching=window.location.href.match(/https:\/\/www\.pixiv\.net\/(\w*\/)?tags\/(.*)\/\w*\?(order=[^\?&]*)?&?(mode=(\w\d*))?&?(p=(\d*))?/);
  197. debug(e.target.tagName);
  198. if(/(\d*)/.test(e.target.textContent)&&(e.target.tagName.toLowerCase()=='span'||e.target.tagName.toLowerCase()=="a")){
  199. page=e.target.textContent.match(/(\d*)/)[1];
  200. }
  201. else if(e.target.tagName.toLowerCase()=='svg'||e.target.tagName.toLowerCase()=='polyline'){
  202. //debug('e.target.parentElement.tagName: '+e.target.parentElement.tagName);
  203. if(e.target.parentElement.tagName.toLowerCase()=='a'){
  204. page=e.target.parentElement.href.match(/p=(\d*)/)[1];
  205.  
  206. }
  207. else {
  208. page=e.target.parentElement.parentElement.href.match(/p=(\d*)/)[1];
  209.  
  210. }
  211. }
  212. //for test
  213. /*else if(matching[7]!=null){
  214. page=matching[7];
  215. }*/
  216. else{
  217. page=1;
  218. }
  219. page=parseInt(page);
  220. debug('page: '+page);
  221. var order=document.querySelector('#sortByPopularity').value;
  222. var obj=new requestObject(window.location.href,page,order);
  223. obj.package=page;
  224. debug('JSON.stringify(obj): '+JSON.stringify(obj));
  225. getBookmark(obj);
  226.  
  227. }
  228. catch (e) {
  229. debug('[Error]: '+e)
  230. }
  231.  
  232. }
  233.  
  234.  
  235. function getBookmark(obj, totalPageNum=1, pageNum=1 ) {
  236. if(config.bookmarkSupport){
  237. let reqObj=new requestObject('','','');
  238. reqObj.url='https://www.pixiv.net/bookmark.php?rest=show&p='+pageNum;
  239. reqObj.responseType='text';
  240. request(reqObj,function (responseDetails,package) {
  241. let dom = new DOMParser().parseFromString(responseDetails.responseText, "text/html");
  242. let count_badge=parseInt(dom.querySelector('span.count-badge').textContent.match(/(\d{1,9})/)[1]);
  243. if(count_badge>0){
  244. for(let elem of dom.querySelectorAll('li.image-item')){
  245. let illustId = elem.querySelector('a').href.match(/(\d{1,20})/)[1];
  246. debug('illustId: '+illustId);
  247. illustId_list.push(illustId);
  248. }
  249. let elm_page_list=dom.querySelector('ul.page-list');
  250. if(elm_page_list!=null){
  251. totalPageNum=elm_page_list.childElementCount;
  252. debug('totalPageNum: '+totalPageNum);
  253. }
  254. }
  255. if(pageNum!=totalPageNum){
  256. pageNum++;
  257. getBookmark(obj, totalPageNum,pageNum);
  258. debug('pageNum: '+pageNum);
  259. }
  260. else{
  261. debug('illustId_list: '+illustId_list);
  262. request(obj,replaceContent);
  263.  
  264. }
  265.  
  266. });
  267.  
  268. }
  269. else {
  270. debug('illustId_list: '+illustId_list);
  271. request(obj,replaceContent);
  272.  
  273. }
  274. }
  275.  
  276. function replaceContent(responseDetails, obj) {
  277. let page =obj.package;
  278. debug("responseDetails.response: "+JSON.stringify(responseDetails.response));
  279. let remoteResponse=responseDetails.response;
  280. if(illustId_list!=[]){
  281. for(let data of remoteResponse.body.illustManga.data){
  282. if(illustId_list.includes(data.illustId)){
  283. debug('data.illustId: '+data.illustId);
  284. data.bookmarkData={"id":"123","private":false};
  285. }
  286. }
  287. }
  288. debug("remoteResponse: "+JSON.stringify(remoteResponse));
  289. debug("remoteResponse.body.illustManga.data[0]: "+JSON.stringify(remoteResponse.body.illustManga.data[0]));
  290. unsafeWindow.newData=JSON.stringify(remoteResponse,null,2);
  291. unsafeWindow.interceptEnable=true;
  292. unsafeWindow.newUrl=obj.url.replace(cloudFlareUrl+'https://www.pixiv.net','');
  293. //trigger fetch by click "Newest" or "Oldest"
  294. var spanList=document.querySelectorAll('span');
  295. for(var span of spanList){
  296. if(/(Newest)|(Oldest)|(按最新排序)|(按旧|舊排序)|(新しい順)|(古い順)|(최신순)|(과거순)/.test(span.textContent)){
  297. if(span.parentElement.tagName.toLowerCase()=='a'){
  298. span.parentElement.click();
  299. break;
  300. }
  301. }
  302. }
  303. var interval=setInterval(function () {
  304. var navList=document.querySelectorAll('nav');
  305. debug('navList.length: '+navList.length)
  306. if(navList.length==2){
  307. nav=navList[1];
  308. debug('nav: '+nav.innerHTML)
  309. nav.addEventListener('click',sortByPopularity);
  310. if(page<=7&&page>1){
  311. //nav button "1" text -> current page number
  312. nav.childNodes[1].childNodes[0].innerText=page;
  313. //nav button "1" href -> current page href
  314. nav.childNodes[1].href=nav.childNodes[page].href;
  315. //current page button text -> "1"
  316. nav.childNodes[page].innerText=1;
  317. //current page button href -> origin nav button "1" href
  318. nav.childNodes[page].href=nav.childNodes[0].href;
  319. //switch two button positon
  320. nav.insertBefore(nav.childNodes[1],nav.childNodes[page]);
  321. nav.insertBefore(nav.childNodes[page],nav.childNodes[1]);
  322.  
  323. }
  324. else if(page>7){
  325. var currentPositionInNav=page%7;
  326. debug("currentPositionInNav: "+currentPositionInNav);
  327. var buttonStartNumber=page-currentPositionInNav;
  328. debug("buttonStartNumber: "+buttonStartNumber);
  329. var navButtonCount=1;
  330. //switch two button positon
  331. nav.insertBefore(nav.childNodes[1],nav.childNodes[currentPositionInNav+1]);
  332. nav.insertBefore(nav.childNodes[currentPositionInNav+1],nav.childNodes[1]);
  333. for(var i=buttonStartNumber;i<=(buttonStartNumber+6);i++){
  334. debug("navButtonCount: "+navButtonCount);
  335. debug("i: "+i);
  336. nav.childNodes[navButtonCount].childNodes[0].innerText=i;
  337. nav.childNodes[navButtonCount].href=nav.childNodes[8].href.replace(/p=\d*/,'p='+(i));
  338. navButtonCount++;
  339. }
  340. }
  341. if(page!=1){
  342. //display previous button
  343. nav.childNodes[0].style='opacity:1!important;';
  344. //previous button href
  345. nav.childNodes[0].href=nav.childNodes[8].href.replace(/p=\d*/,'p='+(page-1));
  346. //next button href
  347. nav.childNodes[8].href=nav.childNodes[8].href.replace(/p=\d*/,'p='+(page+1));
  348.  
  349. }
  350. btn.innerHTML='Sort by popularity';
  351. clearInterval(interval);
  352.  
  353. }
  354. },4000);
  355. }
  356.  
  357.  
  358. function request(object,func) {
  359. GM_xmlhttpRequest({
  360. method: object.method,
  361. url: object.url,
  362. headers: object.headers,
  363. responseType: object.responseType,
  364. overrideMimeType: object.charset,
  365. timeout: 60000,
  366. //synchronous: true
  367. onload: function (responseDetails) {
  368. debug(responseDetails);
  369. //Dowork
  370. func(responseDetails,object);
  371. },
  372. ontimeout: function (responseDetails) {
  373. //Dowork
  374. func(responseDetails);
  375.  
  376. },
  377. ononerror: function (responseDetails) {
  378. debug(responseDetails);
  379. //Dowork
  380. func(responseDetails);
  381.  
  382. }
  383. });
  384. }
  385. function setUserPref(varName, defaultVal, menuText, promtText, sep){
  386. GM_registerMenuCommand(menuText, function() {
  387. var val = prompt(promtText, GM_getValue(varName, defaultVal));
  388. if (val === null) { return; } // end execution if clicked CANCEL
  389. // prepare string of variables separated by the separator
  390. if (sep && val){
  391. var pat1 = new RegExp('\\s*' + sep + '+\\s*', 'g'); // trim space/s around separator & trim repeated separator
  392. var pat2 = new RegExp('(?:^' + sep + '+|' + sep + '+$)', 'g'); // trim starting & trailing separator
  393. //val = val.replace(pat1, sep).replace(pat2, '');
  394. }
  395. //val = val.replace(/\s{2,}/g, ' ').trim(); // remove multiple spaces and trim
  396. GM_setValue(varName, val);
  397. // Apply changes (immediately if there are no existing highlights, or upon reload to clear the old ones)
  398. //if(!document.body.querySelector(".THmo")) THmo_doHighlight(document.body);
  399. //else location.reload();
  400. });
  401. }