// ==UserScript== // @name Wenku Doc Downloader // @namespace http://tampermonkey.net/ // @version 1.7.7 // @description 下载文档,导出纯图片PDF。有限地支持(1)豆丁网 (2)道客巴巴 (3)360个人图书馆(4)得力文库 (5)MBA智库(6)爱问文库(7)原创力文档(8)读根网(9)国标网(10)食典通(11)安全文库网(12)人人文库(13)云展网。在网页左侧中间有按钮区和小猴子图标,说明脚本生效了。【反馈请提供网址】。不支持手机端。你能预览多少页,就可以导出多少页的PDF。 // @author 2690874578@qq.com // @match *://*.docin.com/p-* // @match *://ishare.iask.sina.com.cn/f/* // @match *://ishare.iask.com/f/* // @match *://swf.ishare.down.sina.com.cn/?path=* // @match *://www.deliwenku.com/p-* // @match *://file.deliwenku.com/?num=* // @match *://file3.deliwenku.com/?num=* // @match *://www.doc88.com/p-* // @match *://www.360doc.com/content/* // @match *://doc.mbalib.com/view/* // @match *://www.dugen.com/p-* // @match *://max.book118.com/html/* // @match *://openapi.book118.com/?* // @match *://view-cache.book118.com/pptView.html?* // @match *://*.book118.com/?readpage=* // @match *://c.gb688.cn/bzgk/gb/showGb?* // @match *://www.safewk.com/p-* // @match *://www.renrendoc.com/paper/* // @match *://www.yunzhan365.com/basic/* // @match *://book.yunzhan365.com/*index.html* // @match *://www.bing.com/search?q=Bing+AI&showconv=1* // @require https://cdn.staticfile.org/jspdf/2.5.1/jspdf.umd.min.js // @require https://cdn.staticfile.org/html2canvas/1.4.1/html2canvas.min.js // @icon https://s2.loli.net/2022/01/12/wc9je8RX7HELbYQ.png // @icon64 https://s2.loli.net/2022/01/12/tmFeSKDf8UkNMjC.png // @grant none // @license GPL-3.0-only // @create 2021-11-22 // @note 1. 优化 Bing AI 对话保存功能 // @note 2. 暂不支持原创力 PPT 文档 // @downloadURL none // ==/UserScript== (function () { 'use strict'; /** * 元素选择器 * @param {string | HTMLElement} selector 选择器或元素 * @returns {Array} 元素列表 */ function _$(selector) { if (selector instanceof HTMLElement) { return [selector]; } let self = this?.querySelectorAll ? this : document; return [...self.querySelectorAll(selector)]; } globalThis.wk$ = _$; let utils = { global: globalThis, update: "2023-03-03", /** * 函数装饰器:仅执行一次 func * @param {Function} func * @returns {Promise} */ once: async function(func) { return async function() { let used = false; if (!used) { await func(); used = true; } } }, /** * 将类似于dict的简单object转为get请求的queryString * @param {Object} dict * @returns {string} */ dictToQueryStr: function(dict) { let params = []; for (let prop in dict) { params.push(`${prop}=${dict[prop]}`); } return params.join("&"); }, /** * 返回一个包含计数器的迭代器, 其每次迭代值为 [index, value] * @param {Iterable} iterable * @returns */ enumerate: function* (iterable) { let i = 0; for (let value of iterable) { yield [i, value]; i++; } }, /** * 同步的迭代若干可迭代对象 * @param {...Iterable} iterables * @returns */ zip: function* (...iterables) { // 用于取得一次列表中所有迭代器的值 function _getAllValus(iterators) { if (iterators.length === 0) { return [true, []]; } let values = []; for (let iterator of iterators) { let {value, done} = iterator.next(); if (done) { return [true, []]; } values.push(value); } return [false, values]; } // 强制转为迭代器 let iterators = iterables.map( iterable => iterable[Symbol.iterator]() ); // 逐次迭代 while (true) { let [done, values] = _getAllValus(iterators); if (done) { return; } if (values.length === 1) { yield values[0]; } else { yield values; } } }, /** * 返回指定范围整数生成器 * @param {number} end 如果只提供 end, 则返回 [0, end) * @param {number} end2 如果同时提供 end2, 则返回 [end, end2) * @param {number} step 步长, 可以为负数,不能为 0 * @returns */ range: function*(end, end2=null, step=1) { if (step === 0) { throw new RangeError("step can't be zero"); } end2 = end2 === null ? 0 : end2; let [small, big] = [end, end2].sort((a, b) => a - b); if (step > 0) { for (let i = small; i < big; i += step) { yield i; } } else { for (let i = big; i > small; i += step) { yield i; } } }, /** * 获取整个文档的全部css样式 * @returns {string} css text */ getAllStyles: function() { let styles = []; for (let sheet of document.styleSheets) { let rules; try { rules = sheet.cssRules; } catch(e) { if (!(e instanceof DOMException)) { console.error(e); } continue; } for (let rule of rules) { styles.push(rule.cssText); } } return styles.join("\n\n"); }, /** * 测试数组是否包含另一数组 * @param {Array} arr * @param {Array} sub_arr * @returns {boolean} 包含与否 */ isSubArrIn: function(arr, sub_arr) { for (let i = 0, len = arr.length; i < len; i++) { let matched = true; for (let [j, val] of sub_arr.entries()) { if (val !== arr[i + j]) { matched = false; break; } } if (matched) { return true; } } return false; }, /** * 使用过时的execCommand复制文字 * @param {string} text */ _oldCopy: function(text) { let input = document.createElement("input"); input.value = text; document.body.appendChild(input); input.select(); document.execCommand("copy"); input.remove(); }, /** * 复制text到剪贴板 * @param {string} text * @returns */ copy: function(text) { // 输出到控制台和剪贴板 console.log(text); if (!navigator.clipboard) { this._oldCopy(text); return; } navigator.clipboard.writeText(text) .catch(_ => this._oldCopy(text)); }, /** * 装饰器, 用于打印函数执行耗时 * @param {Function} func 需要计时的函数 * @returns {Promise} 装饰的func => func的返回值 */ recTime: async function(func) { async function inner() { let begin = Date.now(); let res = await func(); let cost = ((Date.now() - begin) / 1000).toFixed(1); console.log(`Function <${func.name}> costed ${cost} seconds.`); return res; } return inner; }, /** * 创建并下载文件 * @param {string} file_name 文件名 * @param {ArrayBuffer | ArrayBufferView | Blob | string} content blob_part */ saveAs: function(file_name, content) { let a = document.createElement("a"); let blob = new Blob([content]); a.download = file_name; let url = URL.createObjectURL(blob); a.href = url; a.click(); URL.revokeObjectURL(url); }, /** * canvas转为PNG格式的blob * @param {HTMLCanvasElement} canvas * @returns {Promise} blob */ canvasToBlob: async function(canvas) { return new Promise(res => canvas.toBlob(res)); }, /** * 显示/隐藏按钮区. * @param {Function} func */ toggleBtnsSec: function() { let sec = wk$(".wk-box")[0]; if (sec.style.display === "none") { sec.style.display = "block"; return; } sec.style.display = "none"; }, /** * 异步地睡眠 delay 毫秒, 可选 max_delay 控制波动范围 * @param {number} delay 等待毫秒 * @param {number} max_delay 最大等待毫秒, 默认为null * @returns */ sleep: async function(delay, max_delay=null) { max_delay = max_delay === null ? delay : max_delay; let _delay = delay + (max_delay - delay) * Math.random(); return new Promise(resolve => setTimeout(resolve, _delay)); }, /** * 允许打印页面 */ allowPrint: function() { let style = document.createElement("style"); style.innerHTML = ` @media print { body { display: block; } } `; document.head.appendChild(style); }, /** * 取得get参数key对应的value * @param {string} key * @returns {string} value */ getUrlParam: function(key) { let params = (new URL(window.location)).searchParams; return params.get(key); }, /** * 在指定节点后面插入节点 * @param {HTMLElement} new_element * @param {HTMLElement} target_element */ insertAfter: function(new_element, target_element) { let parent = target_element.parentNode; if (parent.lastChild === target_element) { parent.appendChild(new_element); } else { parent.insertBefore(new_element, target_element.nextElementSibling); } }, /** * 求main_set去除cut_set后的set * @param {Set} main_set * @param {Set} cut_set * @returns 差集 */ difference: function(main_set, cut_set) { let _diff = new Set(main_set); for (let elem of cut_set) { _diff.delete(elem); } return _diff; }, /** * 抛出set中的第一个元素 * @param {Set} set * @returns 一个元素 */ setPop: function(set) { for (let item of set) { set.delete(item); return item; } }, /** * 增强按钮(默认为蓝色按钮:展开文档)的点击效果 * @param {string} custom_btn 按钮变量名 */ enhanceBtnClick: function(custom_btn = null) { let aim_btn; // 如果不使用自定义按钮元素,则默认为使用蓝色展开文档按钮 if (!custom_btn || custom_btn === "btn_1") { aim_btn = document.querySelector(".btn-1"); } else { aim_btn = document.querySelector(`.${custom_btn.replace("_", "-")}`); } let old_color = aim_btn.style.color; // 保存旧的颜色 let old_text = aim_btn.textContent; // 保存旧的文字内容 // 变黑缩小 aim_btn.style.color = "black"; aim_btn.style.fontWeight = "normal"; aim_btn.textContent = `->${old_text}<-`; // 复原加粗 let changeColorBack = function() { aim_btn.style.color = old_color; aim_btn.style.fontWeight = "bold"; aim_btn.textContent = old_text; }; setTimeout(changeColorBack, 1250); }, /** * 绑定事件到指定按钮,返回按钮引用 * @param {Function} listener click监听器 * @param {string} aim_btn 按钮的变量名 * @param {string} new_text 按钮的新文本,为null则不替换 * @returns 按钮元素的引用 */ setBtnListener: function(listener, aim_btn, new_text=null) { let btn = wk$(`.${aim_btn.replace("_", "-")}`)[0]; // 如果需要,替换按钮内文本 if (new_text) { btn.textContent = new_text; } // 绑定事件,添加到页面上 btn.addEventListener("click", () => { this.enhanceBtnClick(aim_btn); listener(); }); return btn; }, /** * 强制隐藏元素 * @param {string} selector */ forceHide: function(selector) { let cls = "force-hide"; document.querySelectorAll(selector).forEach((elem) => { elem.className += ` ${cls}`; }); // 判断css样式是否已经存在 let style; style = document.querySelector(`style.${cls}`); // 如果已经存在,则无须重复创建 if (style) { return; } // 否则创建 style = document.createElement("style"); style.innerHTML = `style.${cls} { visibility: hidden !important; }`; document.head.appendChild(style); }, /** * 当元素可见时,操作目标元素(异步)。最多为不可见元素等待5秒。 * @param {HTMLElement} elem 一个元素 * @param {Function} callback (elem) => {...} 元素操作函数 */ manipulateElem: async function(elem, callback) { let isVisiable = () => getComputedStyle(elem).display !== "none"; let max = 5 * 5; // 最多等待5秒 let i = 0; // 如果不可见就等待0.2秒/轮 while (!isVisiable() && i <= max) { i++; await utils.sleep(200); } callback(elem); }, /** * 等待直到函数返回true * @param {Function | Promise} isReady 判断条件达成与否的函数 * @param {number} timeout 最大等待秒数, 默认5秒 */ waitUntil: async function(isReady, timeout=5) { let gap = 200; let chances = parseInt(timeout * 1000 / gap); chances = chances < 1? 1: chances; while (! await isReady()) { await this.sleep(200); chances -= 1; if (!chances) { break; } } }, /** * 隐藏按钮,打印页面,显示按钮 */ hideBtnThenPrint: function() { // 隐藏按钮,然后打印页面 let btns = document.querySelectorAll(".btns_section, .hide_btn_wk"); btns.forEach(elem => elem.style.display = "none"); window.print(); // 打印结束,显示按钮 btns.forEach(elem => elem.style.display = "block"); }, /** * 切换按钮显示/隐藏状态 * @param {string} aim_btn 按钮变量名 * @returns 按钮元素的引用 */ toggleBtn: function(aim_btn) { let btn = document.querySelector(`.${aim_btn.replace("_", "-")}`); let display = getComputedStyle(btn).display; // return; if (display === "none") { btn.style.display = "block"; } else { btn.style.display = "none"; } return btn; }, /** * 用input框跳转到对应页码 * @param {Element} cur_page 当前页码 * @param {string | Number} aim_page 目标页码 * @param {string} event_type 键盘事件类型:"keyup" | "keypress" | "keydown" */ toPageNo: function(cur_page, aim_page, event_type) { // 设置跳转页码为目标页码 cur_page.value = (aim_page).toString(); // 模拟回车事件来跳转 let keyboard_event_enter = new KeyboardEvent(event_type, { bubbles: true, cancelable: true, keyCode: 13 }); cur_page.dispatchEvent(keyboard_event_enter); }, /** * 判断给定的url是否与当前页面同源 * @param {string} url * @returns {boolean} */ isSameOrigin: function(url) { let _url = new URL(url); if (location.protocol === _url.protocol && location.host === _url.host && location.port === _url.port) { return true; } return false; }, /** * 在新标签页打开链接,如果提供文件名则下载 * @param {string} url * @param {string} fname 下载文件的名称,默认为空,代表不下载 */ openURL: function(url, fname="") { let a = document.createElement("a"); a.href = url; a.target = "_blank"; if (fname && this.isSameOrigin(url)) { a.download = fname; } a.click(); }, // /** // * 滚动到页面底部 // */ // scrollToBottom: function() { // window.scrollTo({ // top: document.body.scrollHeight, // behavior: "smooth" // }); // }, /** * 用try移除元素 * @param {Element} element 要移除的元素 */ tryToRemoveElement: function(element) { try { element.remove(); } catch (e) { } }, /** * 用try移除若干元素 * @param {Element[]} elements 要移除的元素列表 */ tryToRemoveElements: function(elements) { elements.forEach((elem) => { this.tryToRemoveElement(elem); }); }, /** * 用try移除 [元素列表1, 元素列表2, ...] 中的元素 * @param {Array} elem_list_box 要移除的元素列表构成的列表 */ tryToRemoveSameElem: function(elem_list_box) { for (let elem_list of elem_list_box) { if (!elem_list) { continue; } for (let elem of elem_list) { try { elem.remove(); } catch (e) { console.log(); } } } }, /** * 使文档在页面上居中 * @param {string} selector 文档容器的css选择器 * @param {string} default_offset 文档部分向右偏移的百分比(0-59) * @returns 偏移值是否合法 */ centerDoc: function(selector, default_offset) { let doc_main = document.querySelector(selector); let offset = window.prompt("请输入偏移百分位:", default_offset); // 如果输入的数字不在 0-59 内,提醒用户重新设置 if (offset.length === 1 && offset.search(/[0-9]/) !== -1) { doc_main.style.marginLeft = offset + "%"; return true; } else if (offset.length === 2 && offset.search(/[1-5][0-9]/) !== -1) { doc_main.style.marginLeft = offset + "%"; return true } else { alert("请输入一个正整数,范围在0至59之间,用来使文档居中\n(不同文档偏移量不同,所以需要手动调整)"); return false; } }, /** * 调整按钮内文本 * @param {string} aim_btn 按钮变量名 * @param {string} new_text 新的文本,null则保留旧文本 * @param {Boolean} recommend_btn 是否增加"(推荐)"到按钮文本 * @param {Boolean} use_hint 是否提示"文档已经完全展开,可以导出" */ modifyBtnText: function(aim_btn = "btn_2", new_text = null, recommend_btn = false, use_hint = true) { // 提示文档已经展开 if (use_hint) { let hint = "文档已经完全展开,可以导出"; alert(hint); } let btn = document.querySelector(`.${aim_btn.replace("_", "-")}`); // 要替换的文本 if (new_text) { btn.textContent = new_text; } // 推荐按钮 if (recommend_btn) { btn.textContent += "(推荐)"; } }, /** * html元素列表转为canvas列表 * @param {ArrayLike} elements * @returns {Promise>} */ elementsToCanvases: async function(elements) { if (!window.html2canvas) { await this.loadWebScript( "https://cdn.staticfile.org/html2canvas/1.4.1/html2canvas.min.js" ); } // 如果是空列表或内容不为html元素, 则抛出异常 if (elements.length === 0 || !(elements[0] instanceof HTMLElement)) { throw new Error("htmlToCanvases 未得到任何html元素"); } let tasks = []; for (let elem of elements) { tasks.push(html2canvas(elem)); } // 等待全部page转化完成 return await Promise.all(tasks); }, /** * 将html元素转为canvas再合并到pdf中,最后下载pdf * @param {ArrayLike} elements html元素列表 * @param {string} title 文档标题 */ elementsToPDF: async function(elements, title = "文档") { // 如果是空元素列表,终止函数 let canvases = await this.elementsToCanvases(elements); // canvases.then((canvases) => { // 控制台检查结果 console.log("生成的canvas元素如下:"); console.log(canvases); // 拿到canvas宽、高 let model = canvases[0], width = model.width, height = model.height; // 打包为pdf this.canvasesToPDF(canvases, title, width, height); // }); }, /** * 加载CDN脚本 * @param {string} url */ loadWebScript: async function(url) { let resp = await fetch(url); Function(await resp.text())(); }, b64ToUint6: function(nChr) { return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0; }, /** * b64编码字符串转Uint8Array * @param {string} sBase64 b64编码的字符串 * @param {number} nBlockSize 字节数 * @returns {Uint8Array} arr */ base64DecToArr: function(sBase64, nBlockSize=1) { let sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, nOutLen = nBlockSize ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2, aBytes = new Uint8Array(nOutLen); for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { nMod4 = nInIdx & 3; nUint24 |= this.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; if (nMod4 === 3 || nInLen - nInIdx === 1) { for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; } nUint24 = 0; } } return aBytes; }, /** * canvas转blob * @param {HTMLCanvasElement} canvas * @param {string} type 图像类型,默认为png * @param {number} canvas 品质,默认为1(最小0) * @returns {Promise} */ canvasToBlob: function(canvas, type="image/png", quality=1) { return new Promise(resolve => canvas.toBlob(resolve, type, quality)); }, /** * 存储所有canvas图形为png到一个压缩包 * @param {Iterable} canvases canvas元素列表 * @param {string} title 文档标题 */ canvasesToZip: async function(canvases, title) { // canvas元素转为png图像 // 所有png合并为一个zip压缩包 let zip = new JSZip(); let tasks = []; for (let canvas of canvases) { tasks.push(this.canvasToBlob(canvas)); } let blobs = await Promise.all(tasks); blobs.forEach( (blob, i) => zip.file(`page-${i+1}.png`, blob, { binary: true }) ); // 导出zip let zip_blob = await zip.generateAsync({ type: "blob" }); console.log(zip_blob); // saveAs(content, `${title}.zip`); this.saveAs(`${title}.zip`, zip_blob); }, /** * 将canvas转为png,然后导出PDF * @param {Iterable} canvas_box canvas元素列表 * @param {string} title 文档标题 */ canvasesToPDF: function(canvas_box, title, width = 0, height = 0) { // 如果没有手动指定canvas的长宽,则自动检测 if (!width && !height) { // 先获取第一个canvas用于判断竖向还是横向,以及得到页面长宽 let first_canvas = canvas_box[0]; if (first_canvas.width && parseInt(first_canvas.width) && parseInt(first_canvas.height)) { [width, height] = [first_canvas.width, first_canvas.height]; } else { let [width_str, height_str] = [first_canvas.style.width.replace(/(px)|(rem)|(em)/, ""), first_canvas.style.height.replace(/(px)|(rem)|(em)/, "")]; [width, height] = [parseInt(width_str), parseInt(height_str)]; } } console.log(`canvas数据:宽: ${width}px,高: ${height}px`); // 如果文档第一页的宽比长更大,则landscape,否则portrait let orientation = width > height ? 'l' : 'p'; // jsPDF的第三个参数为format,当自定义时,参数为数字数组。 let pdf = new jspdf.jsPDF(orientation, 'px', [height, width]); // 保存每一页文档到每一页pdf let canvas_list = Array.from(canvas_box); let last_canvas = canvas_list.pop(); canvas_list.forEach(canvas => { pdf.addImage(canvas, 'png', 0, 0, width, height); pdf.addPage(); }); // 添加尾页 pdf.addImage(last_canvas, 'png', 0, 0, width, height); // 导出文件 pdf.save(`${title}.pdf`); }, /** * Image元素列表合并到一个PDF中 * @param {NodeList} imgs Image元素列表 * @param {string} title 文档名 */ imgsToPDF: function(imgs, title) { // 取得宽高 let model = imgs[0]; let width = model.offsetWidth; let height = model.offsetHeight; // 创建pdf let orientation = width > height ? 'l' : 'p'; let pdf = new jspdf.jsPDF(orientation, 'px', [height, width]); // 添加图像到pdf imgs.forEach((img, index) => { pdf.addImage(img, 'PNG', 0, 0, width, height); // 如果当前不是文档最后一页,则需要添加下一个空白页 if (index !== imgs.length - 1) { pdf.addPage(); } }); // 导出文件 pdf.save(`${title}.pdf`); }, /** * imageBitMap转canvas * @param {ImageBitmap} bmp * @returns {HTMLCanvasElement} canvas */ bmpToCanvas: function(bmp) { let canvas = document.createElement("canvas"); canvas.height = bmp.height; canvas.width = bmp.width; let ctx = canvas.getContext("bitmaprenderer"); ctx.transferFromImageBitmap(bmp); return canvas; }, getUrlAsBlob: async function(url) { return (await fetch(url)).blob(); }, /** * 导出图片链接 * @param {Iterable} urls */ saveImgUrls: function(urls) { this.saveAs( "urls.csv", Array.from(urls).join("\n") ); }, /** * 图片blobs合并并导出为单个PDF * @param {Array} blobs * @param {string} title 文档名称, 不含后缀, 默认为"文档" * @param {boolean} filter 是否过滤 type 不以 "image/" 开头的 blob; 默认为 true */ imgBlobsToPDF: async function(blobs, title="文档", filter=true) { // 格式转换:img blob -> bmp let tasks = blobs; if (filter) { tasks = blobs.filter( blob => blob.type.startsWith("image/") ); } tasks = tasks.map( blob => createImageBitmap(blob) ); let bmps = await Promise.all(tasks); // bmp -> canvas let canvases = bmps.map( bmp => this.bmpToCanvas(bmp) ); // 导出PDF this.canvasesToPDF(canvases, title); }, /** * 下载可以简单直接请求的图片,合并到 PDF 并导出 * @param {Iterable} urls 图片链接列表 * @param {string} title 文档名称 * @param {number} min_num 如果成功获取的图片数量 < min_num, 则等待 2 秒后重试; 默认 0 不重试 * @param {boolean} clear 是否在请求完成后清理控制台输出,默认false */ imgUrlsToPDF: async function(urls, title, min_num=0, clear=false) { // 强制转换为迭代器类型,确保支持next方法 urls = urls[Symbol.iterator](); let first = urls.next().value; // 如果不符合同源策略,在打开新标签页 if (!this.isSameOrigin(first)) { console.info("URL 不符合同源策略;转为新标签页打开目标网站"); this.openURL((new URL(first)).origin); return; } let tasks, img_blobs, i = 3; // 根据请求成功数量判断是否循环 do { i -= 1; // 发起请求 tasks = [this.getUrlAsBlob(first)]; // 初始化时加入第一个 // 然后加入剩余的 for (let url of urls) { tasks.push(this.getUrlAsBlob(url)); } // 接收响应 let blobs = await Promise.all(tasks); img_blobs = blobs.filter(blob => blob.type.startsWith("image/")); if (clear) { console.clear(); } if ( min_num && img_blobs.length < min_num && i ) { // 下轮行动前冷却 console.log(`打盹 2 秒`); await utils.sleep(2); } else { // 结束循环 break; } } while (true) this.imgBlobsToPDF(img_blobs, title, false); }, /** * 返回子串个数 * @param {string} str * @param {string} sub */ countSubStr: function(str, sub) { let i = 0; let counter = 0; while (true) { i = str.indexOf(sub, i); if (i === -1) { return counter; } else { i++; counter++; } } }, /** * 创建5个按钮:展开文档、导出图片、导出PDF、未设定4、未设定5;除第1个外默认均为隐藏 */ createBtns: function() { // 创建大容器 let box = document.createElement("div"); box.className = "wk-box"; document.body.appendChild(box); // 创建按钮组 let section = document.createElement("section"); section.className = "btns_section"; section.innerHTML = `

Wenku Doc Downloader

`; box.appendChild(section); // 添加隐藏/展示按钮 // 隐藏【🙈】,展开【🐵】 let hide_btn = document.createElement("p"); hide_btn.className = "hide_btn_wk"; hide_btn.textContent = "🐵"; hide_btn.onclick = () => { // 显示 -> 隐藏 if (getComputedStyle(section).display === "block") { section.style.display = "none"; hide_btn.style.left = "20px"; hide_btn.textContent = "🙈"; // 隐藏 -> 显示 } else { section.style.display = "block"; hide_btn.style.left = "155px"; hide_btn.textContent = "🐵"; } }; box.appendChild(hide_btn); // 设定样式 let style = document.createElement("style"); style.innerHTML = ` .hide_btn_wk { position: fixed; left: 155px; top: 36%; user-select: none; font-size: large; z-index: 2000; } .btns_section{ position: fixed; width: 154px; left: 10px; top: 32%; background: #E7F1FF; border: 2px solid #1676FF; padding: 0px 0px 10px 0px; font-weight: 600; border-radius: 2px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; z-index: 1000; } .logo_tit{ width: 100%; background: #1676FF; text-align: center; font-size:12px ; color: #E7F1FF; line-height: 40px; height: 40px; margin: 0 0 16px 0; } .btn-1{ display: block; width: 128px; height: 28px; background: linear-gradient(180deg, #00E7F7 0%, #FEB800 0.01%, #FF8700 100%); border-radius: 4px; color: #fff; font-size: 12px; border: none; outline: none; margin: 8px auto; font-weight: bold; cursor: pointer; opacity: .9; } .btn-2{ display: none; width: 128px; height: 28px; background: #07C160; border-radius: 4px; color: #fff; font-size: 12px; border: none; outline: none; margin: 8px auto; font-weight: bold; cursor: pointer; opacity: .9; } .btn-3{ display: none; width: 128px; height: 28px; background:#FA5151; border-radius: 4px; color: #fff; font-size: 12px; border: none; outline: none; margin: 8px auto; font-weight: bold; cursor: pointer; opacity: .9; } .btn-4{ display: none; width: 128px; height: 28px; background: #1676FF; border-radius: 4px; color: #fff; font-size: 12px; border: none; outline: none; margin: 8px auto; font-weight: bold; cursor: pointer; opacity: .9; } .btn-5{ display: none; width: 128px; height: 28px; background: #ff6600; border-radius: 4px; color: #fff; font-size: 12px; border: none; outline: none; margin: 8px auto; font-weight: bold; cursor: pointer; opacity: .9; } .btn-1:hover,.btn-2:hover,.btn-3:hover,.btn-4,.btn-5:hover{ opacity: .8;} .btn-1:active,.btn-2:active,.btn-3:active,.btn-4,.btn-5:active{ opacity: 1;}`; document.body.appendChild(style); }, /** * 添加弹窗到 body, 通过 utils.toID("wk-popup") 激发 */ addPopup: function() { let container = document.createElement("div"); container.className = "wk-popup-container"; container.innerHTML = ` ;`; document.body.appendChild(container); }, /** * 设置弹窗标题 * @param {string} text */ setPopupHead: function(text) { wk$(".wk-popup-head")[0].textContent = text; }, /** * 设置弹窗正文 * @param {string} text */ setPopupBody: function(text) { wk$(".wk-popup-body")[0].textContent = text; }, /** * 移除弹窗 */ removePopup: function() { try { wk$(".wk-popup-container")[0].remove(); } catch(e) { console.log(e); } }, /** * 滚动页面到id位置的元素处 * @param {string} id */ toID: function(id) { let a = document.createElement("a"); a.href = "#" + id; a.click(); } }; globalThis.wkutils = utils; /** * 确保特定外部脚本加载的装饰器 * @param {string} global_obj_name * @param {string} cdn_url * @param {Function} func * @returns */ function ensureWebScript(global_obj_name, cdn_url, func) { async function inner(...args) { if (!window[global_obj_name]) { // 根据需要加载依赖 await utils.loadWebScript(cdn_url); } return await func(...args); } return inner; } /** * 确保引用外部依赖的函数都在调用前加载了依赖 */ for (let prop of Object.keys(utils)) { // 跳过非函数 if (!(typeof utils[prop] === "function")) { continue; } // 绑定this到utils utils[prop] = utils[prop].bind(utils); // 为有外部依赖的函数做包装 let obj, url; let name = prop.toLowerCase(); if (name.includes("tozip")) { obj = "JSZip"; url = "https://cdn.staticfile.org/jszip/3.7.1/jszip.min.js"; } else if (name.includes("topdf")) { obj = "jspdf"; url = "https://cdn.staticfile.org/jspdf/2.5.1/jspdf.umd.min.js"; // } else if (name.includes("tocanvas")) { // obj = "html2canvas"; // url = "https://cdn.staticfile.org/html2canvas/1.4.1/html2canvas.min.js"; } else { continue; } utils[prop] = ensureWebScript(obj, url, utils[prop]); } /** * 安全元素选择器,直到元素存在时才返回元素列表,最多等待5秒 * @param {string} selector 选择器 * @returns {Promise>} 元素列表 */ async function _$$(selector) { let self = this?.querySelectorAll ? this : document; function selectAll() { return [...self.querySelectorAll(selector)]; } for (let _ of utils.range(10)) { let elems = selectAll(); if (elems.length > 0) { return elems; } await utils.sleep(500); } throw Error(`"${selector}" not found in 5 seconds`); } globalThis.wk$$ = _$$; console.log("wk: `wkutils` 已经挂载到全局"); /** * 展开道客巴巴的文档 */ async function readAllDoc88() { // 获取“继续阅读”按钮 let continue_btn = wk$("#continueButton")[0]; // 如果存在“继续阅读”按钮 if (continue_btn) { // 跳转到文末(等同于展开全文) let cur_page = wk$("#pageNumInput")[0]; // 取得最大页码 let page_max = cur_page.parentElement.textContent.replace(" / ", ""); // 跳转到尾页 utils.toPageNo(cur_page, page_max, "keypress"); // 返回顶部 await utils.sleep(1000); utils.toPageNo(cur_page, "1", "keypress"); } // 文档展开后,显示按钮2、3 else { // 隐藏按钮 utils.toggleBtn("btn_1"); // 显示按钮 utils.toggleBtn("btn_2"); utils.toggleBtn("btn_3"); utils.toggleBtn("btn_4"); utils.toggleBtn("btn_5"); } } /** * 隐藏搜索框 */ function hideSearchBox() { let elem = wk$("#min-search-result")[0]; let hide = elem => elem.style.display = "none"; utils.manipulateElem(elem, hide); } /** * 隐藏复制弹窗 */ function hideCopyPopup() { let elem = wk$("#ym-window")[0]; let hide = elem => elem.parentElement.style.display = "none"; utils.manipulateElem(elem, hide); } /** * 隐藏选择文字的弹窗 */ function hideSelectPopup() { let elem = wk$("#left-menu")[0]; let hide = elem => elem.style.zIndex = -1; utils.manipulateElem(elem, hide); } /** * 初始化任务 */ function initService() { // 初始化 console.log("正在执行初始化任务"); // 1. 隐藏选中文字的提示框 hideSelectPopup(); // 2. 隐藏搜索框 hideSearchBox(); // 3. 移除vip复制弹窗 hideCopyPopup(); // 4. 查找复制文字可能的api名称 let prop = getCopyAPIValue(); globalThis.doc88JS._apis = Object .getOwnPropertyNames(prop) .filter(name => { if (!name.startsWith("_")) { return false; } if (prop[name] === "") { return true; } }); } /** * 取得 doc88JS.copy_api 所指向属性的值 * @returns */ function getCopyAPIValue() { let aim = globalThis; for (let name of globalThis.doc88JS.copy_api) { aim = aim[name]; } return aim; } /** * 返回选中的文字 * @returns {string} */ function getSelectedText() { // 首次复制文字,需要先找出api if (globalThis.doc88JS.copy_api.length === 3) { // 拼接出路径,得到属性 let prop = getCopyAPIValue(); // 此时是属性,尚未取得值 // 查询值 for (let name of globalThis.doc88JS._apis) { let value = prop[name]; // 值从空字符串变为非空字符串了,确认是目标api名称 if (typeof value === 'string' && value.length > 0 && !value.match(/\d/) // 开头不能是数字,因为可能是 '1-179-195' 这种值 ) { globalThis.doc88JS.copy_api.push(name); break; } } } return getCopyAPIValue(); } /** * 输出选中的文字到剪贴板和控制台 * @returns */ function copySelected() { // 尚未选中文字 if (getComputedStyle(wk$("#left-menu")[0]).display === "none") { console.log("尚未选中文字"); return; } // // 选中文字,搜索文字,弹出搜索框 // let search = wk$("#lmenu_search")[0]; // search.click(); // // 取得input内容 // let input = wk$(".min-text input")[0]; // let text = input.value; // // 清空input // input.value = ""; // 输出到控制台和剪贴板 utils.copy(getSelectedText()); } /** * 捕获 ctrl + c 并关闭弹窗 * @param {KeyboardEvent} keydown * @returns */ function catchCtrlC(keydown) { // 判断是否为 ctrl + c if (!(keydown.code === "KeyC" && keydown.ctrlKey === true)) { return; } // 判断触发间隔 let now = Date.now(); // 距离上次小于1秒 if (now - doc88JS.last_copy_time < 1000 * 1) { doc88JS.last_copy_time = now; return; } // 大于1秒 // 刷新最近一次触发时间 doc88JS.last_copy_time = now; // 复制文字 copySelected(); } /** * 随机改变字体颜色、大小、粗细 * @param {HTMLElement} elem */ function emphasizeText(elem) { let rand = Math.random; elem.style = ` font-weight: ${200 + parseInt(700 * rand())}; font-size: ${(1 + rand()).toFixed(1)}em; color: hsl(${parseInt(360 * rand())}, ${parseInt(40 + 60 * rand())}%, ${parseInt(60 * rand())}%); background-color: yellow; `; } /** * 浏览并加载所有页面 */ async function walkThrough$1() { // 文档容器 let container = wk$("#pageContainer")[0]; container.style.display = "none"; // 页码 let page_num = wk$("#pageNumInput")[0]; // 文末提示 let tail = wk$("#readEndDiv > p")[0]; let origin = tail.textContent; // 按钮 wk$('.btns_section > [class*="btn-"]').forEach( elem => elem.style.display = "none" ); // 逐页渲染 let total = parseInt(Config.p_pagecount); try { for (let i = 1; i <= total; i++) { // 前往页码 GotoPage(i); await utils.waitUntil(async() => { let page = wk$(`#page_${i}`)[0]; // page无法选中说明有弹窗 if (!page) { // 关闭弹窗,等待,然后递归 wk$("#ym-window .DOC88Window_close")[0].click(); await utils.sleep(500); walkThrough$1(); throw new Error("walkThrough 递归完成,终止函数"); } // canvas尚未绘制时width=300 return page.width !== 300; }); // 凸显页码 emphasizeText(page_num); tail.textContent = `请勿反复点击按钮,耐心等待页面渲染:${i}/${total}`; } } catch(e) { // 捕获退出信号,然后退出 console.log(e); return; } // 恢复原本显示 container.style.display = ""; page_num.style = ""; tail.textContent = origin; // 按钮 wk$('.btns_section > [class*="btn-"]').forEach( elem => elem.style.display = "block" ); wk$(".btns_section > .btn-1")[0].style.display = "none"; } /** * 道客巴巴文档下载策略 */ async function doc88() { // 全局对象 globalThis.doc88JS = { last_copy_time: 0, // 上一次 ctrl + c 的时间戳(毫秒) copy_api: ["Core", "Annotation", "api"] }; // 创建脚本启动按钮1、2 utils.createBtns(); // 绑定主函数 let prepare = function() { // 获取canvas元素列表 let node_list = wk$(".inner_page"); // 获取文档标题 let title; if (wk$(".doctopic h1")[0]) { title = wk$(".doctopic h1")[0].title; } else { title = "文档"; } return [node_list, title]; }; // btn_1: 展开文档 utils.setBtnListener(readAllDoc88, "btn_1"); // // btn_2: 加载全部页面 utils.setBtnListener(walkThrough$1, "btn_2", "加载所有页面"); // btn_3: 导出PDF utils.setBtnListener(() => { if (confirm("确定每页内容都加载完成了吗?")) { utils.canvasesToPDF(...prepare()); } }, "btn_3", "导出图片到PDF"); // btn_4: 导出ZIP utils.setBtnListener(() => { if (confirm("确定每页内容都加载完成了吗?")) { utils.canvasesToZip(...prepare()); } }, "btn_4", "导出图片到ZIP"); // btn_5: 复制选中文字 utils.setBtnListener(() => { copySelected(); utils.modifyBtnText("btn_5", "复制成功!", false, false); }, "btn_5", "复制选中文字"); // 为 ctrl + c 添加响应 document.addEventListener("keydown", catchCtrlC); // 执行一次初始化任务 await utils.sleep(1000); initService(); } // 绑定主函数 function getCanvasList() { // 获取全部canvas元素,用于传递canvas元素列表给 btn_2 和 btn_3 let parent_node_list = document.querySelectorAll(".hkswf-content"); let node_list = []; for (let node of parent_node_list) { node_list.push(node.children[0]); } return node_list; } function prepare() { // 获取canvas元素列表 let node_list = getCanvasList(); // 获取文档标题 let title; if (document.querySelector("h1 [title=doc]")) { title = document.querySelector("h1 [title=doc]").nextElementSibling.textContent; } else if (document.querySelector(".doc_title")) { title = document.querySelector(".doc_title").textContent; } else { title = "文档"; } return [node_list, title]; } /** * 下载全部图片链接,适用性:爱问共享资料、得力文库 * @param {string} selector 图形元素的父级元素 */ function savePicUrls(selector) { let pages = document.querySelectorAll(selector); let pic_urls = []; for (let elem of pages) { let pic_obj = elem.children[0]; let url = pic_obj.src; pic_urls.push(url); } let content = pic_urls.join("\n"); // 启动下载 utils.saveAs("urls.csv", content); } // 判断是否有canvas元素 function detectCanvas() { let haveCanvas = getCanvasList().length === 0 ? false : true; // 隐藏按钮 utils.toggleBtn("btn_1"); // 显示按钮 utils.toggleBtn("btn_2"); // 如果没有canvas元素,则认为文档页面由外链图片构成 if (!haveCanvas) { // btn_2: 导出图片链接 utils.setBtnListener(() => { if (confirm("确定每页内容都加载完成了吗?")) { savePicUrls("[id*=img_]"); } }, "btn_2", "导出全部图片链接"); } else { // 显示按钮3 utils.toggleBtn("btn_3"); // btn_2: 导出zip utils.setBtnListener(() => { if (confirm("确定每页内容都加载完成了吗?")) { utils.canvasesToZip(...prepare()); } }, "btn_2", "导出图片到zip"); // btn_3: 导出PDF utils.setBtnListener(() => { if (confirm("确定每页内容都加载完成了吗?")) { utils.canvasesToPDF(...prepare()); } }, "btn_3", "导出图片到PDF"); } } /** * 豆丁文档下载策略 */ function docin() { // 创建脚本启动按钮 utils.createBtns(); // 隐藏底部工具栏 document.querySelector("#j_select").click(); // 选择指针 let tool_bar = document.querySelector(".reader_tools_bar_wrap.tools_bar_small.clear"); tool_bar.style.display = "none"; // btn_1: 判断文档类型 utils.setBtnListener(() => { utils.forceHide(".jz_watermark"); detectCanvas(); }, "btn_1", "判断文档类型"); } function jumpToHost() { // https://swf.ishare.down.sina.com.cn/1DrH4Qt2cvKd.jpg?ssig=DUf5x%2BXnKU&Expires=1673867307&KID=sina,ishare&range={}-{} let url = wk$(".data-detail img, .data-detail embed")[0].src; if (!url) { alert("找不到图片元素"); return; } let url_obj = new URL(url); let path = url_obj.pathname.slice(1); let query = url_obj.search.slice(1).split("&range")[0]; let title = document.title.split(" - ")[0]; let target = `${url_obj.protocol}//${url_obj.host}?path=${path}&fname=${title}&${query}`; // https://swf.ishare.down.sina.com.cn/ globalThis.open(target, "hostage"); // 然后在跳板页面发起对图片的请求 } /** * 爱问文库下载跳转策略 */ function ishare() { // 创建按钮区 utils.createBtns(); // btn_1: 识别文档类型 -> 导出PDF utils.setBtnListener(jumpToHost, "btn_1", "到下载页面"); // btn_2: 不支持爱问办公 utils.setBtnListener(() => null, "btn_2", "不支持爱问办公"); // utils.toggleBtnStatus("btn_4"); } /** * 返回包含对于数量svg元素的html元素 * @param {string} data * @returns {HTMLDivElement} article */ function _createDiv(data) { let num = utils.countSubStr(data, data.slice(0, 10)); let article = document.createElement("div"); article.id = "article"; article.innerHTML = `
${ `
`.repeat(num) } `; // 移除最后一个多出的gap Array.from(article.querySelectorAll(".gap")).at(-1).remove(); return article; } function setGap(height) { let style = wk$(".wk-settings")[0].innerHTML; wk$(".wk-settings")[0].innerHTML = style.replace( /[.]gap.*?{.*?height:.+?;/s, `.gap { height: ${parseInt(height)}px;` ); } function setGapGUI() { let now = getComputedStyle(wk$(".gap")[0]).height; let new_h = prompt(`当前间距:${now}\n请输入新间距:`); if (new_h) { setGap(new_h); } } function getSVGtext(data) { let div = document.createElement("div"); div.innerHTML = data; return div.textContent; } function toDisplayMode1() { let content = globalThis["ishareJS"].content_1; if (!content) { content = globalThis["ishareJS"].text .replace(/\n{2,}/g, "
") .replace(/\n/g, "
") .replace(/\s/g, " ") .replace(/([a-z])([A-Z])/g, "$1 $2"); // 英文简单分词 globalThis["ishareJS"].content_1 = content; } wk$("#root-box")[0].innerHTML = content; } function toDisplayMode2() { let content = globalThis["ishareJS"].content_2; if (!content) { content = globalThis["ishareJS"].text .replace(/\n{2,}/g, "
") .replace(/\n/g, "") .replace(/\s/g, " ") .replace(/([a-z])([A-Z])/g, "$1 $2") .split("
") .map(paragraph => `

${paragraph}

`) .join(""); globalThis["ishareJS"].content_2 = content; wk$(".wk-settings")[0].innerHTML += ` #root-box > p { text-indent: 2em; width: 40em; word-break: break-word; } `; } wk$("#root-box")[0].innerHTML = content; } function changeDisplayModeWrapper() { let flag = true; function inner() { if (flag) { toDisplayMode1(); } else { toDisplayMode2(); } flag = !flag; } return inner; } function handleSVGtext() { globalThis["ishareJS"].text = getSVGtext( globalThis["ishareJS"].data ); let change = changeDisplayModeWrapper(); utils.setBtnListener(change, "btn_4", "切换显示模式"); utils.toggleBtn("btn_2"); utils.toggleBtn("btn_3"); utils.toggleBtn("btn_4"); change(); } /** * 处理svg的url * @param {string} svg_url */ async function handleSVGurl(svg_url) { let resp = await fetch(svg_url); let data = await resp.text(); globalThis["ishareJS"].data = data; let sep = data.slice(0, 10); let svg_texts = data .split(sep) .slice(1) .map(svg_text => sep + svg_text); console.log(`共 ${svg_texts.length} 张图片`); let article = _createDiv(data); let boxes = article.querySelectorAll(".svg-box"); boxes.forEach((obj, i) => { let blob = new Blob([svg_texts[i]], {type: "image/svg+xml"}); let url = URL.createObjectURL(blob); obj.data = url; URL.revokeObjectURL(blob); }); let body = wk$("body")[0]; body.innerHTML = ""; body.appendChild(article); utils.createBtns(); utils.setBtnListener(utils.hideBtnThenPrint, "btn_1", "打印页面到PDF"); utils.setBtnListener(setGapGUI, "btn_2", "重设页间距"); utils.setBtnListener(handleSVGtext, "btn_3", "显示空白点我"); utils.toggleBtn("btn_2"); utils.toggleBtn("btn_3"); } /** * 取得图片下载地址 * @param {string} fname * @param {string} path * @returns */ function getImgUrl(fname, path) { if (!fname) { throw new Error("URL Param `fname` does not exist."); } return location.href .replace(/[?].+?&ssig/, "?ssig") .replace("?", path + "?"); } /** * 下载整个图片包 * @param {string} img_url * @returns */ async function getData(img_url) { let resp = await fetch(img_url); // window.data = await resp.blob(); // throw Error("stop"); let buffer = await resp.arrayBuffer(); return new Uint8Array(buffer); } /** * 分切图片包为若干图片 * @param {Uint8Array} data 多张图片合集数据包 * @returns {Array} 图片列表 */ function parseData(data) { // 判断图像类型/拿到文件头 let head = data.slice(0, 10); let sep = head.join() + ","; // 切断,重组,格式转换 // return String.prototype.split.call(data, head).slice(1).map( // val => new Uint8Array((sep + val).split(",")) // ); // return utils.splitArray(data, head).slice(1).map( // val => Uint8Array.from([...head, ...val]) // ); // 切断,重组,格式转换 return data.join().split(sep).slice(1).map( val => new Uint8Array((sep + val).split(",")) ); } /** * 图像Uint8数组列表合并然后导出PDF * @param {string} fname * @param {Array} img_data_list */ async function imgDataArrsToPDF(fname, img_data_list) { let cover_blob = new Blob([img_data_list[0]]); let cover = await createImageBitmap(cover_blob); utils.canvasesToPDF( img_data_list, fname, cover.width, cover.height ); } async function exportPDF$3() { let fname = utils.getUrlParam("fname"); let path = utils.getUrlParam("path"); let img_url = getImgUrl(fname, path); // 处理svg if (path.includes(".svg")) { document.title = fname; await handleSVGurl(img_url); return; } // 处理常规图像 let data = await getData(img_url); let img_data_list = parseData(data); console.log(`共 ${img_data_list.length} 张图片`); await imgDataArrsToPDF(fname, img_data_list); } function showHints$1() { wk$("h1")[0].textContent = "wk 温馨提示"; wk$("p")[0].innerHTML = [ "下载 270 页的 PPT (70 MB) 需要约 30 秒", "请耐心等待,无需反复点击按钮", "如果很久没反应,请加 QQ 群反馈问题" ].join("
"); wk$("hr")[0].nextSibling.textContent = "403 Page Hostaged By Wenku Doc Downloader"; } /** * 爱问文库下载策略 */ async function ishareData() { // 全局对象 globalThis["ishareJS"] = { data: "", text: "", content_1: "", content_2: "" }; // 显示提示 showHints$1(); // 创建按钮区 utils.createBtns(); // btn_1: 识别文档类型 -> 导出PDF exportPDF$3 = await utils.recTime(exportPDF$3); utils.setBtnListener(exportPDF$3, "btn_1", "下载并导出PDF"); } // /** // * 清理并打印得力文库的文档页 // */ // function printPageDeliwenku() { // // 移除页面上的无关元素 // 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"; // let elem_list = document.querySelectorAll(selector); // for (let elem of elem_list) { // utils.tryToRemoveElement(elem); // } // // 修改页间距 // let outer_pages = document.getElementsByClassName("outer_page"); // for (let page of outer_pages) { // page.style.marginBottom = "20px"; // } // // 使文档居中 // alert("建议使用:\n偏移量: 3\n缩放: 112\n请上下滚动页面,确保每页内容都加载完成以避免空白页\n如果预览时有空白页或文末有绿色按钮,请取消打印重试"); // if (!utils.centerDoc("#boxleft", "3")) { // return; // 如果输入非法,终止函数调用 // } // // 打印文档 // utils.hideBtnThenPrint(); // } // /** // * 点击“继续阅读”,适用性:得力文库 // */ // function readAllDeliwenku() { // // 点击“同意并开始预览全文” // let start_btn = document.getElementsByClassName("pre_button")[0]; // let display = start_btn.parentElement.parentElement.style.display; // // 如果该按钮显示着,则点击,然后滚动至页面底部,最后终止函数 // if (!display) { // start_btn.children[0].click(); // setTimeout(() => { // scroll(0, document.body.scrollHeight); // }, 200); // return; // } // // 增强按钮点击效果 // utils.enhanceBtnClickReaction(); // let read_all_btn = document.getElementsByClassName("fc2e")[0]; // let display2 = read_all_btn.parentElement.parentElement.style.display // // 继续阅读 // if (display2 !== "none") { // // 获取input元素 // let cur_page = document.querySelector("#pageNumInput"); // let page_old = cur_page.value; // let page_max = cur_page.parentElement.nextElementSibling.textContent.replace(" / ", ""); // // 跳转到尾页 // utils.jump2pageNo(cur_page, page_max, "keydown"); // // 跳转回来 // utils.jump2pageNo(cur_page, page_old, "keydown"); // // 切换按钮准备导出 // } else { // // 推荐导出图片链接 // utils.modifyBtnText("btn_2", null, true); // // 隐藏按钮 // utils.toggleBtnStatus("btn_1"); // // 显示按钮 // utils.toggleBtnStatus("btn_2"); // utils.toggleBtnStatus("btn_3"); // // btn_3 橙色按钮 // utils.setBtnEvent(printPageDeliwenku, [], "btn_3", "打印页面到PDF"); // } // } // /** // * 得力文库文档下载策略 // */ // function deliwenkuDeprecated() { // // 创建脚本启动按钮1、2 // utils.createBtns(); // // btn_1: 展开文档 // utils.setBtnEvent(readAllDeliwenku, [], "btn_1"); // // btn_2: 导出图片链接 // utils.setBtnEvent(() => { // if (confirm("确定每页内容都加载完成了吗?")) { // utils.savePicUrls('.inner_page div'); // } // }, [], "btn_2", "导出图片链接"); // // 尝试关闭页面弹窗 // try { document.querySelector("div[title=点击关闭]").click(); } catch (e) { console.log(0); } // // 解除打印限制 // utils.allowPrint(); // } function getPageNum() { // ' / 6 ' -> ' 6 ' let num_str = wk$("span.counts")[0].textContent.split("/")[1]; return parseInt(num_str); } function jumpToHostage() { let url = new URL(wk$("#pageflash_1 > img")[0].src); // '/fileroot/2019-9/23/73598bfa-6b91-4cbe-a548-9996f46653a2/73598bfa-6b91-4cbe-a548-9996f46653a21.gif' let num = getPageNum(); // '七年级上册地理期末试卷精编.doc-得力文库' let fname = document.title.slice(0, -5); let path = url.pathname; let tail = "1.gif"; if (!path.endsWith(tail)) { throw new Error(`url尾部不为【${tail}】!path:【${path}】`); } let base_path = path.slice(0, -5); globalThis.open( `${url.protocol}//${url.host}/?num=${num}&lmt=${lmt}&fname=${fname}&path=${base_path}`, "hostage" ); } function deliwenku() { utils.createBtns(); utils.setBtnListener(jumpToHostage, "btn_1", "到下载页面"); } function showHints() { let info = globalThis["deliJS"]; let body = `

wk: 跳板页面

有时候点一次下载等半天没反应,就再试一次

如果试了 2 次还不行加 QQ 群反馈吧...

导出的PDF如果页面数量少于应有的,那么意味着免费页数就这么多,我也爱莫能助

短时间连续使用导出按钮会导致 IP 被封禁


文档名称:${info.fname}
原始文档页数:${info.num}
最大免费页数:${info.lmt}
`; document.title = utils.getUrlParam("fname"); document.body.innerHTML = body; } /** * url生成器 * @param {string} base_url * @param {number} num */ function* genUrls(base_url, num) { for (let i=1; i<=num; i++) { yield `${base_url}${i}.gif`; } } function genBaseURL(path) { return `${location.protocol}//${location.host}${path}`; } function parseParamsToDeliJS() { let path = utils.getUrlParam("path"); let base_url = genBaseURL(path); let fname = utils.getUrlParam("fname"); let num = parseInt(utils.getUrlParam("num")); let lmt = parseInt(utils.getUrlParam("lmt")); lmt = lmt > 3? lmt: 20; lmt = lmt > num? num: lmt; globalThis["deliJS"] = { base_url, num, fname, lmt }; } async function exportPDF$2() { let info = globalThis["deliJS"]; await utils.imgUrlsToPDF( genUrls(info.base_url, info.num), info.fname, info.lmt, true // 请求完成后清理控制台 ); } /** * 得力文库跳板页面下载策略 */ async function deliFile() { // 从URL解析文档参数 parseParamsToDeliJS(); // 显示提示 showHints(); // 创建按钮区 utils.createBtns(); // btn_1: 导出PDF exportPDF$2 = await utils.recTime(exportPDF$2); utils.setBtnListener(exportPDF$2, "btn_1", "下载并导出PDF"); } function readAll360Doc() { // 展开文档 document.querySelector(".article_showall a").click(); // 隐藏按钮 utils.toggleBtn("btn_1"); // 显示按钮 utils.toggleBtn("btn_2"); utils.toggleBtn("btn_3"); utils.toggleBtn("btn_4"); } function saveText_360Doc() { // 捕获图片链接 let images = document.querySelectorAll("#artContent img"); let content = []; for (let i = 0; i < images.length; i++) { let src = images[i].src; content.push(`图${i+1},链接:${src}`); } // 捕获文本 let text = document.querySelector("#artContent").textContent; content.push(text); // 保存纯文本文档 let title = document.querySelector("#titiletext").textContent; utils.saveAs(`${title}.txt`, content.join("\n")); } function printPage360Doc() { if (!confirm("确定每页内容都加载完成了吗?")) { return; } // # 清理并打印360doc的文档页 // ## 移除页面上无关的元素 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"; let elem_list = document.querySelectorAll(selector); let under_doc_1, under_doc_2; try { under_doc_1 = document.querySelector("#bgchange p.clearboth").nextElementSibling; under_doc_2 = document.querySelector("#bgchange").nextElementSibling.nextElementSibling; } catch (e) { console.log(); } // 执行移除 for (let elem of elem_list) { utils.tryToRemoveElement(elem); } utils.tryToRemoveElement(under_doc_1); utils.tryToRemoveElement(under_doc_2); // 执行隐藏 document.querySelector("a[title]").style.display = "none"; // 使文档居中 alert("建议使用:\n偏移量: 20\n缩放: 默认\n"); if (!utils.centerDoc(".a_left", "20")) { return; // 如果输入非法,终止函数调用 } // 隐藏按钮,然后打印页面 utils.hideBtnThenPrint(); } /** * 阻止监听器生效 * @param {Event} e */ function stopListening(e) { e.stopImmediatePropagation(); } /** * 阻止捕获事件 */ function stopCapturing() { ["click", "mouseup"].forEach( type => { document.body.addEventListener(type, stopListening, true); document["on" + type] = undefined; } ); ["keypress", "keydown"].forEach( type => { window.addEventListener(type, stopListening, true); window["on" + type] = undefined; } ); } /** * 重置图像链接和最大宽度 * @param {Document} doc */ function resetImg(doc=document) { doc.querySelectorAll("img").forEach( elem => { elem.style.maxWidth = "100%"; for (let attr of elem.attributes) { if (attr.name.endsWith("-src")) { elem.setAttribute("src", attr.value); break; } } } ); } /** * 仅保留全屏文档 */ function getFullScreen() { FullScreenObj.init(); wk$("#artContent > p:nth-child(3)")[0]?.remove(); let data = wk$("#artfullscreen__box_scr > table")[0].outerHTML; window.doc360JS = { data }; let html_str = ` ${data} `; wk$("html")[0].replaceWith(wk$("html")[0].cloneNode()); wk$("html")[0].innerHTML = html_str; resetImg(); } function cleanPage() { getFullScreen(); stopCapturing(); } /** * 360doc个人图书馆下载策略 */ function doc360() { // 创建按钮区 utils.createBtns(); // btn_1: 展开文档 utils.setBtnListener(readAll360Doc, "btn_1"); // btn_2: 导出纯文本 utils.setBtnListener(saveText_360Doc, "btn_2", "导出纯文本"); // btn_3: 打印页面到PDF utils.setBtnListener(printPage360Doc, "btn_3", "打印页面到PDF"); // btn_3: 清理页面 utils.setBtnListener(cleanPage, "btn_4", "清理页面(推荐)"); } // /** // * 查找出所有未被捕获的页码,并返回列表 // * @returns 未捕获页码列表 // */ // function getMissedPages() { // let all = []; // 全部页码 // for (let i = 0; i < window.mbaJS.max_page; i++) { // all[i] = i + 1; // } // let missed = []; // 未捕获页码 // let possessed = Array.from(window.mbaJS.canvases_map.keys()); // 已捕获页面 // // 排除并录入未捕获页码 // for (let num of all) { // if (!possessed.includes(`page${num}`)) { // missed.push(num); // } // } // return missed; // } // /** // * 根据键中的id数字对map排序 // * @param {Map} elems_map // * @returns sorted_map // */ // function sortMapByID(elems_map) { // // id形式:page2 // let elems_arr = Array.from(elems_map); // elems_arr.sort((item1, item2) => { // // 从key中取出id // let id1 = parseInt(item1[0].replace("page", "")); // let id2 = parseInt(item2[0].replace("page", "")); // // 升序排序 // return id1 - id2; // }); // // 返回排序好的map // return new Map(elems_arr); // } // /** // * 存储动态加载的canvas元素、textContent // */ // function storeElements_MBA() { // let canvases_map = window.mbaJS.canvases_map; // let texts_map = window.mbaJS.texts_map; // let quality = window.mbaJS.quality; // document.querySelectorAll(".page[data-loaded=true]").forEach( // (elem) => { // let capture = (elem) => { // // (1) 存储页面为canvas图形 // let canvas, data_base64; // // 导出canvas数据防止丢失 // try { // // 存储canvas // canvas = elem.querySelector("canvas[id*=page]"); // if (window.mbaJS.only_text) { // data_base64 = null; // } else { // data_base64 = canvas.toDataURL("image/jpeg", quality); // } // } catch (e) { // // utils.sleep(500); // return; // } // // 增量录入map // let id = canvas.id; // id的形式:page2 // if (!canvases_map.has(id)) { // canvases_map.set(id, data_base64); // } // // 确定canvas长宽 // if (!window.mbaJS.only_text && !window.mbaJS.width) { // window.mbaJS.width = parseInt(canvas.width); // window.mbaJS.height = parseInt(canvas.height); // } // // (2) 存储text // let text = elem.textContent; // if (!texts_map.has(id)) { // texts_map.set(id, text); // } // }; // setTimeout(capture, 500, elem); // }); // if (canvases_map.size === window.mbaJS.max_page) { // // 根据id排序 // window.mbaJS.canvases_map = sortMapByID(window.mbaJS.canvases_map); // window.mbaJS.texts_map = sortMapByID(window.mbaJS.texts_map); // window.mbaJS.finished = true; // window.onscroll = null; // } // } // /** // * 将canvas转为jpeg,然后导出PDF // * @param {Array} base64_list canvas元素列表 // * @param {String} title 文档标题 // */ // function saveCanvasesToPDF_MBA(base64_list, title) { // let width = window.mbaJS.width; // let height = window.mbaJS.height; // console.log(`canvas数据:宽: ${width}px,高: ${height}px`); // // 如果文档第一页的宽比长更大,则landscape,否则portrait // let orientation = width > height ? 'l' : 'p'; // let pdf = new jspdf.jsPDF(orientation, 'px', [height, width]); // // 保存每一页文档到每一页pdf // let i = 0; // for (let base64 of base64_list) { // i += 1; // pdf.addImage(base64, 'JPEG', 0, 0, width, height); // // 如果当前不是文档最后一页,则需要添加下一个空白页 // if (i < window.mbaJS.max_page) { // pdf.addPage(); // } // } // // 导出文件 // pdf.save(`${title}.pdf`); // } // /** // * 判断文档页是否收集完毕,当不行时给出提示 // * @returns boolean // */ // function ready2use() { // removeAds(); // 顺便清理广告 // // 如果是首次点击按钮,给出提示 // if (window.mbaJS.first_hint) { // let hint = [ // "如果浏览速度过快,比如:", // "当前页面还没完全加载好就滚动页面去看下一页", // "那就极有可能导致导出的PDF有空白页或文本有缺漏", // "由防范技术的干扰,该功能目前很不好用,见谅" // ].join("\n"); // alert(hint); // window.mbaJS.first_hint = false; // } // // 如果文档页没有收集完,给出提示 // if (!window.mbaJS.finished) { // let hint = [ // "仍有内容未加载完,无法使用该功能", // "建议从头到尾慢速地再浏览一遍", // "以下是没有加载完成页面的页码:", // getMissedPages().join(",") // ] // alert(hint.join("\n")); // return false; // } // return true; // } // /** // * 用捕获好的canvas转jpg,生成PDF // * @returns // */ // function canvas2PDF_mba() { // if (!ready2use()) { // return; // } // let canvases = window.mbaJS.canvases_map.values(); // // 导出PDF // let title = document.title.split("-")[0].trim(); // saveCanvasesToPDF_MBA(canvases, title); // } // /** // * 拼合捕获好的文本,保存到txt文件 // * @returns // */ // function saveText_mba() { // if (!ready2use()) { // return; // } // let content = Array.from(window.mbaJS.texts_map.values()); // let title = document.title.split("-")[0].trim(); // utils.saveAs(`${title}.txt`, content.join("\n")); // } // /** // * 移除广告 // */ // function removeAds() { // document.querySelectorAll(".doc-ad").forEach((ad_elem) => { // utils.tryToRemoveElement(ad_elem); // }); // } // function mbalib_() { // // 移除广告和左侧工具栏 // removeAds(); // let tool_bar = document.querySelector(".tool-bar"); // utils.tryToRemoveElement(tool_bar); // // 创建按钮 // utils.createBtns(); // // 隐藏按钮 // utils.toggleBtnStatus("btn_1"); // // 显示按钮 // utils.toggleBtnStatus("btn_2"); // utils.toggleBtnStatus("btn_3"); // utils.toggleBtnStatus("btn_4"); // // 取得页数 // let max_page = parseInt(document.querySelector("#numPages").textContent.replace("/ ", "")); // // 为导出内容提供全局变量,便于动态收集文档页元素的存取 // window.mbaJS = { // max_page: max_page, // texts_map: new Map(), // id: text // canvases_map: new Map(), // id: canvas_data_base64 // quality: 1, // canvas转jpg的质量 // width: null, // canvas宽度(px) // height: null, // finished: false, // 是否收集完了全部文档页元素 // first_hint: true, // scroll_count: 0, // 用于统计累计触发scroll的次数, // only_text: false // 是否仅捕获文本 // }; // // 跟随浏览,动态收集页面元素 // window.onscroll = () => { // storeElements_MBA(); // }; // // 跟随浏览,动态收集页面元素 // utils.scrollFunc(storeElements_MBA, window.mbaJS, 20, 50, "mba元素: 收集"); // // 绑定事件 // utils.setBtnListener(saveText_mba, [], "btn_2", "导出纯文本(不稳定)"); // utils.setBtnListener(canvas2PDF_mba, [], "btn_3", "导出PDF(不稳定)"); // // 根据页数决定按钮功能:<40页,导出文本+导出pdf,>40页:导出文本 // let btn_text, aim_btn, hint; // if (max_page > 40) { // btn_text = "失效说明"; // aim_btn = "btn_3"; // hint = [ // "页数超过40,脚本无效", // "只能使用导出文本功能", // "而此脚本会使页面内容加载明显变慢,建议禁用" // ]; // utils.setBtnListener( // () => { // utils.toggleBtnsSec(); // window.onscroll = null; // }, // [], // "btn_4", // "临时禁用脚本" // ); // } else { // btn_text = "空白页说明"; // aim_btn = "btn_4"; // hint = [ // "导致空白页的原因如下", // "加载该页的时间超过2秒 / 明显等待", // "而此脚本会使页面内容加载明显变慢,如果影响严重请禁用" // ]; // } // utils.setBtnListener(() => { // alert(hint.join("\n")); // }, [], aim_btn, btn_text); // } // function mbalib() { // setTimeout(mbalib_, 2000); // } async function getPDF() { if (!window.DEFAULT_URL) { alert("当前文档无法解析,请加 QQ 群反馈"); return; } let title = document.title.split(" - ")[0] + ".pdf"; let blob = await utils.getUrlAsBlob(DEFAULT_URL); utils.saveAs(title, blob); } function mbalib() { utils.createBtns(); utils.setBtnListener(getPDF, "btn_1", "下载PDF"); } /** * 判断是否进入预览模式 * @returns Boolean */ function isInPreview() { let p_elem = wk$("#preview_tips")[0]; if (p_elem && p_elem.style && p_elem.style.display === "none") { return true; } return false; } /** * 确保进入预览模式 */ async function ensureInPreview() { while (!isInPreview()) { // 如果没有进入预览,则先进入 if (typeof window.preview !== "function") { alert("脚本失效,请加 QQ 群反馈"); throw new Error("preview 全局函数不存在"); } await utils.sleep(500); preview(); } } /** * 前往页码 * @param {number} page_num */ function toPage$1(page_num) { // 先尝试官方接口,不行再用模拟的 try { Viewer._GotoPage(page_num); } catch(e) { console.error(e); utils.toPageNo(wk$("#pageNumInput")[0], page_num, "keydown"); } } /** * 展开全文预览,当展开完成后再次调用时,返回true * @returns */ async function walkThrough() { // 隐藏页面 wk$("#pageContainer")[0].style.display = "none"; // 逐页加载 let lmt = window.dugenJS.lmt; for (let i of utils.range(1, lmt + 1)) { toPage$1(i); await utils.waitUntil( () => wk$(`#outer_page_${i}`)[0].style.width.endsWith("px") ); } // 恢复显示 wk$("#pageContainer")[0].style.display = ""; console.log(`共 ${lmt} 页加载完毕`); } /** * 返回当前未加载页面的页码 * @returns not_loaded */ function getNotloadedPages() { // 已经取得的页码 let pages = document.querySelectorAll("[id*=pageflash_]"); let loaded = new Set(); pages.forEach((page) => { let id = page.id.split("_")[1]; id = parseInt(id); loaded.add(id); }); // 未取得的页码 let not_loaded = []; for (let i of utils.range(1, window.dugenJS.lmt + 1)) { if (!loaded.has(i)) { not_loaded.push(i); } } return not_loaded; } /** * 取得全部文档页面的链接,返回urls;如果有页面未加载,则返回null * @returns */ function getImgUrls$1() { let pages = wk$("[id*=pageflash_]"); // 尚未浏览完全部页面,返回false if (pages.length < window.dugenJS.lmt) { let hints = [ "尚未加载完全部页面", "以下页面需要浏览并加载:", getNotloadedPages().join(",") ]; alert(hints.join("\n")); return [false, []]; } // 浏览完全部页面,返回urls return [true, pages.map(page => page.querySelector("img").src)]; } function exportImgUrls() { let [ok, urls] = getImgUrls$1(); if (!ok) { return; } utils.saveAs("urls.csv", urls.join("\n")); } function exportPDF$1() { let [ok, urls] = getImgUrls$1(); if (!ok) { return; } let title = document.title.split("-")[0]; utils.imgUrlsToPDF(urls, title); } /** * dugen文档下载策略 */ async function dugen() { await ensureInPreview(); // 全局对象 window.dugenJS = { lmt: window.lmt ? window.lmt : 20 }; // 创建按钮区 utils.createBtns(); // 绑定监听器 // 按钮1:展开文档 utils.setBtnListener(walkThrough, "btn_1", "加载可预览页面"); // 按钮2:导出图片链接 utils.setBtnListener(exportImgUrls, "btn_2", "导出图片链接"); utils.toggleBtn("btn_2"); // 按钮3:导出PDF utils.setBtnListener(exportPDF$1, "btn_3", "导出PDF"); utils.toggleBtn("btn_3"); } /** * 取得文档类型 * @returns {String} 文档类型str */ function getDocType() { let type_elem = document.querySelector(".title .icon.icon-format"); // ["icon", "icon-format", "icon-format-doc"] let cls_str = type_elem.classList[2]; // "icon-format-doc" let type = cls_str.split("-")[2]; return type; } /** * 判断文档类型是否为type_list其中之一 * @returns 是否为type */ function isTypeof(type_list) { let type = getDocType(); if (type_list.includes(type)) { return true; } return false; } /** * 判断文档类型是否为PPT * @returns 是否为PPT */ function isPPT() { return isTypeof(["ppt", "pptx"]); } /** * 判断文档类型是否为Excel * @returns 是否为Excel */ function isEXCEL() { return isTypeof(["xls", "xlsm", "xlsx"]); } /** * 取得最大页码 * @returns {Number} 最大页码 */ function getPageCounts$1() { // let page_counts_str = document.querySelector(".intro-list").textContent; // let page_counts = parseInt(page_counts_str.match(/(?<=约 )[0-9]{1,3}(?=页)/)[0]); return parseInt( wk$(".counts")[0].textContent.split("/")[1].trim() ); } /** * 取得未加载页面的页码 * @param {Set} loaded 已加载的页码集合 * @returns {Array} not_loaded 未加载页码列表 */ function getNotLoaded(loaded) { let not_loaded = []; let page_counts = window.book118JS.page_counts; for (let i = 1; i <= page_counts; i++) { if (!loaded.has(i)) { not_loaded.push(i); } } return not_loaded; } /** * 取得全部文档页的url * @returns [<是否全部加载>, <未加载页码列表>|] */ function getUrls() { let loaded = new Set(); // 存储已加载页面的页码 let urls = []; // 存储已加载页面的图形src // 收集已加载页面的url document.querySelectorAll("div[data-id]").forEach((div) => { let src = div.querySelector("img").src; if (src) { // "1": "https://view-cache.book118.com/..." loaded.add(parseInt(div.getAttribute("data-id"))); urls.push(src); } }); // 如果所有页面加载完毕 if (loaded.size === window.book118JS.page_counts) { return [true, urls]; } // 否则收集未加载页面的url return [false, getNotLoaded(loaded)]; } /** * 展开全文 */ function readAll() { window.preview.jump(999); utils.toggleBtn("btn_1"); } /** * btn_2: 导出图片链接 */ function wantUrls() { let [flag, res] = getUrls(); // 页面都加载完毕,下载urls if (flag) { utils.saveAs("urls.csv", res.join("\n")); return; } // 没有加载完,提示出未加载好的页码 let hints = [ "仍有页面没有加载", "请浏览并加载如下页面:", res.join(",") ]; alert(hints.join("\n")); } /** * 打开PPT预览页面 */ function openPPTpage() { alert("原创力文档可能改版了,该功能可能无效"); window.preview.getSrc(); let openPPT = () => { let ppt_src = document.querySelector("iframe.preview-iframe").src; utils.openURL(ppt_src); window.preview.close(); }; setTimeout(openPPT, 1000); } /** * 原创力文档(非PPT或Excel)下载策略 */ async function book118_CommonDoc() { await utils.waitUntil( () => !!wk$(".counts")[0] ); // 创建全局对象 window.book118JS = { doc_type: getDocType(), page_counts: getPageCounts$1() }; // 处理非PPT文档 // 创建按钮组 utils.createBtns(); // 绑定监听器到按钮 // 按钮1:展开文档 utils.setBtnListener(readAll, "btn_1"); // 按钮2:导出图片链接 utils.setBtnListener(wantUrls, "btn_2", "导出图片链接"); utils.toggleBtn("btn_2"); // 按钮3:自动导出链接 utils.setBtnListener(toAPIPage, "btn_3", "自动导出链接(荐)"); utils.toggleBtn("btn_3"); } /** * 取得PPT文档最大页码 * @returns PPT文档最大页码int */ async function getPageCountsPPT() { await utils.waitUntil( () => !!wk$("#PageCount")[0].textContent ); return parseInt( wk$("#PageCount")[0].textContent ); } /** * 取得当前的页码 * @returns {Number} this_page */ function getThisPage() { let this_page = document.querySelector("#PageIndex").textContent; this_page = parseInt(this_page); return this_page; } /** * 点击下一动画直到变成下一页,再切回上一页 * @param {Number} next_page 下一页的页码 */ async function __nextFrameUntillNextPage(next_page) { // 如果已经抵达下一页,则返回上一页 let this_page = getThisPage(); // 最后一页直接退出 if (next_page > window.book118JS.page_counts) { return; } // 不是最后一页,但完成了任务 else if (this_page === next_page) { document.querySelector(".btmLeft").click(); await utils.sleep(500); return; } // 否则递归的点击下一动画 document.querySelector(".btmRight").click(); await utils.sleep(500); await __nextFrameUntillNextPage(next_page); } /** * 确保当前页面是最后一帧动画 */ async function ensurePageLoaded() { // 取得当前页码和下一页页码 let this_page = getThisPage(); let next_page = this_page + 1; // 开始点击下一页按钮,直到变成下一页,再点击上一页按钮来返回 await __nextFrameUntillNextPage(next_page); } /** * (异步)转换当前视图为canvas,添加到book118JS.canvases中。在递归终止时显示btn_2。 */ async function docView2Canvas() { await ensurePageLoaded(); // 取得页码 let cur_page = getThisPage(); // 取得视图元素,计数从0开始 let doc_view = document.querySelector(`#view${cur_page-1}`); // 转化为canvas let tasks = window.book118JS.tasks; tasks.push((async() => { let canvas = await html2canvas(doc_view); console.log(`page ${cur_page} finished`); return canvas; })()); // 如果到最后一页 if (cur_page === window.book118JS.page_counts) { // 终止递归,并且显示导出PDF按钮 utils.toggleBtn("btn_2"); return; } // 否则下一次递归(继续捕获下一页) document.querySelector(".pgRight").click(); await utils.sleep(500); await docView2Canvas(); } /** * 将捕获的canvases合并并导出为pdf * @returns */ async function canvases2pdf() { // 已经捕获的页面数量 let stored_amount = window.book118JS.tasks.length; // 总页面数量 let page_counts = window.book118JS.page_counts; // 校验数量 let diff = page_counts - stored_amount; if (diff > 0) { alert(`缺失了 ${diff} 页,可以过一会再点击该按钮试试。`); if (!confirm("是否仍要导出PDF?")) { // 不坚持导出PDF的情况 return; } } // 导出PDF let canvases = await Promise.all(window.book118JS.tasks); window.book118JS.canvases = canvases; // 取得宽高 let model = canvases[0]; let width = model.width; let height = model.height; // 取得标题然后导出pdf utils.canvasesToPDF(canvases, "原创力PPT文档", width, height); } /** * 原创力文档(PPT)下载策略 */ async function book118_PPT() { // 创建全局对象 window.book118JS = { page_counts: await getPageCountsPPT(), canvases: [], // 存储每页文档转化的canvas tasks: [] }; // 创建按钮区 utils.createBtns(); // 绑定监听器到按钮1 utils.setBtnListener(() => { let hints = [ "正在为文档“截图”,请耐心等待过程完成,不要操作", "“截图”可能会有额外一层黑边,原因未知,暂无法处理,烦请谅解" ]; alert(hints.join("\n")); // 隐藏按钮1 utils.toggleBtn("btn_1"); // 开始捕获页面(异步) docView2Canvas(window.book118JS.page_counts); }, "btn_1", "捕获页面"); // 为按钮2绑定监听器 utils.setBtnListener( await utils.recTime(canvases2pdf), "btn_2", "导出PDF" ); } /** * 取得当前页面的excel,返回csv string * @returns {String} csv */ function excel2CSV() { let table = []; let rows = document.querySelectorAll("tr[id]"); // 遍历行 for (let row of rows) { let csv_row = []; // 遍历列(单元格) for (let cell of row.querySelectorAll("td[class*=fi], td.tdrl")) { // 判断单元格是否存储图片 let img = cell.querySelector("img"); if (img) { // 如果是图片,保存图片链接 csv_row.push(img.src); } else { // 否则保存单元格文本 csv_row.push(cell.textContent); } } table.push(csv_row.join(",")); } let csv = table.join("\n"); csv = csv.replace(/\n{2,}/g, "\n"); return csv; } /** * 下载当前表格内容,保存为csv(utf-8编码) */ function wantEXCEL() { let file_name = "原创力表格_UTF-8.csv"; utils.saveAs(file_name, excel2CSV()); } /** * 在Excel预览页面给出操作提示 */ function help() { let hints = [ "【导出表格到CSV】只能导出当前sheet,", "如果有多张sheet请在每个sheet上用按钮分别导出CSV。", "CSV是一种简单的表格格式,可以被Excel打开,", "并转为 xls 或 xlsx 格式存储,", "但CSV本身不能存储图片,所以用图片链接代替,请自行下载图片", "", "本功能导出的CSV文件无法直接用Excel打开,因为中文会乱码。", "有两个办法:", "1. 打开Excel,选择【数据】,选择【从文本/CSV】,", " 选择文件,【文件原始格式】选择【65001: Unicode(UTF-8)】,选择【加载】。", "2. 用【记事本】打开CSV文件,【文件】->【另存为】->", " 【编码】选择【ANSI】->【保存】。现在可以用Excel直接打开它了。" ]; alert(hints.join("\n")); } /** * 原创力文档(EXCEL)下载策略 */ function book118_EXCEL() { // 创建按钮区 utils.createBtns(); // 绑定监听器到按钮 utils.setBtnListener(wantEXCEL, "btn_1", "导出表格到CSV"); utils.setBtnListener(help, "btn_2", "使用说明"); // 显示按钮 utils.toggleBtn("btn_2"); } /** * 打开Excel预览页面 */ function openEXCELpage() { openPPTpage(); } /** * 跳转到图片接口页面 * @returns */ function toAPIPage() { let type = window?.base?.detail?.preview?.channel; if (type !== "pic") { alert("当前文档类型不适用"); return; } let base = window.base; let query = utils.dictToQueryStr({ project_id: 1, aid: base.detail.preview.office.aid, t: base.detail.senddate, view_token: base.detail.preview.pic.view_token, filetype: getDocType(), callback: "jQuery123_456", max: base.detail.preview.pic.preview_page, }); open(`https://openapi.book118.com/?${query}`); } /** * 原创力文档下载策略 */ function book118() { let host = window.location.hostname; if (host === 'max.book118.com') { if (isEXCEL()) { utils.createBtns(); utils.setBtnListener(openEXCELpage, "btn_1", "导出EXCEL"); } else if (isPPT()) { utils.createBtns(); utils.setBtnListener(openPPTpage, "btn_1", "导出PPT"); } else { book118_CommonDoc(); } } else if (host === "view-cache.book118.com") { book118_PPT(); } else if (host.match(/view[0-9]{1,3}.book118.com/)) { book118_EXCEL(); } else { console.log(`wk: Unknown host: ${host}`); } } async function get6urls(page) { let query = window.book118JS.query; let resp = await fetch( `${location.origin}/getPreview.html?&${query}&_=${Date.now()}&page=${page}` ); let text = await resp.text(); let json = text; if (text.startsWith("jQuery")) { json = text.replace(/.+?[(]{/, "{").slice(0, -2); } let info = JSON.parse(json); return Object.entries(info.data); } async function getAllUrls(max) { window.book118JS.requested = 0; let tasks = []; for (let i of wkutils.range(1, max, 6)) { tasks.push(get6urls(i)); // 更新进度 window.book118JS.requested += 1; // 等待间隔 await wkutils.sleep(2000); } let url_bundles = await Promise.all(tasks); return url_bundles.flat(1).map( item => item[1] ); } async function reget(urls) { let i = urls.findIndex(value => value === ""); if (i !== -1) { let new_urls = await get6urls(i); new_urls.forEach(item => { if (item[1] !== "") { urls[item[0]] = item[1]; } }); return true; } return false; } async function exportUrls(max) { let urls = window.book118JS.urls; if (urls.length < 1) { urls = await getAllUrls(max); let i = 0; while (await reget(urls)) { console.log(`reloading: ${++i}`); await wkutils.sleep(1000); } urls = urls.map(url => "https:" + url); window.book118JS.urls = urls; } utils.saveImgUrls(urls); console.log(200); } function makeQuery(params) { let keys = ["project_id", "aid", "t", "view_token", "filetype", "callback"]; return keys.map( key => `${key}=${params.get(key)}` ).join("&"); } async function exportUrlsGUI() { utils.addPopup(); utils.setPopupHead("进度条"); utils.setPopupBody("进度条初始化中..."); let showProgress = () => { if (location.hash === "#wk-popup") { utils.toID("?"); } else { utils.toID("wk-popup"); } }; utils.setBtnListener(showProgress, "btn_2", "进度条"); utils.toggleBtn("btn_2"); utils.toID("wk-popup"); await exportUrls(window.book118JS.max); utils.removePopup(); utils.toggleBtn("btn_2"); } function updateProgress() { let all = parseInt(window.book118JS.max / 6) + 1; utils.setPopupBody(`已经请求:${window.book118JS.requested}/${all}`); } /** * 原创力文档图片接口策略 */ async function book118api() { let url = new URL(location.href); let query = makeQuery(url.searchParams); let _rqst = 0; window.book118JS = { query, urls: [], max: parseInt(url.searchParams.get("max")), get requested() { return _rqst }, set requested(val) { _rqst = val; updateProgress(); }, }; utils.createBtns(); utils.setBtnListener( await utils.recTime(exportUrlsGUI), "btn_1", "导出链接" ); } // test url: https://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=E86BBCE32DA8E67F3DA04ED98F2465DB /** * 绘制0x0的bmp, 作为请求失败时返回的page * @returns {Promise} blank_page */ async function blankBMP() { let canvas = document.createElement("canvas"); [canvas.width, canvas.height] = [0, 0]; return createImageBitmap(canvas); } /** * resp导出bmp * @param {string} page_url * @param {Promise | ImageBitmap} pms_or_bmp * @returns {Promise} page */ async function respToPage(page_url, pms_or_bmp) { let center = globalThis.gb688JS; // 此时是bmp if (pms_or_bmp instanceof ImageBitmap) { return pms_or_bmp; } // 第一次下载, 且无人处理 if (!center.pages_status.get(page_url)) { // 处理中, 设为占用 center.pages_status.set(page_url, 1); // 处理 let resp; try { resp = await pms_or_bmp; } catch(err) { console.log("下载页面失败"); console.error(err); return blankBMP(); } let page_blob = await resp.blob(); let page = await createImageBitmap(page_blob); center.pages.set(page_url, page); // 处理结束, 设为释放 center.pages_status.set(page_url, 0); return page; } // 有人正在下载且出于处理中 while (center.pages_status.get(page_url)) { await utils.sleep(500); } return center.pages.get(page_url); } /** * 获得PNG页面 * @param {string} page_url * @returns {Promise} bmp */ async function getPage(page_url) { // 如果下载过, 直接返回缓存 let pages = globalThis.gb688JS.pages; if (pages.has(page_url)) { return respToPage(page_url, pages.get(page_url)); } // 如果从未下载过, 就下载 let resp = fetch(page_url, { "headers": { "accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "proxy-connection": "keep-alive" }, "referrer": location.href, "referrerPolicy": "strict-origin-when-cross-origin", "body": null, "method": "GET", "mode": "cors", "credentials": "include" }); pages.set(page_url, resp); return respToPage(page_url, resp); } /** * 返回文档页div的裁切和粘贴位置信息: [[cut_x, cut_y, paste_x%, paset_y%],...] * @param {HTMLDivElement} page_div 文档页元素 * @returns {Array>} positions */ function getPostions(page_div) { let positions = []; Array.from(page_div.children).forEach(span => { // 'pdfImg-3-8' -> {left: 30%; top: 80%;} let paste_pos = span.className.split("-").slice(1).map( v => parseInt(v) / 10 ); // '-600px 0px' -> [600, 0] let cut_pos = span.style.backgroundPosition.split(" ").map( v => Math.abs(parseInt(v)) ); positions.push([...cut_pos, ...paste_pos]); }); return positions; } /** * 取得文档页的图像url * @param {HTMLDivElement} page_div * @returns {string} url */ function getPageURL(page_div) { // 拿到目标图像url let path = location.pathname.split("/").slice(0, -1).join("/"); let prefix = location.origin + path + "/"; let url = page_div.getAttribute("bg"); if (!url) { // 'url("viewGbImg?fileName=VS72l67k0jw5g3j0vErP8DTsnWvk5QsqnNLLxaEtX%2FM%3D")' url = page_div.children[0].style.backgroundImage.split('"')[1]; } return prefix + url; } /** * 下载目标图像并拆解重绘, 返回canvas * @param {number} i 第 i 页 (从0开始) * @param {HTMLDivElement} page_div * @returns {Promise} [页码, Canvas] */ async function getAndDrawPage(i, page_div) { // 拿到目标图像 let url = getPageURL(page_div); let page = await getPage(url); // 绘制空白A4纸背景 let [page_w, page_h] = [1190, 1680]; let bg = document.createElement("canvas"); bg.width = page_w; // 注意canvas作为取景框的大小 bg.height = page_h; // 如果不设置等于一个很小的取景框 let bg_ctx = bg.getContext("2d"); bg_ctx.fillStyle = "white"; bg_ctx.fillRect(0, 0, page_w, page_h); // 逐个区块剪切取出并粘贴 // wk$("#viewer .page").forEach(page_div => { getPostions(page_div).forEach(pos => { bg_ctx.drawImage( page, // image source pos[0], // source x pos[1], // source y 120, // source width 169, // source height pos[2] * page_w, // destination x = left: x% pos[3] * page_h, // destination y = top: y% 120, // destination width 169 // destination height ); }); // }); return [i, bg]; } /** * 页面批量请求、裁剪重绘, 合成PDF并下载 */ async function turnPagesToPDF() { // 渲染每页 let tasks = []; wk$("#viewer .page").forEach((page_div, i) => { tasks.push( getAndDrawPage(i, page_div) ); }); // 等待每页渲染完成后,排序 let results = await Promise.all(tasks); results.sort((prev, next) => prev[0] - next[0]); // 合并为PDF并导出 utils.canvasesToPDF( results.map(item => item[1]), // '在线预览|GB 14023-2022' document.title.split("|")[1] ); } /** * 提示预估下载耗时,然后下载 */ function hintThenDownload$1() { // '/93' let page_num = parseInt(wk$("#numPages")[0].textContent.slice(1)); let estimate = Math.ceil(page_num / 3); alert(`页数: ${page_num},预计花费: ${estimate}秒;如遇网络异常可能更久\n请勿反复点击按钮;如果无法导出请 QQ 群反馈`); turnPagesToPDF(); } /** * gb688文档下载策略 */ async function gb688() { // 创建全局对象 globalThis.gb688JS = { pages: new Map(), // {url: bmp} pages_status: new Map() // {url: 0或1} 0释放, 1占用 }; // 创建按钮区 utils.createBtns(); // 绑定监听器 // 按钮1:导出PDF turnPagesToPDF = await utils.recTime(turnPagesToPDF); utils.setBtnListener(hintThenDownload$1, "btn_1", "导出PDF"); } function getPageCounts() { // " / 39" let counts_str = wk$(".counts")[0].textContent.split("/")[1]; let counts = parseInt(counts_str); return counts > 20 ? 20 : counts; } /** * 返回图片基础路径 * @returns {string} base_url */ function getImgBaseURL() { return wk$("#dp")[0].value; } function* genImgURLs() { let counts = getPageCounts(); let base_url = getImgBaseURL(); for (let i=1; i<=counts; i++) { yield base_url + `${i}.gif`; } } /** * 下载图片,转为canvas,合并为PDF并下载 */ function fetchThenExportPDF() { // db2092-2014-河北特种设备使用安全管理规范_安全文库网safewk.com let title = document.title.split("_")[0]; return utils.imgUrlsToPDF(genImgURLs(), title); } /** * 提示预估下载耗时,然后下载 */ async function hintThenDownload() { let hint = [ "只能导出可预览的页面(最多20页)", "请勿短时间反复点击按钮,导出用时大约不到 10 秒", "点完后很久没动静请至 QQ 群反馈" ]; alert(hint.join("\n")); await fetchThenExportPDF(); } /** * safewk文档下载策略 */ async function safewk() { // 创建按钮区 utils.createBtns(); // 绑定监听器 // 按钮1:导出PDF hintThenDownload = await utils.recTime(hintThenDownload); utils.setBtnListener(hintThenDownload, "btn_1", "导出PDF"); } /** * 跳转到页码 * @param {string | number} num */ function toPage(num) { if (window.WebPreview && WebPreview.Page && WebPreview.Page.jump ) { WebPreview.Page.jump(parseInt(num)); } else { console.error("window.WebPreview.Page.jump doesn't exist"); } } /** * 跳转页码GUI版 */ function toPageGUI() { let num = prompt("请输入要跳转的页码")?.trim(); if (/^[0-9]+$/.test(num)) { toPage(num); } else { console.log(`输入值 [${num}] 不是合法整数`); } } /** * 记录prop不存在value到控制台 * @param {string} prop * @param {any} value */ function logEmpty(prop, value) { console.log(`wk: info's [${prop}] is an empty value: [${value}]`); } /** * 验证info对象是否成功采集各项数据,失败时输出到控制台 * @param {Object} info */ function isIntact(info) { for (let prop in info) { let value = info[prop]; if (value === undefined || value === "" || value === NaN) { logEmpty(prop, value); return false; } if (typeof value === "object") { if (!isIntact(value)) { return false; } } } return true; } /** * 更新 WebPreview.Base.data 中的时间戳属性 */ function updateStamp() { let data = window?.WebPreview?.Base?.data; if (data) { let now = Date.now(); data.jsoncallback = 'callback' + now; data.callback = 'callback' + now; data._ = now; } } /** * 收集文档信息,用于跨页面传递 * @returns */ function collectInfo() { let win = window, params = win.previewParams, view = win.WebPreview, ver = view?.version, base = view?.Base, data = view?.Data, pages = parseInt(wk$('meta[property="og:document:page"]')[0].content), preview = data?.preview_page, // 默认值可能不准,需要从加密图像url链接的请求中获取更新值 encrypted_at = base?.page?.start, query_str = utils.dictToQueryStr(base?.data); // WebPreview 为 true 说明>=6页 // general.encrypted 为 true 说明>=21页 let info = { direct: { base_url: params?.pre, fake_type: params?.ShowType === 2 ? ".svg" : ".gif" }, encrypted: { query_str, step: base?.page?.zone, api: base?.host + base?.requestUrl }, general: { WebPreview: !!view, renrendoc_ver: ver, ver_followed: ver ? ver === "2022.10.25_2" : "unknown", pages, encrypted_at, preview, encrypted: preview && encrypted_at ? ( preview < encrypted_at ? false : true // 预览页码小于加密起始页码,肯定不加密 ) : ( // 不存在某个页码,说明页数少,不加密 pages && encrypted_at ? ( pages < encrypted_at ? false : true // 总页码小于加密起始页码,肯定不加密 ) : false // 不存在某个页码,说明页数少,不加密 ) } }; isIntact(info); window.rrdocJS.info = info; return info; } /** * 为请求失败抛出异常 * @param {Response} resp */ function raiseForStatus(resp) { let code = resp.status; if ("45".includes(`${code}`[0])) { throw new Error(`request failed: ${code}`); } } /** * 获取加密图片的链接列表的JSON响应 * @param {Object} info * @param {number} begin 请求的起始页码,一般是 5n + 1 且 n >= 21 * @returns {Promise} */ async function _getEncryptedImgUrlsJson(info, begin) { // 配置请求 let url = new URL( `${info.encrypted.api}?${info.encrypted.query_str}` ); url.searchParams.set("start", begin); // 请求图像链接列表 let resp = await fetch(url); raiseForStatus(resp); let text = await resp.text(); let json = text.replace(/callback[0-9]+[(]/, "").slice(0, -1); return JSON.parse(json); } /** * 通过请求一次加密图片链接列表来更新可预览页数 */ async function updatePreviewPageNum(info) { let begin = info.general.encrypted_at; let data = await _getEncryptedImgUrlsJson(info, begin); console.log(data); let prop = window?.WebPreview?.Data; if (!prop) { logEmpty("window?.WebPreview?.Data", prop); return; } let preview = data?.data?.read_count; if (preview) { prop.preview_page = parseInt(preview); } } /** * 获取加密图片的链接列表(length=5) * @param {Object} info * @param {number} begin 请求的起始页码,一般是 5n + 1 且 n >= 21 * @returns {Promise} */ async function getEncryptedImgUrls(info, begin) { // 请求JSON let data = await _getEncryptedImgUrlsJson(info, begin); // 提取列表 if (data.data && data.data.preview_list) { return data.data.preview_list.map(page => "https:" + page.url); } return []; } /** * 获取全部加密图片链接构成的列表 * @param {Object} info * @returns {Promise>} */ async function getAllEncryptedImgUrls(info) { info = await getLatestInfo(info); let gap = window.rrdocJS.gap, max_gap = window.rrdocJS.max_gap, begin = info.general.encrypted_at, stop = info.general.preview, step = info.encrypted.step, tasks = []; await utils.sleep(gap, max_gap); // 总进度条 window.rrdocJS.all = stop + 1 - begin; for (let i of utils.range(begin, stop + 1, step)) { tasks.push(getEncryptedImgUrls(info, i)); // 当前进度 window.rrdocJS.requested += step; // 等待间隔,2-2.5秒 await utils.sleep(gap, max_gap); } return (await Promise.all(tasks)).flat(); } function _notNull(value, default_value) { return value !== null ? value : default_value; } /** * 获取全部直接请求的图片 * @param {Object} info * @param {number} end 可以手动指定终止页码, 默认为null * @param {string} base 可以手动指定基准url, 默认为null * @param {string} type 可以手动指定图像类型, 默认为null * @returns */ function* genDirectImgUrls(info, end=null, base=null, type=null) { let pages = info.general.pages; end = pages > 20 ? 20 : pages; base = _notNull(base, info.direct.base_url); type = _notNull(type, info.direct.fake_type); for (let i of utils.range(1, end + 1)) { yield `${base}${i}${type}`; } } /** * 提取首个图片链接的 [base_url, type] * @returns {Array} */ function getBaseUrlOnPage() { let img = wk$(".page > img")[0]; let origin = img.getAttribute("data-original"); let url = origin ? origin : img.src; let type = url.slice(-8).split("1.")[1]; return [url.replace("1." + type, ""), type]; } /** * 以旧info查询并返回新的 * @param {Object} info * @returns {Promise} */ async function getLatestInfo(info) { updateStamp(); info = collectInfo(); await updatePreviewPageNum(info); return collectInfo(); } /** * 初始化进度条弹窗 */ function initProgressPopup() { let all = -1; Object.defineProperty( window.rrdocJS, "all", { get: () => all, set: val => all = val } ); utils.addPopup(); utils.setPopupHead("下载进度条"); utils.setPopupBody("进度条初始化中..."); let sent = 0; Object.defineProperty( window.rrdocJS, "requested", { get: () => sent, set: val => { sent = val; // 计算进度 let rate = (val / all * 100), show_val = val <= all ? val : all, remain = 0.5 * ( window.rrdocJS.gap + window.rrdocJS.max_gap ) * 0.001 * (all - val) / window.rrdocJS.info.encrypted.step; rate = (rate <= 100 ? rate : 100).toFixed(2); remain = (remain >= 0 ? remain : 0).toFixed(1); // 更新进度条 utils.setPopupBody( `当前进度: ${rate}%,${show_val}/${all}(+20)页,预计剩余${remain}秒` ); } } ); // 按钮3:显示进度条 utils.setBtnListener( () => utils.toID("wk-popup"), "btn_3", "显示进度条" ); utils.toggleBtn("btn_3"); // 按钮4:为什么下载慢 utils.setBtnListener( () => { let delay = (window.rrdocJS.gap / 1000).toFixed(0); alert(`因网站限制,请求间隔必须大于 ${delay} 秒,请理解`); }, "btn_4", "为什么下载慢" ); utils.toggleBtn("btn_4"); // 显示弹窗 utils.toID("wk-popup"); // 隐藏跳转页码以降低触发网络请求的可能性 utils.toggleBtn("btn_2"); } /** * 销毁进度条弹窗 */ function destoryProgressPopup() { let {all, requested} = window.rrdocJS; Object.defineProperties(window.rrdocJS, { "all": { value: all }, "requested": { value: requested } }); utils.removePopup(); // 隐藏按钮3 utils.toggleBtn("btn_3"); // 显示跳转页码 utils.toggleBtn("btn_2"); } function showDocType() { let type = window.rrdocJS.doc_type; alert(`当前文档类型:${type}\n1类:最多导出5页的链接\n2类:最多导出20页的链接\n3类:不确定`); } async function judgeFileType() { // 创建按钮区 utils.createBtns(); let info = collectInfo(); let pages = info.general.pages; let handler, doc_type; // 判断页数范围 // 小于等于5页 if (pages < 6) { doc_type = 1; handler = () => { let [base, type] = getBaseUrlOnPage(); utils.saveImgUrls( genDirectImgUrls(info, null, base, type) ); }; } // 没有加密图片 else if (!info.general.encrypted) { doc_type = 2; handler = () => utils.saveImgUrls( genDirectImgUrls(info) ); } // 有加密图片 else if (info.general.encrypted) { doc_type = 3; handler = async() => { utils.toggleBtn("btn_1"); initProgressPopup(); let urls = [ ...genDirectImgUrls(info), ...(await getAllEncryptedImgUrls(info)) ]; utils.saveImgUrls(urls); destoryProgressPopup(); }; // 按钮2:转到页码 utils.setBtnListener(toPageGUI, "btn_2", "转到页码"); utils.toggleBtn("btn_2"); // 未知情况 } else { doc_type = -1; console.log(info); alert("未能处理该文档,请加 QQ 群反馈"); return; } window.rrdocJS.doc_type = doc_type; // 按钮1:导出图片链接 utils.setBtnListener(handler, "btn_1", "导出图片链接"); // 按钮5:为什么不全 utils.setBtnListener(showDocType, "btn_5", "显示文档类型"); utils.toggleBtn("btn_5"); } /** * 人人文档下载策略 */ async function renrendoc() { window.rrdocJS = { info: {}, doc_type: null, gap: 2000, // 请求间隔, ms max_gap: 2500, // 请求间隔的波动上限, ms all: -1, // 一共需要请求的页数 requested: 0 // 已经请求的页数 }; await utils.sleep(500); judgeFileType(); // 判断完类型会显示按钮1 } /** * 取得全部图片连接 * @returns {Array} */ function getImgUrls() { // '../files/large/' let pre_path = htmlConfig.bookConfig.largePath; if (pre_path instanceof Array) { pre_path = pre_path[0]; } let base = location.href; let urls = globalThis.htmlConfig.fliphtml5_pages .map(obj => { // "../files/large/d8b6c26f987104455efb3ec5addca7c9.jpg" let path = pre_path + obj.n[0].split("?")[0]; let url = new URL(path, base); // https://book.yunzhan365.com/mctl/itid/files/large/d8b6c26f987104455efb3ec5addca7c9.jpg return url.href; }); globalThis.img_urls = urls; return urls; } /** * 导出图片到PDF */ async function exportPDF() { let urls = getImgUrls(); let title = htmlConfig.meta.title; alert("正在下载图片,请稍等,时长取决于图片数量"); await utils.imgUrlsToPDF(urls, title); } /** * 移除多余空按钮 */ function removeSpareBtns() { utils.tryToRemoveElements( wk$(".btns_section [class*=btn-]").slice(1) ); } /** * 云展网文档下载策略 */ async function yunzhan365() { // 根据网址分别处理 if (location.pathname.startsWith("/basic")) { // utils.setBtnListener(toIndex, "btn_1", "转到全屏页"); return; } // 创建脚本启动按钮 utils.createBtns(); utils.setBtnListener(exportPDF, "btn_1", "导出PDF"); removeSpareBtns(); } /** * 截取当前对话,返回canvas * @param {Function} once 一次性任务 * @returns {Promise} */ async function captureChat(once) { // 执行一次性任务 await once(); return ( await utils.elementsToCanvases([ window.chat ]) )[0]; } /** * 复制媒体到剪贴板 * @param {Blob} blob * @param {string} fname */ async function copyToClipboard(blob) { const data = [new ClipboardItem({ [blob.type]: blob })]; try { await navigator.clipboard.write(data); console.log("图像成功复制到剪贴板"); } catch (err) { console.error(err.name, err.message); } } /** * 导出图片到文件和剪贴板 * @param {HTMLCanvasElement} canvas */ async function exportCanvas(canvas) { let blob = await utils.canvasToBlob(canvas); utils.saveAs("BingAI对话.png", blob); copyToClipboard(blob); } /** * 截图并导出对话 */ async function exportChatAsImage() { // 定义一次性任务 let once = await utils.once(() => { // 移除video元素 utils.tryToRemoveElements( document.querySelectorAll("video") ); // 移除对话外元素 [...document.body.children].slice(2).forEach(elem => elem.remove()); }); await exportCanvas( await captureChat(once) ); } /** * 初始化工作 */ async function init() { let box1 = (await wk$$(".cib-serp-main"))[0], box2 = (await wk$$.call(box1.shadowRoot, "#cib-conversation-main"))[0], chat = (await wk$$.call(box2.shadowRoot, "#cib-chat-main"))[0]; window.chat = chat; let outer_bar = (await wk$$.call(box1.shadowRoot, "#cib-action-bar-main"))[0], bar = (await wk$$.call(outer_bar.shadowRoot, ".root"))[0], ori_btn = (await wk$$.call(bar, ".outside-left-container"))[0]; if (!(ori_btn instanceof HTMLElement)) { throw Error(`${ori_btn} 的类型不是 HTMLElement!`); } // 复制按钮 let button = ori_btn.cloneNode(false); window.button = button; // 更换按钮图标 button.innerHTML = `
保存对话
`; // 为点击绑定回调 button .querySelector(".button-compose-content") .addEventListener("click", exportChatAsImage); // 添加到DOM bar.appendChild(button); } /** * BingAI对话辅助工具 */ async function bingAIChat() { init(); } /** * 主函数:识别网站,执行对应文档下载策略 */ function main() { // 显示当前位置 let host = location.host; console.log(`当前 host: ${host}`); if (host.includes("docin.com")) { docin(); } else if (host === "swf.ishare.down.sina.com.cn") { ishareData(); } else if (host.includes("ishare.iask")) { ishare(); } else if (host === "www.deliwenku.com") { deliwenku(); } else if (host.includes("file") && host.includes("deliwenku.com")) { deliFile(); } else if (host === "www.doc88.com") { doc88(); } else if (host === "www.360doc.com") { doc360(); } else if (host === "doc.mbalib.com") { mbalib(); } else if (host === "www.dugen.com") { dugen(); } else if (host === "c.gb688.cn") { gb688(); } else if (host === "www.safewk.com") { safewk(); } else if (host === "openapi.book118.com") { book118api(); } else if (host.includes("book118.com")) { book118(); } else if (host === "www.renrendoc.com") { renrendoc(); } else if (host.includes("yunzhan365.com")) { yunzhan365(); } else if (host === "www.bing.com") { bingAIChat(); } else { console.log("匹配到了无效网页"); } } let options = { show_buttons: true }; if (options.activation_test) { alert(`Wenku Doc Downloader 已经生效!\n当前网址:\n${window.location.host}`); } // 根据配置选择:是否默认显示 if (!options.show_buttons) { utils.toggleBtnsSec(); } utils.manipulateElem(document.body, main); })();