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

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

目前为 2023-05-06 提交的版本,查看 最新版本

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