- // ==UserScript==
- // @name HyReadEPUBV2
- // @namespace https://qinlili.bid
- // @version 0.2.2
- // @description 适配新版
- // @author 琴梨梨
- // @match https://service.ebook.hyread.com.tw/ebookservice/epubreader/hyread/v3/openbook2.jsp?*
- // @icon https://webcdn2.ebook.hyread.com.tw/Template/store/favicon/favicon.ico
- // @grant none
- // @require https://lib.baomitu.com/jquery/3.6.0/jquery.min.js#sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==
- // @require https://lib.baomitu.com/forge/0.10.0/forge.all.min.js#sha512-vD/QEI4MRcUFkosDvj9l/W20cvpkM5zSLPbWUqwscPySsicOTwapvHLHCQ1k8CCufi+1nOEJlENsAwQJNIygbg==
- // @require https://lib.baomitu.com/jszip/3.10.1/jszip.min.js#sha512-XMVd28F1oH/O71fzwBnV7HucLxVwtxf26XV8P4wPk26EDxuGZ91N8bsOttmnomcCD3CS5ZMRL50H0GgOHvegtg==
- // @require https://cdn.jsdelivr.net/npm/js-base64@3.7.2/base64.min.js
- // @license MPL2.0
- // ==/UserScript==
-
- (function () {
- 'use strict';
- //CODENAME:SALMON
- //初始化依赖
- const SakiProgress = {
- isLoaded: false,
- progres: false,
- pgDiv: false,
- textSpan: false,
- first: false,
- alertMode: false,
- init: function (color) {
- if (!this.isLoaded) {
- this.isLoaded = true;
- console.info("SakiProgress Initializing!\nVersion:1.0.4\nQinlili Tech:Github@qinlili23333");
- this.pgDiv = document.createElement("div");
- this.pgDiv.id = "pgdiv";
- 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;";
- this.pgDiv.style.opacity = 0;
- this.first = document.body.firstElementChild;
- document.body.insertBefore(this.pgDiv, this.first);
- this.first.style.transition = "margin-top 0.5s"
- this.progress = document.createElement("div");
- this.progress.id = "dlprogress"
- 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;"
- if (color) {
- this.setColor(color);
- }
- this.pgDiv.appendChild(this.progress);
- this.textSpan = document.createElement("span");
- this.textSpan.style = "padding-left:4px;font-size:24px;";
- this.textSpan.style.display = "inline-block"
- this.pgDiv.appendChild(this.textSpan);
- var css = ".barBtn:hover{ background-color: #cccccc }.barBtn:active{ background-color: #999999 }";
- var style = document.createElement('style');
- if (style.styleSheet) {
- style.styleSheet.cssText = css;
- } else {
- style.appendChild(document.createTextNode(css));
- }
- document.getElementsByTagName('head')[0].appendChild(style);
- console.info("SakiProgress Initialized!");
- } else {
- console.error("Multi Instance Error-SakiProgress Already Loaded!");
- }
- },
- destroy: function () {
- if (this.pgDiv) {
- document.body.removeChild(this.pgDiv);
- this.isLoaded = false;
- this.progres = false;
- this.pgDiv = false;
- this.textSpan = false;
- this.first = false;
- console.info("SakiProgress Destroyed!You Can Reload Later!");
- }
- },
- setPercent: function (percent) {
- if (this.progress) {
- this.progress.style.width = percent + "%";
- } else {
- console.error("Not Initialized Error-Please Call `init` First!");
- }
- },
- clearProgress: function () {
- if (this.progress) {
- this.progress.style.opacity = 0;
- setTimeout(function () { SakiProgress.progress.style.width = "0%"; }, 500);
- setTimeout(function () { SakiProgress.progress.style.opacity = 1; }, 750);
- } else {
- console.error("Not Initialized Error-Please Call `init` First!")
- }
- },
- hideDiv: function () {
- if (this.pgDiv) {
- if (this.alertMode) {
- setTimeout(function () {
- SakiProgress.pgDiv.style.opacity = 0;
- SakiProgress.first.style.marginTop = "";
- setTimeout(function () {
- SakiProgress.pgDiv.style.display = "none";
- }, 500);
- }, 3000);
- } else {
- this.pgDiv.style.opacity = 0;
- this.first.style.marginTop = "";
- setTimeout(function () {
- SakiProgress.pgDiv.style.display = "none";
- }, 500);
- }
- }
- else {
- console.error("Not Initialized Error-Please Call `init` First!");
- }
- },
- showDiv: function () {
- if (this.pgDiv) {
- this.pgDiv.style.display = "";
- setTimeout(function () { SakiProgress.pgDiv.style.opacity = 1; }, 10);
- this.first.style.marginTop = (this.pgDiv.clientHeight + 8) + "px";
- }
- else {
- console.error("Not Initialized Error-Please Call `init` First!");
- }
- },
- setText: function (text) {
- if (this.textSpan) {
- if (this.alertMode) {
- setTimeout(function () {
- if (!SakiProgress.alertMode) {
- SakiProgress.textSpan.innerText = text;
- }
- }, 3000);
- } else {
- this.textSpan.innerText = text;
- }
- }
- else {
- console.error("Not Initialized Error-Please Call `init` First!");
- }
- },
- setTextAlert: function (text) {
- if (this.textSpan) {
- this.textSpan.innerText = text;
- this.alertMode = true;
- setTimeout(function () { this.alertMode = false; }, 3000);
- }
- else {
- console.error("Not Initialized Error-Please Call `init` First!");
- }
- },
- setColor: function (color) {
- if (this.progress) {
- this.progress.style.backgroundColor = color;
- }
- else {
- console.error("Not Initialized Error-Please Call `init` First!");
- }
- },
- addBtn: function (img) {
- if (this.pgDiv) {
- var btn = document.createElement("img");
- btn.style = "display: inline-block;right:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
- btn.className = "barBtn"
- btn.src = img;
- this.pgDiv.appendChild(btn);
- return btn;
- }
- else {
- console.error("Not Initialized Error-Please Call `init` First!");
- }
- },
- removeBtn: function (btn) {
- if (this.pgDiv) {
- if (btn) {
- this.pgDiv.removeChild(btn);
- }
- }
- else {
- console.error("Not Initialized Error-Please Call `init` First!");
- }
- }
- };
- const sleep = delay => new Promise(resolve => setTimeout(resolve, delay));
- const dlFile = (link, name) => {
- let eleLink = document.createElement('a');
- eleLink.download = name;
- eleLink.style.display = 'none';
- eleLink.href = link;
- document.body.appendChild(eleLink);
- eleLink.click();
- document.body.removeChild(eleLink);
- };
-
-
- SakiProgress.init();
- SakiProgress.showDiv();
- SakiProgress.setText("正在等待密钥...");
- let key = false;
- let dlHyRead = async (key, url) => {
- //开始下载
- await sleep(100);
- let fileList = [];
- SakiProgress.showDiv();
- SakiProgress.setText("正在准备下载...");
- console.log("Igniting Salmon Core ...");
- const basePath = url;
- await sleep(100);
- SakiProgress.setPercent(12);
- SakiProgress.setText("正在读取EPUB信息...");
- let container = await (await fetch(basePath + "META-INF/container.xml")).blob();
- fileList.push({ file: container, path: "META-INF/container.xml" });
- let containerParsed = new DOMParser().parseFromString(await container.text(), "text/xml");
- console.log(containerParsed);
- await sleep(100);
- SakiProgress.setPercent(15);
- SakiProgress.setText("正在读取文件清单...");
- let rootfile = containerParsed.getElementsByTagName("rootfile")[0];
- if (rootfile.getAttribute("media-type") == "application/oebps-package+xml" || confirm("该书的信息似乎不符合标准,继续吗?")) {
- //OEBPS/content.opf
- let itemPath = rootfile.getAttribute("full-path");
- console.log(basePath + itemPath);
- let opf = await (await fetch(basePath + itemPath)).blob();
- fileList.push({ file: opf, path: itemPath });
- let opfParsed = new DOMParser().parseFromString(await opf.text(), "text/xml");
- console.log(opfParsed);
- await sleep(100);
- SakiProgress.setPercent(20);
- SakiProgress.setText("正在索引文件...");
- let itemList = opfParsed.getElementsByTagName("item");
- let baseItemPath = basePath + itemPath.substring(0, itemPath.lastIndexOf("/")) + "/";
- let zipPath = itemPath.substring(0, itemPath.lastIndexOf("/")) + "/"
- for (let i = 0; itemList[i]; i++) {
- SakiProgress.setPercent(20 + i / itemList.length * 60);
- SakiProgress.setText("正在下载文件[" + i + "/" + itemList.length + "]...");
- let item = itemList[i];
- if (item.getAttribute("href")) {
- let itemBlob = await (await fetch(baseItemPath + item.getAttribute("href"))).blob();
- let path = zipPath + item.getAttribute("href");
- let type = item.getAttribute("properties") || "normal";
- fileList.push({ file: itemBlob, path: path, type: type });
- }
- };
- console.log(fileList);
- SakiProgress.setPercent(80);
- SakiProgress.setText("正在解密文本流...");
- await sleep(50);
- let advancedDecrypt = "0";
- let removeList = []
- window.addEventListener("message", e => {
- if (e.data.action == "remove") {
- removeList.push(e.data.filename);
- };
- })
- for (const file of fileList) {
- if (file.path.endsWith(".xhtml")) {
- let fileBuffer = forge.util.createBuffer((await file.file.arrayBuffer()));
- let decryptor = forge.cipher.createDecipher("AES-ECB", key);
- let output
- if (decryptor.start(), decryptor.update(fileBuffer), !decryptor.finish()){
- //解密失败,推测为未加密文件
- output=await file.file.text();
- }else{
- output = decryptor.output.toString();
- };
- if (output.indexOf("//<![CDATA[") > 0) {
- //触发进阶解密:CANVAS解密模式
- if (advancedDecrypt === "0") {
- advancedDecrypt = confirm("检测到EPUB自身已被混淆或加密,是否启用进阶解密?\n该功能不稳定,可能无法正常工作\n启用该功能可以让生成的EPUB文件被更多软件支持\n但会延长十倍甚至九倍解密时间\n进阶解密必须保持浏览器处于前台,严禁最小化或切换到其他标签页\n若解密过程失去响应,请打开F12提交日志反馈")
- }
- if (advancedDecrypt) {
- SakiProgress.setText("初始化进阶解密组件...");
- let urlList = [];
- fileList.forEach(file => {
- urlList.push({
- filename: file.path.substr(file.path.lastIndexOf("/") + 1),
- url: URL.createObjectURL(file.file)
- })
- });
- console.log(urlList);
- let decryptFrame = document.createElement("iframe");
- decryptFrame.frameBorder = 0;
- decryptFrame.style = "padding:100%;z-index:9999;position:fixed;width:100%;margin-top:0px;height:100%;left:0px;right:0px;top:0px;";
- document.body.appendChild(decryptFrame);
- SakiProgress.setText("加载文档数据...");
- let xhtmlParsed = new DOMParser().parseFromString(output, "text/html");
- console.log(xhtmlParsed);
- let scriptsBackup = [];
- for (; xhtmlParsed.getElementsByTagName("script")[0];) {
- scriptsBackup.push(xhtmlParsed.getElementsByTagName("script")[0].outerHTML);
- xhtmlParsed.getElementsByTagName("script")[0].remove();
- };
- let origincss = "";
- [].forEach.call(xhtmlParsed.head.children, node => {
- if (node.rel == "stylesheet") {
- console.log(node);
- origincss = node.getAttribute("href");
- let cssname = node.href;
- cssname = cssname.substr(cssname.lastIndexOf("/") + 1);
- urlList.forEach(file => {
- if (file.filename == cssname) {
- node.href = file.url;
- }
- })
- }
- });
- let hookScript = document.createElement("script");
- hookScript.innerHTML = `
- console.log("Salmon Advanced Decrypt Mode:0x1");
- window.addEventListener("message", e => {
- console.log(e);
- switch (e.data.method) {
- case "file": {
- console.log("Filelist Loaded.");
- window.list = e.data.list;
- break;
- };
- case "restore":{
- //还原css
- [].forEach.call(document.head.children,node=>{
- if(node.rel=="stylesheet"){
- console.log(node);
- node.href=e.data.css;
- }
- });
- break;
- };
- case "convert": {
- //用图片取代canvas
- [].forEach.call(document.getElementsByTagName("canvas"), obj => {
- let img = document.createElement("img");
- document.body.appendChild(img);
- img.src = obj.toDataURL("image/jpeg", 0.95);
- img.width = obj.width;
- img.height = obj.height;
- obj.remove()
- });
- //清理所有不该出现在EPUB里的元素
- for(;document.getElementsByTagName("script")[0];){
- document.getElementsByTagName("script")[0].remove();
- };
- break;
- };
- }
- }, false);
- var valueProp = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
- Object.defineProperty(Image.prototype, 'src', {
- set: function (image) {
- console.log("Hook Image Loader...");
- let filename = image.substr(image.lastIndexOf("/")+1);
- window.list.forEach(file => {
- if (file.filename == filename) {
- image = file.url;
- console.log("Hook Image Success!");
- window.parent.postMessage({action:"remove",filename:filename}, "*");
- }
- })
- valueProp.set.call(this, image);
- }
- });
- (draw => {
- CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
- console.log([sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight]);
- draw.call(this, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
- };
- })(CanvasRenderingContext2D.prototype.drawImage);
- `
- xhtmlParsed.head.appendChild(hookScript);
- let docFrame = xhtmlParsed.documentElement.outerHTML;
- let decryptDoc = decryptFrame.contentDocument;
- //debugger
- decryptDoc.open();
- decryptDoc.write(docFrame);
- await sleep(10);
- decryptFrame.contentWindow.postMessage({
- method: "file",
- list: urlList
- }, "*");
- SakiProgress.setText("加载全文数据...");
- await sleep(10);
- scriptsBackup.forEach(script => {
- decryptDoc.write(script);
- });
- SakiProgress.setText("渲染页面...");
- await sleep(10);
- //debugger
- decryptFrame.contentWindow.postMessage({
- method: "convert"
- }, "*");
- decryptDoc.close();
- SakiProgress.setText("解密页面...");
- await sleep(10);
- decryptFrame.contentWindow.postMessage({
- method: "restore",
- css: origincss
- }, "*");
- await sleep(10);
- output = decryptFrame.contentDocument.documentElement.outerHTML;
- SakiProgress.setText("保存页面...");
- await sleep(10);
- document.body.removeChild(decryptFrame);
- }
- }
- file.file = new Blob([output]);
- }
- };
- SakiProgress.setPercent(84);
- SakiProgress.setText("正在清理...");
- await sleep(100);
- let emptyfile = await (await fetch("data:image/jpeg,1")).blob();
- removeList.forEach(remove => {
- fileList.forEach(file => {
- if (file.path.substr(file.path.lastIndexOf("/") + 1) == remove && file.type == "normal") {
- file.file = emptyfile;
- }
- });
- });
- SakiProgress.setPercent(85);
- SakiProgress.setText("正在生成文件结构...");
- await sleep(100);
- let zip = new JSZip();
- fileList.forEach(file => zip.file(file.path, file.file, { binary: true }));
- await sleep(100);
- SakiProgress.setPercent(90);
- SakiProgress.setText("正在打包...");
- let zipFile = await zip.generateAsync({
- type: "blob",
- compression: "DEFLATE",
- compressionOptions: {
- level: 9
- }
- });
- SakiProgress.setPercent(97);
- SakiProgress.setText("正在导出...");
- await sleep(2000);
- dlFile(URL.createObjectURL(zipFile), document.title + ".epub");
- SakiProgress.clearProgress();
- SakiProgress.setText("下载成功!");
- await sleep(3000);
- SakiProgress.hideDiv();
- alert("恭喜下载成功!\n如果对成品满意的话,记得给脚本好评\n除了关注琴梨梨外,也别忘了关注温柔可爱铸币的塔宝@帅比笙歌超可爱OvO,本脚本开发过程全程以塔宝的直播作为BGM")
- } else {
- SakiProgress.clearProgress();
- SakiProgress.hideDiv();
- console.log("User Cancel.");
- }
- }
- let injectWorker = `self.addEventListener("message",(e)=>{console.log(e.data);})`;
- const originWorker = window.Worker
- window.Worker = function (aurl, options) {
- console.log("捉住Worker请求了喵!")
- console.log(aurl)
- var oReq = new XMLHttpRequest();
- oReq.open("GET", aurl, false);
- oReq.send();
- const mergedWorker = URL.createObjectURL(new Blob([injectWorker + oReq.response]))
- console.log("给Worker加点料!寄汤来咯!")
- let hookWorker = new originWorker(mergedWorker, options);
- let originMessage = hookWorker.postMessage;
- hookWorker.postMessage = (msg, list) => {
- console.log(msg);
- if (msg.token && !key) {
- key = Base64.atob(msg.token);
- SakiProgress.setText("已得到密钥!正在准备文档信息...");
- SakiProgress.setPercent(10);
- dlHyRead(key, msg.url.substr(0, msg.url.indexOf("_epub") + 6))
- }
- return originMessage.call(hookWorker, msg, list);
- }
- hookWorker.addEventListener('message', (event) => {
- console.log(hookWorker);
- console.log(event.data);
- }, true);
- return hookWorker;
- };
- })();