ApabiDownloader

将最高清晰度的图片打包为PDF

  1. // ==UserScript==
  2. // @name ApabiDownloader
  3. // @namespace https://qinlili.bid/
  4. // @version 0.7
  5. // @description 将最高清晰度的图片打包为PDF
  6. // @author 琴梨梨
  7. // @match *://*/OnLineReader/Default.aspx?*
  8. // @match *://cebxol.apabi.com/*
  9. // @match *://cebxol.apabiedu.com/?metaid=*
  10. // @icon data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAABEkswAlNb8AJTS/AAEAxwADxckAHrB7ACEsMQAYIWcAH/A5AAiMEwAiM/0ADxQdAAaMEQAGihEAEya3ABsv+wAmeH8AITK9AAJEBwAUarsAERuhACc2fwAPny0ACo6QgBDXmkApeH8AKfg9AAkMDwAMFl0AEeGtABOmMwALD1cAHXB9AARG0QAQFh0AJLI3AAoUIQArun8AKTo/AAyRVwAnNr0AHvH9ACS1/QAHjFcAAQFNAAvPkwAf6m8AERkbABvl6wABAIkAA8YLACc5vwABwo1AAcKIQBMp+QABAMUAAsRLAA2SGwAbLr0AGS47AAEAiwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwzMR8lPDw8PDw8PDw8Lzw8JjwGIzw8MTw8PDw8PBAzMSU3JRgxJSg8PDw8PDw8JiYaLiU1JSI8PDw8PDAzGzElLSUHJQ08FCk8PDw8CyUlJQksPBUqCikhPDw8PDwxFxIxPDw8AzE8PCw8PAwlJSUlGzw8CikFOjc8PDwrOTwxJTUBCCkEPBYTPDw8PDwnGSICHCk4OzI8PDw8PDwsGTQ8ETEPHDQ2ADw8PDw8PDw8PCA8HR48PDw8PDw8PDw8PDw6PCQOPDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
  11. // @grant none
  12. // @run-at document-idle
  13. // @require https://cdn.jsdelivr.net/npm/jspdf@2.4.0/dist/jspdf.umd.min.js
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18. //TODO:页面过多时自动重新划分
  19.  
  20.  
  21.  
  22.  
  23. //公共库SakiProgress
  24. var SakiProgress = {
  25. isLoaded: false,
  26. progres: false,
  27. pgDiv: false,
  28. textSpan: false,
  29. first: false,
  30. alertMode: false,
  31. init: function (color) {
  32. if (!this.isLoaded) {
  33. this.isLoaded = true;
  34. console.info("SakiProgress Initializing!\nVersion:1.0.3\nQinlili Tech:Github@qinlili23333");
  35. this.pgDiv = document.createElement("div");
  36. this.pgDiv.id = "pgdiv";
  37. this.pgDiv.style = "z-index:9999;position:fixed;background-color:white;min-height:32px;width:auto;height:32px;left:0px;right:0px;top:0px;box-shadow:0px 2px 2px 1px rgba(0, 0, 0, 0.5);transition:opacity 0.5s;display:none;";
  38. this.pgDiv.style.opacity = 0;
  39. this.first = document.body.firstElementChild;
  40. document.body.insertBefore(this.pgDiv, this.first);
  41. this.first.style.transition = "margin-top 0.5s"
  42. this.progress = document.createElement("div");
  43. this.progress.id = "dlprogress"
  44. this.progress.style = "position: absolute;top: 0;bottom: 0;left: 0;background-color: #F17C67;z-index: -1;width:0%;transition: width 0.25s ease-in-out,opacity 0.25s,background-color 1s;"
  45. if (color) {
  46. this.setColor(color);
  47. }
  48. this.pgDiv.appendChild(this.progress);
  49. this.textSpan = document.createElement("span");
  50. this.textSpan.style = "padding-left:4px;font-size:24px;";
  51. this.textSpan.style.display = "inline-block"
  52. this.pgDiv.appendChild(this.textSpan);
  53. var css = ".barBtn:hover{ background-color: #cccccc }.barBtn:active{ background-color: #999999 }";
  54. var style = document.createElement('style');
  55. if (style.styleSheet) {
  56. style.styleSheet.cssText = css;
  57. } else {
  58. style.appendChild(document.createTextNode(css));
  59. }
  60. document.getElementsByTagName('head')[0].appendChild(style);
  61. console.info("SakiProgress Initialized!");
  62. } else {
  63. console.error("Multi Instance Error-SakiProgress Already Loaded!");
  64. }
  65. },
  66. destroy: function () {
  67. if (this.pgDiv) {
  68. document.body.removeChild(this.pgDiv);
  69. this.isLoaded = false;
  70. this.progres = false;
  71. this.pgDiv = false;
  72. this.textSpan = false;
  73. this.first = false;
  74. console.info("SakiProgress Destroyed!You Can Reload Later!");
  75. }
  76. },
  77. setPercent: function (percent) {
  78. if (this.progress) {
  79. this.progress.style.width = percent + "%";
  80. } else {
  81. console.error("Not Initialized Error-Please Call `init` First!");
  82. }
  83. },
  84. clearProgress: function () {
  85. if (this.progress) {
  86. this.progress.style.opacity = 0;
  87. setTimeout(function () { SakiProgress.progress.style.width = "0%"; }, 500);
  88. setTimeout(function () { SakiProgress.progress.style.opacity = 1; }, 750);
  89. } else {
  90. console.error("Not Initialized Error-Please Call `init` First!")
  91. }
  92. },
  93. hideDiv: function () {
  94. if (this.pgDiv) {
  95. if (this.alertMode) {
  96. setTimeout(function () {
  97. SakiProgress.pgDiv.style.opacity = 0;
  98. SakiProgress.first.style.marginTop = "";
  99. setTimeout(function () {
  100. SakiProgress.pgDiv.style.display = "none";
  101. }, 500);
  102. }, 3000);
  103. } else {
  104. this.pgDiv.style.opacity = 0;
  105. this.first.style.marginTop = "";
  106. setTimeout(function () {
  107. SakiProgress.pgDiv.style.display = "none";
  108. }, 500);
  109. }
  110. }
  111. else {
  112. console.error("Not Initialized Error-Please Call `init` First!");
  113. }
  114. },
  115. showDiv: function () {
  116. if (this.pgDiv) {
  117. this.pgDiv.style.display = "";
  118. setTimeout(function () { SakiProgress.pgDiv.style.opacity = 1; }, 10);
  119. this.first.style.marginTop = (this.pgDiv.clientHeight + 8) + "px";
  120. }
  121. else {
  122. console.error("Not Initialized Error-Please Call `init` First!");
  123. }
  124. },
  125. setText: function (text) {
  126. if (this.textSpan) {
  127. if (this.alertMode) {
  128. setTimeout(function () {
  129. if (!SakiProgress.alertMode) {
  130. SakiProgress.textSpan.innerText = text;
  131. }
  132. }, 3000);
  133. } else {
  134. this.textSpan.innerText = text;
  135. }
  136. }
  137. else {
  138. console.error("Not Initialized Error-Please Call `init` First!");
  139. }
  140. },
  141. setTextAlert: function (text) {
  142. if (this.textSpan) {
  143. this.textSpan.innerText = text;
  144. this.alertMode = true;
  145. setTimeout(function () { this.alertMode = false; }, 3000);
  146. }
  147. else {
  148. console.error("Not Initialized Error-Please Call `init` First!");
  149. }
  150. },
  151. setColor: function (color) {
  152. if (this.progress) {
  153. this.progress.style.backgroundColor = color;
  154. }
  155. else {
  156. console.error("Not Initialized Error-Please Call `init` First!");
  157. }
  158. },
  159. addBtn: function (img) {
  160. if (this.pgDiv) {
  161. var btn = document.createElement("img");
  162. btn.style = "display: inline-block;right:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
  163. btn.className = "barBtn"
  164. btn.src = img;
  165. this.pgDiv.appendChild(btn);
  166. return btn;
  167. }
  168. else {
  169. console.error("Not Initialized Error-Please Call `init` First!");
  170. }
  171. },
  172. removeBtn: function (btn) {
  173. if (this.pgDiv) {
  174. if (btn) {
  175. this.pgDiv.removeChild(btn);
  176. }
  177. }
  178. else {
  179. console.error("Not Initialized Error-Please Call `init` First!");
  180. }
  181. }
  182. }
  183. SakiProgress.init();
  184. console.log("Initializing Apabi Downloader...Engine:AvA PDF");
  185. var jsPDF=jspdf.jsPDF;
  186. try{
  187. console.log(jsPDF)
  188. console.log("jsPDF Ready!")
  189. }catch{
  190. console.error("jsPDF Not Ready!")
  191. }
  192. //解除右键
  193. document.body.oncontextmenu = ""
  194. var pageTotal = 0;
  195. var picUrl = ""
  196. var pageCurrent = 1;
  197. var donePage=0;
  198. var urlhost = "";
  199. var PDFfile=false;
  200. var imgList=[];
  201. var imgDataList=[];
  202. var imgEle=document.createElement("img");
  203. document.body.appendChild(imgEle);
  204.  
  205.  
  206.  
  207.  
  208.  
  209.  
  210. //Array多线程快速下载
  211. function downloadPicList(list,dataList) {
  212. for(var j=0;list[j];j++){
  213. toDataURL(list[j],j,function(data,page){
  214. dataList[page]=data;
  215. donePage++
  216. SakiProgress.setPercent(donePage/pageTotal*90)
  217. SakiProgress.setText("已下载"+donePage+"页...")
  218. if(donePage==pageTotal){
  219. SakiProgress.setText("准备生成PDF...")
  220. makePDF();
  221. }
  222. })
  223. }
  224.  
  225.  
  226.  
  227.  
  228. //读取图片
  229. function toDataURL(url,page, callback) {
  230. var xhr = new XMLHttpRequest();
  231. xhr.onreadystatechange=()=>{
  232. if(xhr.readyState === 4 && xhr.status >300) {
  233. console.log("错误,重试:"+url)
  234. toDataURL(url,page,callback);
  235. }
  236. }
  237. xhr.onload = function() {
  238. var reader = new FileReader();
  239. reader.onloadend = function() {
  240. callback(reader.result,page);
  241. }
  242. reader.readAsDataURL(xhr.response);
  243. };
  244. xhr.open('GET', url);
  245. xhr.responseType = 'blob';
  246. xhr.send();
  247. }
  248. }
  249.  
  250.  
  251.  
  252. //制作PDF
  253. function makePDF(){
  254. for(var k=0;imgDataList[k];k++){
  255. imgEle.src=imgDataList[k];
  256. PDFfile.addImage(imgDataList[k],"JPEG",0,0,imgEle.naturalWidth,imgEle.naturalHeight,"Page"+(k+1),"SLOW")
  257. PDFfile.addPage();
  258. SakiProgress.setText("已生成"+k+"页...")
  259. }
  260. SakiProgress.setText("正在制作PDF...")
  261. PDFfile.save("Apabi.pdf",{returnPromise:true}).then(finish => {
  262. SakiProgress.clearProgress;
  263. SakiProgress.hideDiv();
  264. });
  265. }
  266. //批量下载
  267. function batchDownload() {
  268. SakiProgress.showDiv()
  269. SakiProgress.setText("正在读取页面信息...")
  270. //最大化图片尺寸
  271. currentHeight = 4096;
  272. currentWidth = 4096;
  273. pageTotal = document.getElementById("TotalCount").innerText;
  274. console.log("Initializing image list...")
  275. if (document.location.host=="cebxol.apabi.com"||document.location.host=="cebxol.apabiedu.com"){
  276. urlhost="/"
  277. }else{
  278. urlhost="/OnLineReader/"
  279. }
  280. for(var i=1;i<=pageTotal;i++){
  281. imgList[i-1]=window.location.origin + urlhost + encodeURI(getUrl(i));
  282. }
  283. SakiProgress.setText("正在读取参数并建立PDF...")
  284. imgEle.onload=function(){
  285. var ori
  286. if(imgEle.naturalWidth>imgEle.naturalHeight){ori="l"}else{ori="p"}
  287. PDFfile=new jsPDF({
  288. orientation: ori,
  289. unit: 'px',
  290. format: [imgEle.naturalWidth,imgEle.naturalHeight],
  291. putOnlyUsedFonts:true,
  292. });
  293. SakiProgress.setText("正在准备下载页面...")
  294. downloadPicList(imgList,imgDataList)
  295. }
  296. imgEle.src=imgList[0]
  297. }
  298.  
  299. //导出目录
  300. function indexDownload(){
  301. }
  302.  
  303. //创建下载按钮
  304. document.querySelector("body > div.page > div.header").style.width="auto"
  305. var downloadBtn = document.createElement("a");
  306. downloadBtn.innerText = "下载全书";
  307. downloadBtn.onclick = function () { batchDownload() }
  308. document.querySelector("body > div.page > div.header > ul").appendChild(downloadBtn);
  309. var downloadIndexBtn = document.createElement("a");
  310. downloadIndexBtn.innerText = "下载目录";
  311. downloadIndexBtn.onclick = function () { indexDownload() }
  312. document.querySelector("body > div.page > div.header > ul").appendChild(downloadIndexBtn);
  313.  
  314. })();