// ==UserScript== // @name 原创力文档、人人文库文档下载 // @namespace http://tampermonkey.net/ // @version 0.3 // @description 你能看见多少我能下载多少,下载公开免费的PPTX、PDF、DOCX文件。 // @author Mr.Fang // @match https://*.book118.com/* // @match https://*.renrendoc.com/* // @require https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/jspdf/2.4.0/jspdf.umd.min.js // @require https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.34/dist/zip.min.js // @require https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.min.js // @icon https://dtking.cn/favicon.ico // @grant GM_getValue // @grant GM_setValue // @grant GM_download // @grant GM_notification // @grant unsafeWindow // @license Apache-2.0 // @downloadURL none // ==/UserScript== (function() { 'use strict'; let MF = '#MF_fixed{position:fixed;top:50%;transform:translateY(-50%);right:20px;gap:20px;flex-direction:column;z-index:2147483647;display:flex}'; MF += '.MF_box{padding:10px;cursor:pointer;border-color:rgb(0,102,255);border-radius:5px;background-color:white;color:rgb(0,102,255);margin-right:10px;box-shadow:rgb(207,207,207) 1px 1px 9px 3px}.MF_active{color: green;}'; const prefix = "MF_"; class Box { id = ""; // id label = ""; // 按钮文本 fun = ""; // 执行方法 constructor(id, label, fun) { this.id = id; this.label = label; this.fun = fun; } } class Utility { /** * 添加 css 样式 * @param e 节点 * @param data JSON 格式样式 */ style(e, data) { Object.keys(data).forEach(key => { e.style[key] = data[key] }) } attr(e, key) { return e.getAttribute(key) } /** * 追加样式 * @param css 格式样式 */ appendStyle(css) { let style = this.createEl('', 'style'); style.textContent = css; style.type = 'text/css'; let dom = document.head || document.documentElement; dom.appendChild(style); } /** * @description 创建 dom * @param id 必填 * @param elType * @param data */ createEl(id, elType, data) { const el = document.createElement(elType); el.id = id || ''; if (data) { this.style(el, data); } return el; } query(el) { return document.querySelector(el); } queryAll(el) { return document.querySelectorAll(el); } update(el, text) { const elNode = this.query(el); if (!elNode) { console.log('节点不存在'); } else { elNode.innerHTML = text; } } /** * 进度 * @param current 当前数量 -1预览结束 * @param total 总数量 * @param content 内容 */ preview(current, total, content) { if (current === -1) { this.update('#' + prefix + 'text', content ? content : "已完成"); } else { let p = (current / total) * 100; console.log('当前进度', p.toFixed(2)) this.update('#' + prefix + 'text', '进度' + p.toFixed(0) + '%'); } } gui(boxs) { const box = this.createEl(prefix + "fixed", 'div'); for (let x in boxs) { let item = boxs[x]; let el = this.createEl(prefix + item.id, 'button'); el.append(new Text(item.label)); if (x === '0') { el.classList = prefix + 'box ' + prefix + "active"; } else { el.className = prefix + "box"; } if (item.fun) { el.onclick = function() { eval(item.fun); } } box.append(el); } document.body.append(box); } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } notice(content) { // GM_notification({ // text: content, // title: "脚本执行结果通知", // url: location.href, // onclick: (event) => { // event.preventDefault(); // console.log(event) // } // }); } } const u = new Utility(); u.appendStyle(MF); const btns = [ new Box('text', '状态 0 %'), new Box('PPT', '获取地址', 'downloadTypeFile(3)') ] const initBtn = [new Box('start', '自动预览', 'startScroll()'), new Box('stop', '停止预览', 'stopScroll()'), new Box('down', '下载图片', 'downloadTypeFile(1)'), new Box('pdf', '下载PDF', 'downloadTypeFile(2)') ] btns.push(...initBtn) const domain = { REN: "renrendoc.com", BOOK: 'book118.com' }; const { host, href, origin } = window.location; const jsPDF = jspdf.jsPDF; let interval = null; // 定时器 let dom = null; // DOM let attr = ".webpreview-item"; // 列表选择 let attrImage = null; // 图片选择 let beforeFun = null; // 前置方法 let doc = null; let zipWriter = null; let fileType = ''; // 文件类型 docx pdf pptx let downType = 1; // 下载类型 1 ZIP 2 PDF 3 TXT let isPPT = null; // 文件类型 let pdf_w = 446, pdf_h = 631, pdf_ratio = 0.56; /** * 初始化 */ const init = () => { let title = GM_getValue("title"); // 初始化 jspdf 对象 p 竖向 l 横向 doc = new jsPDF({ unit: 'px', compress: true }); // 初始化 zip 对象 zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/zip"), { bufferedWrite: true, useCompressionStream: false }); if (host.includes(domain.REN)) { const node = u.query('h1'); if (node) { title = node.innerText.replaceAll(" ", ""); if (node.nextElementSibling?.children) { const innerText = node.nextElementSibling.children[6].innerText; fileType = innerText.split(":")[1].toLowerCase(); } } beforeFun = "let er = u.query('#load_preview_btn');if (er && er.style.display !== 'none') {er.click()}"; dom = u.query('.main-content'); attrImage = "#page img"; if (fileType.includes('ppt')) { doc = new jsPDF({ orientation: 'l', unit: 'px', compress: true }); pdf_w = pdf_h; pdf_h = 446; } } else if (host.includes(domain.BOOK)) { const node = u.query('h1'); if (node) { u.query('h1>em')?.remove() title = node.innerText.replaceAll(" ", ""); } const number = title.lastIndexOf("."); fileType = title.substring(number + 1).toLowerCase(); beforeFun = "let eb = u.query('#btn_preview_remain');if (eb) {eb.click();}"; dom = document.documentElement || document.body; attrImage = ".webpreview-item img"; if (fileType.includes('ppt')) { doc = new jsPDF({ orientation: 'l', unit: 'px', compress: true }); pdf_w = pdf_h; pdf_h = 446; } } // 文档标题 localStorage.setItem("title", title); GM_setValue("title", title) console.log("当前标题:", title) console.log("当前主机:", host) console.log("当前URL:", href) console.log("当前来源:", origin) console.log("文件类型:", fileType) // 删除缓存数据 localStorage.removeItem("current"); localStorage.removeItem("down"); } window.onload = function() { init() // 在这里执行渲染完成后的操作 console.log('HTML 渲染完成!'); isPPT = u.query("#btn_ppt_front_pc"); // 添加你的代码... if (!isPPT || host.includes(domain.REN)) { u.gui(btns); isPPT = href.includes("pptView"); // isPPT = fileType.includes('ppt'); } else { isPPT.click(); } }; /** * 人人监听侧边栏点击切换问题 */ const renObserve = () => { const targetNode = u.query(".center-wrap"); if (targetNode) { const observerOptions = { childList: true, attributes: false, subtree: false }; // dom 监听器 const observer = new MutationObserver(function(mutationList, observer) { mutationList.forEach((mutation) => { const addedNodes = mutation.addedNodes; if (addedNodes.length) { addedNodes.forEach(item => { if (item.className === 'main-content') { dom = item; } }) } }); }); // 触发监听 observer.observe(targetNode, observerOptions); } } if (host.includes(domain.REN)) { renObserve() } const startScroll = () => { if (isPPT) { // PPT localStorage.setItem("start", "1") startPPT(); } else { if (!interval) { interval = setInterval(() => { loadingImages() }, 500); // 设置滚动速度,单位为毫秒 } } } const stopScroll = () => { if (isPPT) { // PPT localStorage.removeItem('start'); } else { if (interval) { clearInterval(interval); interval = null; } domParseImage() } } /** * 开始解析PPT * @returns {Promise} */ const startPPT = async () => { if (beforeFun) { eval(beforeFun) } localStorage.setItem("start", "1") await autoParsingPPT(); } /** * @description 遍历节点,将没有图的元素滚动到可视范围内 * @author Mr.Fang */ const loadingImages = () => { if (beforeFun) { eval(beforeFun) } const clientHeight = dom.clientHeight; let end = 0; const images = u.queryAll(attr); for (let i = 0; i < images.length; i++) { let item = images[i]; const { top } = item.getBoundingClientRect(); let lastChild = host.includes(domain.REN) ? item.lastChild : item.firstChild; if (lastChild instanceof HTMLImageElement) { if (!lastChild.src && !lastChild.getAttribute('data-src')) { end = 1; dom.scrollTo({ top: dom.scrollTop + top, left: 0, behavior: "smooth", }); u.preview(i + 1, images.length); break; } } else if (lastChild instanceof HTMLDivElement) { end = 1; dom.scrollTo({ top: dom.scrollTop + top, left: 0, behavior: "smooth", }); u.preview(i + 1, images.length); break; } } if (end === 0) { u.preview(-1); u.notice('自动预览结束,请点击下载”图片“或”pdf“文件') stopScroll(); } } const autoParsingPPT = async () => { if (!localStorage.getItem("start")) { u.preview(-1, null, "已终止"); return; } const page = Number(u.query('#PageIndex').innerText); const total = Number(u.query('#PageCount').innerText); const childNodes = u.query("#view").childNodes; const count = childNodes.length; const max_index = page - 1; const current = childNodes[max_index]; // 动作数量 const a_len = u.queryAll(`#view${max_index} #animt${max_index}>div`).length; if (a_len !== 0) { await u.sleep(1000); } const bgs = MF_RecursiveParsingImages(current); await new Promise((resolve) => { html2canvas(current, { useCORS: true }).then(function(canvas) { // 将canvas转换为图片并下载. let data = canvas.toDataURL(); let fileName = max_index + "_" + a_len + ".png"; zipWriter.add(fileName, new zip.Data64URIReader(data)); // 添加PDF // 794px*1123px ; doc.addPage([canvas.width * pdf_ratio, canvas.height * pdf_ratio]); doc.addImage(data, 'JPEG', 0, 0, canvas.width * pdf_ratio, canvas.height * pdf_ratio, max_index + "_" + a_len, 'FAST') if (max_index === 1) { doc.deletePage(1); } resolve(); }); }) if (a_len === 0) { const detail = bgs.map((item, i) => { return zipWriter.add(max_index + "/" + i + ".png", new zip.HttpReader(item)); }); await Promise.all(detail); zipWriter.add(max_index + "/" + "文本描述.txt", new zip.TextReader(current.innerText)); } u.preview(page, total); const pageNext = u.query('#pageNext'); const btmRight = u.query('.btmRight'); if (page !== total) { btmRight.click(); await autoParsingPPT(zipWriter); } } const domParseImage = () => { const listData = []; const images = u.queryAll(attrImage); const length = images.length; for (let i = 0; i < length; i++) { const item = images[i]; let src = item.src || u.attr(item, 'data-src'); if (!src) { continue; } const page = u.attr(item, 'data-page') || u.attr(item.parentElement, 'data-id'); if (src.includes('"http:')) { src = src.replace("http:", "https:") } else if (src.startsWith("//")) { src = "https:" + src; } listData.push({ page, src }); } GM_setValue('listData', JSON.stringify(listData)); localStorage.setItem('listData', JSON.stringify(listData)) } window.addEventListener("message", (e) => { const { type, value } = e.data; if (type === 'parent') { // 父级-子页面消息 if (value.includes(origin)) { MF_ImageToBase64(value).then(data => { childMessage(data); }) } } else if (type === 'child') { // 子页面-到父页面消息 let index = Number(localStorage.getItem("current") || "0"); const { data, width, height } = value; if (fileType.includes('ppt')) { doc.addPage([width * pdf_ratio, height * pdf_ratio]); doc.addImage(data, 'JPEG', 0, 0, width * pdf_ratio, height * pdf_ratio, index, 'FAST') } else { doc.addPage(); doc.addImage(data, 'JPEG', 0, 0, pdf_w, pdf_h, index, 'FAST') } if (index === 1) { doc.deletePage(1); } localStorage.setItem('current', index + 1 + ""); zipWriter.add(index + ".png", new zip.Data64URIReader(data)); handleMultipleDomain(); } else if (type === 'onload') { handleMultipleDomain(); } }) const downloadTypeFile = (type) => { downType = type; localStorage.removeItem("current"); if (isPPT) { // PPT MF_Download(type); } else { if (type === 3 || localStorage.getItem('down')) { MF_Download(type); } else { handleMultipleDomain(); } } } /** * 处理资源多域名问题 */ const handleMultipleDomain = async () => { const images = JSON.parse(GM_getValue('listData')); const length = images.length; localStorage.setItem('length', length); let current = Number(localStorage.getItem("current")) || 0; for (let index = current; index < images.length; index++) { const image = images[index]; const src = image.src; if (src.includes(host)) { // 当前域 const { data, width, height } = await MF_ImageToBase64(src); if (fileType.includes('ppt')) { doc.addPage([width * pdf_ratio, height * pdf_ratio]); doc.addImage(data, 'JPEG', 0, 0, width * pdf_ratio, height * pdf_ratio, max_index + "_" + a_len, 'FAST') } else { doc.addPage(); doc.addImage(data, 'JPEG', 0, 0, pdf_w, pdf_h, index, 'FAST') } if (index === 1) { doc.deletePage(1); } zipWriter.add(index + ".png", new zip.Data64URIReader(data)); u.preview(index + 1, length); current = index; } else { const { host, origin: org } = new URL(src); const attrId = host.replaceAll(".", "") const query = document.getElementById('#' + attrId); if (query) { // 框架是否存在 parentMessage(src, "#" + attrId, org); } else { // TODO 这里有问题,消息第一次无法送达 const el = u.createEl("#" + attrId, 'iframe'); el.src = org; el.style.visibility = "hidden"; document.body.append(el); } localStorage.setItem('current', index + ""); u.preview(index + 1, length); break; } } if (current === length - 1) { MF_Download(downType); } } const childMessage = (message) => { window.parent.postMessage({ type: "child", value: message ? message : '' }, "*") } if (host.includes('file') || host.includes('view-cache')) { window.parent.postMessage({ type: "onload", value: 'success' }, "*") } const parentMessage = (message, attr, org) => { const nodeListOf = document.querySelectorAll('iframe'); for (const nodeListOfElement of nodeListOf) { if (nodeListOfElement.id === attr) { let _window = nodeListOfElement.contentWindow; _window.postMessage({ type: 'parent', value: message ? message : '' }, "*") break; } } } const downloadText = (title) => { const images = JSON.parse(GM_getValue('listData')); const text = images.map(item => { return item.src }).join("\n"); MF_ExportTxt(text, title + ".txt"); } /** * @description 导出文件 * @author Mr.Fang * @time 2024年1月20日18:05:49 * @param {Object} type 1 压缩包 2 pdf 3 txt */ const MF_Download = (type) => { const title = GM_getValue('title') || localStorage.getItem("title") if (type === 1) { // 下载 zip 图片 zipWriter.close().then((blob => { GM_download(URL.createObjectURL(blob), title + '.zip'); URL.revokeObjectURL(blob); })); localStorage.setItem('down', '1'); } else if (type === 2) { // 下载 PDF 文件 doc.save(title + ".pdf", { returnPromise: true }); localStorage.setItem('down', '1'); } else if (type === 3) { downloadText(title); } u.notice(title + '文件下载完成,请在本地下载文件查看!') } /** * @description 导出 txt 文件 * @author Mr.Fang * @time 2024年1月20日18:05:49 * @param {Object} data 数据 * @param {Object} filename 文件名 */ const MF_ExportTxt = (data, filename) => { const csvContent = "data:text/txt;charset=utf-8," + data; const encodedUri = encodeURI(csvContent); const link = document.createElement("a"); link.setAttribute("href", encodedUri); link.setAttribute("download", filename); // 点击链接以下载文件 document.body.appendChild(link); link.click(); document.body.removeChild(link); } /** * @description 递归加载子节点,获取子节点背景,img 属性值 * @author Mr.Fang * @time 2024年1月20日18:05:49 * @param children * @returns {*[]} */ const MF_RecursiveParsingImages = (children) => { const list = []; if (children.childNodes.length) { children.childNodes.forEach(item => { if (item || item instanceof HTMLImageElement) { if (item instanceof HTMLImageElement) { // 图片 let src = item.src; list.push(src); } else if (item.style) { let bgi = item.style.backgroundImage; if (bgi && bgi !== 'initial') { let src = bgi.substring(bgi.indexOf("\"") + 1, bgi.lastIndexOf("\"")); src = src.indexOf("/") === 0 ? src : "/" + src; list.push(origin + src); } } if (item.childNodes.length) { const images = MF_RecursiveParsingImages(item); list.push(...images); } } }) } return list; } /** * @description 加载图片 * @author Mr.Fang * @time 2024年1月20日18:05:49 * @param src 图片地址 * @returns {Promise} */ const MF_ImageToBase64 = (src) => { return new Promise((resolve, reject) => { // 1、创建 Image 对象 const image = new Image(); // 2、onload 加载成功触发 image.onload = function() { try { let canvas = u.createEl('', 'canvas'); canvas.width = image.width; canvas.height = image.height; let context = canvas.getContext('2d'); context.drawImage(image, 0, 0, image.width, image.height); const dataURL = canvas.toDataURL(); resolve({ data: canvas.toDataURL(), width: image.width, height: image.height }); } catch (e) { reject(e); } } image.onerror = reject; image.src = src; }) } })();