HyReadEPUBV2

适配新版

目前为 2022-08-26 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name HyReadEPUBV2
  3. // @namespace https://qinlili.bid
  4. // @version 0.2.2
  5. // @description 适配新版
  6. // @author 琴梨梨
  7. // @match https://service.ebook.hyread.com.tw/ebookservice/epubreader/hyread/v3/openbook2.jsp?*
  8. // @icon https://webcdn2.ebook.hyread.com.tw/Template/store/favicon/favicon.ico
  9. // @grant none
  10. // @require https://lib.baomitu.com/jquery/3.6.0/jquery.min.js#sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==
  11. // @require https://lib.baomitu.com/forge/0.10.0/forge.all.min.js#sha512-vD/QEI4MRcUFkosDvj9l/W20cvpkM5zSLPbWUqwscPySsicOTwapvHLHCQ1k8CCufi+1nOEJlENsAwQJNIygbg==
  12. // @require https://lib.baomitu.com/jszip/3.10.1/jszip.min.js#sha512-XMVd28F1oH/O71fzwBnV7HucLxVwtxf26XV8P4wPk26EDxuGZ91N8bsOttmnomcCD3CS5ZMRL50H0GgOHvegtg==
  13. // @require https://cdn.jsdelivr.net/npm/js-base64@3.7.2/base64.min.js
  14. // @license MPL2.0
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19. //CODENAME:SALMON
  20. //初始化依赖
  21. const SakiProgress = {
  22. isLoaded: false,
  23. progres: false,
  24. pgDiv: false,
  25. textSpan: false,
  26. first: false,
  27. alertMode: false,
  28. init: function (color) {
  29. if (!this.isLoaded) {
  30. this.isLoaded = true;
  31. console.info("SakiProgress Initializing!\nVersion:1.0.4\nQinlili Tech:Github@qinlili23333");
  32. this.pgDiv = document.createElement("div");
  33. this.pgDiv.id = "pgdiv";
  34. 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;";
  35. this.pgDiv.style.opacity = 0;
  36. this.first = document.body.firstElementChild;
  37. document.body.insertBefore(this.pgDiv, this.first);
  38. this.first.style.transition = "margin-top 0.5s"
  39. this.progress = document.createElement("div");
  40. this.progress.id = "dlprogress"
  41. 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;"
  42. if (color) {
  43. this.setColor(color);
  44. }
  45. this.pgDiv.appendChild(this.progress);
  46. this.textSpan = document.createElement("span");
  47. this.textSpan.style = "padding-left:4px;font-size:24px;";
  48. this.textSpan.style.display = "inline-block"
  49. this.pgDiv.appendChild(this.textSpan);
  50. var css = ".barBtn:hover{ background-color: #cccccc }.barBtn:active{ background-color: #999999 }";
  51. var style = document.createElement('style');
  52. if (style.styleSheet) {
  53. style.styleSheet.cssText = css;
  54. } else {
  55. style.appendChild(document.createTextNode(css));
  56. }
  57. document.getElementsByTagName('head')[0].appendChild(style);
  58. console.info("SakiProgress Initialized!");
  59. } else {
  60. console.error("Multi Instance Error-SakiProgress Already Loaded!");
  61. }
  62. },
  63. destroy: function () {
  64. if (this.pgDiv) {
  65. document.body.removeChild(this.pgDiv);
  66. this.isLoaded = false;
  67. this.progres = false;
  68. this.pgDiv = false;
  69. this.textSpan = false;
  70. this.first = false;
  71. console.info("SakiProgress Destroyed!You Can Reload Later!");
  72. }
  73. },
  74. setPercent: function (percent) {
  75. if (this.progress) {
  76. this.progress.style.width = percent + "%";
  77. } else {
  78. console.error("Not Initialized Error-Please Call `init` First!");
  79. }
  80. },
  81. clearProgress: function () {
  82. if (this.progress) {
  83. this.progress.style.opacity = 0;
  84. setTimeout(function () { SakiProgress.progress.style.width = "0%"; }, 500);
  85. setTimeout(function () { SakiProgress.progress.style.opacity = 1; }, 750);
  86. } else {
  87. console.error("Not Initialized Error-Please Call `init` First!")
  88. }
  89. },
  90. hideDiv: function () {
  91. if (this.pgDiv) {
  92. if (this.alertMode) {
  93. setTimeout(function () {
  94. SakiProgress.pgDiv.style.opacity = 0;
  95. SakiProgress.first.style.marginTop = "";
  96. setTimeout(function () {
  97. SakiProgress.pgDiv.style.display = "none";
  98. }, 500);
  99. }, 3000);
  100. } else {
  101. this.pgDiv.style.opacity = 0;
  102. this.first.style.marginTop = "";
  103. setTimeout(function () {
  104. SakiProgress.pgDiv.style.display = "none";
  105. }, 500);
  106. }
  107. }
  108. else {
  109. console.error("Not Initialized Error-Please Call `init` First!");
  110. }
  111. },
  112. showDiv: function () {
  113. if (this.pgDiv) {
  114. this.pgDiv.style.display = "";
  115. setTimeout(function () { SakiProgress.pgDiv.style.opacity = 1; }, 10);
  116. this.first.style.marginTop = (this.pgDiv.clientHeight + 8) + "px";
  117. }
  118. else {
  119. console.error("Not Initialized Error-Please Call `init` First!");
  120. }
  121. },
  122. setText: function (text) {
  123. if (this.textSpan) {
  124. if (this.alertMode) {
  125. setTimeout(function () {
  126. if (!SakiProgress.alertMode) {
  127. SakiProgress.textSpan.innerText = text;
  128. }
  129. }, 3000);
  130. } else {
  131. this.textSpan.innerText = text;
  132. }
  133. }
  134. else {
  135. console.error("Not Initialized Error-Please Call `init` First!");
  136. }
  137. },
  138. setTextAlert: function (text) {
  139. if (this.textSpan) {
  140. this.textSpan.innerText = text;
  141. this.alertMode = true;
  142. setTimeout(function () { this.alertMode = false; }, 3000);
  143. }
  144. else {
  145. console.error("Not Initialized Error-Please Call `init` First!");
  146. }
  147. },
  148. setColor: function (color) {
  149. if (this.progress) {
  150. this.progress.style.backgroundColor = color;
  151. }
  152. else {
  153. console.error("Not Initialized Error-Please Call `init` First!");
  154. }
  155. },
  156. addBtn: function (img) {
  157. if (this.pgDiv) {
  158. var btn = document.createElement("img");
  159. btn.style = "display: inline-block;right:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
  160. btn.className = "barBtn"
  161. btn.src = img;
  162. this.pgDiv.appendChild(btn);
  163. return btn;
  164. }
  165. else {
  166. console.error("Not Initialized Error-Please Call `init` First!");
  167. }
  168. },
  169. removeBtn: function (btn) {
  170. if (this.pgDiv) {
  171. if (btn) {
  172. this.pgDiv.removeChild(btn);
  173. }
  174. }
  175. else {
  176. console.error("Not Initialized Error-Please Call `init` First!");
  177. }
  178. }
  179. };
  180. const sleep = delay => new Promise(resolve => setTimeout(resolve, delay));
  181. const dlFile = (link, name) => {
  182. let eleLink = document.createElement('a');
  183. eleLink.download = name;
  184. eleLink.style.display = 'none';
  185. eleLink.href = link;
  186. document.body.appendChild(eleLink);
  187. eleLink.click();
  188. document.body.removeChild(eleLink);
  189. };
  190.  
  191.  
  192. SakiProgress.init();
  193. SakiProgress.showDiv();
  194. SakiProgress.setText("正在等待密钥...");
  195. let key = false;
  196. let dlHyRead = async (key, url) => {
  197. //开始下载
  198. await sleep(100);
  199. let fileList = [];
  200. SakiProgress.showDiv();
  201. SakiProgress.setText("正在准备下载...");
  202. console.log("Igniting Salmon Core ...");
  203. const basePath = url;
  204. await sleep(100);
  205. SakiProgress.setPercent(12);
  206. SakiProgress.setText("正在读取EPUB信息...");
  207. let container = await (await fetch(basePath + "META-INF/container.xml")).blob();
  208. fileList.push({ file: container, path: "META-INF/container.xml" });
  209. let containerParsed = new DOMParser().parseFromString(await container.text(), "text/xml");
  210. console.log(containerParsed);
  211. await sleep(100);
  212. SakiProgress.setPercent(15);
  213. SakiProgress.setText("正在读取文件清单...");
  214. let rootfile = containerParsed.getElementsByTagName("rootfile")[0];
  215. if (rootfile.getAttribute("media-type") == "application/oebps-package+xml" || confirm("该书的信息似乎不符合标准,继续吗?")) {
  216. //OEBPS/content.opf
  217. let itemPath = rootfile.getAttribute("full-path");
  218. console.log(basePath + itemPath);
  219. let opf = await (await fetch(basePath + itemPath)).blob();
  220. fileList.push({ file: opf, path: itemPath });
  221. let opfParsed = new DOMParser().parseFromString(await opf.text(), "text/xml");
  222. console.log(opfParsed);
  223. await sleep(100);
  224. SakiProgress.setPercent(20);
  225. SakiProgress.setText("正在索引文件...");
  226. let itemList = opfParsed.getElementsByTagName("item");
  227. let baseItemPath = basePath + itemPath.substring(0, itemPath.lastIndexOf("/")) + "/";
  228. let zipPath = itemPath.substring(0, itemPath.lastIndexOf("/")) + "/"
  229. for (let i = 0; itemList[i]; i++) {
  230. SakiProgress.setPercent(20 + i / itemList.length * 60);
  231. SakiProgress.setText("正在下载文件[" + i + "/" + itemList.length + "]...");
  232. let item = itemList[i];
  233. if (item.getAttribute("href")) {
  234. let itemBlob = await (await fetch(baseItemPath + item.getAttribute("href"))).blob();
  235. let path = zipPath + item.getAttribute("href");
  236. let type = item.getAttribute("properties") || "normal";
  237. fileList.push({ file: itemBlob, path: path, type: type });
  238. }
  239. };
  240. console.log(fileList);
  241. SakiProgress.setPercent(80);
  242. SakiProgress.setText("正在解密文本流...");
  243. await sleep(50);
  244. let advancedDecrypt = "0";
  245. let removeList = []
  246. window.addEventListener("message", e => {
  247. if (e.data.action == "remove") {
  248. removeList.push(e.data.filename);
  249. };
  250. })
  251. for (const file of fileList) {
  252. if (file.path.endsWith(".xhtml")) {
  253. let fileBuffer = forge.util.createBuffer((await file.file.arrayBuffer()));
  254. let decryptor = forge.cipher.createDecipher("AES-ECB", key);
  255. let output
  256. if (decryptor.start(), decryptor.update(fileBuffer), !decryptor.finish()){
  257. //解密失败,推测为未加密文件
  258. output=await file.file.text();
  259. }else{
  260. output = decryptor.output.toString();
  261. };
  262. if (output.indexOf("//<![CDATA[") > 0) {
  263. //触发进阶解密:CANVAS解密模式
  264. if (advancedDecrypt === "0") {
  265. advancedDecrypt = confirm("检测到EPUB自身已被混淆或加密,是否启用进阶解密?\n该功能不稳定,可能无法正常工作\n启用该功能可以让生成的EPUB文件被更多软件支持\n但会延长十倍甚至九倍解密时间\n进阶解密必须保持浏览器处于前台,严禁最小化或切换到其他标签页\n若解密过程失去响应,请打开F12提交日志反馈")
  266. }
  267. if (advancedDecrypt) {
  268. SakiProgress.setText("初始化进阶解密组件...");
  269. let urlList = [];
  270. fileList.forEach(file => {
  271. urlList.push({
  272. filename: file.path.substr(file.path.lastIndexOf("/") + 1),
  273. url: URL.createObjectURL(file.file)
  274. })
  275. });
  276. console.log(urlList);
  277. let decryptFrame = document.createElement("iframe");
  278. decryptFrame.frameBorder = 0;
  279. decryptFrame.style = "padding:100%;z-index:9999;position:fixed;width:100%;margin-top:0px;height:100%;left:0px;right:0px;top:0px;";
  280. document.body.appendChild(decryptFrame);
  281. SakiProgress.setText("加载文档数据...");
  282. let xhtmlParsed = new DOMParser().parseFromString(output, "text/html");
  283. console.log(xhtmlParsed);
  284. let scriptsBackup = [];
  285. for (; xhtmlParsed.getElementsByTagName("script")[0];) {
  286. scriptsBackup.push(xhtmlParsed.getElementsByTagName("script")[0].outerHTML);
  287. xhtmlParsed.getElementsByTagName("script")[0].remove();
  288. };
  289. let origincss = "";
  290. [].forEach.call(xhtmlParsed.head.children, node => {
  291. if (node.rel == "stylesheet") {
  292. console.log(node);
  293. origincss = node.getAttribute("href");
  294. let cssname = node.href;
  295. cssname = cssname.substr(cssname.lastIndexOf("/") + 1);
  296. urlList.forEach(file => {
  297. if (file.filename == cssname) {
  298. node.href = file.url;
  299. }
  300. })
  301. }
  302. });
  303. let hookScript = document.createElement("script");
  304. hookScript.innerHTML = `
  305. console.log("Salmon Advanced Decrypt Mode:0x1");
  306. window.addEventListener("message", e => {
  307. console.log(e);
  308. switch (e.data.method) {
  309. case "file": {
  310. console.log("Filelist Loaded.");
  311. window.list = e.data.list;
  312. break;
  313. };
  314. case "restore":{
  315. //还原css
  316. [].forEach.call(document.head.children,node=>{
  317. if(node.rel=="stylesheet"){
  318. console.log(node);
  319. node.href=e.data.css;
  320. }
  321. });
  322. break;
  323. };
  324. case "convert": {
  325. //用图片取代canvas
  326. [].forEach.call(document.getElementsByTagName("canvas"), obj => {
  327. let img = document.createElement("img");
  328. document.body.appendChild(img);
  329. img.src = obj.toDataURL("image/jpeg", 0.95);
  330. img.width = obj.width;
  331. img.height = obj.height;
  332. obj.remove()
  333. });
  334. //清理所有不该出现在EPUB里的元素
  335. for(;document.getElementsByTagName("script")[0];){
  336. document.getElementsByTagName("script")[0].remove();
  337. };
  338. break;
  339. };
  340. }
  341. }, false);
  342. var valueProp = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
  343. Object.defineProperty(Image.prototype, 'src', {
  344. set: function (image) {
  345. console.log("Hook Image Loader...");
  346. let filename = image.substr(image.lastIndexOf("/")+1);
  347. window.list.forEach(file => {
  348. if (file.filename == filename) {
  349. image = file.url;
  350. console.log("Hook Image Success!");
  351. window.parent.postMessage({action:"remove",filename:filename}, "*");
  352. }
  353. })
  354. valueProp.set.call(this, image);
  355. }
  356. });
  357. (draw => {
  358. CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
  359. console.log([sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight]);
  360. draw.call(this, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
  361. };
  362. })(CanvasRenderingContext2D.prototype.drawImage);
  363. `
  364. xhtmlParsed.head.appendChild(hookScript);
  365. let docFrame = xhtmlParsed.documentElement.outerHTML;
  366. let decryptDoc = decryptFrame.contentDocument;
  367. //debugger
  368. decryptDoc.open();
  369. decryptDoc.write(docFrame);
  370. await sleep(10);
  371. decryptFrame.contentWindow.postMessage({
  372. method: "file",
  373. list: urlList
  374. }, "*");
  375. SakiProgress.setText("加载全文数据...");
  376. await sleep(10);
  377. scriptsBackup.forEach(script => {
  378. decryptDoc.write(script);
  379. });
  380. SakiProgress.setText("渲染页面...");
  381. await sleep(10);
  382. //debugger
  383. decryptFrame.contentWindow.postMessage({
  384. method: "convert"
  385. }, "*");
  386. decryptDoc.close();
  387. SakiProgress.setText("解密页面...");
  388. await sleep(10);
  389. decryptFrame.contentWindow.postMessage({
  390. method: "restore",
  391. css: origincss
  392. }, "*");
  393. await sleep(10);
  394. output = decryptFrame.contentDocument.documentElement.outerHTML;
  395. SakiProgress.setText("保存页面...");
  396. await sleep(10);
  397. document.body.removeChild(decryptFrame);
  398. }
  399. }
  400. file.file = new Blob([output]);
  401. }
  402. };
  403. SakiProgress.setPercent(84);
  404. SakiProgress.setText("正在清理...");
  405. await sleep(100);
  406. let emptyfile = await (await fetch("data:image/jpeg,1")).blob();
  407. removeList.forEach(remove => {
  408. fileList.forEach(file => {
  409. if (file.path.substr(file.path.lastIndexOf("/") + 1) == remove && file.type == "normal") {
  410. file.file = emptyfile;
  411. }
  412. });
  413. });
  414. SakiProgress.setPercent(85);
  415. SakiProgress.setText("正在生成文件结构...");
  416. await sleep(100);
  417. let zip = new JSZip();
  418. fileList.forEach(file => zip.file(file.path, file.file, { binary: true }));
  419. await sleep(100);
  420. SakiProgress.setPercent(90);
  421. SakiProgress.setText("正在打包...");
  422. let zipFile = await zip.generateAsync({
  423. type: "blob",
  424. compression: "DEFLATE",
  425. compressionOptions: {
  426. level: 9
  427. }
  428. });
  429. SakiProgress.setPercent(97);
  430. SakiProgress.setText("正在导出...");
  431. await sleep(2000);
  432. dlFile(URL.createObjectURL(zipFile), document.title + ".epub");
  433. SakiProgress.clearProgress();
  434. SakiProgress.setText("下载成功!");
  435. await sleep(3000);
  436. SakiProgress.hideDiv();
  437. alert("恭喜下载成功!\n如果对成品满意的话,记得给脚本好评\n除了关注琴梨梨外,也别忘了关注温柔可爱铸币的塔宝@帅比笙歌超可爱OvO,本脚本开发过程全程以塔宝的直播作为BGM")
  438. } else {
  439. SakiProgress.clearProgress();
  440. SakiProgress.hideDiv();
  441. console.log("User Cancel.");
  442. }
  443. }
  444. let injectWorker = `self.addEventListener("message",(e)=>{console.log(e.data);})`;
  445. const originWorker = window.Worker
  446. window.Worker = function (aurl, options) {
  447. console.log("捉住Worker请求了喵!")
  448. console.log(aurl)
  449. var oReq = new XMLHttpRequest();
  450. oReq.open("GET", aurl, false);
  451. oReq.send();
  452. const mergedWorker = URL.createObjectURL(new Blob([injectWorker + oReq.response]))
  453. console.log("给Worker加点料!寄汤来咯!")
  454. let hookWorker = new originWorker(mergedWorker, options);
  455. let originMessage = hookWorker.postMessage;
  456. hookWorker.postMessage = (msg, list) => {
  457. console.log(msg);
  458. if (msg.token && !key) {
  459. key = Base64.atob(msg.token);
  460. SakiProgress.setText("已得到密钥!正在准备文档信息...");
  461. SakiProgress.setPercent(10);
  462. dlHyRead(key, msg.url.substr(0, msg.url.indexOf("_epub") + 6))
  463. }
  464. return originMessage.call(hookWorker, msg, list);
  465. }
  466. hookWorker.addEventListener('message', (event) => {
  467. console.log(hookWorker);
  468. console.log(event.data);
  469. }, true);
  470. return hookWorker;
  471. };
  472. })();