您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
适配新版
// ==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/[email protected]/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; }; })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址