Wenku Doc Downloader

下载文档,导出PDF或图片压缩包。支持①百度文库②豆丁网③爱问共享资料(新浪文档)④得力文库⑤道客巴巴⑥360doc个人图书馆。在文档页面左侧中间有Wenku Doc Download按钮区,说明脚本生效了。反馈请提供网址。

目前為 2022-01-10 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Wenku Doc Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.4.6
  5. // @description 下载文档,导出PDF或图片压缩包。支持①百度文库②豆丁网③爱问共享资料(新浪文档)④得力文库⑤道客巴巴⑥360doc个人图书馆。在文档页面左侧中间有Wenku Doc Download按钮区,说明脚本生效了。反馈请提供网址。
  6. // @author allenlv2690@gmail.com
  7. // @match *://*.docin.com/p-*
  8. // @match *://ishare.iask.sina.com.cn/f/*
  9. // @match *://www.deliwenku.com/p-*
  10. // @match *://www.doc88.com/p-*
  11. // @match *://www.360doc.com/content/*
  12. // @match *://wenku.baidu.com/*/*
  13. // @require https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.js
  14. // @require https://cdn.bootcdn.net/ajax/libs/jszip/3.6.0/jszip.js
  15. // @require https://cdn.bootcdn.net/ajax/libs/jspdf/2.3.1/jspdf.umd.min.js
  16. // @require https://cdn.bootcdn.net/ajax/libs/html2canvas/0.5.0-beta4/html2canvas.min.js
  17. // @icon https://www.google.com/s2/favicons?domain=limestart.cn
  18. // @grant none
  19. // @license GPL-3.0-only
  20. // @create 2021-11-22
  21. // ==/UserScript==
  22.  
  23. (function () {
  24. 'use strict';
  25.  
  26. let utils = {
  27. /**
  28. * 创建并下载文件
  29. * @param {string} fileName
  30. * @param {string} content
  31. */
  32. createAndDownloadFile: function(fileName, content) {
  33. let aTag = document.createElement('a');
  34. let blob = new Blob([content]);
  35. aTag.download = fileName;
  36. aTag.href = URL.createObjectURL(blob);
  37. aTag.click();
  38. URL.revokeObjectURL(blob);
  39. },
  40.  
  41. /**
  42. * 睡眠 delay 毫秒
  43. * @param {Number} delay
  44. */
  45. sleep: function(delay) {
  46. let start = (new Date()).getTime();
  47. while ((new Date()).getTime() - start < delay) {
  48. continue;
  49. }
  50. },
  51.  
  52. /**
  53. * 允许打印页面
  54. */
  55. allowPrint: function() {
  56. let style = document.createElement("style");
  57. style.innerHTML = `@media print {
  58. body{
  59. display:block;
  60. }
  61. }`;
  62. document.head.appendChild(style);
  63. },
  64.  
  65. /**
  66. * 绑定事件到指定按钮,返回按钮引用
  67. * @param {Function} event click事件
  68. * @param {Array} args 事件的参数列表
  69. * @param {String} aim_btn 按钮的变量名
  70. * @param {String} new_text 按钮的新文本,为null则不替换
  71. * @returns 按钮元素的引用
  72. */
  73. setBtnEvent: function(event, args = [], aim_btn = "btn_3", new_text = null) {
  74. let btn = document.querySelector(`.${aim_btn.replace("_", "-")}`);
  75. // 如果需要,替换按钮内文本
  76. if (new_text) {
  77. btn.textContent = new_text;
  78. }
  79. // 绑定事件,添加到页面上
  80. btn.onclick = () => {
  81. this.enhanceBtnClickReaction(aim_btn);
  82. if (args.length) {
  83. event(...args);
  84. } else {
  85. event();
  86. }
  87. };
  88. return btn;
  89. },
  90.  
  91. /**
  92. * 强制隐藏元素
  93. * @param {String} selector
  94. */
  95. forceHide: function(selector) {
  96. document.querySelectorAll(selector).forEach((elem) => {
  97. elem.className += " force_hide";
  98. });
  99. let style = document.createElement("style");
  100. style.innerHTML = `.force_hide {
  101. visibility: hidden !important;
  102. }`;
  103. document.head.appendChild(style);
  104. },
  105.  
  106. /**
  107. * 隐藏按钮,打印页面,显示按钮
  108. */
  109. hideBtnThenPrint: function() {
  110. // 隐藏按钮,然后打印页面
  111. let section = document.getElementsByClassName("btns_section")[0];
  112. section.style.display = "none";
  113. window.print();
  114. // 打印结束,显示按钮
  115. section.style.removeProperty("display");
  116. },
  117.  
  118. /**
  119. * 返回times个倍数连接的str
  120. * @param {String} str
  121. * @param {Number} times
  122. * @returns multiplied_str
  123. */
  124. multiplyStr: function(str, times) {
  125. let str_list = [];
  126. for (let i = 0; i < times; i++) {
  127. str_list.push(str);
  128. }
  129. return str_list.join("");
  130. },
  131.  
  132. /**
  133. * 增强按钮(默认为蓝色按钮:展开文档)的点击效果
  134. * @param {String} custom_btn 按钮变量名
  135. */
  136. enhanceBtnClickReaction: function(custom_btn = null) {
  137. let aim_btn;
  138. // 如果不使用自定义按钮元素,则默认为使用蓝色展开文档按钮
  139. if (!custom_btn || custom_btn === "btn_1") {
  140. aim_btn = document.querySelector(".btn-1");
  141. } else {
  142. aim_btn = document.querySelector(`.${custom_btn.replace("_", "-")}`);
  143. }
  144.  
  145. let old_color = aim_btn.style.color; // 保存旧的颜色
  146. let old_text = aim_btn.textContent; // 保存旧的文字内容
  147. // 变黑缩小
  148. aim_btn.style.color = "black";
  149. aim_btn.style.fontWeight = "normal";
  150. aim_btn.textContent = `->${old_text}<-`;
  151. // 复原加粗
  152. let changeColorBack = function() {
  153. aim_btn.style.color = old_color;
  154. aim_btn.style.fontWeight = "bold";
  155. aim_btn.textContent = old_text;
  156. };
  157. setTimeout(changeColorBack, 1250);
  158. },
  159.  
  160. /**
  161. * 切换按钮显示/隐藏状态
  162. * @param {String} aim_btn 按钮变量名
  163. * @returns 按钮元素的引用
  164. */
  165. toggleBtnStatus: function(aim_btn) {
  166. let btn = document.querySelector(`.${aim_btn.replace("_", "-")}`);
  167. let display = getComputedStyle(btn).display;
  168. // return;
  169. if (display === "none") {
  170. btn.style.display = "block";
  171. } else {
  172. btn.style.display = "none";
  173. }
  174. return btn;
  175. },
  176.  
  177. /**
  178. * 根据canvas元素数量返回quality值
  179. * @param {Number} canvas_amount
  180. * @returns quality: Number
  181. */
  182. getQualityByCanvasAmount: function(canvas_amount) {
  183. let quality;
  184. if (canvas_amount <= 25) {
  185. quality = 1.0;
  186. } else if (25 < canvas_amount <= 50) {
  187. quality = 0.85;
  188. } else {
  189. quality = 0.7;
  190. }
  191. return quality;
  192. },
  193.  
  194. /**
  195. * 用input框跳转到对应页码
  196. * @param {Element} cur_page 当前页码
  197. * @param {string} aim_page 目标页码
  198. * @param {string} event_type 键盘事件类型:"keyup" | "keypress" | "keydown"
  199. */
  200. jump2pageNo: function(cur_page, aim_page, event_type) {
  201. // 设置跳转页码为目标页码
  202. cur_page.value = aim_page;
  203. // 模拟回车事件来跳转
  204. let keyboard_event_enter = new KeyboardEvent(event_type, {
  205. bubbles: true,
  206. cancelable: true,
  207. keyCode: 13
  208. });
  209. cur_page.dispatchEvent(keyboard_event_enter);
  210. },
  211.  
  212. /**
  213. * 滚动到页面底部
  214. */
  215. scrollToBottom: function() {
  216. window.scrollTo({
  217. top: document.body.scrollHeight,
  218. behavior: "smooth"
  219. });
  220. },
  221.  
  222. /**
  223. * 用try移除元素
  224. * @param {Element} element 要移除的元素
  225. */
  226. tryToRemoveElement: function(element) {
  227. try {
  228. element.remove();
  229. } catch (e) {
  230. console.log();
  231. }
  232. },
  233.  
  234. /**
  235. * 用try移除 [元素列表1, 元素列表2, ...] 中的元素
  236. * @param {Array} elem_list_box 要移除的元素列表构成的列表
  237. */
  238. tryToRemoveSameElem: function(elem_list_box) {
  239. for (let elem_list of elem_list_box) {
  240. if (!elem_list) {
  241. continue;
  242. }
  243. for (let elem of elem_list) {
  244. try {
  245. elem.remove();
  246. } catch (e) {
  247. console.log();
  248. }
  249. }
  250. }
  251. },
  252.  
  253. /**
  254. * 使文档在页面上居中
  255. * @param {String} selector 文档容器的css选择器
  256. * @param {String} default_offset 文档部分向右偏移的百分比(0-59)
  257. * @returns 偏移值是否合法
  258. */
  259. centerDoc: function(selector, default_offset) {
  260. let doc_main = document.querySelector(selector);
  261. let offset = window.prompt("请输入偏移百分位:", default_offset);
  262. // 如果输入的数字不在 0-59 内,提醒用户重新设置
  263. if (offset.length === 1 && offset.search(/[0-9]/) !== -1) {
  264. doc_main.style.marginLeft = offset + "%";
  265. return true;
  266. } else if (offset.length === 2 && offset.search(/[1-5][0-9]/) !== -1) {
  267. doc_main.style.marginLeft = offset + "%";
  268. return true
  269. } else {
  270. alert("请输入一个正整数,范围在0至59之间,用来使文档居中\n(不同文档偏移量不同,所以需要手动调整)");
  271. return false;
  272. }
  273. },
  274.  
  275. /**
  276. * 调整按钮内文本
  277. * @param {String} aim_btn 按钮变量名
  278. * @param {String} new_text 新的文本,null则保留旧文本
  279. * @param {Boolean} recommend_btn 是否增加"(推荐)"到按钮文本
  280. * @param {Boolean} use_hint 是否提示"文档已经完全展开,可以导出"
  281. */
  282. modifyBtnText: function(aim_btn = "btn_2", new_text = null, recommend_btn = false, use_hint = true) {
  283. // 提示文档已经展开
  284. if (use_hint) {
  285. let hint = "文档已经完全展开,可以导出";
  286. alert(hint);
  287. }
  288. let btn = document.querySelector(`.${aim_btn.replace("_", "-")}`);
  289. // 要替换的文本
  290. if (new_text) {
  291. btn.textContent = new_text;
  292. }
  293. // 推荐按钮
  294. if (recommend_btn) {
  295. btn.textContent += "(推荐)";
  296. }
  297. },
  298.  
  299. /**
  300. * 将html元素转为canvas再合并到pdf中,最后下载pdf
  301. * @param {Array} elem_list html元素列表
  302. * @param {String} title 文档标题
  303. */
  304. html2PDF: async function(elem_list, title = "文档") {
  305. // 如果是空元素列表,返回null并终止函数
  306. if (elem_list.length === 0) {
  307. console.log("html2PDF was called, but no canvas element avaiable.");
  308. return null;
  309. }
  310. let tasks = []; // 存放异步任务
  311. let contents = []; // 存放canvas元素
  312. for (let elem of elem_list) {
  313. let task = html2canvas(elem).then((canvas) => {
  314. contents.push(canvas);
  315. });
  316. tasks.push(task);
  317. }
  318. // 等待全部page转化完成
  319. await Promise.all(tasks);
  320. // 控制台检查结果
  321. console.log("生成的canvas元素如下:");
  322. console.log(contents);
  323.  
  324. // 拿到canvas宽、高:如果第二页存在,就用第二页的宽高,如果不存在就用第一页的
  325. let model_page = document.querySelector("#pageNo-2") ? document.querySelector("#pageNo-2") : document.querySelector("#pageNo-1");
  326. let width, height;
  327. width = model_page.offsetWidth;
  328. height = model_page.offsetHeight;
  329. // 打包为pdf
  330. this.saveCanvasesToPDF(contents, title, width, height);
  331. },
  332.  
  333. /**
  334. * 下载全部图片链接,适用性:爱问共享资料、得力文库
  335. * @param {string} selector 图形元素的父级元素
  336. */
  337. savePicUrls: function(selector) {
  338. let pages = document.querySelectorAll(selector);
  339. let pic_urls = [];
  340.  
  341. for (let elem of pages) {
  342. let pic_obj = elem.children[0];
  343. let url = pic_obj.src;
  344. pic_urls.push(url);
  345. }
  346. let content = pic_urls.join("\n");
  347. // 启动下载
  348. this.createAndDownloadFile("urls.csv", content);
  349. },
  350.  
  351. /**
  352. * 存储所有canvas图形为png到一个压缩包
  353. * @param {Array} node_list canvas元素列表
  354. * @param {String} title 文档标题
  355. */
  356. saveCanvasesToZip: function(node_list, title) {
  357. // canvas元素转为png图像
  358. // 所有png合并为一个zip压缩包
  359. let zip = new JSZip();
  360. let n = node_list.length;
  361.  
  362. for (let i = 0; i < n; i++) {
  363. let canvas = node_list[i];
  364. let data_base64 = canvas.toDataURL();
  365. let blob = atob(data_base64.split(",")[1]);
  366. zip.file(`page-${i+1}.png`, blob, { binary: true });
  367. }
  368.  
  369. // 导出zip
  370. // promise.then(onCompleted, onRejected);
  371. zip.generateAsync({ type: "blob" }).then(function(content) {
  372. // see filesaver.js
  373. console.log(content);
  374. saveAs(content, `${title}.zip`);
  375. });
  376. },
  377.  
  378. /**
  379. * 将canvas转为jpeg,然后导出PDF
  380. * @param {Array} node_list canvas元素列表
  381. * @param {String} title 文档标题
  382. */
  383. saveCanvasesToPDF: function(node_list, title, width = 0, height = 0) {
  384. // 如果没有手动指定canvas的长宽,则自动检测
  385. if (!width && !height) {
  386. // 先获取第一个canvas用于判断竖向还是横向,以及得到页面长宽
  387. let first_canvas = node_list[0];
  388. // 如果style的长宽不存在,则直接用canvas的元素长宽
  389. let width_str, height_str;
  390. if (first_canvas.width && parseInt(first_canvas.width) && parseInt(first_canvas.height)) {
  391. [width_str, height_str] = [first_canvas.width, first_canvas.height];
  392. } else {
  393. [width_str, height_str] = [first_canvas.style.width.replace(/(px)|(rem)|(em)/, ""), first_canvas.style.height.replace(/(px)|(rem)|(em)/, "")];
  394. }
  395. // jsPDF的第三个参数为format,当自定义时,参数为数字数组。
  396. [width, height] = [parseFloat(width_str), parseFloat(height_str)];
  397. }
  398. console.log(`canvas数据:宽: ${width}px,高: ${height}px`);
  399. // 如果文档第一页的宽比长更大,则landscape,否则portrait
  400. let orientation = width > height ? 'l' : 'p';
  401. let pdf = new jspdf.jsPDF(orientation, 'px', [height, width]);
  402.  
  403. // 根据canvas数量确定quality
  404. let quality = this.getQualityByCanvasAmount(node_list.length);
  405.  
  406. // 保存每一页文档到每一页pdf
  407. node_list.forEach(function(canvas, index) {
  408. pdf.addImage(canvas.toDataURL("image/jpeg", quality), 'JPEG', 0, 0, width, height);
  409. // 如果当前不是文档最后一页,则需要添加下一个空白页
  410. if (index !== node_list.length - 1) {
  411. pdf.addPage();
  412. }
  413. });
  414.  
  415. // 导出文件
  416. pdf.save(`${title}.pdf`);
  417. },
  418.  
  419. /**
  420. * 取得elem的class为class_name的父级元素
  421. * @param {String} class_name
  422. * @param {Element} elem
  423. * @param {object} JSobj 默认为window.baiduJS
  424. */
  425. getParentByClassName: function(class_name, elem, JSobj = window.baiduJS) {
  426. let parent = elem.parentElement;
  427. let now_name;
  428. try {
  429. now_name = parent.className;
  430. } catch (e) {
  431. // 没有父级元素了,返回null
  432. return null;
  433. }
  434. let iterator_count = JSobj.iterator_count;
  435. if (iterator_count > 9) {
  436. // 超过最大迭代次数,认为不存在,返回null
  437. return null;
  438. }
  439. // 如果类名匹配,返回该节点
  440. if (now_name === class_name) {
  441. iterator_count = 0;
  442. return parent;
  443. }
  444. return this.getParentByClassName(class_name, parent, JSobj);
  445. },
  446.  
  447. /**
  448. * 创建5个按钮:展开文档、导出图片、导出PDF、未设定4、未设定5;默认均为隐藏
  449. */
  450. createBtns: function() {
  451. // 创建按钮组
  452. let section = document.createElement("section");
  453. section.className = "btns_section";
  454. section.innerHTML = `
  455. <p class="logo_tit">Wenku Doc Downloader</p>
  456. <button class="btn-1" title="请先滑到底部,使内容加载完,防止出现空白页">展开文档 😈</button>
  457. <button class="btn-2">导出图片 🖼️</button>
  458. <button class="btn-3">导出PDF 🌼</button>
  459. <button class="btn-4">未设定4</button>
  460. <button class="btn-5">未设定5</button>`;
  461. document.body.appendChild(section);
  462.  
  463. // 设定样式
  464. let style = document.createElement("style");
  465. style.innerHTML = `
  466. .btns_section{
  467. position: fixed;
  468. width: 154px;
  469. left: 10px;
  470. top: 32%;
  471. background: #E7F1FF;
  472. border: 2px solid #1676FF;
  473. padding: 0px 0px 10px 0px;
  474. font-weight: 600;
  475. border-radius: 2px;
  476. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
  477. 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
  478. 'Segoe UI Emoji', 'Segoe UI Symbol';
  479. z-index: 5000;
  480. }
  481. .logo_tit{
  482. width: 100%;
  483. background: #1676FF;
  484. text-align: center;
  485. font-size:12px ;
  486. color: #E7F1FF;
  487. line-height: 40px;
  488. height: 40px;
  489. margin: 0 0 16px 0;
  490. }
  491.  
  492. .btn-1{
  493. display: block;
  494. width: 128px;
  495. height: 28px;
  496. background: linear-gradient(180deg, #00E7F7 0%, #FEB800 0.01%, #FF8700 100%);
  497. border-radius: 4px;
  498. color: #fff;
  499. font-size: 12px;
  500. border: none;
  501. outline: none;
  502. margin: 8px auto;
  503. font-weight: bold;
  504. cursor: pointer;
  505. opacity: .9;
  506. }
  507. .btn-2{
  508. display: none;
  509. width: 128px;
  510. height: 28px;
  511. background: #07C160;
  512. border-radius: 4px;
  513. color: #fff;
  514. font-size: 12px;
  515. border: none;
  516. outline: none;
  517. margin: 8px auto;
  518. font-weight: bold;
  519. cursor: pointer;
  520. opacity: .9;
  521. }
  522. .btn-3{
  523. display: none;
  524. width: 128px;
  525. height: 28px;
  526. background:#FA5151;
  527. border-radius: 4px;
  528. color: #fff;
  529. font-size: 12px;
  530. border: none;
  531. outline: none;
  532. margin: 8px auto;
  533. font-weight: bold;
  534. cursor: pointer;
  535. opacity: .9;
  536. }
  537. .btn-4{
  538. display: none;
  539. width: 128px;
  540. height: 28px;
  541. background: #1676FF;
  542. border-radius: 4px;
  543. color: #fff;
  544. font-size: 12px;
  545. border: none;
  546. outline: none;
  547. margin: 8px auto;
  548. font-weight: bold;
  549. cursor: pointer;
  550. opacity: .9;
  551. }
  552. .btn-5{
  553. display: none;
  554. width: 128px;
  555. height: 28px;
  556. background: #ff6600;
  557. border-radius: 4px;
  558. color: #fff;
  559. font-size: 12px;
  560. border: none;
  561. outline: none;
  562. margin: 8px auto;
  563. font-weight: bold;
  564. cursor: pointer;
  565. opacity: .9;
  566. }
  567. .btn-1:hover,.btn-2:hover,.btn-3:hover,.btn-4,.btn-5:hover{ opacity: .8;}
  568. .btn-1:active,.btn-2:active,.btn-3:active,.btn-4,.btn-5:active{ opacity: 1;}`;
  569. document.head.appendChild(style);
  570. }
  571. };
  572.  
  573. /**
  574. * 清理百度文库页面的无关元素
  575. */
  576. function clearPage_Baidu() {
  577. let selectors = [
  578. "#hd, .aside, .reader-tools-bar-wrap, .sb-con, .bg-opacity",
  579. ".doc-tag-wrap, .doc-bottom-wrap, .ft, #ft, .crubms-wrap, .banner-ad",
  580. "#activity-tg, .top-ads-banner-wrap, .reader_ab_test, .tag-tips, .doc-value, .owner-desc-wrap"
  581. ];
  582. let elem_list = document.querySelectorAll(selectors.join(", "));
  583. for (let elem of elem_list) {
  584. utils.tryToRemoveElement(elem);
  585. }
  586. let nut_selector = ".fix-searchbar-wrap, #hd";
  587. utils.forceHide(nut_selector);
  588. }
  589.  
  590. /**
  591. * 清理无关页面元素并打印页面
  592. */
  593. function printPage_Baidu() {
  594. let read_more_btn = document.querySelector("#html-reader-go-more");
  595. let unfold_all;
  596.  
  597. if (read_more_btn) {
  598. unfold_all = read_more_btn.style.display === "none";
  599. } else {
  600. unfold_all = !Boolean(document.querySelector("[id*=next-pageList]"));
  601. }
  602. if (unfold_all) {
  603. if (confirm("文档都加载完毕了吗?")) {
  604. alert("如果出现大片空白页,说明文档有保护机制,无法打印");
  605. utils.allowPrint();
  606. clearPage_Baidu();
  607. utils.hideBtnThenPrint();
  608. }
  609. } else {
  610. alert("文档尚未完全展开,无法使用该功能。");
  611. }
  612. }
  613.  
  614. /**
  615. * 提取文字,导出txt。适用于百度文库
  616. */
  617. function saveText_Baidu() {
  618. // 判断是否存在文字元素
  619. let elems = document.querySelectorAll(".reader-txt-layer");
  620. if (!elems.length) {
  621. alert("当前页面没有文字元素\n如果你看到了文字说明它的原文档就是图片,所以提取不到文字");
  622. return;
  623. }
  624. // 判断页面是否加载完成
  625. if (!window.baiduJS.finished) {
  626. alert("仍有内容未加载完,无法使用该功能\n建议从头到尾慢速地再浏览一遍");
  627. return;
  628. }
  629. let title = document.title.split("-")[0].trim(); // 取得文档标题
  630. let page_texts = [];
  631. for (let elem of window.baiduJS.elems_map.values()) {
  632. // 取得该页文档下的全部文字
  633. let text = elem.textContent;
  634. page_texts.push(text);
  635. }
  636. utils.createAndDownloadFile(`${title}.txt`, page_texts.join("\n"));
  637. }
  638.  
  639. /**
  640. * 动态存储".ppt-image-wrap img"图形,并导出urls
  641. * @returns
  642. */
  643. function savePicUrls_Baidu() {
  644. let urls = [];
  645. let elems = document.querySelectorAll(".ppt-image-wrap img");
  646. if (!elems.length) {
  647. alert("当前页面没有PPT图形");
  648. return;
  649. }
  650. elems.forEach((elem) => {
  651. if (elem.hasAttribute("src")) {
  652. urls.push(elem.src);
  653. } else {
  654. urls.push(elem.getAttribute("data-src"));
  655. }
  656. });
  657. utils.createAndDownloadFile("urls.csv", urls.join("\n"));
  658. }
  659.  
  660. /**
  661. * 动态存储".reader-pic-item"图形,并导出urls
  662. * @returns
  663. */
  664. function savePicUrls_BaiduNonPPT() {
  665. // 判断是否存在非PPT图形元素
  666. let elems = document.querySelectorAll(".reader-pic-item");
  667. if (!elems.length) {
  668. alert("当前页面没有非PPT图形");
  669. return;
  670. }
  671. // 判断是否页面都加载完成
  672. if (!window.baiduJS.finished) {
  673. alert("仍有内容未加载完,无法使用该功能\n建议从头到尾慢速地再浏览一遍");
  674. return;
  675. }
  676. // 找到img元素,导出urls
  677. let img_urls = [];
  678. for (let elem of window.baiduJS.elems_map.values()) {
  679. // 取得img元素
  680. elem.querySelectorAll(".reader-pic-item").forEach((img) => {
  681. // 取得img链接
  682. let url = img.style.backgroundImage.split('"')[1];
  683. img_urls.push(url);
  684. });
  685. }
  686. utils.createAndDownloadFile("urls.csv", img_urls.join("\n"));
  687. }
  688.  
  689. /**
  690. * 根据键中的id数字对map排序
  691. * @param {Map} elems_map
  692. * @returns sorted_map
  693. */
  694. function sortMapByID$1(elems_map) {
  695. // id形式:pageNo-10
  696. let elems_arr = Array.from(elems_map);
  697. elems_arr.sort((item1, item2) => {
  698. // 从key中取出id
  699. let id1 = parseInt(item1[0].split("-")[1]);
  700. let id2 = parseInt(item2[0].split("-")[1]);
  701. // 升序排序
  702. return id1 - id2;
  703. });
  704. // 返回排序好的map
  705. return new Map(elems_arr);
  706. }
  707.  
  708. /**
  709. * 存储html元素。适用于百度文库的文字型文档
  710. */
  711. function storeHtmlElemts_Baidu() {
  712. let elems_map = window.baiduJS.elems_map;
  713. document.querySelectorAll("[class*=reader-main]").forEach(
  714. (elem) => {
  715. let origin_page_elem = utils.getParentByClassName("bd", elem);
  716. // 复制元素防止丢失
  717. let page_elem = origin_page_elem.cloneNode(true);
  718. let id = page_elem.id; // id的形式:pageNo-10
  719. if (!elems_map.has(id)) {
  720. elems_map.set(id, page_elem);
  721. }
  722. });
  723. if (elems_map.size === window.baiduJS.max_page) {
  724. // 根据id排序
  725. window.baiduJS.elems_map = sortMapByID$1(window.baiduJS.elems_map);
  726. window.baiduJS.finished = true;
  727. window.onscroll = () => { console.log(); };
  728. }
  729. }
  730.  
  731. /**
  732. * 百度文档下载策略
  733. */
  734. function baiduWenku() {
  735. // 原文档解析到预览文档
  736. if (location.href.includes("\u002f\u0076\u0069\u0065\u0077\u002f")) {
  737. utils.createBtns();
  738. let jump2sharePage_Baidu = function() {
  739. location.href = `https://${location.host}${location.pathname.replace("\u0076\u0069\u0065\u0077", "\u0073\u0068\u0061\u0072\u0065")}`;
  740. };
  741. utils.setBtnEvent(jump2sharePage_Baidu, [], "btn_1");
  742. // 预览文档清理广告
  743. } else if (location.href.includes("\u002f\u0073\u0068\u0061\u0072\u0065\u002f")) {
  744. utils.createBtns();
  745. // 隐藏按钮
  746. utils.toggleBtnStatus("btn_1");
  747. // 显示按钮
  748. utils.toggleBtnStatus("btn_2");
  749. utils.toggleBtnStatus("btn_3");
  750. utils.toggleBtnStatus("btn_4");
  751. utils.toggleBtnStatus("btn_5");
  752.  
  753. // 为导出内容提供全局变量,便于动态收集文档页元素的存取
  754. let max_page = parseInt(document.querySelector(".page-count").textContent.replace("/", ""));
  755.  
  756. window.baiduJS = {
  757. max_page: max_page,
  758. elems_map: new Map(), // id: element
  759. iterator_count: 0, // getParentByClassName的最大迭代次数为9
  760. finished: false // 是否收集完了全部文档页元素
  761. };
  762. // 跟随浏览,动态收集页面元素
  763. window.onscroll = storeHtmlElemts_Baidu;
  764. // 绑定事件到按钮
  765. utils.setBtnEvent(printPage_Baidu, [], "btn_2", "打印页面到PDF");
  766. utils.setBtnEvent(saveText_Baidu, [], "btn_3", "导出纯文本");
  767. utils.setBtnEvent(savePicUrls_Baidu, [], "btn_4", "导出图片链接(仅PPT)");
  768. utils.setBtnEvent(savePicUrls_BaiduNonPPT, [], "btn_5", "导出图片链接(除PPT)");
  769. } else {
  770. console.log(`无法识别的页面:${location.href}`);
  771. }
  772. }
  773.  
  774. /**
  775. * 展开道客巴巴的文档
  776. */
  777. function readAllDoc88() {
  778. // 获取“继续阅读”按钮
  779. let continue_btn = document.querySelector("#continueButton");
  780. // 如果存在“继续阅读”按钮
  781. if (continue_btn) {
  782. // 跳转到文末(等同于展开全文)
  783. let cur_page = document.querySelector("#pageNumInput");
  784. // 取得最大页码
  785. let page_max = cur_page.parentElement.textContent.replace(" / ", "");
  786. // 跳转到尾页
  787. utils.jump2pageNo(cur_page, page_max, "keypress");
  788. // 返回顶部
  789. setTimeout(utils.jump2pageNo(cur_page, "1", "keypress"), 1000);
  790. }
  791. // 文档展开后,显示按钮2、3
  792. else {
  793. // 隐藏按钮
  794. utils.toggleBtnStatus("btn_1");
  795. // 显示按钮
  796. utils.toggleBtnStatus("btn_2");
  797. utils.toggleBtnStatus("btn_3");
  798. }
  799. }
  800.  
  801. /**
  802. * 道客巴巴文档下载策略
  803. */
  804. function doc88() {
  805. // 创建脚本启动按钮1、2
  806. utils.createBtns();
  807.  
  808. // 绑定主函数
  809. let prepare = function() {
  810. // 获取canvas元素列表
  811. let node_list = document.querySelectorAll(".inner_page");
  812. // 获取文档标题
  813. let title;
  814. if (document.querySelector(".doctopic h1")) {
  815. title = document.querySelector(".doctopic h1").title;
  816. } else {
  817. title = "文档";
  818. }
  819. return [node_list, title];
  820. };
  821.  
  822. // btn_1: 展开文档
  823. utils.setBtnEvent(() => {
  824. readAllDoc88();
  825. }, [], "btn_1");
  826. // btn_2: 导出zip
  827. utils.setBtnEvent(() => {
  828. if (confirm("确定每页内容都加载完成了吗?")) {
  829. utils.saveCanvasesToZip(...prepare());
  830. }
  831. }, [], "btn_2", "导出图片到zip");
  832. // btn_3: 导出PDF
  833. utils.setBtnEvent(() => {
  834. if (confirm("确定每页内容都加载完成了吗?")) {
  835. utils.saveCanvasesToPDF(...prepare());
  836. }
  837. }, [], "btn_3", "导出图片到PDF");
  838. }
  839.  
  840. // 绑定主函数
  841. function getCanvasList() {
  842. // 获取全部canvas元素,用于传递canvas元素列表给 btn_2 和 btn_3
  843. let parent_node_list = document.querySelectorAll(".hkswf-content");
  844. let node_list = [];
  845. for (let node of parent_node_list) {
  846. node_list.push(node.children[0]);
  847. }
  848. return node_list;
  849. }
  850.  
  851.  
  852. function prepare() {
  853. // 获取canvas元素列表
  854. let node_list = getCanvasList();
  855. // 获取文档标题
  856. let title;
  857. if (document.querySelector("h1 [title=doc]")) {
  858. title = document.querySelector("h1 [title=doc]").nextElementSibling.textContent;
  859. } else if (document.querySelector(".doc_title")) {
  860. title = document.querySelector(".doc_title").textContent;
  861. } else {
  862. title = "文档";
  863. }
  864. return [node_list, title];
  865. }
  866.  
  867.  
  868. // 判断是否有canvas元素
  869. function detectCanvas() {
  870. let haveCanvas = getCanvasList().length === 0 ? false : true;
  871.  
  872. // 隐藏按钮
  873. utils.toggleBtnStatus("btn_1");
  874. // 显示按钮
  875. utils.toggleBtnStatus("btn_2");
  876.  
  877. // 如果没有canvas元素,则认为文档页面由外链图片构成
  878. if (!haveCanvas) {
  879. // btn_2: 导出图片链接
  880. utils.setBtnEvent(() => {
  881. if (confirm("确定每页内容都加载完成了吗?")) {
  882. utils.savePicUrls("[id*=img_]");
  883. }
  884. }, [], "btn_2", "导出全部图片链接");
  885. } else {
  886. // 显示按钮3
  887. utils.toggleBtnStatus("btn_3");
  888. // btn_2: 导出zip
  889. utils.setBtnEvent(() => {
  890. if (confirm("确定每页内容都加载完成了吗?")) {
  891. utils.saveCanvasesToZip(...prepare());
  892. }
  893. }, [], "btn_2", "导出图片到zip");
  894. // btn_3: 导出PDF
  895. utils.setBtnEvent(() => {
  896. if (confirm("确定每页内容都加载完成了吗?")) {
  897. utils.saveCanvasesToPDF(...prepare());
  898. }
  899. }, [], "btn_3", "导出图片到PDF");
  900. }
  901. }
  902.  
  903.  
  904. /**
  905. * 豆丁文档下载策略
  906. */
  907. function docin() {
  908. // 创建脚本启动按钮
  909. utils.createBtns();
  910.  
  911. // 隐藏底部工具栏
  912. document.querySelector("#j_select").click(); // 选择指针
  913. let tool_bar = document.querySelector(".reader_tools_bar_wrap.tools_bar_small.clear");
  914. tool_bar.style.display = "none";
  915.  
  916. // btn_1: 判断文档类型
  917. utils.setBtnEvent(() => {
  918. utils.forceHide(".jz_watermark");
  919. detectCanvas();
  920. }, [], "btn_1", "判断文档类型");
  921. }
  922.  
  923. /**
  924. * 点击“展开继续阅读”,适用性:爱尚共享资料
  925. */
  926. function readAlliShare() {
  927. // 获取“继续阅读”元素
  928. let red_btn = document.getElementsByClassName("red-color")[0];
  929. let red_text = red_btn.textContent;
  930. // 如果可以展开,则展开
  931. if (red_text.search("点击可继续阅读") !== -1) {
  932. red_btn.click();
  933. setTimeout(readAlliShare, 1000);
  934. }
  935. // 否则启动按钮2,准备清理页面然后打印为PDF
  936. else {
  937. // 隐藏按钮
  938. utils.toggleBtnStatus("btn_1");
  939. // 显示按钮
  940. utils.toggleBtnStatus("btn_2");
  941. utils.toggleBtnStatus("btn_3");
  942.  
  943. // 显示svg图片的链接
  944. let page1 = document.querySelector('[data-num="1"] .data-detail embed');
  945. if (!page1) {
  946. // 如果不存在svg图形,终止后续代码
  947. console.log("当前页面不存在svg图形");
  948. return;
  949. }
  950. let page2 = document.querySelector('[data-num="2"] .data-detail embed');
  951. let [svg1_src_div, svg2_src_div] = [document.createElement("div"), document.createElement("div")];
  952. svg1_src_div.innerHTML = `<div id="src-1"
  953. style="font-weight: bold;font-size: 20px; height: 100px; width: 100%">
  954. 访问以下链接以复制文字:<br>${page1.src}
  955. </div>`;
  956. svg2_src_div.innerHTML = `<div id="src-1"
  957. style="font-weight: bold;font-size: 20px; height: 100px; width: 100%">
  958. 访问以下链接以复制文字:<br>${page2.src}
  959. </div>`;
  960. // 添加到页面上
  961. page1.parentElement.parentElement.parentElement.append(svg1_src_div);
  962. page2.parentElement.parentElement.parentElement.append(svg2_src_div);
  963. }
  964. }
  965.  
  966.  
  967. /**
  968. * 清理并打印爱问共享资料的文档页
  969. * @returns 如果输入偏移量非法,返回空值以终止函数
  970. */
  971. function printPageiShare() {
  972. // # 清理并打印爱问共享资料的文档页
  973. // ## 移除页面上无关的元素
  974. // ### 移除单个元素
  975. let topbanner = document.getElementsByClassName("detail-topbanner")[0];
  976. let header = document.getElementsByClassName("new-detail-header")[0];
  977. let fixright = document.getElementById("fix-right");
  978. let redpacket = document.getElementsByClassName("loginRedPacket-dialog")[0];
  979. let fixedrightfull = document.getElementsByClassName("fixed-right-full")[0];
  980. let footer = document.getElementsByClassName("website-footer")[0];
  981. let guess = document.getElementsByClassName("guess-you-like-warpper")[0];
  982. let detailtopbox = document.getElementsByClassName("detail-top-box")[0];
  983. let fullscreen = document.getElementsByClassName("reader-fullScreen")[0];
  984. let endhint = document.getElementsByClassName("endof-trial-reading")[0];
  985. let crumb_arrow;
  986. try { crumb_arrow = document.getElementsByClassName("crumb-arrow")[0].parentElement; } catch (e) { console.log(); }
  987. let copyright = document.getElementsByClassName("copyright-container")[0];
  988. let state_btn = document.getElementsByClassName("state-bottom")[0];
  989. let comments = document.getElementsByClassName("user-comments-wrapper")[0];
  990. // ### 执行移除
  991. let elem_list = [
  992. topbanner,
  993. header,
  994. fixright,
  995. redpacket,
  996. fixedrightfull,
  997. footer,
  998. guess,
  999. detailtopbox,
  1000. fullscreen,
  1001. endhint,
  1002. crumb_arrow,
  1003. copyright,
  1004. state_btn,
  1005. comments
  1006. ];
  1007. for (let elem of elem_list) {
  1008. utils.tryToRemoveElement(elem);
  1009. }
  1010. // ### 移除全部同类元素
  1011. let elem_list_2 = document.querySelectorAll(".tui-detail, .adv-container");
  1012. for (let elem_2 of elem_list_2) {
  1013. utils.tryToRemoveElement(elem_2);
  1014. }
  1015. // 使文档居中
  1016. alert("建议使用:\n偏移量: 18\n缩放: 默认\n如果预览中有广告,就取消打印\n再点一次按钮,预览中应该就没有广告了");
  1017. if (!utils.centerDoc("doc-main", "18")) {
  1018. return; // 如果输入非法,终止函数调用
  1019. }
  1020. // 隐藏按钮,然后打印页面
  1021. utils.hideBtnThenPrint();
  1022. }
  1023.  
  1024.  
  1025. /**
  1026. * 爱问共享资料文档下载策略
  1027. */
  1028. function ishare() {
  1029. // 创建脚本启动按钮1、2
  1030. utils.createBtns();
  1031.  
  1032. // btn_1: 展开文档
  1033. utils.setBtnEvent(readAlliShare, [], "btn_1");
  1034. // btn_2: 导出图片链接
  1035. utils.setBtnEvent(() => {
  1036. utils.savePicUrls(".data-detail");
  1037. }, [], "btn_2", "导出图片链接(推荐)");
  1038. // btn_3: 打印页面到PDF
  1039. utils.setBtnEvent(printPageiShare, [], "btn_3", "打印页面到PDF");
  1040.  
  1041. // 移除底部下载条
  1042. let detailfixed = document.getElementsByClassName("detail-fixed")[0];
  1043. utils.tryToRemoveElement(detailfixed);
  1044. }
  1045.  
  1046. /**
  1047. * 清理并打印得力文库的文档页
  1048. */
  1049. function printPageDeliwenku() {
  1050. // 移除页面上的无关元素
  1051. let selector = ".hr-wrap, #readshop, .nav_uis, .bookdesc, #boxright, .QQ_S1, .QQ_S, #outer_page_more, .works-manage-box.shenshu, .works-intro, .mt10.related-pic-box, .mt10.works-comment, .foot_nav, .siteInner";
  1052. let elem_list = document.querySelectorAll(selector);
  1053. for (let elem of elem_list) {
  1054. utils.tryToRemoveElement(elem);
  1055. }
  1056. // 修改页间距
  1057. let outer_pages = document.getElementsByClassName("outer_page");
  1058. for (let page of outer_pages) {
  1059. page.style.marginBottom = "20px";
  1060. }
  1061. // 使文档居中
  1062. alert("建议使用:\n偏移量: 3\n缩放: 112\n请上下滚动页面,确保每页内容都加载完成以避免空白页\n如果预览时有空白页或文末有绿色按钮,请取消打印重试");
  1063. if (!utils.centerDoc("#boxleft", "3")) {
  1064. return; // 如果输入非法,终止函数调用
  1065. }
  1066. // 打印文档
  1067. utils.hideBtnThenPrint();
  1068. }
  1069.  
  1070.  
  1071. /**
  1072. * 点击“继续阅读”,适用性:得力文库
  1073. */
  1074. function readAllDeliwenku() {
  1075. // 点击“同意并开始预览全文”
  1076. let start_btn = document.getElementsByClassName("pre_button")[0];
  1077. let display = start_btn.parentElement.parentElement.style.display;
  1078. // 如果该按钮显示着,则点击,然后滚动至页面底部,最后终止函数
  1079. if (!display) {
  1080. start_btn.children[0].click();
  1081. setTimeout("scroll(0, document.body.scrollHeight)", 200);
  1082. return;
  1083. }
  1084. // 增强按钮点击效果
  1085. utils.enhanceBtnClickReaction();
  1086.  
  1087. let read_all_btn = document.getElementsByClassName("fc2e")[0];
  1088. let display2 = read_all_btn.parentElement.parentElement.style.display;
  1089. // 继续阅读
  1090. if (display2 !== "none") {
  1091. // 获取input元素
  1092. let cur_page = document.querySelector("#pageNumInput");
  1093. let page_old = cur_page.value;
  1094. let page_max = cur_page.parentElement.nextElementSibling.textContent.replace(" / ", "");
  1095. // 跳转到尾页
  1096. utils.jump2pageNo(cur_page, page_max, "keydown");
  1097. // 跳转回来
  1098. utils.jump2pageNo(cur_page, page_old, "keydown");
  1099.  
  1100. // 切换按钮准备导出
  1101. } else {
  1102. // 推荐导出图片链接
  1103. utils.modifyBtnText("btn_2", null, true);
  1104. // 隐藏按钮
  1105. utils.toggleBtnStatus("btn_1");
  1106. // 显示按钮
  1107. utils.toggleBtnStatus("btn_2");
  1108. utils.toggleBtnStatus("btn_3");
  1109. // btn_3 橙色按钮
  1110. utils.setBtnEvent(printPageDeliwenku, [], "btn_3", "打印页面到PDF");
  1111. }
  1112. }
  1113.  
  1114.  
  1115. /**
  1116. * 得力文库文档下载策略
  1117. */
  1118. function deliwenku() {
  1119. // 创建脚本启动按钮1、2
  1120. utils.createBtns();
  1121.  
  1122. // btn_1: 展开文档
  1123. utils.setBtnEvent(readAllDeliwenku, [], "btn_1");
  1124. // btn_2: 导出图片链接
  1125. utils.setBtnEvent(() => {
  1126. if (confirm("确定每页内容都加载完成了吗?")) {
  1127. utils.savePicUrls('.inner_page div');
  1128. }
  1129. }, [], "btn_2", "导出图片链接");
  1130.  
  1131. // 尝试关闭页面弹窗
  1132. try { document.querySelector("div[title=点击关闭]").click(); } catch (e) { console.log(0); }
  1133. // 解除打印限制
  1134. utils.allowPrint();
  1135. }
  1136.  
  1137. function readAll360Doc() {
  1138. // 展开文档
  1139. document.querySelector(".articleMaxH").setAttribute("class", "");
  1140. // 隐藏按钮
  1141. utils.toggleBtnStatus("btn_1");
  1142. // 显示按钮
  1143. utils.toggleBtnStatus("btn_2");
  1144. utils.toggleBtnStatus("btn_3");
  1145. }
  1146.  
  1147.  
  1148. function saveText_360Doc() {
  1149. // 捕获图片链接
  1150. let images = document.querySelectorAll("#artContent img");
  1151. let content = [];
  1152.  
  1153. for (let i = 0; i < images.length; i++) {
  1154. let src = images[i].src;
  1155. content.push(`图${i+1},链接:${src}`);
  1156. }
  1157. // 捕获文本
  1158. let text = document.querySelector("#artContent").textContent;
  1159. content.push(text);
  1160.  
  1161. // 保存纯文本文档
  1162. let title = document.querySelector("#titiletext").textContent;
  1163. utils.createAndDownloadFile(`${title}.txt`, content.join("\n"));
  1164. }
  1165.  
  1166.  
  1167. function printPage360Doc() {
  1168. // # 清理并打印360doc的文档页
  1169. // ## 移除页面上无关的元素
  1170. let selector = ".fontsize_bgcolor_controler, .atfixednav, .header, .a_right, .article_data, .prev_next, .str_border, .youlike, .new_plbox, .str_border, .ul-similar, #goTop2, #divtort, #divresaveunder, .bottom_controler, .floatqrcode";
  1171. let elem_list = document.querySelectorAll(selector);
  1172. let under_doc_1, under_doc_2;
  1173. try {
  1174. under_doc_1 = document.querySelector("#bgchange p.clearboth").nextElementSibling;
  1175. under_doc_2 = document.querySelector("#bgchange").nextElementSibling.nextElementSibling;
  1176. } catch (e) { console.log(); }
  1177. // 执行移除
  1178. for (let elem of elem_list) {
  1179. utils.tryToRemoveElement(elem);
  1180. }
  1181. utils.tryToRemoveElement(under_doc_1);
  1182. utils.tryToRemoveElement(under_doc_2);
  1183. // 执行隐藏
  1184. document.querySelector("a[title]").style.display = "none";
  1185.  
  1186. // 使文档居中
  1187. alert("建议使用:\n偏移量: 20\n缩放: 默认\n");
  1188. if (!utils.centerDoc(".a_left", "20")) {
  1189. return; // 如果输入非法,终止函数调用
  1190. }
  1191. // 隐藏按钮,然后打印页面
  1192. utils.hideBtnThenPrint();
  1193. }
  1194.  
  1195.  
  1196. /**
  1197. * 360doc个人图书馆下载策略
  1198. */
  1199. function doc360() {
  1200. // 创建按钮区
  1201. utils.createBtns();
  1202. // btn_1: 展开文档
  1203. utils.setBtnEvent(readAll360Doc, [], "btn_1");
  1204. // btn_2: 导出纯文本
  1205. utils.setBtnEvent(saveText_360Doc, [], "btn_2", "导出纯文本");
  1206. // btn_3: 打印页面到PDF
  1207. utils.setBtnEvent(() => {
  1208. if (confirm("确定每页内容都加载完成了吗?")) {
  1209. printPage360Doc();
  1210. }
  1211. }, [], "btn_3", "打印页面到PDF");
  1212. }
  1213.  
  1214. /**
  1215. * 查找出所有未被捕获的页码,并返回列表
  1216. * @returns 未捕获页码列表
  1217. */
  1218. function getMissedPages() {
  1219. let all = []; // 全部页码
  1220. for (let i = 0; i < window.mbaJS.max_page; i++) {
  1221. all[i] = `${i+1}`;
  1222. }
  1223. let missed = []; // 未捕获页码
  1224. let possessed = Array.from(window.mbaJS.canvases_map.keys()); // 已捕获页面
  1225.  
  1226. // 排除并录入未捕获页码
  1227. for (let num of all) {
  1228. if (!possessed.includes(`page${num}`)) {
  1229. missed.push(num);
  1230. }
  1231. }
  1232. return missed;
  1233. }
  1234.  
  1235.  
  1236. /**
  1237. * 根据键中的id数字对map排序
  1238. * @param {Map} elems_map
  1239. * @returns sorted_map
  1240. */
  1241. function sortMapByID(elems_map) {
  1242. // id形式:page2
  1243. let elems_arr = Array.from(elems_map);
  1244. elems_arr.sort((item1, item2) => {
  1245. // 从key中取出id
  1246. let id1 = parseInt(item1[0].replace("page", ""));
  1247. let id2 = parseInt(item2[0].replace("page", ""));
  1248. // 升序排序
  1249. return id1 - id2;
  1250. });
  1251. // 返回排序好的map
  1252. return new Map(elems_arr);
  1253. }
  1254.  
  1255.  
  1256. /**
  1257. * 存储动态加载的canvas元素、textContent
  1258. */
  1259. function storeElements_MBA() {
  1260. let canvases_map = window.mbaJS.canvases_map;
  1261. let texts_map = window.mbaJS.texts_map;
  1262. let quality = window.mbaJS.quality;
  1263.  
  1264. document.querySelectorAll(".page[data-loaded=true]").forEach(
  1265. (elem) => {
  1266. let capture = (elem) => {
  1267. let canvas, data_base64;
  1268. // 导出canvas数据防止丢失
  1269. try {
  1270. // 存储canvas
  1271. canvas = elem.querySelector("canvas[id*=page]");
  1272. data_base64 = canvas.toDataURL("image/jpeg", quality);
  1273. } catch (e) {
  1274. // utils.sleep(500);
  1275. return;
  1276. }
  1277. // 增量录入map
  1278. let id = canvas.id; // id的形式:page2
  1279. if (!canvases_map.has(id)) {
  1280. canvases_map.set(id, data_base64);
  1281. }
  1282. // 确定canvas长宽
  1283. if (!window.mbaJS.width) {
  1284. window.mbaJS.width = parseInt(canvas.width);
  1285. window.mbaJS.height = parseInt(canvas.height);
  1286. }
  1287.  
  1288. // 存储text
  1289. let text = elem.textContent;
  1290. if (!texts_map.has(id)) {
  1291. texts_map.set(id, text);
  1292. }
  1293. };
  1294. setTimeout(capture, 500, elem);
  1295. });
  1296. if (canvases_map.size === window.mbaJS.max_page) {
  1297. // 根据id排序
  1298. window.mbaJS.canvases_map = sortMapByID(window.mbaJS.canvases_map);
  1299. window.mbaJS.texts_map = sortMapByID(window.mbaJS.texts_map);
  1300. window.mbaJS.finished = true;
  1301. window.onscroll = closeLoginPop;
  1302. }
  1303. }
  1304.  
  1305.  
  1306. /**
  1307. * 将canvas转为jpeg,然后导出PDF
  1308. * @param {Array} base64_list canvas元素列表
  1309. * @param {String} title 文档标题
  1310. */
  1311. function saveCanvasesToPDF_MBA(base64_list, title) {
  1312. let width = window.mbaJS.width;
  1313. let height = window.mbaJS.height;
  1314.  
  1315. console.log(`canvas数据:宽: ${width}px,高: ${height}px`);
  1316. // 如果文档第一页的宽比长更大,则landscape,否则portrait
  1317. let orientation = width > height ? 'l' : 'p';
  1318. let pdf = new jspdf.jsPDF(orientation, 'px', [height, width]);
  1319.  
  1320. // 保存每一页文档到每一页pdf
  1321. let i = 0;
  1322. for (let base64 of base64_list) {
  1323. i += 1;
  1324. pdf.addImage(base64, 'JPEG', 0, 0, width, height);
  1325. // 如果当前不是文档最后一页,则需要添加下一个空白页
  1326. if (i < window.mbaJS.max_page) {
  1327. pdf.addPage();
  1328. }
  1329. }
  1330. // 导出文件
  1331. pdf.save(`${title}.pdf`);
  1332. }
  1333.  
  1334. /**
  1335. * 判断文档页是否收集完毕,当不行时给出提示
  1336. * @returns boolean
  1337. */
  1338. function ready2use() {
  1339. removeAds(); // 顺便清理广告
  1340. // 如果是首次点击按钮,给出提示
  1341. if (window.mbaJS.first_hint) {
  1342. let hint = [
  1343. "如果浏览速度过快,比如:",
  1344. "当前页面还没完全加载好就滚动页面去看下一页",
  1345. "那就极有可能导致导出的PDF有空白页或文本有缺漏",
  1346. "由防范技术的干扰,该功能目前很不好用,见谅"
  1347. ].join("\n");
  1348. alert(hint);
  1349. window.mbaJS.first_hint = false;
  1350. }
  1351. // 如果文档页没有收集完,给出提示
  1352. if (!window.mbaJS.finished) {
  1353. let hint = [
  1354. "仍有内容未加载完,无法使用该功能",
  1355. "建议从头到尾慢速地再浏览一遍",
  1356. "以下是没有加载完成页面的页码:",
  1357. getMissedPages().join(",")
  1358. ];
  1359. alert(hint.join("\n"));
  1360. return false;
  1361. }
  1362. return true;
  1363. }
  1364.  
  1365.  
  1366. /**
  1367. * 用捕获好的canvas转jpg,生成PDF
  1368. * @returns
  1369. */
  1370. function canvas2PDF_mba() {
  1371. if (!ready2use()) {
  1372. return;
  1373. }
  1374. let canvases = window.mbaJS.canvases_map.values();
  1375. // 导出PDF
  1376. let title = document.title.split("-")[0].trim();
  1377. saveCanvasesToPDF_MBA(canvases, title);
  1378. }
  1379.  
  1380.  
  1381. /**
  1382. * 拼合捕获好的文本,保存到txt文件
  1383. * @returns
  1384. */
  1385. function saveText_mba() {
  1386. if (!ready2use()) {
  1387. return;
  1388. }
  1389. let content = Array.from(window.mbaJS.texts_map.values());
  1390. let title = document.title.split("-")[0].trim();
  1391. utils.createAndDownloadFile(`${title}.txt`, content.join("\n"));
  1392. }
  1393.  
  1394.  
  1395. /**
  1396. * 移除广告
  1397. */
  1398. function removeAds() {
  1399. document.querySelectorAll(".doc-ad").forEach((ad_elem) => {
  1400. utils.tryToRemoveElement(ad_elem);
  1401. });
  1402. }
  1403.  
  1404.  
  1405. function mbalib_() {
  1406. // 移除广告和左侧工具栏
  1407. removeAds();
  1408. let tool_bar = document.querySelector(".tool-bar");
  1409. utils.tryToRemoveElement(tool_bar);
  1410.  
  1411. // 创建按钮
  1412. utils.createBtns();
  1413. // 隐藏按钮
  1414. utils.toggleBtnStatus("btn_1");
  1415. // 显示按钮
  1416. utils.toggleBtnStatus("btn_2");
  1417. utils.toggleBtnStatus("btn_3");
  1418. utils.toggleBtnStatus("btn_4");
  1419.  
  1420. // 取得页数
  1421. let max_page = parseInt(document.querySelector("#numPages").textContent.replace("/ ", ""));
  1422. let quality = utils.getQualityByCanvasAmount(max_page);
  1423. // 根据页数决定提示信息
  1424. let btn_text = (max_page > 40) ? "警告!(点我)" : "空白页说明";
  1425. utils.setBtnEvent(() => {
  1426. let hint;
  1427. if (max_page > 40) {
  1428. hint = [
  1429. "页数超过40,功能基本无效。具体表现是:",
  1430. "1. 你需要全文加载完一遍才能使用按钮",
  1431. "2. 导出的PDF几乎无法避免的出现大片空白页"
  1432. ];
  1433. } else {
  1434. hint = [
  1435. "导致空白页的原因如下",
  1436. "加载该页的时间超过2秒 / 明显等待",
  1437. "很抱歉,这种状况暂无解决方案"
  1438. ];
  1439. }
  1440. alert(hint.join("\n"));
  1441. }, [], "btn_4", btn_text);
  1442.  
  1443. // 为导出内容提供全局变量,便于动态收集文档页元素的存取
  1444. window.mbaJS = {
  1445. max_page: max_page,
  1446. texts_map: new Map(), // id: text
  1447. canvases_map: new Map(), // id: canvas_data_base64
  1448. quality: quality, // canvas转jpg的质量
  1449. width: null, // canvas宽度(px)
  1450. height: null,
  1451. finished: false, // 是否收集完了全部文档页元素
  1452. first_hint: true
  1453. };
  1454. // 跟随浏览,动态收集页面元素
  1455. window.onscroll = () => {
  1456. storeElements_MBA();
  1457. closeLoginPop();
  1458. };
  1459. // 绑定事件
  1460. utils.setBtnEvent(saveText_mba, [], "btn_2", "导出纯文本(不稳定)");
  1461. utils.setBtnEvent(canvas2PDF_mba, [], "btn_3", "导出PDF(不稳定)");
  1462. }
  1463.  
  1464.  
  1465. function mbalib() {
  1466. setTimeout(mbalib_, 2000);
  1467. }
  1468.  
  1469. /**
  1470. * 主函数:识别网站,执行对应文档下载策略
  1471. */
  1472. function main() {
  1473. let host = window.location.host;
  1474. console.log(`当前host: ${host}`);
  1475.  
  1476. if (host.includes("docin.com")) {
  1477. docin();
  1478. } else if (host === "ishare.iask.sina.com.cn") {
  1479. ishare();
  1480. } else if (host === "www.deliwenku.com") {
  1481. deliwenku();
  1482. } else if (host === "www.doc88.com") {
  1483. doc88();
  1484. } else if (host === "www.360doc.com") {
  1485. doc360();
  1486. } else if (host === "wenku.baidu.com") {
  1487. baiduWenku();
  1488. } else if (host === "doc.mbalib.com") {
  1489. mbalib();
  1490. } else {
  1491. console.log("匹配到了无效网页");
  1492. }
  1493. }
  1494.  
  1495. let options = {
  1496. fast_mode: false,
  1497. activation_test: false
  1498. };
  1499. if (options.cli_mode) {
  1500. (() => {
  1501. loadExternalScripts();
  1502. setTimeout(main, 2000);
  1503. return;
  1504. })();
  1505. }
  1506. if (options.activation_test) {
  1507. alert(`Wenku Doc Downloader 已经生效!\n当前网址:\n${window.location.host}`);
  1508. }
  1509. if (options.fast_mode) {
  1510. main();
  1511. } else {
  1512. window.onload = main;
  1513. }
  1514.  
  1515. })();