pixiv_sort_by_popularity

non premium menber use "Sort by popularity"

当前为 2020-02-21 提交的版本,查看 最新版本

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