P站画师个人作品批量下载工具

配合Aria2,一键批量下载P站画师的全部作品

当前为 2021-03-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name PixivUserBatchDownload
  3. // @name:zh-CN P站画师个人作品批量下载工具
  4. // @name:zh-TW P站畫師個人作品批量下載工具
  5. // @name:zh-HK P站畫師個人作品批量下載工具
  6. // @description Batch download pixiv user's images in one key.(Based on Aria2)
  7. // @description:zh-CN 配合Aria2,一键批量下载P站画师的全部作品
  8. // @description:zh-TW 配合Aria2,一鍵批量下載P站畫師的全部作品
  9. // @description:zh-HK 配合Aria2,一鍵批量下載P站畫師的全部作品
  10. // @version 5.16.137
  11. // @author Mapaler <mapaler@163.com>
  12. // @copyright 2016~2021+, Mapaler <mapaler@163.com>
  13. // @namespace http://www.mapaler.com/
  14. // @icon https://www.pixiv.net/favicon.ico
  15. // @homepage https://github.com/Mapaler/PixivUserBatchDownload
  16. // @supportURL https://github.com/Mapaler/PixivUserBatchDownload/issues
  17. //-@updateURL https://gf.qytechs.cn/scripts/17879/code/PixivUserBatchDownload.meta.js
  18. //-@downloadURL https://gf.qytechs.cn/scripts/17879/code/PixivUserBatchDownload.user.js
  19. // @include *://www.pixiv.net/*
  20. // @exclude *://www.pixiv.net/upload.php*
  21. // @exclude *://www.pixiv.net/messages.php*
  22. // @exclude *://www.pixiv.net/ranking.php*
  23. // @exclude *://www.pixiv.net/info.php*
  24. // @exclude *://www.pixiv.net/ranking_report_user.php*
  25. // @exclude *://www.pixiv.net/setting*
  26. // @exclude *://www.pixiv.net/stacc*
  27. // @exclude *://www.pixiv.net/premium*
  28. // @exclude *://www.pixiv.net/discovery*
  29. // @exclude *://www.pixiv.net/howto*
  30. // @exclude *://www.pixiv.net/idea*
  31. // @exclude *://www.pixiv.net/ads*
  32. // @exclude *://www.pixiv.net/terms*
  33. // @exclude *://www.pixiv.net/novel*
  34. // @exclude *://www.pixiv.net/cate_r18*
  35. // @exclude *://www.pixiv.net/manage*
  36. // @exclude *://www.pixiv.net/report*
  37. //-@exclude *://www.pixiv.net/search.php*
  38. //-@exclude *://www.pixiv.net/tags*
  39. // @resource pubd-style https://github.com/Mapaler/PixivUserBatchDownload/raw/master/PixivUserBatchDownload%20ui.css?v=5.16.129
  40. // @require https://cdn.staticfile.org/crypto-js/4.0.0/core.min.js
  41. // @require https://cdn.staticfile.org/crypto-js/4.0.0/md5.min.js
  42. // @require https://cdn.staticfile.org/crypto-js/4.0.0/sha256.min.js
  43. // @require https://cdn.staticfile.org/crypto-js/4.0.0/enc-base64.min.js
  44. //-@grant unsafeWindow
  45. // @grant window.close
  46. // @grant window.focus
  47. // @grant GM_xmlhttpRequest
  48. // @grant GM_getValue
  49. // @grant GM_setValue
  50. // @grant GM_deleteValue
  51. //-@grant GM_listValues
  52. // @grant GM_addStyle
  53. // @grant GM_getResourceText
  54. //-@grant GM_getResourceURL
  55. // @grant GM_addValueChangeListener
  56. //-@grant GM_notification
  57. // @grant GM_registerMenuCommand
  58. // @grant GM_unregisterMenuCommand
  59. // @connect pixiv.net
  60. // @connect pximg.net
  61. // @connect localhost
  62. // @connect 127.0.0.1
  63. // @noframes
  64. // ==/UserScript==
  65.  
  66. /*jshint esversion: 6, shadow: true */
  67. (function() {
  68. 'use strict';
  69.  
  70. //获取当前是否是本地开发状态
  71. const mdev = Boolean(localStorage.getItem("pubd-dev"));
  72.  
  73. /*
  74. * 公共变量区
  75. */
  76. if (mdev) console.log("GM_info信息:",GM_info); //开发模式时显示meta数据
  77.  
  78. const scriptVersion = GM_info.script.version.trim(); //本程序的版本
  79. const scriptIcon = GM_info.script.icon64 || GM_info.script.icon; //本程序的图标
  80. const scriptName = (defaultName=>{ //本程序的名称
  81. if (typeof(GM_info) != "undefined") //使用了扩展
  82. {
  83. if (GM_info.script.name_i18n)
  84. {
  85. return GM_info.script.name_i18n[navigator.language.replace("-","_")]; //支持Tampermonkey
  86. } else
  87. {
  88. return GM_info.script.localizedName || //支持Greasemonkey 油猴子 3.x
  89. GM_info.script.name; //支持Violentmonkey 暴力猴
  90. }
  91. }
  92. return defaultName;
  93. })('PixivUserBatchDownload');
  94.  
  95. const pubd = { //储存程序设置
  96. configVersion: 2, //当前设置版本,用于处理老版本设置的改变
  97. touch: false, //是否为手机版
  98. loggedIn: false, //登陆了(未启用)
  99. start: null, //开始按钮指针
  100. menu: null, //菜单指针
  101. dialog: { //窗口们的指针
  102. config: null, //设置窗口
  103. login: null, //登陆窗口
  104. refresh_token: null, //刷新token窗口
  105. downthis: null, //下载当前窗口
  106. downillust: null, //下载当前作品窗口
  107. importdata: null, //导入数据窗口(还未开发)
  108. multiple: null, //多画师批量下载窗口(还未开发)
  109. },
  110. oAuth: null, //储存账号密码
  111. downSchemes: [], //储存下载方案
  112. downbreak: false, //是否停止发送Aria2的flag
  113. ajaxTimes: 0, //已经从P站获取信息的次数(用来判断是否要减速)
  114. fastStarList: null, //储存快速收藏的简单数字
  115. starUserlists: [], //储存完整的下载列表
  116. };
  117.  
  118. //储存vue框架下P站页面主要内容的DIV位置,现在由程序自行搜索判断,搜索依据为 mainDivSearchCssSelectorArray。
  119. //后面的 :scope 基本都是指的 mainDiv
  120. var mainDiv = null;
  121. //#root下能够独占区分不同页面的路径
  122. //本来开始按钮插入点可以另外设置,但是刚好可以用,于是就用了同一个了
  123. const mainDivSearchCssSelectorArray = [
  124. '#spa-contents .user-stats', //手机版用户页
  125. '#spa-contents .user-details-card', //手机版作品页
  126. ':scope>div>div>div:nth-of-type(2)>div>div:nth-of-type(2)>div>div:nth-of-type(2)', //用户资料首页,版本3
  127. ':scope>div>div>div>div:nth-of-type(2)>div:nth-of-type(2)', //用户资料首页,版本2
  128. ':scope>div>div>div>div:nth-of-type(2)>div>div:nth-of-type(2)', //用户资料首页
  129. ':scope>div>div>aside>section', //作品页
  130. ':scope>div>div:nth-of-type(2)>div>div', //关注页
  131. ];
  132. //搜索页,列表的ul位置(用来显示收藏状态)
  133. const searchListCssPath = ':scope>div>div:nth-of-type(4)>div>section>div:nth-of-type(2)>ul';
  134.  
  135.  
  136. //作者页面“主页”按钮的CSS位置(用来获取作者ID)
  137. const userMainPageCssPath = ":scope>div>div:nth-of-type(2)>nav>a";
  138. //作品页,收藏按钮的CSS位置(用来获取当前作品ID)
  139. const artWorkStarCssPath = ":scope>div>div>main>section>div>div>figcaption>div>div>ul>li:nth-of-type(2)>a";
  140. //作品页,作者头像链接的CSS位置(用来获取作者ID)
  141. const artWorkUserHeadCssPath = ":scope>div>div>aside>section>h2>div>a";
  142.  
  143. //匹配P站内容的正则表达式
  144. const illustPathRegExp = /(\/.+\/\d{4}\/\d{2}\/\d{2}\/\d{2}\/\d{2}\/\d{2}\/((\d+)(?:-([0-9a-zA-Z]+))?(?:_p|_ugoira)))\d+(?:_\w+)?\.([\w\d]+)/i; //P站画作地址 path部分 正则匹配式
  145. const limitingPathRegExp = /(\/common\/images\/(limit_(?:mypixiv|unknown)_\d+))\.([\w\d]+)/i; //P站无权访问作品地址 path部分 正则匹配式
  146.  
  147. const limitingFilenamePattern = 'limit_(mypixiv|unknown)'; //P站上锁图片文件名正则匹配式
  148. //Header使用
  149. const PixivAppVersion = "6.2.1"; //Pixiv APP的版本
  150. const AndroidVersion = "12.0.0"; //安卓的版本
  151. const UA = `PixivAndroidApp/${PixivAppVersion} (Android ${PixivAppVersion}; Android SDK built for x64)`; //向P站请求数据时的UA
  152. const X_Client_Hash_Salt = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c"; //X_Client加密的slat,目前是固定值
  153. const Referer = "https://app-api.pixiv.net/";
  154. const ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; //重要
  155. //登陆时的固定参数
  156. const client_id = "MOBrBDS8blbauoSck0ZfDbtuzpyT"; //安卓版固定数据
  157. const client_secret = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"; //安卓版固定数据
  158.  
  159. let thisPageUserid = null, //当前页面的画师ID
  160. thisPageIllustid = null, //当前页面的作品ID
  161. downIllustMenuId = null, //下载当前作品的菜单的ID(Tampermonker菜单内的指针)
  162. recommendList = null; //推荐作品列表Dom位置
  163.  
  164. const startDelayAjaxTimes = 100; //开始执行延迟的ajax次数
  165. const ajaxDelayDuration = 1000; //每次延迟的时间
  166. const changeTermwiseCount = 6000; //图片数量大于这个值就从按作者发送切换为按图片发送
  167.  
  168. /*
  169. * 初始化数据库
  170. */
  171. if (mdev)
  172. {
  173. const dbName = "PUBD";
  174. var db;
  175. const DBOpenRequest = indexedDB.open(dbName);
  176.  
  177. DBOpenRequest.onsuccess = function(event) {
  178. db = event.target.result; //DBOpenRequest.result;
  179. console.log("PUBD:数据库已可使用");
  180. };
  181. DBOpenRequest.onerror = function(event) {
  182. // 错误处理
  183. console.log("PUBD:数据库无法启用",event);
  184. };
  185. DBOpenRequest.onupgradeneeded = function(event) {
  186. let db = event.target.result;
  187.  
  188. // 建立一个对象仓库来存储用户的相关信息,我们选择 user.id 作为键路径(key path)
  189. // 因为 user.id 可以保证是不重复的
  190. let usersStore = db.createObjectStore("users", { keyPath: "user.id" });
  191. // 建立一个索引来通过姓名来搜索用户。名字可能会重复,所以我们不能使用 unique 索引
  192. usersStore.createIndex("name", "user.name", { unique: false });
  193. // 使用账户建立索引,我们确保用户的账户不会重复,所以我们使用 unique 索引
  194. usersStore.createIndex("account", "user.account", { unique: true });
  195.  
  196. let illustsStore = db.createObjectStore("illusts", { keyPath: "id" });
  197. illustsStore.createIndex("type", "type", { unique: false });
  198. illustsStore.createIndex("userid", "user.id", { unique: false });
  199. // 使用事务的 oncomplete 事件确保在插入数据前对象仓库已经创建完毕
  200. illustsStore.transaction.oncomplete = function(event) {
  201. console.log("PUBD:数据库建立完毕");
  202. };
  203. };
  204. }
  205. /*
  206. * 获取初始状态
  207. */
  208. //尝试获取旧版网页对象
  209. /*if (typeof(unsafeWindow) != "undefined")
  210. { //原来的信息-除少部分页面外已失效2020年7月9日
  211. const pixiv = unsafeWindow.pixiv;
  212. }*/
  213. if (typeof(pixiv) != "undefined")
  214. {
  215. if (mdev) console.log("PUBD:本页面存在 pixiv 对象:",pixiv);
  216. thisPageUserid = parseInt(pixiv.context.userId);
  217. if (pixiv.user.loggedIn)
  218. {
  219. pubd.loggedIn = true;
  220. }
  221. if (/touch/i.test(pixiv.touchSourcePath))
  222. {
  223. pubd.touch = true; //新版的手机页面也还是老板结构-2020年7月9日
  224. document.body.classList.add('pubd-touch');
  225. }
  226. }
  227.  
  228. //尝试获取当前页面画师ID
  229. const metaPreloadData = document.querySelector('#meta-preload-data'); //HTML源代码里有,会被前端删掉的数据
  230. if (metaPreloadData != undefined) //更加新的存在于HTML元数据中的页面信息
  231. {
  232. pubd.loggedIn = true;
  233. if (mdev) console.log("PUBD:本页面抢救出 metaPreloadData 对象:",metaPreloadData);
  234. const preloadData = JSON.parse(metaPreloadData.content);
  235. if (mdev) console.log("PUBD:metaPreloadData 中的 preloadData 元数据:",preloadData);
  236. if (preloadData.user) thisPageUserid = parseInt(Object.keys(preloadData.user)[0]);
  237. if (preloadData.illust) thisPageIllustid = parseInt(Object.keys(preloadData.illust)[0]); //必须判断是否存在,否则会出现can't convert undefined to object错误
  238. }
  239. //获取是否为老的手机版
  240. if (location.host.includes("touch")) //typeof(pixiv.AutoView)!="undefined"
  241. {
  242. pubd.touch = true;
  243. document.body.classList.add('pubd-touch');
  244. }
  245.  
  246. /*
  247. * 自定义对象区
  248. */
  249.  
  250. //生成P站需要的时间格式,如 "2019-09-03T18:51:40+08:00"
  251. Date.prototype.toPixivString = function() {
  252. function pad(num, length=2)
  253. { //填充截取法补前导0
  254. let ns = num.toString();
  255. if (ns.length >= length)
  256. return ns;
  257. else
  258. { //这里用slice和substr均可
  259. return (Array(length).join('0') + num.toString()).slice(-length);
  260. }
  261. }
  262. const timezoneOffset = this.getTimezoneOffset(); //时区差值
  263. const str =
  264. [
  265. this.getFullYear(), //年
  266. this.getMonth()+1, //月
  267. this.getDate() //日
  268. ].map(n=>pad(n)).join('-') +
  269. 'T' +
  270. [
  271. this.getHours(), //时
  272. this.getMinutes(), //秒
  273. this.getSeconds() //分
  274. ].map(n=>pad(n)).join(':') +
  275. (timezoneOffset<=0?"+":"-") + //时区正负
  276. [
  277. timezoneOffset/60, //时区差时
  278. timezoneOffset%60, //时区差分
  279. ].map(n=>pad(Math.floor(Math.abs(n)))).join(':');
  280. return str;
  281. };
  282. /*
  283. //一个被收藏的画师
  284. class StarUser{
  285. constructor(id){
  286. const user = this;
  287. user.id = id;
  288. user.infoDone = false;
  289. user.downDone = false;
  290. user.userinfo = null;
  291. user.illusts = null;
  292. }
  293. }
  294. */
  295.  
  296. //一个画师收藏列表
  297. class UsersStarList{
  298. constructor(title,userArr = []){
  299. this.title = title;
  300. this.users = new Set(Array.from(userArr));
  301. this.users.delete(null);
  302. }
  303. add(userid)
  304. {
  305. if (isNaN(userid)) userid = parseInt(userid,10);
  306. if (!isNaN(userid) && userid != null) this.users.add(userid);
  307. }
  308. delete(userid)
  309. {
  310. if (isNaN(userid)) userid = parseInt(userid,10);
  311. this.users.delete(userid);
  312. }
  313. has(userid)
  314. {
  315. if (isNaN(userid)) userid = parseInt(userid,10);
  316. return this.users.has(userid);
  317. }
  318. toggle(userid)
  319. { //切换有无
  320. if (isNaN(userid)) userid = parseInt(userid,10);
  321. const _users = this.users;
  322. if (_users.has(userid) || isNaN(userid) || userid == null)
  323. {
  324. _users.delete(userid);
  325. return false;
  326. }else
  327. {
  328. _users.add(userid);
  329. return true;
  330. }
  331. }
  332. importArray(arr)
  333. {
  334. const arrMaxLength = 500000;
  335. arr = arr.filter(uid=>!isNaN(uid));
  336. if (arr.length>arrMaxLength)
  337. {
  338. alert(`PUBD:收藏用户最多仅允许添加 ${arrMaxLength.toLocaleString()} 个数据。`);
  339. arr = arr.splice(500000); //删除50万以后的
  340. }
  341. const _users = this.users;
  342. arr.forEach(uid=>_users.add(uid));
  343. }
  344. exportArray()
  345. {
  346. return Array.from(this.users);
  347. }
  348. }
  349. //一个本程序使用的headers数据
  350. class HeadersObject{
  351. constructor(importObj){
  352. const header = this;
  353. const timeStr = new Date().toPixivString();
  354. header["App-OS"] = "android";
  355. header["App-OS-Version"] = AndroidVersion;
  356. header["App-Version"] = PixivAppVersion;
  357. header["User-Agent"] = UA;
  358. header["Content-Type"] = ContentType; //重要
  359. header["Referer"] = Referer; // jshint ignore:line
  360. header["X-Client-Hash"] = CryptoJS.MD5(timeStr + X_Client_Hash_Salt).toString();
  361. header["X-Client-Time"] = timeStr;
  362.  
  363. if (typeof(obj) == "object") Object.assign(this, importObj);
  364. }
  365. }
  366.  
  367. //储存一项图片列表分析数据的对象
  368. var Works = function(){
  369. this.done = false; //是否分析完毕
  370. this.item = []; //储存图片数据
  371. this.break = false; //储存停止分析的Flag
  372. this.runing = false; //是否正在运行的Flasg
  373. this.next_url = ""; //储存下一页地址(断点续传)
  374. };
  375.  
  376. Math.randomInteger = function(max, min = 0)
  377. {
  378. return this.floor(this.random()*(max-min+1)+min);
  379. }
  380. //认证方案
  381. class oAuth2
  382. {
  383. constructor(existAuth){
  384. if (typeof(existAuth) == "object" && existAuth.auth_data)
  385. {
  386. Object.assign(this, existAuth);
  387. }else
  388. {
  389. this.code_verifier = this.random_code_verifier();
  390. this.login_time = null;
  391. this.authorization_code = null;
  392. this.auth_data = null;
  393. this.idp_urls = { //默认的综合网址集
  394. "account-edit": "https://accounts.pixiv.net/api/v2/account/edit",
  395. "auth-token": "https://oauth.secure.pixiv.net/auth/token",
  396. "auth-token-redirect-uri": "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback",
  397. };
  398. }
  399. }
  400. random_code_verifier()
  401. {
  402. const len = Math.randomInteger(43, 128); //产生43~128位
  403. const unreservedChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
  404. const charsLength = unreservedChars.length;
  405. let array = new Uint8Array(len);
  406. window.crypto.getRandomValues(array); //获取符合密码学要求的安全的随机值
  407. array = array.map(x => unreservedChars.charCodeAt(x % charsLength)); //将0~255转换到可选字符位置区间
  408. const radomCode = String.fromCharCode(...array); //将数字变回字符串
  409. return radomCode;
  410. }
  411. get_code_challenge(code_challenge_method = "S265")
  412. {
  413. if (code_challenge_method == "S265")
  414. {
  415. const bytes = CryptoJS.SHA256(this.code_verifier);
  416. const base64 = bytes.toString(CryptoJS.enc.Base64);
  417. const base64url = this.base64_to_base64url(base64);
  418. return base64url;
  419. }
  420. else
  421. {
  422. return this.code_verifier;
  423. }
  424. }
  425. base64_to_base64url(base64)
  426. {
  427. let base64url = base64;
  428. base64url = base64url.replace(/\=/g,'');
  429. base64url = base64url.replace(/\+/g,'-');
  430. base64url = base64url.replace(/\//g,'_');
  431. return base64url;
  432. }
  433. refresh_idp_urls(options = {})
  434. {
  435. const thisAuth = this;
  436. //登陆的Auth API
  437. GM_xmlhttpRequest({
  438. url: "https://app-api.pixiv.net/idp-urls",
  439. method: "get",
  440. responseType: "text",
  441. headers: new HeadersObject(),
  442. onload: function(response) {
  443. let jo;
  444. try {
  445. jo = JSON.parse(response.responseText);
  446. } catch (e) {
  447. console.error("获取综合网址集失败,返回可能不是JSON格式。", e, response);
  448. if(options.onload_notJson) options.onload_notJson(response.responseText);
  449. return;
  450. }
  451. if (jo.has_error || jo.errors) {
  452. console.error("获取综合网址集失败,返回错误消息", jo);
  453. if(options.onload_hasError) options.onload_hasError(jo);
  454. return;
  455. } else { //登陆成功
  456. Object.assign(thisAuth.idp_urls, jo);
  457. console.info("获取综合网址集成功", jo);
  458. if(options.onload) options.onload(jo);
  459. return;
  460. }
  461. },
  462. onerror: function(response) {
  463. console.error("获取登陆重定向网址失败,网络请求发生错误", response);
  464. if(options.onerror) options.onerror(response);
  465. return;
  466. }
  467. });
  468. }
  469. get_login_url()
  470. {
  471. const loginURL = new URL("https://app-api.pixiv.net/web/v1/login");
  472. loginURL.searchParams.set("code_challenge", this.get_code_challenge());
  473. loginURL.searchParams.set("code_challenge_method","S256");
  474. loginURL.searchParams.set("client","pixiv-android");
  475. return loginURL;
  476. }
  477. login(authorization_code, options = {})
  478. {
  479. this.authorization_code = authorization_code;
  480.  
  481. const thisAuth = this;
  482. const postObj = new URLSearchParams();
  483. postObj.set("code_verifier", thisAuth.code_verifier);
  484. postObj.set("code", authorization_code);
  485. postObj.set("grant_type","authorization_code");
  486. postObj.set("redirect_uri",thisAuth.idp_urls["auth-token-redirect-uri"]);
  487. postObj.set("client_id", client_id);
  488. postObj.set("client_secret", client_secret);
  489. postObj.set("include_policy","true");
  490.  
  491. this.refresh_idp_urls({
  492. onload: function(){
  493. //登陆的Auth API
  494. GM_xmlhttpRequest({
  495. url: thisAuth.idp_urls["auth-token"],
  496. method: "post",
  497. responseType: "text",
  498. headers: new HeadersObject(),
  499. data: postObj.toString(),
  500. onload: function(response) {
  501. let jo;
  502. try {
  503. jo = JSON.parse(response.responseText);
  504. } catch (e) {
  505. console.error("登录(不可用)失败,返回可能不是JSON格式,或本程序异常。", e, response);
  506. if(options.onload_notJson) options.onload_notJson(response.responseText);
  507. return;
  508. }
  509. if (jo.has_error || jo.errors) {
  510. console.error("登录(不可用)失败,返回错误消息", jo);
  511. if(options.onload_hasError) options.onload_hasError(jo);
  512. return;
  513. } else { //登陆成功
  514. thisAuth.auth_data = jo;
  515. thisAuth.login_time = new Date().getTime();
  516. console.info("登陆成功", jo);
  517. if(options.onload) options.onload(jo);
  518. return;
  519. }
  520. },
  521. onerror: function(response) {
  522. console.error("登录(不可用)失败,网络请求发生错误", response);
  523. if(options.onerror) options.onerror(response);
  524. return;
  525. }
  526. });
  527. }
  528. });
  529. }
  530. refresh_token(options = {})
  531. {
  532. const thisAuth = this;
  533. const postObj = new URLSearchParams();
  534. postObj.set("client_id", client_id);
  535. postObj.set("client_secret", client_secret);
  536. postObj.set("grant_type","refresh_token");
  537. postObj.set("refresh_token",thisAuth.auth_data.refresh_token);
  538. postObj.set("include_policy","true");
  539.  
  540. //登陆的Auth API
  541. GM_xmlhttpRequest({
  542. url: thisAuth.idp_urls["auth-token"],
  543. method: "post",
  544. responseType: "text",
  545. headers: new HeadersObject(),
  546. data: postObj.toString(),
  547. onload: function(response) {
  548. let jo;
  549. try {
  550. jo = JSON.parse(response.responseText);
  551. } catch (e) {
  552. console.error("刷新Token失败,返回可能不是JSON格式,或本程序异常。", e, response);
  553. if(options.onload_notJson) options.onload_notJson(response.responseText);
  554. return;
  555. }
  556. if (jo.has_error || jo.errors) {
  557. console.error("刷新Token失败,返回错误消息", jo);
  558. if(options.onload_hasError) options.onload_hasError(jo);
  559. return;
  560. } else { //登陆成功
  561. thisAuth.auth_data = jo;
  562. thisAuth.login_time = new Date().getTime();
  563. console.info("刷新Token成功", jo);
  564.  
  565. if(options.onload) options.onload(jo);
  566. return;
  567. }
  568. },
  569. onerror: function(response) {
  570. console.error("刷新Token失败,网络请求发生错误", response);
  571. if(options.onerror) options.onerror(response);
  572. return;
  573. }
  574. });
  575. }
  576. save()
  577. {
  578. GM_setValue("pubd-oauth", this);
  579. }
  580. }
  581. //一个掩码
  582. var Mask = function(name, logic, content){
  583. this.name = name;
  584. this.logic = logic;
  585. this.content = content;
  586. };
  587. //一个下载方案
  588. var DownScheme = function(name) {
  589. //默认值
  590. this.name = name ? name : "默认方案";
  591. this.rpcurl = "http://localhost:6800/jsonrpc";
  592. this.proxyurl = "";
  593. this.https2http = false;
  594. this.downfilter = "";
  595. this.savedir = "D:/PixivDownload/";
  596. this.savepath = "%{illust.user.id}/%{illust.filename}%{page}.%{illust.extention}";
  597. this.textout = "%{illust.url_without_page}%{page}.%{illust.extention}\n";
  598. this.masklist = [];
  599. };
  600. DownScheme.prototype.maskAdd = function(name, logic, content) {
  601. var mask = new Mask(name, logic, content);
  602. this.masklist.push(mask);
  603. return mask;
  604. };
  605. DownScheme.prototype.maskRemove = function(index) {
  606. this.masklist.splice(index, 1);
  607. };
  608. DownScheme.prototype.loadFromJson = function(json) {
  609. if (typeof(json) == "string") {
  610. try {
  611. json = JSON.parse(json);
  612. } catch (e) {
  613. console.error("读取的方案数据是字符串,但非JSON。",e);
  614. return false;
  615. }
  616. }
  617. const _this = this;
  618. Object.keys(_this).forEach(function(key){
  619. if (key=="masklist")
  620. {
  621. _this.masklist.length = 0; //清空之前的
  622. json.masklist.forEach(function(mask){
  623. _this.masklist.push(new Mask(mask.name, mask.logic, mask.content));
  624. });
  625. }else if(json[key] != undefined)
  626. {
  627. _this[key] = json[key];
  628. }
  629. });
  630. return true;
  631. };
  632.  
  633. //创建菜单类
  634. var pubdMenu = function(classname) {
  635. //生成菜单项
  636. function buildMenuItem(title, classname, callback, submenu) {
  637. var item = document.createElement("li");
  638. if (title == 0) //title为0时,只添加一条菜单分割线
  639. {
  640. item.className = "pubd-menu-line" + (classname ? " " + classname : "");
  641. return item;
  642. }
  643. item.className = "pubd-menu-item" + (classname ? " " + classname : "");
  644.  
  645. //如果有子菜单则添加子菜单
  646. if (typeof(submenu) == "object") {
  647. item.classList.add("pubd-menu-includesub"); //表明该菜单项有子菜单
  648. submenu.classList.add("pubd-menu-submenu"); //表明该菜单是子菜单
  649. //a.addEventListener("mouseenter",function(){callback.show()});
  650. //a.addEventListener("mouseleave",function(){callback.hide()});
  651. item.appendChild(submenu);
  652. item.subitem = submenu;
  653. }else
  654. {
  655. item.subitem = null; //子菜单默认为空
  656. }
  657.  
  658. //添加链接
  659. var a = item.appendChild(document.createElement("a"));
  660. a.className = "pubd-menu-item-a";
  661. //添加图标
  662. var icon = a.appendChild(document.createElement("i"));
  663. icon.className = "pubd-icon";
  664. //添加文字
  665. var span = a.appendChild(document.createElement("span"));
  666. span.className = "text";
  667. span.innerHTML = title;
  668.  
  669. //添加菜单操作
  670. if (typeof(callback) == "string") { //为字符串时,当作链接处理
  671. a.target = "_blank";
  672. a.href = callback;
  673. } else if (typeof(callback) == "function") { //为函数时,当作按钮处理
  674. item.addEventListener("click", callback);
  675. //a.onclick = callback;
  676. }
  677. return item;
  678. }
  679.  
  680. var menu = document.createElement("ul");
  681. menu.className = "pubd-menu display-none" + (classname ? " " + classname : "");
  682. menu.item = [];
  683. //显示该菜单
  684. menu.show = function() {
  685. menu.classList.remove("display-none");
  686. };
  687. menu.hide = function() {
  688. menu.classList.add("display-none");
  689. };
  690. //添加菜单项
  691. menu.add = function(title, classname, callback, submenu) {
  692. var itm = buildMenuItem(title, classname, callback, submenu);
  693. this.appendChild(itm);
  694. this.item.push(itm);
  695. return itm;
  696. };
  697. //鼠标移出菜单时消失
  698. menu.addEventListener("mouseleave", function(e) {
  699. this.hide();
  700. });
  701. return menu;
  702. };
  703.  
  704. //创建通用对话框类
  705. var Dialog = function(caption, classname, id) {
  706. //构建标题栏按钮
  707. function buildDlgCptBtn(text, classname, callback) {
  708. if (!callback) classname = "";
  709. const btn = document.createElement("a");
  710. btn.className = "dlg-cpt-btn" + (classname ? " " + classname : "");
  711. if (typeof(callback) == "string") {
  712. btn.target = "_blank";
  713. btn.href = callback;
  714. } else {
  715. if (callback)
  716. btn.addEventListener("click", callback);
  717. }
  718. var btnTxt = btn.appendChild(document.createElement("span"));
  719. btnTxt.className = "dlg-cpt-btn-text";
  720. btnTxt.innerHTML = text;
  721.  
  722. return btn;
  723. }
  724.  
  725. var dlg = document.createElement("div");
  726. if (id) dlg.id = id;
  727. dlg.className = "pubd-dialog display-none" + (classname ? " " + classname : "");
  728.  
  729. //添加图标与标题
  730. var cpt = dlg.appendChild(document.createElement("div"));
  731. cpt.className = "caption";
  732. dlg.icon = cpt.appendChild(document.createElement("i"));
  733. dlg.icon.className = "pubd-icon";
  734. var captionDom = cpt.appendChild(document.createElement("span"));
  735. Object.defineProperty(dlg , "caption", {
  736. get() {
  737. return captionDom.textContent;
  738. },
  739. set(str) {
  740. captionDom.innerHTML = str;
  741. }
  742. });
  743. dlg.caption = caption;
  744.  
  745. //添加标题栏右上角按钮 captionButtons
  746. var cptBtns = dlg.cptBtns = dlg.appendChild(document.createElement("div"));
  747. cptBtns.className = "dlg-cpt-btns";
  748. //添加按钮的函数
  749. cptBtns.add = function(text, classname, callback) {
  750. var btn = buildDlgCptBtn(text, classname, callback);
  751. this.insertBefore(btn, this.firstChild);
  752. return btn;
  753. };
  754. //添加关闭按钮
  755. cptBtns.close = cptBtns.add("X", "dlg-btn-close", (function() {
  756. dlg.classList.add("display-none");
  757. }));
  758.  
  759. //添加内容区域
  760. var content = dlg.content = dlg.appendChild(document.createElement("div"));
  761. content.className = "dlg-content";
  762.  
  763. //窗口激活
  764. dlg.active = function() {
  765. if (!this.classList.contains("pubd-dlg-active")) { //如果没有激活的话才执行
  766. var dlgs = document.querySelectorAll(".pubd-dialog"); //获取网页已经载入的所有的窗口
  767. for (var dlgi = 0; dlgi < dlgs.length; dlgi++) { //循环所有窗口
  768. if (dlgs[dlgi] != this)
  769. {
  770. dlgs[dlgi].classList.remove("pubd-dlg-active"); //取消激活
  771. dlgs[dlgi].style.zIndex = parseInt(window.getComputedStyle(dlgs[dlgi], null).getPropertyValue("z-index")) - 1; //从当前网页最终样式获取该窗体z级,并-1.
  772. }
  773. }
  774. this.classList.add("pubd-dlg-active"); //添加激活
  775. this.style.zIndex = ""; //z级归零
  776. }
  777. };
  778. //窗口初始化
  779. dlg.initialise = function() { //窗口初始化默认情况下什么也不做,具体在每个窗口再设置
  780. return;
  781. };
  782. //窗口显示
  783. dlg.show = function(posX, posY, arg) {
  784. if (posX) dlg.style.left = posX + "px"; //更改显示时初始坐标
  785. if (posY) dlg.style.top = posY + "px";
  786. dlg.initialise(arg); //对窗体进行初始化(激活为可见前提前修改窗体内容)
  787. dlg.classList.remove("display-none");
  788. dlg.active(); //激活窗口
  789. };
  790. //窗口隐藏
  791. dlg.hide = function() { //默认情况下等同于关闭窗口
  792. dlg.cptBtns.close.click();
  793. };
  794. //添加鼠标拖拽移动
  795. var drag = dlg.drag = [0, 0]; //[X,Y] 用以储存窗体开始拖动时的鼠标相对窗口坐标差值。
  796. //startDrag(cpt, dlg);
  797. cpt.addEventListener("mousedown", function(e) { //按下鼠标则添加移动事件
  798. var eX = e.pageX>0?e.pageX:0, eY = e.pageY>0?e.pageY:0; //不允许鼠标坐标向上、左超出网页。
  799. drag[0] = eX - dlg.offsetLeft;
  800. drag[1] = eY - dlg.offsetTop;
  801. var handler_mousemove = function(e) { //移动鼠标则修改窗体坐标
  802. var eX = e.pageX>0?e.pageX:0, eY = e.pageY>0?e.pageY:0; //不允许鼠标坐标向上、左超出网页。
  803. dlg.style.left = (eX - drag[0]) + 'px';
  804. dlg.style.top = (eY - drag[1]) + 'px';
  805. };
  806. var handler_mouseup = function(e) { //抬起鼠标则取消移动事件
  807. document.removeEventListener("mousemove", handler_mousemove);
  808. };
  809. document.addEventListener("mousemove", handler_mousemove);
  810. document.addEventListener("mouseup", handler_mouseup, { once: true });
  811. });
  812. //点击窗口任何区域激活窗口
  813. dlg.addEventListener("mousedown", function(e) {
  814. dlg.active();
  815. });
  816. return dlg;
  817. };
  818.  
  819. //创建框架类
  820. var Frame = function(title, classname) {
  821. var frame = document.createElement("div");
  822. frame.className = "pubd-frame" + (classname ? " " + classname : "");
  823.  
  824. var caption = frame.caption = frame.appendChild(document.createElement("div"));
  825. caption.className = "pubd-frame-caption";
  826. caption.innerHTML = title;
  827. var content = frame.content = frame.appendChild(document.createElement("div"));
  828. content.className = "pubd-frame-content";
  829.  
  830. frame.name = function() {
  831. return this.caption.textContent;
  832. };
  833. frame.rename = function(newName) {
  834. if (typeof(newName) == "string" && newName.length > 0) {
  835. this.caption.innerHTML = newName;
  836. return true;
  837. } else
  838. return false;
  839. };
  840.  
  841. return frame;
  842. };
  843.  
  844. //创建带Label的Input类
  845. const LabelInput = function(text, classname, name, type, value, beforeText, title) {
  846. var label = document.createElement("label");
  847. if (text) label.appendChild(document.createTextNode(text));
  848. label.className = classname;
  849. if (title) label.title = title;
  850.  
  851. var ipt = label.input = document.createElement("input");
  852. ipt.name = name;
  853. ipt.id = ipt.name;
  854. ipt.type = type;
  855. ipt.value = value;
  856.  
  857. if (beforeText && label.childNodes.length>0)
  858. label.insertBefore(ipt, label.firstChild);
  859. else
  860. label.appendChild(ipt);
  861. return label;
  862. };
  863.  
  864. //构建错误信息显示模块
  865. const ErrorMsg = function() {
  866. const error_msg_list = document.createElement("ul");
  867. error_msg_list.className = "error-msg-list";
  868. //添加错误显示功能
  869. error_msg_list.clear = function() {
  870. this.innerHTML = ""; //清空当前信息
  871. };
  872. error_msg_list.add = function(arg) {
  873. function addLine(str)
  874. {
  875. const li = document.createElement("li");
  876. li.className = "error-msg-list-item";
  877. li.appendChild(document.createTextNode(str));
  878. return li;
  879. }
  880. const fragment = document.createDocumentFragment();
  881. if (Array.isArray(arg)) //数组
  882. {
  883. arg.forEach(str=>fragment.appendChild(addLine(str)));
  884. }
  885. else //单文本
  886. {
  887. fragment.appendChild(addLine(arg));
  888. }
  889. this.appendChild(fragment);
  890. };
  891.  
  892. error_msg_list.replace = function(arg) {
  893. this.clear();
  894. this.add(arg);
  895. };
  896. return error_msg_list;
  897. }
  898.  
  899. //创建进度条类
  900. const Progress = function(classname, align_right) {
  901. const progress = document.createElement("div");
  902. progress.className = "pubd-progress" + (classname ? " " + classname : "");
  903. if (align_right) progress.classList.add("pubd-progress-right");
  904.  
  905. progress.scaleNum = 0;
  906.  
  907. const bar = progress.appendChild(document.createElement("div"));
  908. bar.className = "pubd-progress-bar";
  909.  
  910. const txt = progress.appendChild(document.createElement("span"));
  911. txt.className = "pubd-progress-text";
  912.  
  913. progress.set = function(scale, pos = 2, str = null) {
  914. const percentStr = (scale * 100).toFixed(pos) + "%"; //百分比的数字
  915. this.scaleNum = Math.min(Math.max(scale, 0), 1);
  916. bar.style.width = percentStr;
  917. txt.textContent = str || percentStr;
  918. };
  919. Object.defineProperty(progress , "scale", {
  920. get() {
  921. return this.scaleNum;
  922. },
  923. set(num) {
  924. this.set(num);
  925. }
  926. });
  927.  
  928. return progress;
  929. };
  930.  
  931. //创建 卡片类
  932. function InfoCard(datas) {
  933. var cardDiv = this.dom = document.createElement("div");
  934. cardDiv.className = "pubd-infoCard";
  935. var thumbnailDiv = cardDiv.appendChild(document.createElement("div"));
  936. thumbnailDiv.className = "pubd-infoCard-thumbnail";
  937. var thumbnailImgDom = thumbnailDiv.appendChild(document.createElement("img"));
  938. var infosDlDom = cardDiv.appendChild(document.createElement("dl"));
  939. infosDlDom.className = "pubd-infoCard-dl";
  940. Object.defineProperty(this , "thumbnail", {
  941. get() {
  942. return thumbnailImgDom.src;
  943. },
  944. set(url) {
  945. thumbnailImgDom.src = url;
  946. }
  947. });
  948. var infoObj;
  949. this.reload = function() //重构Card文本区域
  950. {
  951. infosDlDom.classList.add('display-none');
  952. for (let ci = infosDlDom.childNodes.length-1;ci >= 0;ci--)
  953. { //删掉所有老子元素
  954. var x = infosDlDom.childNodes[ci];
  955. x.remove();
  956. x = null;
  957. }
  958. const fragment = document.createDocumentFragment();
  959. Object.entries(infoObj).forEach(entry=>{
  960. const dt = fragment.appendChild(document.createElement("dt"));
  961. const dd = fragment.appendChild(document.createElement("dd"));
  962. dt.appendChild(document.createTextNode(entry[0]));
  963. if (entry[1]) dd.appendChild(document.createTextNode(entry[1]));
  964. });
  965. infosDlDom.appendChild(fragment);
  966. infosDlDom.classList.remove('display-none');
  967. };
  968.  
  969. Object.defineProperty(this , "infos", {
  970. get() {
  971. return infoObj;
  972. },
  973. set(obj) {
  974. infoObj = obj;
  975. this.reload();
  976. }
  977. });
  978. this.infos = datas || {}; //使用传入data进行初始设定
  979. }
  980. //创建下拉框类
  981. var Select = function(classname, name) {
  982. var select = document.createElement("select");
  983. select.className = "pubd-select" + (classname ? " " + classname : "");
  984. select.name = name;
  985. select.id = select.name;
  986.  
  987. select.add = function(text, value) {
  988. var opt = new Option(text, value);
  989. this.options.add(opt);
  990. };
  991. select.remove = function(index) {
  992. var x = this.options.remove(index);
  993. x = null;
  994. };
  995.  
  996. return select;
  997. };
  998.  
  999. //创建Aria2类
  1000. var Aria2 = (function() {
  1001. var jsonrpc_version = '2.0';
  1002.  
  1003. function get_auth(url) {
  1004. return url.match(/^(?:(?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(?:\/\/)?(?:([^:@]*(?::[^:@]*)?)?@)?/)[1];
  1005. }
  1006.  
  1007. function request(jsonrpc_path, method, params, callback, priority) {
  1008. if (callback == undefined) callback = ()=>{};
  1009. var auth = get_auth(jsonrpc_path);
  1010. jsonrpc_path = jsonrpc_path.replace(/^((?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(\/\/)?(?:(?:[^:@]*(?::[^:@]*)?)?@)?(.*)/, '$1$2$3'); // auth string not allowed in url for firefox
  1011.  
  1012. var request_obj = {
  1013. jsonrpc: jsonrpc_version,
  1014. method: method,
  1015. id: priority ? 1 : Date.now(),
  1016. };
  1017. if (params) request_obj.params = params;
  1018. if (auth && auth.indexOf('token:') == 0)
  1019. {
  1020. if (method == "system.multicall")
  1021. { //多项目操作时单独设置token
  1022. params.forEach(function(param){
  1023. param.forEach(function(method){
  1024. method.params.unshift(auth);
  1025. });
  1026. });
  1027. }else
  1028. {
  1029. params.unshift(auth);
  1030. }
  1031. }
  1032.  
  1033. var headers = { "Content-Type": ContentType };
  1034. if (auth && auth.indexOf('token:') != 0) {
  1035. headers.Authorization = "Basic " + btoa(auth);
  1036. }
  1037. GM_xmlhttpRequest({
  1038. url: jsonrpc_path + "?tm=" + (new Date()).getTime().toString(),
  1039. method: "POST",
  1040. responseType: "text",
  1041. data: JSON.stringify(request_obj),
  1042. headers: headers,
  1043. onload: function(response) {
  1044. try {
  1045. var JSONreq = JSON.parse(response.response);
  1046. callback(JSONreq);
  1047. } catch (e) {
  1048. console.error("Aria2发送信息错误", e, response);
  1049. callback(false);
  1050. }
  1051. },
  1052. onerror: function(response) {
  1053. console.error(response);
  1054. callback(false);
  1055. }
  1056. });
  1057. }
  1058.  
  1059. return function(jsonrpc_path) {
  1060. const _this = this;
  1061. _this.jsonrpc_path = jsonrpc_path;
  1062. _this.addUri = function(uri, options, callback) {
  1063. request(_this.jsonrpc_path, 'aria2.addUri', [
  1064. [uri, ], options
  1065. ], callback);
  1066. };
  1067. _this.addTorrent = function(base64txt, options, callback) {
  1068. request(_this.jsonrpc_path, 'aria2.addTorrent', [base64txt, [], options], callback);
  1069. };
  1070. _this.getVersion = function(callback) {
  1071. request(_this.jsonrpc_path, 'aria2.getVersion', [], callback, true);
  1072. };
  1073. _this.getGlobalOption = function(callback) {
  1074. request(_this.jsonrpc_path, 'aria2.getGlobalOption', [], callback, true);
  1075. };
  1076. _this.system = {
  1077. multicall:function(params,callback){
  1078. request(_this.jsonrpc_path, 'system.multicall', params, callback);
  1079. },
  1080. };
  1081. return this;
  1082. };
  1083. })();
  1084.  
  1085. /*
  1086. * 自定义函数区
  1087. */
  1088. //仿GM_notification函数v1.2,发送网页通知。
  1089. //此函数非Debug用,为了替换选项较少但是兼容其格式的GM_notification插件
  1090. function GM_notification(text, title, image, onclick) {
  1091. const options = {};
  1092. let rTitle, rText;
  1093. let ondone, onclose;
  1094. const dataMode = Boolean(typeof(text) == "string"); //GM_notification有两种模式,普通4参数模式和option对象模式
  1095. if (dataMode)
  1096. { //普通模式
  1097. rTitle = title;
  1098. rText = text;
  1099. options.body = text;
  1100. options.icon = image;
  1101. }else
  1102. { //选项模式
  1103. const details = text;
  1104. rTitle = details.title;
  1105. rText = details.text;
  1106. if (details.text) options.body = details.text;
  1107. if (details.image) options.icon = details.image;
  1108. if (details.timeout) options.timestamp = details.timeout;
  1109. ondone = title;
  1110. onclose = image;
  1111. //if (details.highlight) options.highlight = details.highlight; //没找到这个功能
  1112. }
  1113.  
  1114. function sendNotification(general){
  1115. const n = new Notification(rTitle, options);
  1116. if (general)
  1117. { //普通模式
  1118. if (onclick) n.onclick = onclick;
  1119. }else
  1120. { //选项模式,这里和TamperMonkey API不一样,区分了关闭和点击。
  1121. if (ondone) n.onclick = ondone;
  1122. if (onclose) n.onclose = onclose;
  1123. }
  1124. }
  1125. // 先检查浏览器是否支持
  1126. if (!("Notification" in window)) {
  1127. alert(rTitle + "\r\n" + rText);
  1128. // 检查用户是否同意接受通知
  1129. } else if (Notification.permission === "granted") {
  1130. Notification.requestPermission(function(permission) {
  1131. sendNotification(dataMode);
  1132. });
  1133. }
  1134. // 否则我们需要向用户获取权限
  1135. else if (Notification.permission !== 'denied') {
  1136. Notification.requestPermission(function(permission) {
  1137. // 如果用户同意,就可以向他们发送通知
  1138. if (permission === "granted") {
  1139. sendNotification(dataMode);
  1140. }
  1141. });
  1142. }
  1143. }
  1144. //有默认值的获取设置
  1145. function getValueDefault(name, defaultValue) {
  1146. var value = GM_getValue(name);
  1147. if (value != null)
  1148. return value;
  1149. else
  1150. return defaultValue;
  1151. }
  1152. //加入了Auth的网络请求函数
  1153. function xhrGenneral(url, onload_suceess_Cb, onload_hasError_Cb, onload_notJson_Cb, onerror_Cb, dlog=str=>str) {
  1154. const headersObj = new HeadersObject();
  1155. const auth = pubd.oAuth.auth_data;
  1156. if (auth) {
  1157. headersObj.Authorization = auth.token_type[0].toUpperCase() + auth.token_type.substring(1) + " " + auth.access_token;
  1158. } else {
  1159. console.info(dlog("未登录(不可用)账户,尝试以非登录(不可用)状态获取信息"));
  1160. }
  1161.  
  1162. GM_xmlhttpRequest({
  1163. url: url,
  1164. method: "get",
  1165. responseType: "text",
  1166. headers: headersObj,
  1167. onload: function(response) {
  1168. let jo;
  1169. try {
  1170. jo = JSON.parse(response.responseText);
  1171. } catch (e) {
  1172. console.error(dlog("错误:返回可能不是JSON格式,或本程序异常"), e, response);
  1173. onload_notJson_Cb(response);
  1174. return;
  1175. }
  1176.  
  1177. if (jo)
  1178. {
  1179. if (mdev) console.log("请求URL %s,结果 %o",url,JSON.parse(response.responseText));
  1180. //jo.error.message 是JSON字符串的错误信息,Token错误的时候返回的又是普通字符串
  1181. //jo.error.user_message 是单行文本的错误信息
  1182. if (jo.error) {
  1183. if (jo.error.message.includes("Error occurred at the OAuth process.")) {
  1184. if (auth) {
  1185. console.warn(dlog("授权 Token 过期,开始自动更新。"),jo);
  1186. //自动重新登陆
  1187. pubd.dialog.refresh_token.show(
  1188. (document.body.clientWidth - 370)/2,
  1189. window.pageYOffset+300,
  1190. {
  1191. onload: ()=>{
  1192. pubd.dialog.refresh_token.hide();
  1193. xhrGenneral(url, onload_suceess_Cb, onload_hasError_Cb, onload_notJson_Cb, onerror_Cb);
  1194. },
  1195. onload_hasError: onload_hasError_Cb,
  1196. onload_notJson: onload_notJson_Cb,
  1197. onerror: onerror_Cb,
  1198. }
  1199. );
  1200. } else {
  1201. console.info(dlog("非登录(不可用)模式尝试获取信息失败"),jo);
  1202. onload_hasError_Cb(jo);
  1203. }
  1204. return;
  1205. }else if (jo.error.message.includes("Rate Limit")) {
  1206. console.warn(dlog("获取信息速度太快,触发P站速度限制,1分钟后自动重试。"),jo);
  1207. setTimeout(()=>{
  1208. xhrGenneral(url, onload_suceess_Cb, onload_hasError_Cb, onload_notJson_Cb, onerror_Cb);
  1209. }, 1000 * 60)
  1210. return;
  1211. }else
  1212. {
  1213. onload_hasError_Cb(jo);
  1214. return;
  1215. }
  1216. } else { //登陆成功
  1217. //console.info("JSON返回成功",jo);
  1218. onload_suceess_Cb(jo);
  1219. return;
  1220. }
  1221. }
  1222. },
  1223. onerror: function(response) {
  1224. console.error(dlog("错误:网络请求发送失败"), response);
  1225. onerror_Cb(response);
  1226. }
  1227. });
  1228. }
  1229. //用id来获取动画帧数据
  1230. function getUgoiraMeta(iid, onload_suceess_Cb, onload_hasError_Cb, onload_notJson_Cb, onerror_Cb)
  1231. {
  1232. xhrGenneral(
  1233. "https://app-api.pixiv.net/v1/ugoira/metadata?illust_id=" + iid,
  1234. onload_suceess_Cb,
  1235. onload_hasError_Cb,
  1236. onload_notJson_Cb,
  1237. onerror_Cb
  1238. );
  1239. }
  1240. //为了区分设置窗口和保存的设置,产生一个新的下载方案数组
  1241. function NewDownSchemeArrayFromJson(jsonarr) {
  1242. if (typeof(jsonarr) == "string") {
  1243. try {
  1244. jsonarr = JSON.parse(jsonarr);
  1245. } catch (e) {
  1246. console.error("PUBD:拷贝新下载方案数组时失败(是字符串,但不是JSON)", e);
  1247. return false;
  1248. }
  1249. }
  1250. var sarr = [];
  1251. if (jsonarr instanceof Array) {
  1252. jsonarr.forEach(json=>{
  1253. var scheme = new DownScheme();
  1254. scheme.loadFromJson(json);
  1255. sarr.push(scheme);
  1256. });
  1257. }
  1258. return sarr;
  1259. }
  1260. //获取URL参数
  1261. function getQueryString(name,url) {
  1262. if (Boolean(window.URL && window.URLSearchParams))
  1263. { //浏览器原生支持的API
  1264. const urlObj = new URL(url || document.location);
  1265. return urlObj.searchParams.get(name);
  1266. }else
  1267. {
  1268. const reg = new RegExp(`(?:^|&)${name}=([^&]*)(?:&|$)`, "i");
  1269. const searchStr = url || location.search.substr(1);
  1270. const r = searchStr.match(reg);
  1271. if (r != null)
  1272. {
  1273. return decodeURIComponent(r[1]);
  1274. }else
  1275. {
  1276. return null;
  1277. }
  1278. }
  1279. }
  1280. //从图片URL获取图片属性
  1281. function parseIllustUrl(url) {
  1282. let src
  1283. try {
  1284. src = new URL(url);
  1285. } catch (error) {
  1286. return null;
  1287. }
  1288. const obj = {
  1289. domain: src.host, //为了兼容老的
  1290. parsedURL: { //目前用到的不多,只保留这两个值
  1291. host: src.host, //域(即主机名)后跟端口
  1292. protocol: src.protocol, //URL 协议名
  1293. }
  1294. };
  1295. const parsedURL = obj.parsedURL;
  1296. let regRes = new RegExp(illustPathRegExp.source, illustPathRegExp.flags).exec(src.pathname);
  1297. if (regRes)
  1298. {
  1299. //为了兼容老的
  1300. obj.url_without_page = `${src.origin}${regRes[1]}`;
  1301. obj.filename = regRes[2];
  1302. //id直接在原始数据有
  1303. obj.token = regRes[4];
  1304. obj.extention = regRes[5];
  1305.  
  1306. parsedURL.path_before_page = regRes[1];
  1307. parsedURL.filename = regRes[2];
  1308. parsedURL.id = regRes[3];
  1309. parsedURL.token = regRes[4];
  1310. parsedURL.extention = regRes[5];
  1311. }else if (regRes = new RegExp(limitingPathRegExp.source, limitingPathRegExp.flags).exec(src.pathname)) //上锁图片
  1312. {
  1313. //为了兼容老的
  1314. obj.url_without_page = `${src.origin}${regRes[1]}`;
  1315. obj.filename = regRes[2];
  1316. //id直接在原始数据有
  1317. obj.extention = regRes[3];
  1318.  
  1319. parsedURL.path_before_page = regRes[1];
  1320. parsedURL.limited = true;
  1321. parsedURL.filename = regRes[2];
  1322. parsedURL.extention = regRes[3];
  1323. }else
  1324. {
  1325. parsedURL.unknown = true;
  1326. }
  1327. return obj;
  1328. }
  1329. //从一个作品数据得到原始图片的下载地址
  1330. function getIllustDownUrl(illust, page, https2http = false)
  1331. {
  1332. return `${https2http ? 'http:' : illust.parsedURL.protocol}//${illust.parsedURL.host}${illust.parsedURL.path_before_page}${page}.${illust.extention}`;
  1333. }
  1334.  
  1335. //获取当前用户ID
  1336. function getCurrentUserId()
  1337. {
  1338. //从URL获取作者ID
  1339. function getUserIdFromUrl(url) {
  1340. let userid = parseInt(getQueryString("id",url),10); //老地址:https://www.pixiv.net/member_illust.php?id=3896348
  1341. if (!userid)
  1342. {
  1343. const regSrc = new RegExp("users/(\\d+)", "ig"); //新地址:https://www.pixiv.net/users/3896348
  1344. const regRes = regSrc.exec(url.pathname);
  1345. if (regRes) {
  1346. return parseInt(regRes[1],10);
  1347. }
  1348. }
  1349. return userid;
  1350. }
  1351. let userid = getUserIdFromUrl(document.location);
  1352. if(!userid)
  1353. {
  1354. userid = thisPageUserid;
  1355. if (mainDiv)
  1356. {
  1357. const userMainPageLink = mainDiv.querySelector(userMainPageCssPath); //作者主页的“主页”按钮
  1358. //var artWorkLink = mainDiv.querySelector(artWorkStarCssPath);
  1359. const userHeadLink = mainDiv.querySelector(artWorkUserHeadCssPath);
  1360. if (userMainPageLink) //如果是作者页面
  1361. {
  1362. userid = getUserIdFromUrl(userMainPageLink);
  1363. }
  1364. if (userHeadLink) //如果是作品页面
  1365. {
  1366. userid = getUserIdFromUrl(userHeadLink);
  1367. }
  1368. if(pubd.touch)
  1369. {
  1370. const touch_userHeadLink = mainDiv.querySelector('.user-details-card .user-details-icon'); //如果是作品页面
  1371. if (touch_userHeadLink) //如果是作品页面
  1372. {
  1373. userid = getUserIdFromUrl(touch_userHeadLink);
  1374. }
  1375. }
  1376. }
  1377. }
  1378. return userid;
  1379. }
  1380. //检查并快速添加画师收藏的函数
  1381. function toggleStar(userid)
  1382. {
  1383. userid = userid || getCurrentUserId();
  1384. const res = pubd.fastStarList.toggle(userid);
  1385. if (res)
  1386. { //添加
  1387. pubd.start.star.classList.add("stars");
  1388. }else
  1389. { //删除
  1390. pubd.start.star.classList.remove("stars");
  1391. }
  1392.  
  1393. GM_setValue("pubd-faststar-list",pubd.fastStarList.exportArray());
  1394. }
  1395. //检查是否有画师并改变星星状态
  1396. function checkStar()
  1397. {
  1398. const userid = getCurrentUserId();
  1399. const res = pubd.fastStarList.has(userid);
  1400. if (res)
  1401. { //存在,则标记
  1402. pubd.start.star.classList.add("stars");
  1403. return true;
  1404. }else
  1405. { //不存在,则去掉标记
  1406. pubd.start.star.classList.remove("stars");
  1407. return false;
  1408. }
  1409. }
  1410.  
  1411. //构建开始按钮
  1412. function buildbtnStart() {
  1413. const btnStart = document.createElement("div");
  1414. btnStart.id = "pubd-start";
  1415. btnStart.className = "pubd-start";
  1416. //添加图标
  1417. const star = btnStart.star = btnStart.appendChild(document.createElement("i"));
  1418. star.className = "pubd-icon star";
  1419. star.title = "快速收藏当前画师(开发中功能,目前没用)";
  1420. //添加文字
  1421. const caption = btnStart.caption = btnStart.appendChild(document.createElement("div"));
  1422. caption.className = "text";
  1423. caption.innerHTML = "使用PUBD扒图";
  1424. caption.title = "快速下载当前画师";
  1425. //添加文字
  1426. const menu = btnStart.menu = btnStart.appendChild(document.createElement("i"));
  1427. menu.className = "pubd-icon menu";
  1428. menu.title = "PUBD菜单";
  1429.  
  1430. //鼠标移入和按下都起作用
  1431. //btnStart.addEventListener("mouseenter",function(){pubd.menu.show()});
  1432. star.onclick = function(){toggleStar();};
  1433. menu.onclick = function(){pubd.menu.classList.toggle("display-none");};
  1434. caption.onclick = function(){pubd.menu.downthis.click();};
  1435. return btnStart;
  1436. }
  1437.  
  1438. //构建开始菜单
  1439. function buildbtnMenu() {
  1440. /*
  1441. var menu2 = new pubdMenu();
  1442. menu2.add("子菜单1","",function(){alert("子菜单1")});
  1443. menu2.add("子菜单2","",function(){alert("子菜单2")});
  1444. var menu1 = new pubdMenu();
  1445. menu1.add("子菜单1","",function(){alert("子菜单1")});
  1446. menu1.add("子菜单2","",null,menu2);
  1447. var menu3 = new pubdMenu();
  1448. menu3.add("子菜单1","",function(){alert("子菜单1")});
  1449. menu3.add("子菜单2","",function(){alert("子菜单2")});
  1450. menu3.add("子菜单2","",function(){alert("子菜单3")});
  1451. menu3.add("子菜单2","",function(){alert("子菜单4")});
  1452. var menu4 = new pubdMenu();
  1453. menu4.add("子菜单1","",null,menu3);
  1454. menu4.add("子菜单2","",function(){alert("子菜单2")});
  1455. menu4.add("子菜单2","",function(){alert("子菜单5")});
  1456. menu4.add("子菜单2","",function(){alert("子菜单6")});
  1457. */
  1458. var menu = new pubdMenu("pubd-menu-main");
  1459. menu.id = "pubd-menu";
  1460. menu.downillust = menu.add("下载当前作品", "pubd-menu-this-illust", function(e) {
  1461. pubd.dialog.downillust.show(
  1462. (document.body.clientWidth - 500)/2,
  1463. window.pageYOffset+150,
  1464. {id:getQueryString('illust_id',
  1465. pubd.touch ?
  1466. mainDiv.querySelector('.illust-details-content .work-stats>a') : //手机版
  1467. mainDiv.querySelector(artWorkStarCssPath) //新版Vue结构
  1468. )}
  1469. );
  1470. menu.hide();
  1471. });
  1472. menu.downthis = menu.add("下载该画师所有作品", "pubd-menu-this-user", function(e) {
  1473. pubd.dialog.downthis.show(
  1474. (document.body.clientWidth - 440)/2,
  1475. window.pageYOffset+100,
  1476. {id:getCurrentUserId()}
  1477. );
  1478. menu.hide();
  1479. });
  1480. /*
  1481. menu.add("占位用","",null,menu1);
  1482. menu.add("没功能","",null,menu4);
  1483. menu.add("多个画师下载",null,function()
  1484. {//做成“声音”的设备样子
  1485. alert("这个功能也没有开发")
  1486. }
  1487. );
  1488. */
  1489. menu.add(0);
  1490. if (mdev) menu.downmult = menu.add("多画师下载", "pubd-menu-multiple", function(e) {
  1491. pubd.dialog.multiple.show(
  1492. (document.body.clientWidth - 440)/2,
  1493. window.pageYOffset+100
  1494. );
  1495. menu.hide();
  1496. });
  1497. menu.add("选项", "pubd-menu-setting", function(e) {
  1498. pubd.dialog.config.show(
  1499. (document.body.clientWidth - 400)/2,
  1500. window.pageYOffset+50
  1501. );
  1502. menu.hide();
  1503. });
  1504. return menu;
  1505. }
  1506.  
  1507. //构建Token剩余时间进度条
  1508. function buildProgressToken()
  1509. {
  1510. const progress = new Progress("pubd-token-expires", true);
  1511. progress.animateHook = null; //储存Token进度条动画句柄
  1512. progress.token_animate = function(){
  1513. const _this = progress;
  1514. if (!pubd.oAuth.auth_data)
  1515. {
  1516. _this.set(0, 2, "尚未登陆");
  1517. clearInterval(_this.animateHook);
  1518. return;
  1519. }
  1520. const nowdate = new Date();
  1521. const olddate = new Date(pubd.oAuth.login_time);
  1522. const expires_in = parseInt(pubd.oAuth.auth_data.expires_in);
  1523. const differ = expires_in - (nowdate - olddate) / 1000;
  1524. if (differ > 0) {
  1525. const scale = differ / expires_in;
  1526. _this.set(scale, 2, "Token有效剩余 " + parseInt(differ) + " 秒");
  1527. } else {
  1528. _this.set(0, 2, "Token已失效,请刷新");
  1529. clearInterval(_this.animateHook);
  1530. }
  1531. //console.log("Token有效剩余" + differ + "秒"); //检测动画后台是否停止
  1532. }
  1533. //开始动画
  1534. progress.start_token_animate = function(){
  1535. const _this = progress;
  1536. _this.stop_token_animate();
  1537. requestAnimationFrame(_this.token_animate);
  1538. _this.animateHook = setInterval(()=>requestAnimationFrame(_this.token_animate), 1000);
  1539. };
  1540. //停止动画
  1541. progress.stop_token_animate = function(){
  1542. const _this = progress;
  1543. clearInterval(_this.animateHook);
  1544. };
  1545. return progress;
  1546. }
  1547.  
  1548. //构建设置对话框
  1549. function buildDlgConfig() {
  1550. const dlg = new Dialog("PUBD选项 v" + scriptVersion, "pubd-config", "pubd-config");
  1551. dlg.cptBtns.add("反馈", "dlg-btn-debug", "https://github.com/Mapaler/PixivUserBatchDownload/issues");
  1552. dlg.cptBtns.add("?", "dlg-btn-help", "https://github.com/Mapaler/PixivUserBatchDownload/wiki");
  1553. dlg.token_ani = null; //储存Token进度条动画句柄
  1554.  
  1555. var dl = dlg.content.appendChild(document.createElement("dl"));
  1556.  
  1557. var dt = dl.appendChild(document.createElement("dt"));
  1558.  
  1559. var dd = dl.appendChild(document.createElement("dd"));
  1560.  
  1561. dlg.frmLogin = dd.appendChild(new Frame("Pixiv访问权限", "pubd-token"));
  1562.  
  1563. var dl_t = dlg.frmLogin.content.appendChild(document.createElement("dl"));
  1564.  
  1565. var dd_t = dl_t.appendChild(document.createElement("dd"));
  1566.  
  1567. var ul_t = dd_t.appendChild(document.createElement("ul"));
  1568. ul_t.className = "horizontal-list";
  1569. var li_t = ul_t.appendChild(document.createElement("li"));
  1570. const userAvatar = li_t.appendChild(document.createElement("div"));
  1571. userAvatar.className = "user-avatar";
  1572. userAvatar.img = userAvatar.appendChild(document.createElement("img"));
  1573. userAvatar.img.className = "avatar-img";
  1574.  
  1575. var li_t = ul_t.appendChild(document.createElement("li"));
  1576. const userName = li_t.appendChild(document.createElement("div"));
  1577. userName.className = "user-name";
  1578. const userAccount = li_t.appendChild(document.createElement("div"));
  1579. userAccount.className = "user-account";
  1580.  
  1581. var li_t = ul_t.appendChild(document.createElement("li"));
  1582. //登陆/退出
  1583. const btnLogin = li_t.appendChild(document.createElement("button"));
  1584. btnLogin.className = "pubd-tologin";
  1585. btnLogin.onclick = function(){
  1586. if (dlg.frmLogin.classList.contains("logged-in"))
  1587. {
  1588. //退出
  1589. pubd.oAuth = new oAuth2();
  1590. pubd.oAuth.save();
  1591.  
  1592. dlg.refreshLoginState();
  1593. }else
  1594. {
  1595. //登陆
  1596. pubd.dialog.login.show(
  1597. (document.body.clientWidth - 370)/2,
  1598. window.pageYOffset+200
  1599. );
  1600. }
  1601. }
  1602.  
  1603. const tokenInfo = dlg.tokenInfo = dl_t.appendChild(document.createElement("dd"));
  1604. tokenInfo.className = "pubd-token-info";
  1605.  
  1606. const progress = dlg.tokenExpires = tokenInfo.appendChild(buildProgressToken());
  1607.  
  1608. const btnRefresh = tokenInfo.appendChild(document.createElement("button"));
  1609. btnRefresh.className = "pubd-open-refresh-token";
  1610. btnRefresh.appendChild(document.createTextNode("刷新许可"));
  1611. btnRefresh.onclick = function() {
  1612. //刷新许可
  1613. pubd.dialog.refresh_token.show(
  1614. (document.body.clientWidth - 370)/2,
  1615. window.pageYOffset+300
  1616. );
  1617. };
  1618.  
  1619. dlg.refreshLoginState = function() {
  1620. if (!pubd.oAuth) return;
  1621. const auth_data = pubd.oAuth.auth_data;
  1622. if (auth_data)
  1623. {
  1624. userAvatar.img.src = auth_data.user.profile_image_urls.px_50x50;
  1625. userAvatar.img.alt = userAvatar.title = auth_data.user.name;
  1626. userName.textContent = auth_data.user.name;
  1627. userAccount.textContent = auth_data.user.account;
  1628. btnLogin.textContent = "退出";
  1629. progress.start_token_animate();
  1630. btnRefresh.disabled = false;
  1631. dlg.frmLogin.classList.add("logged-in");
  1632. }else
  1633. {
  1634. userAvatar.img.src = "";
  1635. userAvatar.img.alt = userAvatar.title = "";
  1636. userName.textContent = "未登录(不可用)";
  1637. userAccount.textContent = "Not logged in";
  1638. btnLogin.textContent = "登陆";
  1639. progress.token_animate();
  1640. progress.stop_token_animate();
  1641. btnRefresh.disabled = true;
  1642. dlg.frmLogin.classList.remove("logged-in");
  1643. }
  1644. }
  1645.  
  1646. //“通用分析选项”窗口选项
  1647. var dt = document.createElement("dt");
  1648. dl.appendChild(dt);
  1649. var dd = document.createElement("dd");
  1650.  
  1651. var frm = new Frame("通用分析选项", "pubd-commonanalyseoptions");
  1652. var chk_getugoiraframe = new LabelInput("获取动图帧数", "pubd-getugoiraframe", "pubd-getugoiraframe", "checkbox", "1", true);
  1653. dlg.getugoiraframe = chk_getugoiraframe.input;
  1654.  
  1655. frm.content.appendChild(chk_getugoiraframe);
  1656. dd.appendChild(frm);
  1657. dl.appendChild(dd);
  1658.  
  1659. //“下载该画师”窗口选项
  1660. var dt = document.createElement("dt");
  1661. dl.appendChild(dt);
  1662. var dd = document.createElement("dd");
  1663.  
  1664. var frm = new Frame("下载窗口", "pubd-frm-downthis");
  1665. var chk_autoanalyse = new LabelInput("打开窗口自动获取数据", "pubd-autoanalyse", "pubd-autoanalyse", "checkbox", "1", true);
  1666. dlg.autoanalyse = chk_autoanalyse.input;
  1667. var chk_autodownload = new LabelInput("获取完成自动发送下载", "pubd-autodownload", "pubd-autodownload", "checkbox", "1", true);
  1668. dlg.autodownload = chk_autodownload.input;
  1669.  
  1670. frm.content.appendChild(chk_autoanalyse);
  1671. frm.content.appendChild(chk_autodownload);
  1672. dd.appendChild(frm);
  1673. dl.appendChild(dd);
  1674.  
  1675. //向Aria2的发送模式
  1676. var dt = dl.appendChild(document.createElement("dt"));
  1677. var dd = dl.appendChild(document.createElement("dd"));
  1678.  
  1679. var frm = dd.appendChild(new Frame("向Aria2逐项发送模式", "pubd-frm-termwisetype"));
  1680. var radio0 = frm.content.appendChild(new LabelInput("完全逐项(按图片)", "pubd-termwisetype", "pubd-termwisetype", "radio", "0", true));
  1681. var radio1 = frm.content.appendChild(new LabelInput("半逐项(按作品)", "pubd-termwisetype", "pubd-termwisetype", "radio", "1", true));
  1682. var radio2 = frm.content.appendChild(new LabelInput("不逐项(按作者)", "pubd-termwisetype", "pubd-termwisetype", "radio", "2", true));
  1683. dlg.termwiseType = [radio0.input, radio1.input, radio2.input];
  1684.  
  1685. //“发送完成后,点击通知”窗口选项
  1686. var dt = dl.appendChild(document.createElement("dt"));
  1687. var dd = dl.appendChild(document.createElement("dd"));
  1688.  
  1689. var frm = dd.appendChild(new Frame("发送完成通知", "pubd-frm-clicknotification"));
  1690. var radio0 = frm.content.appendChild(new LabelInput("点击通知什么也不做", "pubd-clicknotification", "pubd-clicknotification", "radio", "0", true));
  1691. var radio1 = frm.content.appendChild(new LabelInput("点击通知激活该窗口", "pubd-clicknotification", "pubd-clicknotification", "radio", "1", true));
  1692. var radio2 = frm.content.appendChild(new LabelInput("点击通知关闭该窗口", "pubd-clicknotification", "pubd-clicknotification", "radio", "2", true));
  1693. var radio3 = frm.content.appendChild(new LabelInput("通知自动消失关闭该窗口", "pubd-clicknotification", "pubd-clicknotification", "radio", "3", true));
  1694. dlg.noticeType = [radio0.input, radio1.input, radio2.input, radio3.input];
  1695.  
  1696. //配置方案储存
  1697. dlg.schemes = null;
  1698. dlg.reloadSchemes = function() { //重新读取所有下载方案
  1699. if (dlg.schemes.length < 1) {
  1700. alert("目前本程序没有任何下载方案,需要正常使用请先新建方案。");
  1701. }
  1702. dlg.downSchemeDom.options.length = 0;
  1703. dlg.schemes.forEach(function(item, index) {
  1704. dlg.downSchemeDom.add(item.name, index);
  1705. });
  1706. if (dlg.downSchemeDom.options.length > 0)
  1707. dlg.selectScheme(0);
  1708. };
  1709. dlg.loadScheme = function(scheme) { //读取一个下载方案
  1710. if (scheme == undefined) {
  1711. dlg.rpcurl.value = "";
  1712. dlg.proxyurl.value = "";
  1713. dlg.https2http.checked = false;
  1714. dlg.downfilter.value = "";
  1715. dlg.savedir.value = "";
  1716. dlg.savepath.value = "";
  1717. dlg.textout.value = "";
  1718. dlg.loadMasklistFromArray([]);
  1719. } else {
  1720. dlg.rpcurl.value = scheme.rpcurl;
  1721. dlg.proxyurl.value = scheme.proxyurl;
  1722. dlg.https2http.checked = scheme.https2http;
  1723. dlg.downfilter.value = scheme.downfilter;
  1724. dlg.savedir.value = scheme.savedir;
  1725. dlg.savepath.value = scheme.savepath;
  1726. dlg.textout.value = scheme.textout;
  1727. dlg.loadMasklistFromArray(scheme.masklist);
  1728. }
  1729. };
  1730. dlg.addMask = function(name, logic, content, value) { //向掩码列表添加一个新的掩码
  1731. if (value == undefined)
  1732. value = dlg.masklist.options.length;
  1733. var text = name + " : " + logic + " : " + content;
  1734. var opt = new Option(text, value);
  1735. dlg.masklist.options.add(opt);
  1736. };
  1737. dlg.loadMask = function(mask) { //读取一个掩码到三个文本框,只是用来查看
  1738. dlg.mask_name.value = mask.name;
  1739. dlg.mask_logic.value = mask.logic;
  1740. dlg.mask_content.value = mask.content;
  1741. };
  1742. dlg.loadMasklistFromArray = function(masklist) { //从掩码数组重置掩码列表
  1743. dlg.masklist.length = 0;
  1744. masklist.forEach(function(item, index) {
  1745. dlg.addMask(item.name, item.logic, item.content, index);
  1746. });
  1747. };
  1748. //选择一个方案,同时读取设置
  1749. dlg.selectScheme = function(index) {
  1750. if (index == undefined) index = 0;
  1751. if (dlg.downSchemeDom.options.length < 1 || dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  1752. var scheme = dlg.schemes[index];
  1753. dlg.loadScheme(scheme);
  1754. dlg.downSchemeDom.selectedIndex = index;
  1755. };
  1756. //选择一个掩码,同时读取设置
  1757. dlg.selectMask = function(index) {
  1758. if (dlg.downSchemeDom.options.length < 1 || dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  1759. if (dlg.masklist.options.length < 1 || dlg.masklist.selectedOptions.length < 1) { return; }
  1760. var scheme = dlg.schemes[dlg.downSchemeDom.selectedIndex];
  1761. var mask = scheme.masklist[index];
  1762. dlg.loadMask(mask);
  1763. dlg.masklist.selectedIndex = index;
  1764. };
  1765.  
  1766. //配置方案选择
  1767. var dt = dl.appendChild(document.createElement("dt"));
  1768. dt.textContent = "默认下载方案";
  1769. var dd = dl.appendChild(document.createElement("dd"));
  1770. var slt = dlg.downSchemeDom = dd.appendChild(new Select("pubd-downscheme"));
  1771. slt.onchange = function() {
  1772. dlg.selectScheme(this.selectedIndex);
  1773. };
  1774.  
  1775. var ipt = dd.appendChild(document.createElement("input"));
  1776. ipt.type = "button";
  1777. ipt.className = "pubd-downscheme-new";
  1778. ipt.value = "新建";
  1779. ipt.onclick = function() {
  1780. var schemName = prompt("请输入方案名", "我的方案");
  1781. if (schemName)
  1782. {
  1783. var scheme = new DownScheme(schemName);
  1784. var length = dlg.schemes.push(scheme);
  1785. dlg.downSchemeDom.add(scheme.name, length - 1);
  1786. dlg.downSchemeDom.selectedIndex = length - 1;
  1787. dlg.loadScheme(scheme);
  1788. //dlg.reloadSchemes();
  1789. }
  1790. };
  1791.  
  1792. var ipt = dd.appendChild(document.createElement("input"));
  1793. ipt.type = "button";
  1794. ipt.className = "pubd-downscheme-remove";
  1795. ipt.value = "删除";
  1796. ipt.onclick = function() {
  1797. if (dlg.downSchemeDom.options.length < 1) { alert("已经没有方案了"); return; }
  1798. if (dlg.downSchemeDom.selectedOptions.length < 1) { alert("没有选中方案"); return; }
  1799. var index = dlg.downSchemeDom.selectedIndex;
  1800. var c = confirm("你确定要删除“" + dlg.schemes[index].name + "”方案吗?");
  1801. if (c)
  1802. {
  1803. var x = dlg.schemes.splice(index, 1);
  1804. x = null;
  1805. dlg.downSchemeDom.remove(index);
  1806. var index = dlg.downSchemeDom.selectedIndex;
  1807. if (index < 0) dlg.reloadSchemes(); //没有选中的,重置
  1808. else dlg.loadScheme(dlg.schemes[index]);
  1809. }
  1810. };
  1811.  
  1812. //配置方案详情设置
  1813. var dt = dl.appendChild(document.createElement("dt"));
  1814. var dd = dl.appendChild(document.createElement("dd"));
  1815. dd.className = "pubd-selectscheme-bar";
  1816.  
  1817. var frm = dd.appendChild(new Frame("当前方案设置", "pubd-selectscheme"));
  1818.  
  1819. var dl_ss = frm.content.appendChild(document.createElement("dl"));
  1820.  
  1821.  
  1822. //Aria2 URL
  1823.  
  1824. var dt = dl_ss.appendChild(document.createElement("dt"));
  1825. dt.textContent = "Aria2 JSON-RPC 路径";
  1826. var rpcchk = dlg.rpcchk = dt.appendChild(document.createElement("span")); //显示检查状态用
  1827. rpcchk.className = "pubd-rpcchk-info";
  1828. rpcchk.runing = false;
  1829. var dd = dl_ss.appendChild(document.createElement("dd"));
  1830. var rpcurl = dlg.rpcurl = dd.appendChild(document.createElement("input"));
  1831. rpcurl.type = "url";
  1832. rpcurl.className = "pubd-rpcurl";
  1833. rpcurl.name = "pubd-rpcurl";
  1834. rpcurl.id = rpcurl.name;
  1835. rpcurl.placeholder = "Aria2的信息接收路径";
  1836. rpcurl.onchange = function() {
  1837. dlg.rpcchk.innerHTML = "";
  1838. dlg.rpcchk.runing = false;
  1839. if (dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  1840. var schemeIndex = dlg.downSchemeDom.selectedIndex;
  1841. dlg.schemes[schemeIndex].rpcurl = rpcurl.value;
  1842. };
  1843.  
  1844. var ipt = dd.appendChild(document.createElement("input"));
  1845. ipt.type = "button";
  1846. ipt.className = "pubd-rpcchk";
  1847. ipt.value = "检查路径";
  1848. ipt.onclick = function() {
  1849. if (rpcchk.runing) return;
  1850. if (rpcurl.value.length < 1) {
  1851. rpcchk.textContent = "路径为空";
  1852. return;
  1853. }
  1854. rpcchk.textContent = "正在连接...";
  1855. rpcchk.runing = true;
  1856. var aria2 = new Aria2(rpcurl.value);
  1857. aria2.getVersion(function(rejo) {
  1858. if (rejo)
  1859. rpcchk.textContent = "发现Aria2 ver" + rejo.result.version;
  1860. else
  1861. rpcchk.textContent = "Aria2连接失败";
  1862. rpcchk.runing = false;
  1863. });
  1864. };
  1865. var dt = dl_ss.appendChild(document.createElement("dt"));
  1866. dt.textContent = "Aria2 代理服务器地址";
  1867. var dd = dl_ss.appendChild(document.createElement("dd"));
  1868. var proxyurl = dlg.proxyurl = dd.appendChild(document.createElement("input"));
  1869. proxyurl.type = "text";
  1870. proxyurl.className = "pubd-proxyurl";
  1871. proxyurl.name = "pubd-proxyurl";
  1872. proxyurl.id = proxyurl.name;
  1873. proxyurl.placeholder = "[http://][USER:PASSWORD@]HOST[:PORT]";
  1874. proxyurl.onchange = function() {
  1875. if (dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  1876. const schemeIndex = dlg.downSchemeDom.selectedIndex;
  1877. dlg.schemes[schemeIndex].proxyurl = this.value;
  1878. };
  1879.  
  1880. //额外设置,https转http
  1881. var dt = document.createElement("dt");
  1882. dl_ss.appendChild(dt);
  1883. var dd = document.createElement("dd");
  1884. var chk_https2http = new LabelInput("图片网址https转http", "pubd-https2http", "pubd-https2http", "checkbox", "1", true, "某些Linux没有正确安装新版OpenSSL,https的图片链接会下载失败。");
  1885. dlg.https2http = chk_https2http.input;
  1886. dlg.https2http.onchange = function() {
  1887. if (dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  1888. var schemeIndex = dlg.downSchemeDom.selectedIndex;
  1889. dlg.schemes[schemeIndex].https2http = this.checked;
  1890. };
  1891. dd.appendChild(chk_https2http);
  1892. dl_ss.appendChild(dd);
  1893.  
  1894. //下载过滤
  1895. var dt = dl_ss.appendChild(document.createElement("dt"));
  1896. dt.textContent = "下载过滤器";
  1897. var dta = dt.appendChild(document.createElement("a"));
  1898. dta.className = "pubd-help-link";
  1899. dta.textContent = "(?)";
  1900. dta.href = "https://github.com/Mapaler/PixivUserBatchDownload/wiki/%E4%B8%8B%E8%BD%BD%E8%BF%87%E6%BB%A4%E5%99%A8";
  1901. dta.target = "_blank";
  1902. var dd = document.createElement("dd");
  1903. var downfilter = document.createElement("input");
  1904. downfilter.type = "text";
  1905. downfilter.className = "pubd-downfilter";
  1906. downfilter.name = "pubd-downfilter";
  1907. downfilter.id = downfilter.name;
  1908. downfilter.placeholder = "符合条件的图片将不会被发送到Aria2";
  1909. downfilter.onchange = function() {
  1910. if (dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  1911. var schemeIndex = dlg.downSchemeDom.selectedIndex;
  1912. dlg.schemes[schemeIndex].downfilter = downfilter.value;
  1913. };
  1914. dlg.downfilter = downfilter;
  1915. dd.appendChild(downfilter);
  1916. dl_ss.appendChild(dd);
  1917.  
  1918. //下载目录
  1919. var dt = document.createElement("dt");
  1920. dl_ss.appendChild(dt);
  1921. dt.textContent = "下载目录";
  1922. var dd = document.createElement("dd");
  1923. var savedir = document.createElement("input");
  1924. savedir.type = "text";
  1925. savedir.className = "pubd-savedir";
  1926. savedir.name = "pubd-savedir";
  1927. savedir.id = savedir.name;
  1928. savedir.placeholder = "文件下载到的目录";
  1929. savedir.onchange = function() {
  1930. if (dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  1931. var schemeIndex = dlg.downSchemeDom.selectedIndex;
  1932. dlg.schemes[schemeIndex].savedir = savedir.value;
  1933. };
  1934. dlg.savedir = savedir;
  1935. dd.appendChild(savedir);
  1936. dl_ss.appendChild(dd);
  1937.  
  1938. //保存路径
  1939. var dt = dl_ss.appendChild(document.createElement("dt"));
  1940. dt.textContent = "保存路径";
  1941. var dta = dt.appendChild(document.createElement("a"));
  1942. dta.className = "pubd-help-link";
  1943. dta.textContent = "(?)";
  1944. dta.href = "https://github.com/Mapaler/PixivUserBatchDownload/wiki/%E6%8E%A9%E7%A0%81";
  1945. dta.target = "_blank";
  1946. var dd = document.createElement("dd");
  1947. var savepath = document.createElement("input");
  1948. savepath.type = "text";
  1949. savepath.className = "pubd-savepath";
  1950. savepath.name = "pubd-savepath";
  1951. savepath.id = savepath.name;
  1952. savepath.placeholder = "分组保存的文件夹和文件名";
  1953. savepath.onchange = function() {
  1954. if (dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  1955. var schemeIndex = dlg.downSchemeDom.selectedIndex;
  1956. dlg.schemes[schemeIndex].savepath = savepath.value;
  1957. };
  1958. dlg.savepath = savepath;
  1959. dd.appendChild(savepath);
  1960. dl_ss.appendChild(dd);
  1961.  
  1962. //输出文本
  1963. var dt = dl_ss.appendChild(document.createElement("dt"));
  1964. dt.textContent = "文本输出模式格式";
  1965. var dta = dt.appendChild(document.createElement("a"));
  1966. dta.className = "pubd-help-link";
  1967. dta.textContent = "(?)";
  1968. dta.href = "https://github.com/Mapaler/PixivUserBatchDownload/wiki/%e9%80%89%e9%a1%b9%e7%aa%97%e5%8f%a3#%E6%96%87%E6%9C%AC%E8%BE%93%E5%87%BA%E6%A8%A1%E5%BC%8F%E6%A0%BC%E5%BC%8F";
  1969. dta.target = "_blank";
  1970. var dd = document.createElement("dd");
  1971. dd.className = "pubd-textout-bar";
  1972. var textout = document.createElement("textarea");
  1973. textout.className = "pubd-textout";
  1974. textout.name = "pubd-textout";
  1975. textout.id = textout.name;
  1976. textout.placeholder = "直接输出文本信息时的格式";
  1977. textout.wrap = "off";
  1978. textout.onchange = function() {
  1979. if (dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  1980. var schemeIndex = dlg.downSchemeDom.selectedIndex;
  1981. dlg.schemes[schemeIndex].textout = textout.value;
  1982. };
  1983. dlg.textout = textout;
  1984. dd.appendChild(textout);
  1985. dl_ss.appendChild(dd);
  1986.  
  1987.  
  1988. //自定义掩码
  1989. var dt = dl_ss.appendChild(document.createElement("dt"));
  1990. dt.textContent = "自定义掩码";
  1991. var dta = dt.appendChild(document.createElement("a"));
  1992. dta.className = "pubd-help-link";
  1993. dta.textContent = "(?)";
  1994. dta.href = "https://github.com/Mapaler/PixivUserBatchDownload/wiki/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%A9%E7%A0%81";
  1995. dta.target = "_blank";
  1996. var dd = document.createElement("dd");
  1997. dl_ss.appendChild(dd);
  1998. //▼掩码名
  1999. var ipt = document.createElement("input");
  2000. ipt.type = "text";
  2001. ipt.className = "pubd-mask-name";
  2002. ipt.name = "pubd-mask-name";
  2003. ipt.id = ipt.name;
  2004. ipt.placeholder = "自定义掩码名";
  2005. dlg.mask_name = ipt;
  2006. dd.appendChild(ipt);
  2007. //▲掩码名
  2008. //▼执行条件
  2009. var ipt = document.createElement("input");
  2010. ipt.type = "text";
  2011. ipt.className = "pubd-mask-logic";
  2012. ipt.name = "pubd-mask-logic";
  2013. ipt.id = ipt.name;
  2014. ipt.placeholder = "执行条件";
  2015. dlg.mask_logic = ipt;
  2016. dd.appendChild(ipt);
  2017. //▲执行条件
  2018. var ipt = document.createElement("input");
  2019. ipt.type = "button";
  2020. ipt.className = "pubd-mask-add";
  2021. ipt.value = "+";
  2022. ipt.onclick = function() { //增加自定义掩码
  2023. if (dlg.downSchemeDom.selectedOptions.length < 1) { alert("没有选中下载方案"); return; }
  2024. if (dlg.mask_name.value.length < 1) { alert("掩码名称为空"); return; }
  2025. if (dlg.mask_logic.value.length < 1) { alert("执行条件为空"); return; }
  2026. if (dlg.mask_content.value.includes("%{" + dlg.mask_logic.value + "}")) { alert("该掩码调用自身,会形成死循环。"); return; }
  2027. var schemeIndex = dlg.downSchemeDom.selectedIndex;
  2028. dlg.schemes[schemeIndex].maskAdd(dlg.mask_name.value, dlg.mask_logic.value, dlg.mask_content.value);
  2029. dlg.addMask(dlg.mask_name.value, dlg.mask_logic.value, dlg.mask_content.value);
  2030. dlg.mask_name.value = dlg.mask_logic.value = dlg.mask_content.value = "";
  2031. };
  2032. dd.appendChild(ipt);
  2033. var mask_remove = document.createElement("input");
  2034. mask_remove.type = "button";
  2035. mask_remove.className = "pubd-mask-remove";
  2036. mask_remove.value = "-";
  2037. mask_remove.onclick = function() { //删除自定义掩码
  2038. if (dlg.downSchemeDom.selectedOptions.length < 1) { alert("没有选中下载方案"); return; }
  2039. if (dlg.masklist.options.length < 1) { alert("已经没有掩码了"); return; }
  2040. if (dlg.masklist.selectedOptions.length < 1) { alert("没有选中掩码"); return; }
  2041. var schemeIndex = dlg.downSchemeDom.selectedIndex;
  2042. var maskIndex = dlg.masklist.selectedIndex;
  2043. dlg.schemes[schemeIndex].maskRemove(maskIndex);
  2044. dlg.masklist.remove(maskIndex);
  2045. for (var mi = maskIndex; mi < dlg.masklist.options.length; mi++) {
  2046. dlg.masklist.options[mi].value = mi;
  2047. }
  2048. };
  2049. dd.appendChild(mask_remove);
  2050.  
  2051. //▼掩码内容
  2052. var ipt = document.createElement("input");
  2053. ipt.type = "text";
  2054. ipt.className = "pubd-mask-content";
  2055. ipt.name = "pubd-mask-content";
  2056. ipt.id = ipt.name;
  2057. ipt.placeholder = "掩码内容";
  2058. dlg.mask_content = ipt;
  2059. dd.appendChild(ipt);
  2060. //▲掩码内容
  2061. dl_ss.appendChild(dd);
  2062.  
  2063. //▼掩码列表
  2064. var dd = document.createElement("dd");
  2065. dd.className = "pubd-mask-list-bar";
  2066. var masklist = new Select("pubd-mask-list", "pubd-mask-list");
  2067. masklist.size = 5;
  2068. masklist.onchange = function() { //读取选中的掩码
  2069. dlg.selectMask(this.selectedIndex);
  2070. };
  2071. dlg.masklist = masklist;
  2072. dd.appendChild(masklist);
  2073. //▲掩码列表
  2074. dl_ss.appendChild(dd);
  2075.  
  2076. //保存按钮栏
  2077. var dt = document.createElement("dt");
  2078. dl.appendChild(dt);
  2079. var dd = document.createElement("dd");
  2080. dd.className = "pubd-config-savebar";
  2081. var ipt = document.createElement("input");
  2082. ipt.type = "button";
  2083. ipt.className = "pubd-reset";
  2084. ipt.value = "清空选项";
  2085. ipt.onclick = function() {
  2086. if (confirm("您确定要将PUBD保存的所有设置,以及方案全部删除吗?\n(⚠️不可恢复)")==true){
  2087. dlg.reset();
  2088. return true;
  2089. }else{
  2090. return false;
  2091. }
  2092. };
  2093. dd.appendChild(ipt);
  2094. var ipt = document.createElement("input");
  2095. ipt.type = "button";
  2096. ipt.className = "pubd-save";
  2097. ipt.value = "保存选项";
  2098. ipt.onclick = function() {
  2099. dlg.save();
  2100. };
  2101. dd.appendChild(ipt);
  2102. dl.appendChild(dd);
  2103.  
  2104. //保存设置函数
  2105. dlg.save = function() {
  2106.  
  2107. //作品发送完成后,如何处理通知
  2108. var noticeType = 0;
  2109. dlg.noticeType.some(function(item){
  2110. if (item.checked) noticeType = parseInt(item.value);
  2111. return item.checked;
  2112. });
  2113. //逐项发送模式
  2114. var termwiseType = 2;
  2115. dlg.termwiseType.some(function(item){
  2116. if (item.checked) termwiseType = parseInt(item.value);
  2117. return item.checked;
  2118. });
  2119.  
  2120. GM_setValue("pubd-getugoiraframe", dlg.getugoiraframe.checked); //获取动图帧数
  2121. GM_setValue("pubd-autoanalyse", dlg.autoanalyse.checked); //自动分析
  2122. GM_setValue("pubd-autodownload", dlg.autodownload.checked); //自动下载
  2123. GM_setValue("pubd-noticeType", noticeType); //处理通知
  2124. GM_setValue("pubd-termwiseType", termwiseType); //逐项发送
  2125. GM_setValue("pubd-downschemes", dlg.schemes); //下载方案
  2126. GM_setValue("pubd-defaultscheme", dlg.downSchemeDom.selectedIndex); //默认方案
  2127. GM_setValue("pubd-configversion", pubd.configVersion); //设置版本
  2128.  
  2129. GM_notification({text:"设置已保存", title:scriptName, image:scriptIcon});
  2130. pubd.downSchemes = NewDownSchemeArrayFromJson(dlg.schemes);
  2131. pubd.dialog.downthis.reloadSchemes();
  2132. pubd.dialog.downillust.reloadSchemes();
  2133. };
  2134. //重置设置函数
  2135. dlg.reset = function() {
  2136. GM_deleteValue("pubd-auth"); //登陆相关信息
  2137. GM_deleteValue("pubd-getugoiraframe"); //获取动图帧数
  2138. GM_deleteValue("pubd-autoanalyse"); //自动分析
  2139. GM_deleteValue("pubd-autodownload"); //自动下载
  2140. GM_deleteValue("pubd-noticeType"); //处理通知
  2141. GM_deleteValue("pubd-termwiseType"); //逐项发送
  2142. GM_deleteValue("pubd-downschemes"); //下载方案
  2143. GM_deleteValue("pubd-defaultscheme"); //默认方案
  2144. GM_deleteValue("pubd-configversion"); //设置版本
  2145. GM_notification({text:"已清空重置设置", title:scriptName, image:scriptIcon});
  2146. };
  2147. //窗口关闭
  2148. dlg.close = function() {
  2149. progress.stop_token_animate();
  2150. };
  2151. //关闭窗口按钮
  2152. dlg.cptBtns.close.addEventListener("click", dlg.close);
  2153. //窗口初始化
  2154. dlg.initialise = function() {
  2155.  
  2156. dlg.getugoiraframe.checked = getValueDefault("pubd-getugoiraframe", true);
  2157. dlg.autoanalyse.checked = getValueDefault("pubd-autoanalyse", false);
  2158. dlg.autodownload.checked = getValueDefault("pubd-autodownload", false);
  2159. (dlg.noticeType[parseInt(getValueDefault("pubd-noticeType", 0))] || dlg.noticeType[0]).checked = true;
  2160. (dlg.termwiseType[parseInt(getValueDefault("pubd-termwiseType", 2))] || dlg.termwiseType[2]).checked = true;
  2161.  
  2162. dlg.schemes = NewDownSchemeArrayFromJson(pubd.downSchemes);
  2163. dlg.reloadSchemes();
  2164. dlg.selectScheme(getValueDefault("pubd-defaultscheme", 0));
  2165. dlg.refreshLoginState();
  2166. };
  2167. return dlg;
  2168. }
  2169.  
  2170. //构建登陆对话框
  2171. function buildDlgLogin() {
  2172. const dlg = new Dialog("登陆账户", "pubd-login", "pubd-login");
  2173. dlg.newAuth = null;
  2174.  
  2175. var frm = dlg.content.appendChild(new Frame("1.做好获取 APP 登陆连接的准备", "pubd-auth-help"));
  2176. const aHelp = frm.content.appendChild(document.createElement("a"));
  2177. aHelp.appendChild(document.createTextNode("如何获取 APP 登陆连接?"));
  2178. aHelp.target = "_blank";
  2179. aHelp.href = "https://github.com/Mapaler/PixivUserBatchDownload/wiki/%E8%8E%B7%E5%8F%96APP%E7%99%BB%E9%99%86%E9%93%BE%E6%8E%A5";
  2180.  
  2181. var frm = dlg.content.appendChild(new Frame("2.进行官方 APP 登录(不可用)", "pubd-auth-weblogin"));
  2182. const aLogin = frm.content.appendChild(document.createElement("a"));
  2183. aLogin.appendChild(document.createTextNode("访问官方登陆页面"));
  2184. aLogin.className = "pubd-login-official-link";
  2185. aLogin.target = "_blank";
  2186.  
  2187. var frm = dlg.content.appendChild(new Frame("3.填写 APP 登陆连接", "pubd-auth-applogin"));
  2188. dlg.content.appendChild(frm);
  2189.  
  2190. var div = frm.content.appendChild(document.createElement("div"));
  2191. const pixivLink = div.appendChild(document.createElement("input"));
  2192. pixivLink.type = "url";
  2193. pixivLink.className = "pubd-pixiv-app-link";
  2194. pixivLink.placeholder = "例如:pixiv://account/login?code=xxxxxx&via=login";
  2195.  
  2196. const btnLogin = div.appendChild(document.createElement("button"));
  2197. btnLogin.className = "pubd-login-auth";
  2198. btnLogin.appendChild(document.createTextNode("登陆"));
  2199. //登陆按钮
  2200. btnLogin.onclick = function() {
  2201. if (/^pixiv:\/\//i.test(pixivLink.value))
  2202. {
  2203. const loginLink = new URL(pixivLink.value);
  2204. const authorization_code = loginLink.searchParams.get("code");
  2205. if (authorization_code)
  2206. {
  2207. //使用token登陆
  2208. dlg.error.replace("登陆中···");
  2209. const options = {
  2210. onload:function(jore) { //onload_suceess_Cb
  2211. dlg.error.replace("登陆成功");
  2212. dlg.newOAuth.save(); //保存新的认证
  2213. pubd.oAuth = dlg.newOAuth; //使用新的认证替换原来的认证
  2214. pubd.dialog.config.refreshLoginState();
  2215. },
  2216. onload_hasError:function(jore) { //onload_haserror_Cb //返回错误消息
  2217. dlg.error.replace(["错误代码:" + jore.errors.system.code, jore.errors.system.message]);
  2218. },
  2219. onload_notJson:function(re) { //onload_notjson_Cb //返回不是JSON
  2220. dlg.error.replace(["服务器返回不是 JSON 格式", re]);
  2221. },
  2222. onerror:function(re) { //onerror_Cb //网络请求发生错误
  2223. dlg.error.replace("网络请求发生错误");
  2224. },
  2225. }
  2226. dlg.newOAuth.login(authorization_code, options);
  2227. }else
  2228. {
  2229. alert("PUBD:登陆链接中未找到 code");
  2230. }
  2231. }else
  2232. {
  2233. alert("PUBD:输入的链接格式不正确");
  2234. }
  2235. };
  2236.  
  2237. //错误信息
  2238. dlg.error = dlg.content.appendChild(new ErrorMsg());
  2239.  
  2240. dlg.content.appendChild(document.createElement("hr"));
  2241.  
  2242. var frm = dlg.content.appendChild(new Frame("使用现有刷新许可证登陆", "pubd-refresh_token-login"));
  2243. dlg.content.appendChild(frm);
  2244.  
  2245. var div = frm.content.appendChild(document.createElement("div"));
  2246. const iptRefreshToken = div.appendChild(document.createElement("input"));
  2247. iptRefreshToken.type = "text";
  2248. iptRefreshToken.className = "pubd-refresh-token";
  2249. iptRefreshToken.placeholder = "refresh_token";
  2250.  
  2251. const btnRefreshToken = div.appendChild(document.createElement("button"));
  2252. btnRefreshToken.className = "pubd-login-refresh_token";
  2253. btnRefreshToken.appendChild(document.createTextNode("登陆"));
  2254. //登陆按钮
  2255. btnRefreshToken.onclick = function() {
  2256. if (!pubd.oAuth.auth_data)
  2257. {
  2258. pubd.oAuth.auth_data = {};
  2259. }
  2260. pubd.oAuth.auth_data.refresh_token = iptRefreshToken.value;
  2261. //刷新许可
  2262. pubd.dialog.refresh_token.show(
  2263. (document.body.clientWidth - 370)/2,
  2264. window.pageYOffset+300
  2265. );
  2266. };
  2267.  
  2268. //窗口初始化
  2269. dlg.initialise = function() {
  2270. this.error.clear();
  2271.  
  2272. //每次打开这个窗口,都创建一个新的认证
  2273. this.newOAuth = new oAuth2();
  2274. aLogin.href = this.newOAuth.get_login_url();
  2275. };
  2276. return dlg;
  2277. }
  2278.  
  2279. //构建token刷新对话框
  2280. function buildDlgRefreshToken() {
  2281. const dlg = new Dialog("刷新许可", "pubd-refresh-token pubd-dialog-transparent", "pubd-refresh-token");
  2282.  
  2283. //Logo部分
  2284. const logo_box = dlg.content.appendChild(document.createElement("div"));
  2285. logo_box.className = "logo-box";
  2286. const logo = logo_box.appendChild(document.createElement("img"));
  2287. logo.className = "pixiv-logo";
  2288. logo.src = "https://s.pximg.net/accounts/assets/6bea8becc71d27cd20649ffbc047e456.svg";
  2289. logo.alt = "pixiv logo";
  2290.  
  2291. const progress = dlg.tokenExpires = dlg.content.appendChild(buildProgressToken());
  2292.  
  2293. const lblRefreshToken = dlg.content.appendChild(document.createElement("label"));
  2294. lblRefreshToken.textContent = "刷新用许可证代码(refresh_token)";
  2295. const iptRefreshToken = lblRefreshToken.appendChild(document.createElement("input"));
  2296. iptRefreshToken.className = "pubd-refresh-token";
  2297. iptRefreshToken.type = "text";
  2298. iptRefreshToken.readOnly = true;
  2299.  
  2300. //错误信息
  2301. dlg.error = dlg.content.appendChild(new ErrorMsg());
  2302.  
  2303. //窗口关闭
  2304. dlg.close = function() {
  2305. progress.stop_token_animate();
  2306. };
  2307. //关闭窗口按钮
  2308. dlg.cptBtns.close.addEventListener("click", dlg.close);
  2309.  
  2310. //窗口初始化
  2311. dlg.initialise = function(arg = {}) {
  2312. this.error.clear();
  2313. iptRefreshToken.value = pubd.oAuth.auth_data.refresh_token;
  2314. progress.start_token_animate();
  2315. dlg.error.replace("刷新许可中···");
  2316. const options = {
  2317. onload:function(jore) { //onload_suceess_Cb
  2318. pubd.oAuth.save();
  2319. dlg.error.replace("成功更新");
  2320. iptRefreshToken.value = jore.refresh_token;
  2321. progress.start_token_animate();
  2322. pubd.dialog.config.refreshLoginState();
  2323. if (arg.onload) arg.onload(jore);
  2324. },
  2325. onload_hasError:function(jore) { //onload_haserror_Cb //返回错误消息
  2326. dlg.error.replace(["错误代码:" + jore.errors.system.code, jore.errors.system.message]);
  2327. if (arg.onload_hasError) arg.onload_hasError(jore);
  2328. },
  2329. onload_notJson:function(re) { //onload_notjson_Cb //返回不是JSON
  2330. dlg.error.replace(["服务器返回不是 JSON 格式", re]);
  2331. if (arg.onload_notJson) arg.onload_notJson(re);
  2332. },
  2333. onerror:function(re) { //onerror_Cb //网络请求发生错误
  2334. dlg.error.replace("网络请求发生错误");
  2335. if (arg.onerror) arg.onerror(re);
  2336. },
  2337. }
  2338. pubd.oAuth.refresh_token(options);
  2339. };
  2340. return dlg;
  2341. }
  2342.  
  2343. //构建通用下载对话框
  2344. function buildDlgDown(caption, classname, id) {
  2345. var dlg = new Dialog(caption, classname, id);
  2346.  
  2347. var dl = dlg.content.appendChild(document.createElement("dl"));
  2348.  
  2349. var dt = document.createElement("dt");
  2350. dl.appendChild(dt);
  2351. dt.innerHTML = ""; //用户头像等信息
  2352. var dd = document.createElement("dd");
  2353. dlg.infoCard = new InfoCard(); //创建信息卡
  2354. dd.appendChild(dlg.infoCard.dom);
  2355. dl.appendChild(dd);
  2356.  
  2357. var dt = document.createElement("dt");
  2358. dl.appendChild(dt);
  2359. dt.innerHTML = "进程日志";
  2360.  
  2361. var dd = document.createElement("dd");
  2362. var ipt = document.createElement("textarea");
  2363. ipt.readOnly = true;
  2364. ipt.className = "pubd-down-log";
  2365. ipt.wrap = "off";
  2366. dlg.logTextarea = ipt;
  2367. dd.appendChild(ipt);
  2368. dl.appendChild(dd);
  2369.  
  2370. //下载方案
  2371. dlg.schemes = null;
  2372.  
  2373. dlg.reloadSchemes = function() { //重新读取所有下载方案
  2374. dlg.schemes = pubd.downSchemes;
  2375.  
  2376. dlg.downSchemeDom.options.length = 0;
  2377. dlg.schemes.forEach(function(item, index) {
  2378. dlg.downSchemeDom.add(item.name, index);
  2379. });
  2380. if (getValueDefault("pubd-defaultscheme",0) >= 0)
  2381. dlg.selectScheme(getValueDefault("pubd-defaultscheme",0));
  2382. else if (dlg.downSchemeDom.options.length > 0)
  2383. dlg.selectScheme(0);
  2384. };
  2385.  
  2386. //选择一个方案,同时读取设置
  2387. dlg.selectScheme = function(index) {
  2388. if (index == undefined) index = 0;
  2389. if (dlg.downSchemeDom.options.length < 1 || dlg.downSchemeDom.selectedOptions.length < 1) { return; }
  2390. dlg.downSchemeDom.selectedIndex = index;
  2391. };
  2392.  
  2393. var dt = document.createElement("dt");
  2394. dl.appendChild(dt);
  2395. dt.innerHTML = "选择下载方案";
  2396. var dd = document.createElement("dd");
  2397. var slt = new Select("pubd-downscheme");
  2398. dlg.downSchemeDom = slt;
  2399. dd.appendChild(slt);
  2400. dl.appendChild(dd);
  2401.  
  2402. //下载按钮栏
  2403. var dt = document.createElement("dt");
  2404. dl.appendChild(dt);
  2405. var dd = document.createElement("dd");
  2406. dd.className = "pubd-downthis-downbar";
  2407.  
  2408. var textdown = document.createElement("input");
  2409. textdown.type = "button";
  2410. textdown.className = "pubd-textdown";
  2411. textdown.value = "输出\n文本";
  2412. textdown.onclick = function(event) {
  2413. dlg.textdownload(event);
  2414. };
  2415. textdown.disabled = true;
  2416. dlg.textdown = textdown;
  2417. dd.appendChild(textdown);
  2418.  
  2419. var startdown = document.createElement("input");
  2420. startdown.type = "button";
  2421. startdown.className = "pubd-startdown";
  2422. startdown.value = "发送到Aria2";
  2423. startdown.onclick = function() {
  2424. dlg.startdownload();
  2425. };
  2426. startdown.disabled = true;
  2427. dlg.startdown = startdown;
  2428. dd.appendChild(startdown);
  2429. dl.appendChild(dd);
  2430.  
  2431. //文本输出栏
  2432. var dt = document.createElement("dt");
  2433. dl.appendChild(dt);
  2434. var dd = document.createElement("dd");
  2435. dd.className = "pubd-down-textout-bar";
  2436. dl.appendChild(dd);
  2437.  
  2438. var ipt = document.createElement("textarea");
  2439. ipt.readOnly = true;
  2440. ipt.className = "pubd-down-textout display-none";
  2441. ipt.wrap = "off";
  2442. dlg.textoutTextarea = ipt;
  2443. dd.appendChild(ipt);
  2444.  
  2445. //显示日志相关
  2446. dlg.logArr = []; //用于储存一行一行的日志信息。
  2447. dlg.logClear = function() {
  2448. dlg.logArr.length = 0;
  2449. this.logTextarea.value = "";
  2450. };
  2451. dlg.log = function(text) {
  2452. dlg.logArr.push(text);
  2453. this.logTextarea.value = this.logArr.join("\n");
  2454. this.logTextarea.scrollTop = this.logTextarea.scrollHeight;
  2455. };
  2456.  
  2457. return dlg;
  2458. }
  2459.  
  2460. //构建当前画师下载对话框
  2461. function buildDlgDownThis(userid) {
  2462. //一个用户的信息
  2463. var UserInfo = function() {
  2464. this.done = false; //是否已完成用户信息获取
  2465. this.info = {
  2466. profile: null,
  2467. user: null,
  2468. };
  2469. this.illusts = new Works();
  2470. this.bookmarks = new Works();
  2471. };
  2472.  
  2473. var dlg = new buildDlgDown("下载当前画师", "pubd-down pubd-downthis", "pubd-downthis");
  2474. dlg.infoCard.infos = {"ID":userid};
  2475.  
  2476. dlg.user = new UserInfo();
  2477. dlg.works = null; //当前处理对象
  2478.  
  2479. var dt = document.createElement("dt");
  2480. var dd = document.createElement("dd");
  2481. dlg.infoCard.dom.insertAdjacentElement("afterend",dt);
  2482. dt.insertAdjacentElement("afterend",dd);
  2483.  
  2484. var frm = dd.appendChild(new Frame("下载内容"));
  2485. var radio1 = frm.content.appendChild(new LabelInput("他的作品", "pubd-down-content", "pubd-down-content", "radio", "0", true));
  2486. var radio2 = frm.content.appendChild(new LabelInput("他的收藏", "pubd-down-content", "pubd-down-content", "radio", "1", true));
  2487. dlg.dcType = [radio1.input, radio2.input];
  2488. radio1.input.onclick = function() { reAnalyse(this); };
  2489. radio2.input.onclick = function() { reAnalyse(this); };
  2490.  
  2491. function reAnalyse(radio) {
  2492. if (radio.checked == true) {
  2493. if (radio.value == 0)
  2494. dlg.user.bookmarks.break = true; //radio值为0,使收藏中断
  2495. else
  2496. dlg.user.illusts.break = true; //radio值为1,使作品中断
  2497.  
  2498. dlg.analyse(radio.value, dlg.infoCard.infos.ID);
  2499. }
  2500. }
  2501.  
  2502. var dt = document.createElement("dt");
  2503. dd.insertAdjacentElement("afterend",dt);
  2504. dt.innerHTML = "信息获取进度";
  2505. var dd = document.createElement("dd");
  2506. dt.insertAdjacentElement("afterend",dd);
  2507. var progress = new Progress();
  2508. dlg.progress = progress;
  2509. dd.appendChild(progress);
  2510.  
  2511. var btnBreak = document.createElement("input");
  2512. btnBreak.type = "button";
  2513. btnBreak.className = "pubd-breakdown";
  2514. btnBreak.value = "中断操作";
  2515. btnBreak.onclick = function() {
  2516. dlg.user.illusts.break = true; //使作品中断
  2517. dlg.user.bookmarks.break = true; //使收藏中断
  2518. pubd.downbreak = true; //使下载中断
  2519. };
  2520. dlg.logTextarea.parentNode.previousElementSibling.appendChild(btnBreak);
  2521.  
  2522. //分析
  2523. dlg.analyse = function(contentType, userid, callbackAfterAnalyse) {
  2524. if (!userid) {dlg.log("错误:没有用户ID。"); return;}
  2525. contentType = contentType == undefined ? 0 : parseInt(contentType);
  2526. var works = contentType == 0 ? dlg.user.illusts : dlg.user.bookmarks; //将需要分析的数据储存到works里
  2527. dlg.works = works;
  2528.  
  2529. if (works.runing) {
  2530. dlg.log("已经在进行分析操作了");
  2531. return;
  2532. }
  2533. works.break = false; //暂停flag为false
  2534. works.runing = true; //运行状态为true
  2535. pubd.ajaxTimes = 0; //ajax提交次数恢复为0
  2536.  
  2537. dlg.textdown.disabled = true; //禁用下载按钮
  2538. dlg.startdown.disabled = true; //禁用输出文本按钮
  2539. dlg.progress.set(0); //进度条归零
  2540. dlg.logClear(); //清空日志
  2541.  
  2542. //根据用户信息是否存在,决定分析用户还是图像
  2543. if (!dlg.user.done) {
  2544. startAnalyseUser(userid, contentType);
  2545. } else {
  2546. dlg.log("ID:" + userid + " 用户信息已存在");
  2547. startAnalyseWorks(dlg.user, contentType); //开始获取第一页
  2548. }
  2549.  
  2550. function startAnalyseUser(userid, contentType) {
  2551.  
  2552. dlg.log("开始获取ID为 " + userid + " 的用户信息");
  2553. ++pubd.ajaxTimes;
  2554. xhrGenneral(
  2555. "https://app-api.pixiv.net/v1/user/detail?user_id=" + userid,
  2556. function(jore) { //onload_suceess_Cb
  2557. works.runing = true;
  2558. dlg.user.done = true;
  2559. dlg.user.info = Object.assign(dlg.user.info, jore);
  2560.  
  2561. if (mdev)
  2562. {
  2563. const usersStore = db.transaction("users", "readwrite").objectStore("users");
  2564. let usersStoreRequest = usersStore.get(jore.user.id);
  2565. usersStoreRequest.onsuccess = function(event) {
  2566. // 获取我们想要更新的数据
  2567. let data = event.target.result;
  2568. if (data)
  2569. console.log("上次的头像",data.user.profile_image_urls);
  2570. if (!data || //没有老数据
  2571. !data.avatarBlob || //没有头像
  2572. data.user.profile_image_urls.medium != jore.user.profile_image_urls.medium //换了头像
  2573. )
  2574. {
  2575. console.debug("需要更新头像图片",jore.user.profile_image_urls);
  2576. GM_xmlhttpRequest({
  2577. url: jore.user.profile_image_urls.medium,
  2578. method: "get",
  2579. responseType: "blob",
  2580. headers: new HeadersObject(),
  2581. onload: function(response) {
  2582. console.info("用户头像Blob结果", response.response);
  2583. var obj_url = URL.createObjectURL(response.response);
  2584. var newImg = new Image();
  2585. newImg.src = obj_url;
  2586. URL.revokeObjectURL(obj_url);
  2587. document.body.appendChild(newImg);
  2588.  
  2589. var newData = data ? Object.assign(data,jore) : jore;
  2590. newData.avatarBlob = response.response;
  2591. // 把更新过的对象放回数据库
  2592. const usersStore = db.transaction("users", "readwrite").objectStore("users");
  2593. var requestUpdate = usersStore.put(newData);
  2594. requestUpdate.onerror = function(event) {// 错误处理
  2595. console.error(`${newData.user.name} 更新数据库头像发生错误`,newData);
  2596. };
  2597. requestUpdate.onsuccess = function(event) {// 完成,数据已更新!
  2598. console.debug(`${newData.user.name} ${data?"更新":"添加"}到头像用户数据库`,newData);
  2599. };
  2600. return;
  2601. },
  2602. onerror: function(response) {
  2603. console.error("抓取头像失败", response);
  2604. return;
  2605. }
  2606. });
  2607. }else
  2608. {
  2609. var newData = data ? Object.assign(data,jore) : jore;
  2610. // 把更新过的对象放回数据库
  2611. var requestUpdate = usersStore.put(newData);
  2612. requestUpdate.onerror = function(event) {// 错误处理
  2613. console.error(`${newData.user.name} 发生错误`,newData);
  2614. };
  2615. requestUpdate.onsuccess = function(event) {// 完成,数据已更新!
  2616. console.debug(`${newData.user.name} ${data?"更新":"添加"}到用户数据库`,newData);
  2617. };
  2618. }
  2619. };
  2620. usersStoreRequest.onerror = function(event) {// 错误处理
  2621. console.error(`${jore.user.name} 数据库里没有?`,jore);
  2622. };
  2623. }
  2624.  
  2625. dlg.infoCard.thumbnail = jore.user.profile_image_urls.medium;
  2626. dlg.infoCard.infos = Object.assign(dlg.infoCard.infos, {
  2627. "昵称": jore.user.name,
  2628. "作品投稿数": jore.profile.total_illusts + jore.profile.total_manga,
  2629. "公开收藏数": jore.profile.total_illust_bookmarks_public,
  2630. });
  2631. startAnalyseWorks(dlg.user, contentType); //分析完成后开始获取第一页
  2632. },
  2633. function(jore) { //onload_haserror_Cb //返回错误消息
  2634. works.runing = false;
  2635. dlg.log("错误信息:" + (jore.error.message || jore.error.user_message));
  2636. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  2637. dlg.startdown.disabled = false;
  2638. return;
  2639. },
  2640. function(re) { //onload_notjson_Cb //返回不是JSON
  2641. dlg.log("错误:返回不是JSON,或本程序异常");
  2642. works.runing = false;
  2643. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  2644. dlg.startdown.disabled = false;
  2645. },
  2646. function(re) { //onerror_Cb //网络请求发生错误
  2647. dlg.log("错误:网络请求发生错误");
  2648. works.runing = false;
  2649. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  2650. dlg.startdown.disabled = false;
  2651. },
  2652. function(str) { //dlog,推送错误消息
  2653. dlg.log(str);
  2654. return str;
  2655. }
  2656. );
  2657. }
  2658.  
  2659. //开始分析作品的前置操作
  2660. function startAnalyseWorks(user, contentType) {
  2661. var uInfo = user.info;
  2662. var works, total, contentName, apiurl;
  2663. //获取作品,contentType == 0,获取收藏,contentType == 1
  2664. if (contentType == 0) {
  2665. works = user.illusts;
  2666. total = uInfo.profile.total_illusts + uInfo.profile.total_manga;
  2667. contentName = "作品";
  2668. apiurl = "https://app-api.pixiv.net/v1/user/illusts?user_id=" + uInfo.user.id;
  2669. } else {
  2670. works = user.bookmarks;
  2671. total = uInfo.profile.total_illust_bookmarks_public;
  2672. contentName = "收藏";
  2673. apiurl = "https://app-api.pixiv.net/v1/user/bookmarks/illust?user_id=" + uInfo.user.id + "&restrict=public";
  2674. }
  2675. if (works.item.length > 0) { //断点续传
  2676. dlg.log(`${contentName} 断点续传进度 ${works.item.length}/${total}`);
  2677. dlg.progress.set(works.item.length / total); //设置当前下载进度
  2678. }
  2679. analyseWorks(user, contentType, apiurl); //开始获取第一页
  2680. }
  2681. //分析作品递归函数
  2682. function analyseWorks(user, contentType, apiurl) {
  2683. var uInfo = user.info;
  2684. var works, total, contentName;
  2685. if (contentType == 0) {
  2686. works = user.illusts;
  2687. total = uInfo.profile.total_illusts + uInfo.profile.total_manga;
  2688. contentName = "作品";
  2689. } else {
  2690. works = user.bookmarks;
  2691. total = uInfo.profile.total_illust_bookmarks_public;
  2692. contentName = "收藏";
  2693. }
  2694. if (works.done) {
  2695. //返回所有动图
  2696. var ugoiras = works.item.filter(function(item) {
  2697. return item.type == "ugoira";
  2698. });
  2699. dlg.log(`共存在 ${ugoiras.length} 件动图`);
  2700. if (ugoiras.some(function(item) { //如果有没有帧数据的动图
  2701. return item.ugoira_metadata == undefined;
  2702. })) {
  2703. if (!getValueDefault("pubd-getugoiraframe",true)) {
  2704. dlg.log("由于用户设置,跳过获取动图帧数。");
  2705. } else {
  2706. analyseUgoira(works, ugoiras, function() { //开始分析动图
  2707. analyseWorks(user, contentType, apiurl); //开始获取下一页
  2708. });
  2709. return;
  2710. }
  2711. }//没有动图则继续
  2712. if (works.item.length < total)
  2713. dlg.log("可能因为权限原因,无法获取到所有 " + contentName);
  2714.  
  2715. //计算一下总页数
  2716. works.picCount = works.item.reduce(function(pV,cItem){
  2717. var page = cItem.page_count;
  2718. if (cItem.type == "ugoira" && cItem.ugoira_metadata) //动图
  2719. {
  2720. page = cItem.ugoira_metadata.frames.length;
  2721. }
  2722. return pV+=page;
  2723. },0);
  2724.  
  2725. dlg.log(`${contentName} ${works.item.length} 件(约 ${works.picCount} 张图片)已获取完毕。`);
  2726. dlg.progress.set(1);
  2727. works.runing = false;
  2728. works.next_url = "";
  2729. dlg.textdown.disabled = false;
  2730. dlg.startdown.disabled = false;
  2731. if (callbackAfterAnalyse) callbackAfterAnalyse();
  2732. return;
  2733. }
  2734. if (works.break) {
  2735. dlg.log("检测到 " + contentName + " 中断进程命令");
  2736. works.break = false;
  2737. works.runing = false;
  2738. dlg.textdown.disabled = false; //启用按钮,中断暂停时,可以操作目前的进度。
  2739. dlg.startdown.disabled = false;
  2740. return;
  2741. }
  2742.  
  2743. setTimeout(()=>{
  2744. xhrGenneral(
  2745. apiurl,
  2746. function(jore) { //onload_suceess_Cb
  2747. works.runing = true;
  2748. var illusts = jore.illusts;
  2749. illusts.forEach(function(work) {
  2750. const original = work.page_count > 1 ?
  2751. work.meta_pages[0].image_urls.original : //漫画多图
  2752. work.meta_single_page.original_image_url; //单张图片或动图,含漫画单图
  2753.  
  2754. //取得解析后的网址
  2755. const parsedUrl = parseIllustUrl(original);
  2756. //合并到work里
  2757. Object.assign(work, parsedUrl);
  2758. if (parsedUrl.parsedURL.limited)
  2759. {
  2760. dlg.log(`${contentName} ${work.id} 非公开,无权获取下载地址。`);
  2761. }else if(parsedUrl.parsedURL.unknown)
  2762. {
  2763. dlg.log(`${contentName} ${work.id} 未知的原图网址格式。`);
  2764. }
  2765.  
  2766. works.item.push(work);
  2767.  
  2768. if (mdev)
  2769. {
  2770. const illustsStore = db.transaction("illusts", "readwrite").objectStore("illusts");
  2771. const illustsStoreRequest = illustsStore.put(work);
  2772. illustsStoreRequest.onsuccess = function(event) {
  2773. //console.debug(`${work.title} 已添加到作品数据库`);
  2774. };
  2775. }
  2776. });
  2777.  
  2778. dlg.log(`${contentName} 获取进度 ${works.item.length}/${total}`);
  2779. if (works == dlg.works) dlg.progress.set(works.item.length / total); //如果没有中断则设置当前下载进度
  2780. if (jore.next_url) { //还有下一页
  2781. works.next_url = jore.next_url;
  2782. } else { //没有下一页
  2783. works.done = true;
  2784. }
  2785. analyseWorks(user, contentType, jore.next_url); //开始获取下一页
  2786. },
  2787. function(jore) { //onload_haserror_Cb //返回错误消息
  2788. works.runing = false;
  2789. dlg.log("错误信息:" + (jore.error.message || jore.error.user_message));
  2790. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  2791. dlg.startdown.disabled = false;
  2792. return;
  2793. },
  2794. function(re) { //onload_notjson_Cb //返回不是JSON
  2795. dlg.log("错误:返回不是JSON,或本程序异常");
  2796. works.runing = false;
  2797. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  2798. dlg.startdown.disabled = false;
  2799. },
  2800. function(re) { //onerror_Cb //网络请求发生错误
  2801. dlg.log("错误:网络请求发生错误");
  2802. works.runing = false;
  2803. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  2804. dlg.startdown.disabled = false;
  2805. }
  2806. );
  2807. },pubd.ajaxTimes++ > startDelayAjaxTimes ? ajaxDelayDuration : 0);
  2808. }
  2809.  
  2810. function analyseUgoira(works, ugoirasItems, callback) {
  2811. var dealItems = ugoirasItems.filter(function(item) {
  2812. return (item.type == "ugoira" && item.ugoira_metadata == undefined);
  2813. });
  2814. if (dealItems.length < 1) {
  2815. dlg.log("动图获取完毕");
  2816. dlg.progress.set(1); //设置当前下载进度
  2817. callback();
  2818. return;
  2819. }
  2820. if (works.break) {
  2821. dlg.log("检测到中断进程命令");
  2822. works.break = false;
  2823. works.runing = false;
  2824. dlg.textdown.disabled = false; //中断暂停时,可以操作目前的进度。
  2825. dlg.startdown.disabled = false;
  2826. return;
  2827. }
  2828.  
  2829. var work = dealItems[0]; //当前处理的图
  2830.  
  2831. setTimeout(()=>{
  2832. if (pubd.ajaxTimes == startDelayAjaxTimes) dlg.log(`已提交超过 ${startDelayAjaxTimes} 次请求,为避免被P站限流,现在开始每次请求将间隔 ${ajaxDelayDuration/1000} 秒。`);
  2833. getUgoiraMeta(
  2834. work.id,
  2835. function(jore) { //onload_suceess_Cb
  2836. works.runing = true;
  2837. //var illusts = jore.illusts;
  2838. work = Object.assign(work, jore);
  2839.  
  2840. if (mdev)
  2841. {
  2842. const illustsStore = db.transaction("illusts", "readwrite").objectStore("illusts");
  2843. const illustsStoreRequest = illustsStore.put(work);
  2844. illustsStoreRequest.onsuccess = function(event) {
  2845. console.debug(`${work.title} 已更新动画帧数据到数据库`);
  2846. };
  2847. }
  2848.  
  2849. dlg.log("动图信息 获取进度 " + (ugoirasItems.length - dealItems.length + 1) + "/" + ugoirasItems.length);
  2850. dlg.progress.set(1 - dealItems.length / ugoirasItems.length); //设置当前下载进度
  2851. analyseUgoira(works, ugoirasItems, callback); //开始获取下一项
  2852. },
  2853. function(jore) { //onload_haserror_Cb //返回错误消息
  2854. if(work.restrict > 0) //非公共权限
  2855. { //添加一条空信息
  2856. work.ugoira_metadata = {
  2857. frames: [],
  2858. zip_urls: {
  2859. medium: "",
  2860. },
  2861. };
  2862. dlg.log("无访问权限,跳过本条。");
  2863. analyseUgoira(works, ugoirasItems, callback); //开始获取下一项
  2864. }else
  2865. {
  2866. works.runing = false;
  2867. dlg.log("错误信息:" + (jore.error.message || jore.error.user_message));
  2868. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  2869. dlg.startdown.disabled = false;
  2870. }
  2871. return;
  2872. },
  2873. function(re) { //onload_notjson_Cb //返回不是JSON
  2874. dlg.log("错误:返回不是JSON,或本程序异常");
  2875. works.runing = false;
  2876. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  2877. dlg.startdown.disabled = false;
  2878. },
  2879. function(re) { //onerror_Cb //网络请求发生错误
  2880. dlg.log("错误:网络请求发生错误");
  2881. works.runing = false;
  2882. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  2883. dlg.startdown.disabled = false;
  2884. }
  2885. );
  2886. },pubd.ajaxTimes++ > startDelayAjaxTimes ? ajaxDelayDuration : 0);
  2887. }
  2888. };
  2889. //输出文本按钮
  2890. dlg.textdownload = function(event) {
  2891. if (dlg.downSchemeDom.selectedOptions.length < 1) { alert("没有选中方案"); return; }
  2892. var scheme = dlg.schemes[dlg.downSchemeDom.selectedIndex];
  2893. var contentType = dlg.dcType[1].checked ? 1 : 0;
  2894. var userInfo = dlg.user.info;
  2895. var illustsItems = contentType == 0 ? dlg.user.illusts.item : dlg.user.bookmarks.item; //将需要分析的数据储存到works里
  2896. dlg.log("正在生成文本信息");
  2897.  
  2898. try {
  2899. var outTxtArr;
  2900. if (event.ctrlKey)
  2901. {
  2902. outTxtArr = showMask(scheme.textout, scheme.masklist, userInfo, null, 0);
  2903. }else
  2904. {
  2905. outTxtArr = illustsItems.map(function(illust) {
  2906. var page_count = illust.page_count;
  2907. if (illust.type == "ugoira" && illust.ugoira_metadata) //动图
  2908. {
  2909. page_count = illust.ugoira_metadata.frames.length;
  2910. }
  2911. var outArr = []; //输出内容
  2912. for (var pi = 0; pi < page_count; pi++) {
  2913. if (returnLogicValue(scheme.downfilter, userInfo, illust, pi) || new RegExp(limitingFilenamePattern, "ig").exec(illust.filename)) {
  2914. //跳过此次输出
  2915. continue;
  2916. }else{
  2917. outArr.push(showMask(scheme.textout, scheme.masklist, userInfo, illust, pi));
  2918. }
  2919. }
  2920. return outArr.join("");
  2921. }).join("");
  2922. }
  2923. dlg.textoutTextarea.value = outTxtArr;
  2924. dlg.textoutTextarea.classList.remove("display-none");
  2925. dlg.log("文本信息输出成功");
  2926. } catch (error) {
  2927. console.log(error);
  2928. }
  2929. };
  2930. //开始下载按钮
  2931. dlg.startdownload = function() {
  2932. dlg.textoutTextarea.classList.add("display-none");
  2933. if (dlg.downSchemeDom.selectedOptions.length < 1) { alert("没有选中方案"); return; }
  2934. var scheme = dlg.schemes[dlg.downSchemeDom.selectedIndex];
  2935. var contentType = dlg.dcType[1].checked ? 1 : 0;
  2936. var userInfo = dlg.user.info;
  2937. var works = (contentType == 0 ? dlg.user.illusts : dlg.user.bookmarks);
  2938. var illustsItems = works.item.concat(); //为了不改变原数组,新建一个数组
  2939.  
  2940. let termwiseType = parseInt(getValueDefault("pubd-termwiseType", 2));
  2941. if (works.picCount > changeTermwiseCount && termwiseType ==2)
  2942. {
  2943. dlg.log(`图片总数超过${changeTermwiseCount}张,自动切换为使用按作品逐项发送模式。`);
  2944. termwiseType = 1;
  2945. }
  2946. if (termwiseType == 0)
  2947. dlg.log("开始按图片逐项发送(约 "+works.picCount+" 次请求),⏳请耐心等待。");
  2948. else if (termwiseType == 1)
  2949. dlg.log("开始按作品逐项发送(约 "+illustsItems.length+" 次请求),⏳请耐心等待。");
  2950. else if (termwiseType == 2)
  2951. dlg.log("开始按作者发送,数据量较大时有较高延迟。\n⏳请耐心等待完成通知,勿多次点击。");
  2952. else
  2953. {
  2954. alert("错误:未知的逐项模式" + termwiseType);
  2955. console.error("PUBD:错误:未知的逐项模式:", termwiseType);
  2956. return;
  2957. }
  2958. var downP = { progress: dlg.progress, current: 0, max: 0 };
  2959. downP.max = works.picCount; //获取总需要下载发送的页数
  2960.  
  2961. var aria2 = new Aria2(scheme.rpcurl); //生成一个aria2对象
  2962. sendToAria2_illust(aria2, termwiseType, illustsItems, userInfo, scheme, downP, function() {
  2963. aria2 = null;
  2964. dlg.log("😄 " + userInfo.user.name + " 下载信息发送完毕");
  2965. var ntype = parseInt(getValueDefault("pubd-noticeType", 0)); //获取结束后如何处理通知
  2966. var bodyText = "" + userInfo.user.name + " 的相关插画已全部发送到指定的Aria2";
  2967. if (ntype == 1)
  2968. bodyText += "\n\n点击此通知 🔙返回 页面。";
  2969. else if (ntype == 2)
  2970. bodyText += "\n\n点击此通知 ❌关闭 页面。";
  2971. else if (ntype == 3)
  2972. bodyText += "\n\n通知结束时页面将 🅰️自动❌关闭。";
  2973. GM_notification(
  2974. {
  2975. text:bodyText,
  2976. title:"下载信息发送完毕",
  2977. image:userInfo.user.profile_image_urls.medium
  2978. },
  2979. function(){ //点击了通知
  2980. var ntype = parseInt(getValueDefault("pubd-noticeType", 0));
  2981. if (ntype == 1)
  2982. window.focus();
  2983. else if (ntype == 2)
  2984. window.close();
  2985. },
  2986. function(){ //关闭了通知
  2987. var ntype = parseInt(getValueDefault("pubd-noticeType", 0));
  2988. if (ntype == 3)
  2989. window.close();
  2990. }
  2991. );
  2992. });
  2993. };
  2994. //启动初始化
  2995. dlg.initialise = function(arg) {
  2996. var dcType = 0;
  2997. if (dlg.user.bookmarks.runing) //如果有程序正在运行,则覆盖设置。
  2998. dcType = 1;
  2999. else if (dlg.user.illusts.runing)
  3000. dcType = 0;
  3001. dlg.dcType[dcType].checked = true;
  3002.  
  3003. let uid = arg.id;
  3004. if (arg && arg.id>0) //提供了ID
  3005. {
  3006. if (arg.id != dlg.infoCard.infos.ID)
  3007. { //更换新的id
  3008. dlg.infoCard.thumbnail = "";
  3009. dlg.infoCard.infos = {"ID":arg.id}; //初始化窗口id
  3010. dlg.user = new UserInfo(); //重置用户数据
  3011. }
  3012. }else if(!dlg.infoCard.infos.ID) //没有ID
  3013. {
  3014. uid = parseInt(prompt("没有用户ID,请手动输入。", "ID缺失"),10);
  3015. dlg.infoCard.infos = {"ID":uid}; //初始化窗口id
  3016. }
  3017. if (getValueDefault("pubd-autoanalyse",false)) {
  3018.  
  3019. //开始自动分析的话,也自动添加到快速收藏
  3020. if (!pubd.fastStarList.has(userid)) { //不存在,则添加
  3021. pubd.fastStarList.add(uid);
  3022. pubd.start.star.classList.add("stars");
  3023. GM_setValue("pubd-faststar-list",pubd.fastStarList.exportArray());
  3024. console.debug(`已将 ${uid} 添加到快速收藏`);
  3025. }else if (mdev)
  3026. {
  3027. console.debug(`快速收藏中已存在 ${uid}`);
  3028. }
  3029.  
  3030. dlg.analyse(dcType, uid, function(){
  3031. if (getValueDefault("pubd-autodownload",false)) { //自动开始
  3032. dlg.log("🅰️自动开始发送");
  3033. dlg.startdownload();
  3034. }
  3035. });
  3036. }
  3037. dlg.reloadSchemes();
  3038. };
  3039.  
  3040. return dlg;
  3041. }
  3042.  
  3043. //构建当前作品下载对话框
  3044. function buildDlgDownIllust(illustid) {
  3045. var dlg = new buildDlgDown("下载当前作品", "pubd-down pubd-downillust", "pubd-downillust");
  3046. dlg.infoCard.infos = {"ID":illustid};
  3047. dlg.work = null; //当前处理对象
  3048.  
  3049. //分析
  3050. dlg.analyse = function(illustid,callbackAfterAnalyse) {
  3051. if (!illustid) {dlg.log("错误:没有作品ID。"); return;}
  3052.  
  3053. dlg.textdown.disabled = true; //禁用下载按钮
  3054. dlg.startdown.disabled = true; //禁用输出文本按钮
  3055. dlg.logClear(); //清空日志
  3056.  
  3057. if (dlg.work != undefined)
  3058. {
  3059. dlg.textdown.disabled = false;
  3060. dlg.startdown.disabled = false;
  3061. console.log("当前作品JSON数据:",dlg.work);
  3062. dlg.log("图片信息获取完毕");
  3063. if (callbackAfterAnalyse) callbackAfterAnalyse();
  3064. }else
  3065. {
  3066. dlg.log("开始获取作品信息");
  3067. analyseWork(illustid); //开始获取第一页
  3068. }
  3069.  
  3070. //分析作品递归函数
  3071. function analyseWork(illustid) {
  3072. xhrGenneral(
  3073. "https://app-api.pixiv.net/v1/illust/detail?illust_id=" + illustid,
  3074. function(jore) { //onload_suceess_Cb
  3075. var work = dlg.work = jore.illust;
  3076. const original = work.page_count > 1 ?
  3077. work.meta_pages[0].image_urls.original : //漫画多图
  3078. work.meta_single_page.original_image_url; //单张图片或动图,含漫画单图
  3079.  
  3080. //取得解析后的网址
  3081. const parsedUrl = parseIllustUrl(original);
  3082. //合并到work里
  3083. Object.assign(work, parsedUrl);
  3084. if (parsedUrl.parsedURL.limited)
  3085. {
  3086. dlg.log(`${contentName} ${work.id} 非公开,无权获取下载地址。`);
  3087. }else if(parsedUrl.parsedURL.unknown)
  3088. {
  3089. dlg.log(`${contentName} ${work.id} 未知的原图网址格式。`);
  3090. }
  3091. if (mdev)
  3092. {
  3093. const illustsStoreRequest = db.transaction("illusts", "readwrite").objectStore("illusts").put(work);
  3094. illustsStoreRequest.onsuccess = function(event) {
  3095. console.debug(`${work.title} 已添加到作品数据库`);
  3096. };
  3097. }
  3098.  
  3099. dlg.infoCard.thumbnail = work.image_urls.square_medium;
  3100. var iType = "插画";
  3101. if (work.type == "ugoira")
  3102. iType = "动画";
  3103. else if (work.type == "manga")
  3104. iType = "漫画";
  3105. if (work.page_count>1)
  3106. iType += "(多图)";
  3107.  
  3108. dlg.infoCard.infos = Object.assign(dlg.infoCard.infos, {
  3109. "作品名称": work.title,
  3110. "作品类型": iType,
  3111. "作品页数": work.page_count,
  3112. });
  3113.  
  3114. if (work.type == "ugoira" && work.ugoira_metadata == undefined && getValueDefault("pubd-getugoiraframe",true))
  3115. {
  3116. analyseUgoira(work, function() { //开始分析动图
  3117. dlg.textdown.disabled = false;
  3118. dlg.startdown.disabled = false;
  3119. dlg.infoCard.infos["作品页数"] = work.ugoira_metadata.frames.length;
  3120. dlg.infoCard.reload(); //必须要reload
  3121. dlg.log("图片信息获取完毕");
  3122. console.log("当前作品JSON数据:",work);
  3123. if (callbackAfterAnalyse) callbackAfterAnalyse();
  3124. });
  3125. return;
  3126. }else
  3127. {
  3128. if (!getValueDefault("pubd-getugoiraframe",true)) {
  3129. dlg.log("由于用户设置,跳过获取动图帧数。");
  3130. }
  3131. dlg.textdown.disabled = false;
  3132. dlg.startdown.disabled = false;
  3133. dlg.log("图片信息获取完毕");
  3134. console.log("当前作品JSON数据:",work);
  3135. if (callbackAfterAnalyse) callbackAfterAnalyse();
  3136. }
  3137. },
  3138. function(jore) { //onload_haserror_Cb //返回错误消息
  3139. dlg.log("错误信息:" + (jore.error.message || jore.error.user_message));
  3140. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  3141. dlg.startdown.disabled = false;
  3142. return;
  3143. },
  3144. function(re) { //onload_notjson_Cb //返回不是JSON
  3145. dlg.log("错误:返回不是JSON,或本程序异常");
  3146. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  3147. dlg.startdown.disabled = false;
  3148. },
  3149. function(re) { //onerror_Cb //网络请求发生错误
  3150. dlg.log("错误:网络请求发生错误");
  3151. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  3152. dlg.startdown.disabled = false;
  3153. }
  3154. );
  3155. }
  3156.  
  3157. function analyseUgoira(work, callback) {
  3158. getUgoiraMeta(
  3159. work.id,
  3160. function(jore) { //onload_suceess_Cb
  3161. work = Object.assign(work, jore);
  3162. if (mdev)
  3163. {
  3164. const illustsStoreRequest = db.transaction("illusts", "readwrite").objectStore("illusts").put(work);
  3165. illustsStoreRequest.onsuccess = function(event) {
  3166. console.debug(`${work.title} 已更新动画帧数据到数据库`);
  3167. };
  3168. }
  3169. dlg.log("动图信息获取完成");
  3170. callback(); //开始获取下一项
  3171. },
  3172. function(jore) { //onload_haserror_Cb //返回错误消息
  3173. if(work.restrict > 0) //非公共权限
  3174. { //添加一条空信息
  3175. work.ugoira_metadata = {
  3176. frames: [],
  3177. zip_urls: {
  3178. medium: "",
  3179. },
  3180. };
  3181. dlg.log("无访问权限,跳过本条。");
  3182. callback(); //开始获取下一项
  3183. }else
  3184. {
  3185. works.runing = false;
  3186. dlg.log("错误信息:" + (jore.error.message || jore.error.user_message));
  3187. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  3188. dlg.startdown.disabled = false;
  3189. }
  3190. return;
  3191. },
  3192. function(re) { //onload_notjson_Cb //返回不是JSON
  3193. dlg.log("错误:返回不是JSON,或本程序异常");
  3194. works.runing = false;
  3195. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  3196. dlg.startdown.disabled = false;
  3197. },
  3198. function(re) { //onerror_Cb //网络请求发生错误
  3199. dlg.log("错误:网络请求发生错误");
  3200. works.runing = false;
  3201. dlg.textdown.disabled = false; //错误暂停时,可以操作目前的进度。
  3202. dlg.startdown.disabled = false;
  3203. }
  3204. );
  3205. }
  3206. };
  3207. //输出文本按钮
  3208. dlg.textdownload = function(event) {
  3209. var illust = dlg.work;
  3210. if (illust == undefined) {dlg.log("没有获取作品数据。"); return;}
  3211. if (dlg.downSchemeDom.selectedOptions.length < 1) { alert("没有选中方案"); return; }
  3212. var scheme = dlg.schemes[dlg.downSchemeDom.selectedIndex];
  3213. dlg.log("正在生成文本信息");
  3214. try {
  3215. var page_count = illust.page_count;
  3216. if (illust.type == "ugoira" && illust.ugoira_metadata) //动图
  3217. {
  3218. page_count = illust.ugoira_metadata.frames.length;
  3219. }
  3220. var outArr = []; //输出内容
  3221. for (var pi = 0; pi < page_count; pi++) {
  3222. if (returnLogicValue(scheme.downfilter, null, illust, pi) || new RegExp(limitingFilenamePattern, "ig").exec(illust.filename)) {
  3223. //跳过此次输出
  3224. continue;
  3225. }else{
  3226. outArr.push(showMask(scheme.textout, scheme.masklist, null, illust, pi));
  3227. }
  3228. }
  3229. var outTxt = outArr.join("");
  3230. dlg.textoutTextarea.value = outTxt;
  3231. dlg.textoutTextarea.classList.remove("display-none");
  3232. dlg.log("文本信息输出成功");
  3233. } catch (error) {
  3234. console.log(error);
  3235. }
  3236. };
  3237. //开始下载按钮
  3238. dlg.startdownload = function() {
  3239. dlg.textoutTextarea.classList.add("display-none");
  3240. if (dlg.downSchemeDom.selectedOptions.length < 1) { alert("没有选中方案"); return; }
  3241. var scheme = dlg.schemes[dlg.downSchemeDom.selectedIndex];
  3242.  
  3243. var termwiseType = parseInt(getValueDefault("pubd-termwiseType", 2));
  3244. if (termwiseType == 0)
  3245. dlg.log("开始按图片逐项发送,⏳请耐心等待。");
  3246. else if (termwiseType == 1 || termwiseType == 2)
  3247. dlg.log("一次性发送整个作品,⏳请耐心等待。");
  3248. else
  3249. {
  3250. alert("错误:未知的逐项模式" + termwiseType);
  3251. console.error("PUBD:错误:未知的逐项模式:", termwiseType);
  3252. return;
  3253. }
  3254.  
  3255. var aria2 = new Aria2(scheme.rpcurl); //生成一个aria2对象
  3256. sendToAria2_illust(aria2, termwiseType, [dlg.work], null, scheme, null, function() {
  3257. aria2 = null;
  3258. dlg.log("😄 当前作品下载信息发送完毕");
  3259. });
  3260. };
  3261. //启动初始化
  3262. dlg.initialise = function(arg) {
  3263. if (arg && arg.id>0) //提供了ID
  3264. {
  3265. if (arg.id != dlg.infoCard.infos.ID)
  3266. { //更换新的id
  3267. dlg.infoCard.thumbnail = "";
  3268. dlg.infoCard.infos = {"ID":arg.id}; //初始化窗口id
  3269. dlg.work = null; //重置作品数据
  3270. }
  3271. }else if(!dlg.infoCard.infos.ID) //没有ID
  3272. {
  3273. dlg.infoCard.infos = {"ID":parseInt(prompt("没有作品ID,请手动输入。", "ID缺失"))}; //初始化窗口id
  3274. }
  3275. dlg.analyse(dlg.infoCard.infos.ID, function(){
  3276. if (getValueDefault("pubd-autodownload",false)) { //自动开始
  3277. dlg.log("🅰️自动开始发送");
  3278. dlg.startdownload();
  3279. }
  3280. });
  3281. dlg.reloadSchemes();
  3282. };
  3283.  
  3284. return dlg;
  3285. }
  3286.  
  3287. //构建导入数据对话框
  3288. function buildDlgImportData() {
  3289. var dlg = new Dialog("导入数据", "pubd-import", "pubd-import");
  3290. var dl = dlg.content.appendChild(document.createElement("dl"));
  3291.  
  3292. var dt = dl.appendChild(document.createElement("dt"));
  3293. dt.innerHTML = "导入内容";
  3294.  
  3295. var dd = dl.appendChild(document.createElement("dd"));
  3296. dd.className = "pubd-import-textarea-bar";
  3297. var ipt = dd.appendChild(document.createElement("textarea"));
  3298. ipt.className = "pubd-import-textarea";
  3299. dlg.importTxt = ipt;
  3300. var dd = dl.appendChild(document.createElement("dd"));
  3301. var btn = dd.appendChild(document.createElement("input"));
  3302. btn.type = "button";
  3303. btn.className = "pubd-import-done";
  3304. btn.value = "导入";
  3305.  
  3306. //启动初始化
  3307. dlg.initialise = function(arg) {
  3308. ipt.value = "";
  3309. if (arg)
  3310. {
  3311. btn.onclick = function()
  3312. {//返回文本框的内容
  3313. arg.callback(ipt.value);
  3314. dlg.hide();
  3315. };
  3316. }else
  3317. {
  3318. btn.onclick = function()
  3319. {
  3320. alert("窗口异常启动,未提供回调函数");
  3321. };
  3322. }
  3323. };
  3324. return dlg;
  3325. }
  3326.  
  3327. //构建多画师下载管理对话框
  3328. function buildDlgMultiple() {
  3329. var dlg = new Dialog("多画师下载管理", "pubd-multiple", "pubd-multiple");
  3330. var dl = dlg.content.appendChild(document.createElement("dl"));
  3331.  
  3332. var dt = dl.appendChild(document.createElement("dt"));
  3333. var dd = dl.appendChild(document.createElement("dd"));
  3334. var frm = dd.appendChild(new Frame("导出Pivix账号关注", "pubd-frm-userlist"));
  3335. var dl_input_frm = frm.content.appendChild(document.createElement("dl"));
  3336. var dt = dl_input_frm.appendChild(document.createElement("dt"));
  3337. var dd = dl_input_frm.appendChild(document.createElement("dd"));
  3338.  
  3339. var ipt = dd.appendChild(document.createElement("input"));
  3340. ipt.type = "button";
  3341. ipt.className = "pubd-userlist-inputstar-public";
  3342. ipt.value = "导出公开关注";
  3343. ipt.onclick = function() {
  3344. };
  3345.  
  3346. var ipt = dd.appendChild(document.createElement("input"));
  3347. ipt.type = "button";
  3348. ipt.className = "pubd-userlist-inputstar-public";
  3349. ipt.value = "导出非公开关注";
  3350. ipt.onclick = function() {
  3351. };
  3352.  
  3353. /*
  3354. var ipt = dd.appendChild(document.createElement("input"));
  3355. ipt.type = "button";
  3356. ipt.className = "pubd-userlist-backup";
  3357. ipt.value = "备份列表JSON"
  3358. ipt.onclick = function() {
  3359. }
  3360.  
  3361. var ipt = dd.appendChild(document.createElement("input"));
  3362. ipt.type = "button";
  3363. ipt.className = "pubd-userlist-restore";
  3364. ipt.value = "导入备份"
  3365. ipt.onclick = function() {
  3366. }
  3367. */
  3368.  
  3369. var dt = dl.appendChild(document.createElement("dt"));
  3370. dt.innerHTML = "选择收藏列表";
  3371. var dd = dl.appendChild(document.createElement("dd"));
  3372. var slt = dd.appendChild(new Select("pubd-staruserlists"));
  3373. slt.onchange = function() {
  3374. dlg.loadTheList(this.selectedIndex);
  3375. };
  3376. //每次脚本预加载的时候事先生成列表
  3377. slt.options.add(new Option('快速收藏',0));
  3378. //重新读取所有收藏列表
  3379. dlg.reloadStarList = function() {
  3380. while (slt.length>0)
  3381. {
  3382. const x = slt.options[0];
  3383. x.remove();
  3384. x = null;
  3385. }
  3386. slt.options.length = 0;
  3387. pubd.starUserlists.forEach((ulist,idx) => slt.options.add(new Option(ulist.title,idx)));
  3388. };
  3389. dlg.loadTheList = function(listIdx) {
  3390. const listArr = listIdx > 0 ? pubd.starUserlists[listIdx].exportArray() : pubd.fastStarList.exportArray();
  3391. const ulDom = dlg.ulDom;
  3392. ulDom.classList.add("display-none");
  3393. while (ulDom.childNodes.length)
  3394. {
  3395. const x = ulDom.childNodes[0];
  3396. if (x.nodeName == 'li')
  3397. {
  3398. const l = x.querySelector('label');
  3399. l.ipt.remove();
  3400. delete l.ipt;
  3401. l.card.dom.remove();
  3402. delete l.card.dom
  3403. delete l.card;
  3404. l.remove();
  3405. l = null;
  3406. }
  3407. x.remove();
  3408. x = null;
  3409. }
  3410. const fragment = document.createDocumentFragment();
  3411. console.log(listArr)
  3412. listArr.forEach(uid=>{ //添加每一个作者的信息
  3413. const uli = fragment.appendChild(document.createElement('li'));
  3414. uli.className = 'user-card-li';
  3415. uli.setAttribute('data-user-id',uid);
  3416. const lbl = uli.appendChild(new LabelInput(null,'user-card-lbl',`user-${uid}`,'checkbox',uid));
  3417. const card = lbl.card = new InfoCard({
  3418. "ID": uid,
  3419. "昵称": null,
  3420. "作品获取程度": null,
  3421. "数据更新时间": null,
  3422. });
  3423. lbl.appendChild(card.dom);
  3424. });
  3425. ulDom.appendChild(fragment);
  3426. ulDom.classList.remove("display-none");
  3427. };
  3428.  
  3429. dlg.userListDom = slt;
  3430.  
  3431. var dd = dl.appendChild(document.createElement("dd"));
  3432. var ipt = dd.appendChild(document.createElement("input"));
  3433. ipt.type = "button";
  3434. ipt.className = "pubd-userlist-new";
  3435. ipt.value = "新建";
  3436. ipt.onclick = function() {
  3437. var schemName = prompt("请输入方案名", "我的方案");
  3438. if (schemName)
  3439. {
  3440. var scheme = new DownScheme(schemName);
  3441. var length = dlg.schemes.push(scheme);
  3442. dlg.downSchemeDom.add(scheme.name, length - 1);
  3443. dlg.downSchemeDom.selectedIndex = length - 1;
  3444. dlg.loadScheme(scheme);
  3445. //dlg.reloadSchemes();
  3446. }
  3447. };
  3448.  
  3449. var ipt = dd.appendChild(document.createElement("input"));
  3450. ipt.type = "button";
  3451. ipt.className = "pubd-userlist-rename";
  3452. ipt.value = "重命名列表";
  3453. ipt.onclick = function() {
  3454. };
  3455.  
  3456. var ipt = dd.appendChild(document.createElement("input"));
  3457. ipt.type = "button";
  3458. ipt.className = "pubd-userlist-remove";
  3459. ipt.value = "删除";
  3460. ipt.onclick = function() {
  3461. if (dlg.downSchemeDom.options.length < 1) { alert("已经没有方案了"); return; }
  3462. if (dlg.downSchemeDom.selectedOptions.length < 1) { alert("没有选中方案"); return; }
  3463. var index = dlg.downSchemeDom.selectedIndex;
  3464. dlg.schemes.splice(index, 1);
  3465. dlg.downSchemeDom.remove(index);
  3466. var index = dlg.downSchemeDom.selectedIndex;
  3467. if (index < 0) dlg.reloadSchemes(); //没有选中的,重置
  3468. else dlg.loadScheme(dlg.schemes[index]);
  3469. };
  3470.  
  3471. var dd = dl.appendChild(document.createElement("dd"));
  3472. var frm = dd.appendChild(new Frame("当前列表", "pubd-frm-userlist"));
  3473. var dl_ul_frm = frm.content.appendChild(document.createElement("dl"));
  3474. var dt = dl_ul_frm.appendChild(document.createElement("dt"));
  3475. var dd = dl_ul_frm.appendChild(document.createElement("dd"));
  3476.  
  3477. var ipt = dd.appendChild(document.createElement("input"));
  3478. ipt.type = "button";
  3479. ipt.className = "pubd-userlist-this-add";
  3480. ipt.value = "添加画师ID";
  3481. ipt.onclick = function() {
  3482. };
  3483. var ipt = dd.appendChild(document.createElement("input"));
  3484. ipt.type = "button";
  3485. ipt.className = "pubd-userlist-this-remove";
  3486. ipt.value = "删除选中画师";
  3487. ipt.onclick = function() {
  3488. };
  3489. var ipt = dd.appendChild(document.createElement("input"));
  3490. ipt.type = "button";
  3491. ipt.className = "pubd-userlist-this-reset-getdata";
  3492. ipt.value = "重置数据获取状态";
  3493. ipt.onclick = function() {
  3494. };
  3495. var ipt = dd.appendChild(document.createElement("input"));
  3496. ipt.type = "button";
  3497. ipt.className = "pubd-userlist-this-reset-downloaded";
  3498. ipt.value = "重置下载状态";
  3499. ipt.onclick = function() {
  3500. };
  3501.  
  3502. var dt = dl_ul_frm.appendChild(document.createElement("dt"));
  3503. dt.innerHTML = "画师列表";
  3504. var ipt = dt.appendChild(document.createElement("input"));
  3505. ipt.type = "button";
  3506. ipt.className = "pubd-userlist-break";
  3507. ipt.value = "中断操作";
  3508. ipt.onclick = function() {
  3509. };
  3510. var dd = dl_ul_frm.appendChild(document.createElement("dd"));
  3511. var dl_ul = dd.appendChild(document.createElement("ul"));
  3512. dlg.ulDom = dl_ul;
  3513. dl_ul.className = "pubd-userlist-ul";
  3514.  
  3515. var dt = dl.appendChild(document.createElement("dt"));
  3516. var dd = dl.appendChild(document.createElement("dd"));
  3517. var ipt = dd.appendChild(document.createElement("input"));
  3518. ipt.type = "button";
  3519. ipt.className = "pubd-userlist-this-getdata";
  3520. ipt.value = "获取画师数据";
  3521. ipt.onclick = function() {
  3522. };
  3523.  
  3524. var ipt = dd.appendChild(document.createElement("input"));
  3525. ipt.type = "button";
  3526. ipt.className = "pubd-userlist-textdown";
  3527. ipt.value = "输出文本";
  3528. ipt.onclick = function() {
  3529. };
  3530. var ipt = dd.appendChild(document.createElement("input"));
  3531. ipt.type = "button";
  3532. ipt.className = "pubd-userlist-download";
  3533. ipt.value = "下载列表内画师作品";
  3534. ipt.onclick = function() {
  3535. };
  3536. //启动初始化
  3537. dlg.initialise = function(arg) {
  3538. dlg.loadTheList(0); //加载快速收藏列表
  3539. };
  3540. return dlg;
  3541. }
  3542.  
  3543. //作品循环递归输出
  3544. function sendToAria2_illust(aria2, termwiseType, illusts, userInfo, scheme, downP, callback) {
  3545. if (illusts.length < 1) //做完了
  3546. {
  3547. callback();
  3548. return;
  3549. }
  3550. if (pubd.downbreak)
  3551. {
  3552. GM_notification({text:"已中断向Aria2发送下载信息。但Aria2本身仍未停止下载已添加内容,请手动停止。", title:scriptName, image:scriptIcon});
  3553. pubd.downbreak = false;
  3554. return;
  3555. }
  3556. if (termwiseType == 0) //完全逐项
  3557. {
  3558. var illust = illusts.shift(); //读取首个作品
  3559. sendToAria2_Page(aria2, illust, 0, userInfo, scheme, downP, function() {
  3560. sendToAria2_illust(aria2, termwiseType, illusts, userInfo, scheme, downP, callback); //发送下一个作品
  3561. });
  3562. return; //不再继续执行
  3563. }else if (termwiseType == 1) //部分逐项(每作品合并)
  3564. {
  3565. var illust = illusts.shift(); //读取首个作品
  3566. var page_count = illust.page_count; //作品页数
  3567. if (illust.type == "ugoira" && illust.ugoira_metadata) //修改动图的页数
  3568. {
  3569. page_count = illust.ugoira_metadata.frames.length;
  3570. }
  3571. if (new RegExp(limitingFilenamePattern, "ig").exec(illust.filename)) //无权查看的文件
  3572. {
  3573. if (downP) downP.progress.set((downP.current += page_count) / downP.max); //直接加上一个作品所有页数
  3574. sendToAria2_illust(aria2, termwiseType, illusts, userInfo, scheme, downP, callback); //调用自身
  3575. return;
  3576. }
  3577. var aria2_params = [];
  3578. for (let page=0;page<page_count;page++)
  3579. {
  3580. if (returnLogicValue(scheme.downfilter, userInfo, illust, page)) {
  3581. //跳过此次下载
  3582. //console.info("符合下载过滤器定义,跳过下载:", illust);
  3583. continue;
  3584. } else {
  3585. var aria2_method = {'methodName':'aria2.addUri','params':[]};
  3586. var url = getIllustDownUrl(illust, page, scheme.https2http);
  3587.  
  3588. aria2_method.params.push([url]); //添加下载链接
  3589. var options = {
  3590. "out": replacePathSafe(showMask(scheme.savepath, scheme.masklist, userInfo, illust, page), 1),
  3591. "referer": Referer,
  3592. "user-agent": UA,
  3593. };
  3594. if (scheme.savedir.length > 0) {
  3595. options.dir = replacePathSafe(showMask(scheme.savedir, scheme.masklist, userInfo, illust, page), 0);
  3596. }
  3597. if (scheme.proxyurl.length > 0) {
  3598. options["all-proxy"] = scheme.proxyurl;
  3599. }
  3600. aria2_method.params.push(options);
  3601. aria2_params.push(aria2_method);
  3602. }
  3603. }
  3604. if (aria2_params.length>0)
  3605. {
  3606. aria2.system.multicall([aria2_params],function(res){
  3607. if (res === false) {
  3608. alert("发送到指定的Aria2失败,请检查到Aria2连接是否正常。");
  3609. return;
  3610. }
  3611. if (downP) downP.progress.set((downP.current += page_count) / downP.max); //直接加上一个作品所有页数
  3612. sendToAria2_illust(aria2, termwiseType, illusts, userInfo, scheme, downP, callback); //调用自身
  3613. });
  3614. }else
  3615. { //这个作品全部跳过的时候
  3616. if (downP) downP.progress.set((downP.current += page_count) / downP.max); //直接加上一个作品所有页数
  3617. sendToAria2_illust(aria2, termwiseType, illusts, userInfo, scheme, downP, callback); //调用自身
  3618. }
  3619. return;
  3620. }else if(termwiseType == 2) //不逐项,每作者合并
  3621. {
  3622. var aria2_params = [];
  3623. for (var illustIndex = 0; illustIndex < illusts.length; illustIndex++)
  3624. {
  3625. var illust = illusts[illustIndex];
  3626. if (new RegExp(limitingFilenamePattern, "ig").exec(illust.filename)) continue; //无权查看的文件,直接继续
  3627.  
  3628. var page_count = illust.page_count; //作品页数
  3629. if (illust.type == "ugoira" && illust.ugoira_metadata) //修改动图的页数
  3630. {
  3631. page_count = illust.ugoira_metadata.frames.length;
  3632. }
  3633. for (let page=0;page<page_count;page++)
  3634. {
  3635. if (returnLogicValue(scheme.downfilter, userInfo, illust, page)) {
  3636. //跳过此次下载
  3637. //console.info("符合下载过滤器定义,跳过下载:", illust);
  3638. continue;
  3639. } else {
  3640. var aria2_method = {'methodName':'aria2.addUri','params':[]};
  3641. var url = getIllustDownUrl(illust, page, scheme.https2http);
  3642.  
  3643. aria2_method.params.push([url]); //添加下载链接
  3644. var options = {
  3645. "out": replacePathSafe(showMask(scheme.savepath, scheme.masklist, userInfo, illust, page), 1),
  3646. "referer": Referer,
  3647. "user-agent": UA,
  3648. };
  3649. if (scheme.savedir.length > 0) {
  3650. options.dir = replacePathSafe(showMask(scheme.savedir, scheme.masklist, userInfo, illust, page), 0);
  3651. }
  3652. if (scheme.proxyurl.length > 0) {
  3653. options["all-proxy"] = scheme.proxyurl;
  3654. }
  3655. aria2_method.params.push(options);
  3656. aria2_params.push(aria2_method);
  3657. }
  3658. }
  3659. }
  3660. if (aria2_params.length>0)
  3661. {
  3662. aria2.system.multicall([aria2_params],function(res){
  3663. if (res === false) {
  3664. alert("发送到指定的Aria2失败,请检查到Aria2连接是否正常。不排除数据过大,可考虑临时使用逐项或半逐项模式。");
  3665. var l= JSON.stringify(aria2_params).length/1024;
  3666. console.error("Aria2接受失败。数据量在未添加token的情况下有" + (
  3667. (l>1024)?
  3668. ((l/1024)+"MB"):
  3669. (l+"KB")
  3670. ),aria2_params);
  3671. return;
  3672. }
  3673. if (downP) downP.progress.set((downP.current = downP.max) / downP.max); //直接加上所有页数
  3674. sendToAria2_illust(aria2, termwiseType, [], userInfo, scheme, downP, callback); //调用自身
  3675. });
  3676. }else
  3677. { //这个作品全部跳过的时候
  3678. if (downP) downP.progress.set((downP.current = downP.max) / downP.max); //直接加上所有页数
  3679. sendToAria2_illust(aria2, termwiseType, [], userInfo, scheme, downP, callback); //调用自身
  3680. }
  3681. return;
  3682. }
  3683. }
  3684. //作品每页循环递归输出
  3685. function sendToAria2_Page(aria2, illust, page, userInfo, scheme, downP, callback) {
  3686. if (pubd.downbreak) {
  3687. GM_notification({text:"已中断向Aria2发送下载信息。但Aria2本身仍未停止下载已添加内容,请手动停止。", title:scriptName, image:scriptIcon});
  3688. pubd.downbreak = false;
  3689. return;
  3690. }
  3691. var page_count = illust.page_count;
  3692. if (illust.type == "ugoira" && illust.ugoira_metadata) //动图的帧数当页数
  3693. {
  3694. page_count = illust.ugoira_metadata.frames.length;
  3695. }
  3696. if (new RegExp(limitingFilenamePattern, "ig").exec(illust.filename)) //无法查看的文件,直接把page加到顶
  3697. {
  3698. page = page_count;
  3699. downP.progress.set((downP.current += page_count) / downP.max); //直接加上所有页数
  3700. }
  3701. if (page >= page_count) //本作品页数已经完毕
  3702. {
  3703. callback();
  3704. return;
  3705. }
  3706. var url = getIllustDownUrl(illust, page, scheme.https2http);
  3707.  
  3708. if (returnLogicValue(scheme.downfilter, userInfo, illust, page)) {
  3709. //跳过此次下载
  3710. downP.progress.set(++downP.current / downP.max); //设置进度
  3711. sendToAria2_Page(aria2, illust, ++page, userInfo, scheme, downP, callback); //递归调用自身
  3712. //console.info("符合下载过滤器定义,跳过下载:", illust);
  3713. } else {
  3714. var options = {
  3715. "out": replacePathSafe(showMask(scheme.savepath, scheme.masklist, userInfo, illust, page), 1),
  3716. "referer": Referer,
  3717. "user-agent": UA,
  3718. };
  3719.  
  3720. if (scheme.savedir.length > 0) {
  3721. options.dir = replacePathSafe(showMask(scheme.savedir, scheme.masklist, userInfo, illust, page), 0);
  3722. }
  3723. if (scheme.proxyurl.length > 0) {
  3724. options["all-proxy"] = scheme.proxyurl;
  3725. }
  3726. aria2.addUri(url, options, function(res) {
  3727. if (res === false) {
  3728. alert("发送到指定的Aria2失败,请检查到Aria2连接是否正常。");
  3729. return;
  3730. }
  3731. downP.progress.set(++downP.current / downP.max); //设置进度
  3732. sendToAria2_Page(aria2, illust, ++page, userInfo, scheme, downP, callback); //递归调用自身
  3733. });
  3734. }
  3735. }
  3736. //返回掩码值
  3737. function showMask(oldStr, maskList, user, illust, page) {
  3738. //ES6原生模式,将来再启用
  3739. /*const cm = function(maskName) //customMask
  3740. {
  3741. const cusMask = maskList.find(mask=>mask.name == maskName);
  3742. if (cusMask) { //如果有对应的自定义掩码
  3743. if (returnLogicValue(cusMask.logic, user, illust, page)) //mask的逻辑判断
  3744. return eval("`" + cusMask.content +"`"); //递归
  3745. else
  3746. return "";
  3747. }
  3748. }
  3749. let newStr = eval("`" + oldStr +"`"); //需要解决旧有路径里\右斜杠的问题*/
  3750.  
  3751. //以下均为传统掩码
  3752. var newStr = oldStr;
  3753. //var pattern = "%{([^}]+)}"; //旧的,简单匹配
  3754. var regPattern = "%{(.*?(?:[^\\\\](?:\\\\{2})+|[^\\\\]))}"; //新的,支持转义符
  3755. var regResult = null;
  3756.  
  3757. /* jshint ignore:start */
  3758.  
  3759. //不断循环直到没有掩码
  3760. while ((regResult = new RegExp(regPattern).exec(newStr)) != null) {
  3761. var mskO = regResult[0], //包含括号的原始掩码
  3762. mskN = regResult[1]; //去掉掩码括号
  3763. if (mskN != undefined) {
  3764. //去掉转义符的掩码名
  3765. mskN = (mskN != undefined) ? mskN.replace(/\\{/ig, "{").replace(/\\}/ig, "}").replace(/\\\\/ig, "\\") : null;
  3766. //搜寻自定义掩码
  3767. var cusMask = maskList.find(mask=>mask.name == mskN);
  3768. if (cusMask) { //如果有对应的自定义掩码
  3769. try {
  3770. if (returnLogicValue(cusMask.logic, user, illust, page)) //mask的逻辑判断
  3771. newStr = newStr.replace(mskO, cusMask.content);
  3772. else
  3773. newStr = newStr.replace(mskO, "");
  3774. } catch (e) {
  3775. console.error(mskO + " 自定义掩码出现了异常情况", e);
  3776. }
  3777. } else { //普通掩码
  3778. try {
  3779. var evTemp = eval(mskN);
  3780. if (evTemp != undefined)
  3781. newStr = newStr.replace(mskO, evTemp.toString());
  3782. else
  3783. newStr = newStr.replace(mskO, "");
  3784. } catch (e) {
  3785. newStr = newStr.replace(mskO, "");
  3786. console.error(mskO + " 掩码出现了异常情况", e);
  3787. }
  3788. }
  3789. }
  3790. }
  3791. /* jshint ignore:end */
  3792.  
  3793. return newStr;
  3794. }
  3795. //返回逻辑值
  3796. function returnLogicValue(logic, user, illust, page) {
  3797. try {
  3798. if (logic.length == 0) return false;
  3799. /* jshint ignore:start */
  3800. const evTemp = Boolean(eval(logic));
  3801. /* jshint ignore:end */
  3802. return evTemp;
  3803. } catch (e) {
  3804. console.error("逻辑运算出现了异常情况,逻辑内容:","(" + logic + ")", e);
  3805. return false;
  3806. }
  3807. }
  3808.  
  3809. function replacePathSafe(str, type) //去除Windows下无法作为文件名的字符,目前为了支持Linux暂不替换两种斜杠吧。
  3810. { //keepTree表示是否要保留目录树的字符(\、/和:)
  3811. if (typeof(str) == "undefined")
  3812. {
  3813. return "";
  3814. }
  3815. let nstr = str.toString(); //新字符
  3816. nstr = nstr.replace(/\u0000-\u001F\u007F-\u00A0/ig, ""); //替换所有的控制字符
  3817. var patternStrs = [
  3818. "[\\*\\?\"<>\\|]", //只替换路径中完全不能出现的特殊字符
  3819. "[\\*\\?\"<>\\|\\r\\n]", //上述字符加冒号:,用于非驱动器路径
  3820. "[\\*\\?\"<>\\|\\r\\n\\\\\\/]", //完全替换所有不能出现的特殊字符,包含斜杠
  3821. ];
  3822. if (patternStrs[type] != undefined)
  3823. {
  3824. nstr = nstr.replace(new RegExp(patternStrs[type],"ig"), "_"); //只替换路径中完全不能出现的特殊字符
  3825. }
  3826. return nstr;
  3827. }
  3828.  
  3829. //主引导程序
  3830. function Main(touch) {
  3831. if (!mdev) GM_addStyle(GM_getResourceText("pubd-style")); //不是开发模式时加载CSS资源
  3832.  
  3833. //删除以前储存的账号密码
  3834. let cfgVer = GM_getValue("pubd-configversion");
  3835. if (cfgVer && cfgVer < pubd.configVersion)
  3836. {
  3837. GM_deleteValue("pubd-auth");
  3838. }
  3839.  
  3840. //载入设置
  3841. pubd.oAuth = new oAuth2(GM_getValue("pubd-oauth"));
  3842.  
  3843. pubd.downSchemes = NewDownSchemeArrayFromJson(getValueDefault("pubd-downschemes",[]));
  3844. //对下载方案的修改添加监听
  3845. GM_addValueChangeListener("pubd-downschemes", function(name, old_value, new_value, remote) {
  3846. pubd.downSchemes = NewDownSchemeArrayFromJson(new_value); //重新读取下载方案(可能被其他页面修改的)
  3847. });
  3848. //快速收藏列表的监听修改
  3849. //pubd.fastStarList = getValueDefault("pubd-faststar-list",[]);
  3850. pubd.fastStarList = new UsersStarList("快速收藏",getValueDefault("pubd-faststar-list",[]));
  3851. GM_addValueChangeListener("pubd-faststar-list", function(name, old_value, new_value, remote) {
  3852. pubd.fastStarList = null;
  3853. pubd.fastStarList = new UsersStarList("快速收藏",getValueDefault("pubd-faststar-list",[]));
  3854. if (mdev) console.log('收藏有变化',pubd.fastStarList.users);
  3855. checkStar();
  3856.  
  3857. //更改推荐列表里的收藏显示状态
  3858. if (recommendList)
  3859. {
  3860. const liNodes = recommendList.querySelectorAll(":scope>li");
  3861. liNodes.forEach(linode=>{ //这个node是每个新增列表里的li
  3862. const userLink = linode.querySelector("div>div:last-of-type>div>a");
  3863. const uidRes = /\d+/.exec(userLink.pathname);
  3864. if (uidRes.length)
  3865. {
  3866. const uid = parseInt(uidRes[0],10); //得到这个作品的作者ID
  3867. if (pubd.fastStarList.has(uid))
  3868. {
  3869. linode.classList.add("pubd-stared"); //添加隐藏用的css
  3870. }
  3871. else
  3872. {
  3873. linode.classList.remove("pubd-stared"); //添加隐藏用的css
  3874. }
  3875. }
  3876. })
  3877. }
  3878. //将来还需要在更改收藏时,就自动刷新所有的其他推荐列表
  3879. //put my code
  3880. });
  3881.  
  3882. //登陆信息的监听修改
  3883. GM_addValueChangeListener("pubd-oauth", function(name, old_value, new_value, remote) {
  3884. pubd.oAuth = new oAuth2(new_value);
  3885. });
  3886.  
  3887. //预先添加所有视窗,即便没有操作按钮也能通过菜单打开
  3888. let fragment = document.createDocumentFragment();
  3889. pubd.dialog.config = fragment.appendChild(buildDlgConfig());
  3890. pubd.dialog.login = fragment.appendChild(buildDlgLogin());
  3891. pubd.dialog.refresh_token = fragment.appendChild(buildDlgRefreshToken());
  3892. pubd.dialog.downthis = fragment.appendChild(buildDlgDownThis(thisPageUserid));
  3893. pubd.dialog.downillust = fragment.appendChild(buildDlgDownIllust(thisPageIllustid));
  3894. pubd.dialog.importdata = fragment.appendChild(buildDlgImportData());
  3895. pubd.dialog.multiple = fragment.appendChild(buildDlgMultiple());
  3896.  
  3897. let btnDlgInsertPlace = document.body; //视窗插入点,直接插入到body就行
  3898. btnDlgInsertPlace.appendChild(fragment);
  3899. //添加Tampermonkey扩展菜单内的入口
  3900. GM_registerMenuCommand("PUBD-选项", function(){
  3901. pubd.dialog.config.show(
  3902. (document.body.clientWidth - 400)/2,
  3903. window.pageYOffset+50
  3904. );
  3905. });
  3906. GM_registerMenuCommand("PUBD-下载该画师", function(){
  3907. pubd.dialog.downthis.show(
  3908. (document.body.clientWidth - 440)/2,
  3909. window.pageYOffset+100,
  3910. {id:getCurrentUserId()}
  3911. );
  3912. });
  3913.  
  3914. if (mdev)
  3915. GM_registerMenuCommand("PUBD-导入窗口测试", function(){
  3916. pubd.dialog.importdata.show(
  3917. (document.body.clientWidth - 370)/2,
  3918. window.pageYOffset+200,
  3919. {callback:function(txt){
  3920. const importArr = txt.split("\n");
  3921. const needAddArr = importArr.map(str=>{
  3922. let res = null;
  3923. if (
  3924. Boolean(res = new RegExp("^(\\d+)$","ig").exec(str)) ||
  3925. Boolean(res = new RegExp("member.+?\\?id=(\\d+)","ig").exec(str)) ||
  3926. Boolean(res = new RegExp("users/(\\d+)","ig").exec(str))
  3927. )
  3928. {
  3929. return parseInt(res[1],10);
  3930. }else
  3931. {
  3932. if (str.length>0)
  3933. console.log("未知的字符串",str);
  3934. return null;
  3935. }
  3936. }).filter(Boolean);
  3937. console.log(needAddArr);
  3938. if (needAddArr.length>0)
  3939. {
  3940. console.log(`新增了${needAddArr.length}个收藏`);
  3941. pubd.fastStarList.importArray(needAddArr);
  3942. GM_setValue("pubd-faststar-list",pubd.fastStarList.exportArray());
  3943. }
  3944. }}
  3945. );
  3946. });
  3947.  
  3948.  
  3949. //建立开始按钮
  3950. let btnStartBox = document.createElement("div");
  3951. btnStartBox.className = "pubd-btnStartInsertPlace";
  3952. pubd.start = btnStartBox.appendChild(buildbtnStart());
  3953. pubd.menu = btnStartBox.appendChild(buildbtnMenu());
  3954. //添加开始按钮,开始按钮内容直接固定为 btnStartBox
  3955. function insertStartBtn(btnStartInsertPlace)
  3956. {
  3957. if (btnStartInsertPlace == undefined)
  3958. {
  3959. console.error("PUBD:未找到开始按钮插入点。");
  3960. return false;
  3961. }else
  3962. {
  3963. if (/^\/artworks\//i.test(location.pathname)) //如果是作品页面,显示下载当前作品按钮
  3964. {
  3965. pubd.menu.downillust.classList.remove("display-none");
  3966. downIllustMenuId = GM_registerMenuCommand("PUBD-下载该作品", function(){
  3967. pubd.dialog.downillust.show(
  3968. (document.body.clientWidth - 500)/2,
  3969. window.pageYOffset+150,
  3970. {id:getQueryString('illust_id',
  3971. pubd.touch ?
  3972. mainDiv.querySelector('.illust-details-content .work-stats>a') : //手机版
  3973. mainDiv.querySelector(artWorkStarCssPath) //新版Vue结构
  3974. )}
  3975. );
  3976. });
  3977. }else
  3978. {
  3979. pubd.menu.downillust.classList.add("display-none");
  3980. GM_unregisterMenuCommand(downIllustMenuId);
  3981. }
  3982. checkStar(); //检查是否有收藏
  3983. //插入开始操作按钮
  3984. btnStartInsertPlace.appendChild(btnStartBox);
  3985. console.log("PUBD:网页发生变动,已重新呈现开始按钮。");
  3986. return true;
  3987. }
  3988. }
  3989.  
  3990. /*
  3991. 手机版网页的root
  3992. #spa-contents 会被删掉重新添加,所以只能用更上一层
  3993. */
  3994. const vueRoot = document.querySelector("#root"); //vue框架的root div
  3995. const wrapper = document.querySelector("#wrapper"); //仍然少量存在的老板页面
  3996. const touchRoot = wrapper ? wrapper.querySelector("#contents") : null;
  3997. if (window.MutationObserver && (vueRoot || touch)) //如果支持MutationObserver,且是vue框架
  3998. {
  3999. let reInsertStart = true; //是否需要重新插入开始按钮
  4000. let subRoot = null; //P站改版,在root下面多了一层
  4001. let changeIllustUser = new MutationObserver(function(mutationsList, observer) {
  4002. if (mdev) console.log("作者链接 href 改变了",mutationsList);
  4003. checkStar();
  4004. });
  4005. let observerLoop = new MutationObserver(function(mutationsList, observer) {
  4006. //当在P站首页的时候,不需要生效
  4007. if (location.pathname.substr(1).length == 0)
  4008. {
  4009. console.log("PUBD:本页面不需要执行。");
  4010. return;
  4011. }
  4012.  
  4013. //如果被删除的节点里有我们的开始按钮,就重新插入;或者搜索列表被删除
  4014. if (mutationsList.some(mutation=>Array.from(mutation.removedNodes).some(node=>(node.contains(btnStartBox) || node.contains(recommendList)))))
  4015. {
  4016. console.log('已经添加的开始按钮因为页面改动被删除了');
  4017. mainDiv = null;
  4018. reInsertStart = true;
  4019. }
  4020.  
  4021. //搜索新的主div并插入开始按钮
  4022. if (reInsertStart)
  4023. {
  4024. Array.from((touch ? touchRoot : subRoot).children).some(node=>
  4025. {
  4026. recommendList = node.querySelector(searchListCssPath);
  4027. if (recommendList) //如果是搜索界面
  4028. {
  4029. if (mdev) console.log("发现搜索列表",recommendList);
  4030. mainDiv = node; //重新选择主div
  4031. reInsertStart = false;
  4032. return true;
  4033. }else //添加开始菜单
  4034. {
  4035. return mainDivSearchCssSelectorArray.some(cssS=>{
  4036. let btnStartInsertPlace = node.querySelector(cssS);
  4037. if(btnStartInsertPlace != undefined)
  4038. {
  4039. mainDiv = node; //重新选择主div
  4040. if (mdev) console.log("开始按钮插入点CSS路径为",mainDiv,cssS);
  4041. reInsertStart = !insertStartBtn(btnStartInsertPlace); //插入开始按钮
  4042.  
  4043. const userHeadLink = mainDiv.querySelector(artWorkUserHeadCssPath);
  4044. if (userHeadLink) //如果是作品页面
  4045. {
  4046. changeIllustUser.observe(userHeadLink, {attributeFilter:["href"]});
  4047. }
  4048. return true;
  4049. }else return false;
  4050. });
  4051. }
  4052. }
  4053. );
  4054. }
  4055.  
  4056. //作品页面显示推荐的部分
  4057. const otherWorks = (touch || !mainDiv) ? null : mainDiv.querySelector(":scope>div>aside:nth-of-type(2)");
  4058. if (otherWorks)
  4059. { //已发现推荐列表大部位
  4060. if (recommendList = otherWorks.querySelector("section>div:nth-of-type(2) ul"))
  4061. {
  4062. if (mdev) console.log("发现推荐列表",recommendList);
  4063. }
  4064.  
  4065. //不知道为什么这个代码又不需要了
  4066. /*if (!recommendList &&
  4067. mutationsList.some(mutation=>
  4068. otherWorks.contains(mutation.target) && //目标属于推荐部分
  4069. Array.from(mutation.addedNodes).some(node=>node.nodeName == "SECTION" || node.querySelector("section")) //新增node要么是section,要么包括section
  4070. )
  4071. ) //当改变目标为这个aside,并且新增的是section时
  4072. {
  4073. recommendList = otherWorks.querySelector("section>div:nth-of-type(2) ul");
  4074. if (mdev) console.log("发现推荐列表",recommendList);
  4075. }*/
  4076. }
  4077. if (recommendList)
  4078. {
  4079. let mutationsList_target = mutationsList.filter(mutation=>mutation.target==recommendList);
  4080. if (mutationsList_target.length)
  4081. { //当改变目标为推荐列表时,并且新增的是section时
  4082. mutationsList_target.forEach(mutation=>
  4083. mutation.addedNodes.forEach(linode=>{ //这个node是每个新增列表里的li
  4084. const userLink = linode.querySelector("div>div:last-of-type>div>a");
  4085. if (!userLink) return;
  4086. const uidRes = /\d+/.exec(userLink.pathname);
  4087. if (uidRes.length)
  4088. {
  4089. const uid = parseInt(uidRes[0],10); //得到这个作品的作者ID
  4090. if (pubd.fastStarList.has(uid))
  4091. {
  4092. linode.classList.add("pubd-stared"); //添加隐藏用的css
  4093. }
  4094. }
  4095. })
  4096. );
  4097. }
  4098. if (mutationsList.some(mutation=>Array.from(mutation.removedNodes).some(node=>node.contains(recommendList))))
  4099. { //如果被删除的节点里有推荐列表,重新标空
  4100. recommendList = null;
  4101. }
  4102. }
  4103. });
  4104. //只执行一次的,插找P站新的根节点的位置
  4105. let observerFindSubRoot = new MutationObserver(function(mutationsList, observer) {
  4106. mutationsList.some(mutation=>Array.from(mutation.addedNodes).some(node=>{
  4107. if(!node.id.length){
  4108. subRoot = node;
  4109. observer.disconnect();
  4110. if (mdev) console.log("子root为",subRoot);
  4111. observerLoop.observe(subRoot, {childList:true, subtree:true});
  4112. return true;
  4113. }else
  4114. {
  4115. return false;
  4116. }
  4117. }));
  4118. });
  4119. if (vueRoot)
  4120. {
  4121. observerFindSubRoot.observe(vueRoot, {childList:true, subtree:false});
  4122. }else
  4123. {
  4124. observerLoop.observe(touchRoot, {childList:true, subtree:true});
  4125. }
  4126. }else if(vueRoot == undefined)
  4127. {
  4128. if (wrapper) //仍然少量存在的老板页面
  4129. {
  4130. console.log('PUBD:你访问的是仍然少量存在的老板页面。');
  4131. insertStartBtn(document.querySelector("._user-profile-card")) || //老版用户资料页
  4132. insertStartBtn(document.querySelector(".ui-layout-west aside")) || //老版作品页
  4133. insertStartBtn(document.querySelector(".introduction")) //老版未登录(不可用)页面
  4134. ;
  4135. }else
  4136. {
  4137. console.log('PUBD:未找到 root div,可能P站又改版了,程序得修改。');
  4138. }
  4139. }else
  4140. {
  4141. alert('PUBD:您的浏览器不支持 MutationObserver,请使用最新浏览器。');
  4142. }
  4143. }
  4144.  
  4145. Main(pubd.touch); //开始主程序
  4146. })();

QingJ © 2025

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