怠惰小説下載器 ZIP 擴充

下載時分章節儲存 TXT 並打包為 ZIP

  1. // ==UserScript==
  2. // @name DownloadAllContent ZIP addon
  3. // @name:zh-CN 怠惰小说下载器 ZIP 扩展
  4. // @name:zh-TW 怠惰小説下載器 ZIP 擴充
  5. // @namespace hoothin
  6. // @version 0.6.1
  7. // @description Save content as ZIP for DownloadAllContent
  8. // @description:zh-CN 下载时分章节保存 TXT 并打包为 ZIP
  9. // @description:zh-TW 下載時分章節儲存 TXT 並打包為 ZIP
  10. // @author hoothin
  11. // @match *://*/*
  12. // @grant unsafeWindow
  13. // @grant GM_xmlhttpRequest
  14. // @require https://unpkg.com/jszip@3.7.1/dist/jszip.js
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19. const threadNum = 20;//圖片下載綫程數
  20. const ocrApi = "";
  21. //const ocrApi = "http://127.0.0.1:416/?img=%t";//ocr 識別 api
  22. var _unsafeWindow = (typeof unsafeWindow == 'undefined') ? window : unsafeWindow;
  23. var zipTips, loadingImgs = [], loadingIndex = 0, loadedNum = 0, ocrNum = 0, zip, blobDict = {}, ocrDict = {};
  24.  
  25. async function dataURLToBlob(dataurl, ext = "jpeg") {
  26. return await new Promise((resolve) => {
  27. if (!dataurl) return resolve(null);
  28. var canvas = document.createElement('CANVAS');
  29. var ctx = canvas.getContext('2d');
  30. var img = new Image();
  31. img.setAttribute("crossOrigin", "anonymous");
  32. img.onload = function() {
  33. canvas.width = img.width;
  34. canvas.height = img.height;
  35. ctx.drawImage(img, 0, 0);
  36. var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  37. for(var i = 0; i < imageData.data.length; i += 4) {
  38. if(imageData.data[i + 3] == 0) {
  39. imageData.data[i] = 255;
  40. imageData.data[i + 1] = 255;
  41. imageData.data[i + 2] = 255;
  42. imageData.data[i + 3] = 255;
  43. }
  44. }
  45. ctx.putImageData(imageData, 0, 0);
  46. canvas.toBlob(blob => {
  47. resolve(blob);
  48. }, `image/${ext}`);
  49. };
  50. img.onerror = function() {
  51. resolve(null);
  52. };
  53. img.src = dataurl;
  54. });
  55. }
  56.  
  57. async function blobToDataURL(blob) {
  58. return await new Promise((resolve) => {
  59. var a = new FileReader();
  60. a.readAsDataURL(blob);
  61. a.onload = function (e) {
  62. resolve(e.target.result);
  63. };
  64. a.onerror = function (e) {
  65. resolve(null);
  66. };
  67. });
  68. }
  69.  
  70. async function urlToBlob(url) {
  71. return await new Promise((resolve) => {
  72. GM_xmlhttpRequest({
  73. method: 'GET',
  74. url: url,
  75. responseType:'blob',
  76. timeout:20000,
  77. headers: {
  78. origin: location.origin,
  79. referer: location.href,
  80. accept: "*/*"
  81. },
  82. onload: async function(d) {
  83. resolve(d.response);
  84. },
  85. onerror: function() {
  86. resolve(null);
  87. },
  88. ontimeout: function() {
  89. resolve(null);
  90. }
  91. });
  92. })
  93. }
  94.  
  95. async function downloadImage(cb) {
  96. if (loadedNum >= loadingImgs.length || loadingIndex >= loadingImgs.length) return;
  97. let i = loadingIndex;
  98. loadingIndex++;
  99. let src = loadingImgs[i];
  100. let blob = await urlToBlob(src);
  101. if (blob) {
  102. zip.file("imgs/" + i + ".jpg", blob);
  103. zipTips.innerText = `Download images ${loadedNum + "/" + loadingImgs.length}...`;
  104. blobDict[src] = blob;
  105. }
  106. loadedNum++;
  107. if (loadedNum >= loadingImgs.length) {
  108. if (ocrApi) {
  109. loadingIndex = 0;
  110. let length = Math.min(loadingImgs.length, threadNum);
  111. for (let i = 0; i < length; i++) {
  112. recognizeImage(cb);
  113. }
  114. } else {
  115. cb();
  116. }
  117. } else downloadImage(cb);
  118. }
  119.  
  120. async function recognizeImage(cb) {
  121. if (ocrNum >= loadingImgs.length || loadingIndex >= loadingImgs.length) return;
  122. let i = loadingIndex;
  123. loadingIndex++;
  124. let src = loadingImgs[i];
  125. let dataURL = await blobToDataURL(blobDict[src]);
  126. let blob = await dataURLToBlob(dataURL, "jpeg");
  127. dataURL = await blobToDataURL(blob);
  128. let result = await new Promise((resolve) => {
  129. GM_xmlhttpRequest({
  130. method: 'GET',
  131. url: ocrApi.replace("%t", encodeURIComponent(dataURL.replace("data:image/jpeg;base64,", ""))),
  132. timeout:20000,
  133. onload: function(d) {
  134. resolve(d.response);
  135. },
  136. onerror: function() {
  137. resolve(null);
  138. },
  139. ontimeout: function() {
  140. resolve(null);
  141. }
  142. });
  143. });
  144. ocrNum++;
  145. zipTips.innerText = `Recognize images ${ocrNum + "/" + loadingImgs.length}...`;
  146. if (result) {
  147. ocrDict[i] = result;
  148. }
  149. if (ocrNum >= loadingImgs.length) {
  150. cb();
  151. } else recognizeImage(cb);
  152. }
  153.  
  154. function downloadImages(cb) {
  155. let length = Math.min(loadingImgs.length, threadNum);
  156. if (length == 0) {
  157. return cb();
  158. }
  159. for (let i = 0; i < length; i++) {
  160. downloadImage(cb);
  161. }
  162. }
  163.  
  164. const mdImgReg = /!\[img\]\((http.+?)\)/;
  165. _unsafeWindow.downloadAllContentSaveAsZip = async (rCats, info, callback) => {
  166. loadingIndex = 0;
  167. loadedNum = 0;
  168. ocrNum = 0;
  169. if (!zipTips) {
  170. zipTips = document.createElement("div");
  171. }
  172. if (!zipTips.parentNode) {
  173. let txtDownWords = _unsafeWindow.txtDownWords;
  174. if (txtDownWords) {
  175. txtDownWords.appendChild(zipTips);
  176. }
  177. }
  178. console.log("Begin compress to ZIP...");
  179. zipTips.innerText = "Begin compress to ZIP...";
  180. zip = new JSZip();
  181. zip.file("readme.txt", info);
  182. let zipTemp = [];
  183. for (let i = 0; i < rCats.length; i++) {
  184. let cat = rCats[i];
  185. if (!cat) continue;
  186. let catTitle = cat.match(/.*?\r\n/);
  187. if (!catTitle) continue;
  188. catTitle = catTitle[0].trim();
  189. cat = cat.replace(catTitle, "").replace(/^[\n\r]+/, "");
  190. let imgMatch = cat.match(mdImgReg), hasImg = false;
  191. while (imgMatch) {
  192. let index = loadingImgs.indexOf(imgMatch[1]);
  193. if (index == -1) {
  194. index = loadingImgs.length;
  195. loadingImgs.push(imgMatch[1]);
  196. }
  197. cat = cat.replace(imgMatch[0], `![img](imgs/${index}.jpg)`);
  198. imgMatch = cat.match(mdImgReg);
  199. hasImg = true;
  200. }
  201. zipTemp.push({title: catTitle.replace(/[\*\/:<>\?\\\|\r\n]/g, "_").slice(0, 50), hasImg: hasImg, content: cat});
  202. }
  203. downloadImages(() => {
  204. zipTemp.forEach(d => {
  205. if (ocrApi) {
  206. d.hasImg = false;
  207. Object.keys(ocrDict).forEach(key => {
  208. d.content = d.content.replace(new RegExp(`!\\[img\\]\\(imgs/${key}\\.jpg\\)`, 'g'), ocrDict[key]);
  209. });
  210. }
  211. zip.file(d.title + (d.hasImg ? ".md" : ".txt"), d.content);
  212. });
  213. zip.generateAsync({type: "blob", compression: "DEFLATE"}, meta => {zipTips.innerText = "percent: " + ((meta && meta.percent && meta.percent.toFixed(2)) || "100") + "%"}).then(function(content){
  214. callback(content);
  215. zipTips.innerText = "";
  216. })
  217. });
  218. }
  219. })();

QingJ © 2025

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