// ==UserScript==
// @name DownloadAllContent ZIP addon
// @name:zh-CN 怠惰小说下载器 ZIP 扩展
// @name:zh-TW 怠惰小説下載器 ZIP 擴充
// @namespace hoothin
// @version 0.6
// @description Save content as ZIP for DownloadAllContent
// @description:zh-CN 下载时分章节保存 TXT 并打包为 ZIP
// @description:zh-TW 下載時分章節儲存 TXT 並打包為 ZIP
// @author hoothin
// @match *://*/*
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @require https://unpkg.com/jszip@3.7.1/dist/jszip.js
// ==/UserScript==
(function() {
'use strict';
const threadNum = 20;//圖片下載綫程數
const ocrApi = "";
//const ocrApi = "http://127.0.0.1:416/?img=%t";//ocr 識別 api
var _unsafeWindow = (typeof unsafeWindow == 'undefined') ? window : unsafeWindow;
var zipTips, loadingImgs = [], loadingIndex = 0, loadedNum = 0, ocrNum = 0, zip, blobDict = {}, ocrDict = {};
async function dataURLToBlob(dataurl, ext = "jpeg") {
return await new Promise((resolve) => {
if (!dataurl) return resolve(null);
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext('2d');
var img = new Image();
img.setAttribute("crossOrigin", "anonymous");
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for(var i = 0; i < imageData.data.length; i += 4) {
if(imageData.data[i + 3] == 0) {
imageData.data[i] = 255;
imageData.data[i + 1] = 255;
imageData.data[i + 2] = 255;
imageData.data[i + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
canvas.toBlob(blob => {
resolve(blob);
}, `image/${ext}`);
};
img.onerror = function() {
resolve(null);
};
img.src = dataurl;
});
}
async function blobToDataURL(blob) {
return await new Promise((resolve) => {
var a = new FileReader();
a.readAsDataURL(blob);
a.onload = function (e) {
resolve(e.target.result);
};
a.onerror = function (e) {
resolve(null);
};
});
}
async function urlToBlob(url) {
return await new Promise((resolve) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType:'blob',
timeout:20000,
headers: {
origin: location.origin,
referer: location.href,
accept: "*/*"
},
onload: async function(d) {
resolve(d.response);
},
onerror: function() {
resolve(null);
},
ontimeout: function() {
resolve(null);
}
});
})
}
async function downloadImage(cb) {
if (loadedNum >= loadingImgs.length || loadingIndex >= loadingImgs.length) return;
let i = loadingIndex;
loadingIndex++;
let src = loadingImgs[i];
let blob = await urlToBlob(src);
if (blob) {
zip.file("imgs/" + i + ".jpg", blob);
zipTips.innerText = `Download images ${loadedNum + "/" + loadingImgs.length}...`;
blobDict[src] = blob;
}
loadedNum++;
if (loadedNum >= loadingImgs.length) {
if (ocrApi) {
loadingIndex = 0;
let length = Math.min(loadingImgs.length, threadNum);
for (let i = 0; i < length; i++) {
recognizeImage(cb);
}
} else {
cb();
}
} else downloadImage(cb);
}
async function recognizeImage(cb) {
if (ocrNum >= loadingImgs.length || loadingIndex >= loadingImgs.length) return;
let i = loadingIndex;
loadingIndex++;
let src = loadingImgs[i];
let dataURL = await blobToDataURL(blobDict[src]);
let blob = await dataURLToBlob(dataURL, "jpeg");
dataURL = await blobToDataURL(blob);
let result = await new Promise((resolve) => {
GM_xmlhttpRequest({
method: 'GET',
url: ocrApi.replace("%t", encodeURIComponent(dataURL.replace("data:image/jpeg;base64,", ""))),
timeout:20000,
onload: function(d) {
resolve(d.response);
},
onerror: function() {
resolve(null);
},
ontimeout: function() {
resolve(null);
}
});
});
ocrNum++;
zipTips.innerText = `Recognize images ${ocrNum + "/" + loadingImgs.length}...`;
if (result) {
ocrDict[i] = result;
}
if (ocrNum >= loadingImgs.length) {
cb();
} else recognizeImage(cb);
}
function downloadImages(cb) {
let length = Math.min(loadingImgs.length, threadNum);
if (length == 0) {
return cb();
}
for (let i = 0; i < length; i++) {
downloadImage(cb);
}
}
const mdImgReg = /!\[img\]\((http.+?)\)/;
_unsafeWindow.downloadAllContentSaveAsZip = async (rCats, info, callback) => {
loadingIndex = 0;
loadedNum = 0;
ocrNum = 0;
if (!zipTips) {
zipTips = document.createElement("div");
}
if (!zipTips.parentNode) {
let txtDownWords = _unsafeWindow.txtDownWords;
if (txtDownWords) {
txtDownWords.appendChild(zipTips);
}
}
console.log("Begin compress to ZIP...");
zipTips.innerText = "Begin compress to ZIP...";
zip = new JSZip();
zip.file("readme.txt", info);
let zipTemp = [];
for (let i = 0; i < rCats.length; i++) {
let cat = rCats[i];
if (!cat) continue;
let catTitle = cat.match(/.*?\r\n/);
if (!catTitle) continue;
catTitle = catTitle[0].trim();
cat = cat.replace(catTitle, "").replace(/^[\n\r]+/, "");
let imgMatch = cat.match(mdImgReg), hasImg = false;
while (imgMatch) {
let index = loadingImgs.indexOf(imgMatch[1]);
if (index == -1) {
index = loadingImgs.length;
loadingImgs.push(imgMatch[1]);
}
cat = cat.replace(imgMatch[0], `![img](imgs/${index}.jpg)`);
imgMatch = cat.match(mdImgReg);
hasImg = true;
}
zipTemp.push({title: (i + 1) + " - " + catTitle.replace(/[\*\/:<>\?\\\|\r\n]/g, "_").slice(0, 50), hasImg: hasImg, content: cat});
}
downloadImages(() => {
zipTemp.forEach(d => {
if (ocrApi) {
d.hasImg = false;
Object.keys(ocrDict).forEach(key => {
d.content = d.content.replace(new RegExp(`!\\[img\\]\\(imgs/${key}\\.jpg\\)`, 'g'), ocrDict[key]);
});
}
zip.file(d.title + (d.hasImg ? ".md" : ".txt"), d.content);
});
zip.generateAsync({type: "blob", compression: "DEFLATE"}, meta => {zipTips.innerText = "percent: " + ((meta && meta.percent && meta.percent.toFixed(2)) || "100") + "%"}).then(function(content){
callback(content);
zipTips.innerText = "";
})
});
}
})();