// ==UserScript== // @name 百度文库VIP文档免费下载|全文阅读|开启右键复制,淘宝、天猫、京东商品优惠券查询 // @version 1.5.3 // @description 【本脚本功能】解除继续阅读限制,导出 PDF 文件,净化弹窗、广告,开启文库本地 VIP,淘宝、天猫、京东商品优惠券查询 // @author zhihu // @antifeature membership 为防止接口被盗!该脚本需要输入验证码之后才能使用完整功能,感谢理解 // @antifeature referral-link 【此提示为GreasyFork代码规范要求含有查券功能的脚本必须添加,实际使用无任何强制跳转,代码可查,请知悉】 // @license End-User License Agreement // @require https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js // @require https://cdn.staticfile.org/limonte-sweetalert2/11.1.9/sweetalert2.all.min.js // @match *://wenku.baidu.com/* // @match *://wk.baidu.com/* // @match *://item.taobao.com/* // @match *://chaoshi.detail.tmall.com/* // @match *://*detail.tmall.com/* // @match *://*detail.tmall.hk/* // @match *://*item.jd.com/* // @match *://npcitem.jd.hk/* // @match *://*.yiyaojd.com/* // @icon https://www.baidu.com/favicon.ico // @connect bdimg.com // @connect tool.wezhicms.com // @grant unsafeWindow // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // @run-at document-start // @namespace http://zhihupe.com/ // @downloadURL none // ==/UserScript== // 文档净化 (function () { 'use strict'; const author = "zhihu"; var url = window.location.href; function Toast(msg, duration = 3000) { var m = document.createElement('div'); m.innerHTML = msg; m.style.cssText = "max-width:60%;min-width: 150px;padding:0 14px;height: 40px;color: rgb(255, 255, 255);line-height: 40px;text-align: center;border-radius: 4px;position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 999999;background: rgba(0, 0, 0,.7);font-size: 16px;"; document.body.appendChild(m); setTimeout(() => { var d = 0.5; m.style.webkitTransition = '-webkit-transform ' + d + 's ease-in, opacity ' + d + 's ease-in'; m.style.opacity = '0'; document.body.removeChild(m) }, duration); } //弹窗提示 function GetVip(){ // 注册个 MutationObserver,根治各种垃圾弹窗 let count = 0; const blackListSelector = [ '.vip-pay-pop-v2-wrap', '.reader-pop-manager-view-containter', '.fc-ad-contain', '.shops-hot', '.video-rec-wrap', '.pay-doc-marquee', '.card-vip', '.vip-privilege-card-wrap', '.doc-price-voucher-wrap', '.vip-activity-wrap-new', '.creader-root .hx-warp', '.hx-recom-wrapper', '.hx-bottom-wrapper', '.hx-right-wrapper.sider-edge' ] const killTarget = (item) => { if (item.nodeType !== Node.ELEMENT_NODE) return false; let el = item; if (blackListSelector.some(i => (item.matches(i) || (el = item.querySelector(i))))) el?.remove(), count++; return true } const observer = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { killTarget(mutation.target) for (const item of mutation.addedNodes) { killTarget(item) } } }); observer.observe(document, { childList: true, subtree: true }); window.addEventListener("load", () => { console.log(`[-] 文库净化:共清理掉 ${count} 个弹窗~`); }); // 启用 VIP,解锁继续阅读 let pageData, pureViewPageData; Object.defineProperty(unsafeWindow, 'pageData', { set: v => pageData = v, get() { if (!pageData) return pageData; // 启用 VIP if('vipInfo' in pageData) { pageData.vipInfo.global_svip_status = 1; pageData.vipInfo.global_vip_status = 1; pageData.vipInfo.isVip = 1; pageData.vipInfo.isWenkuVip = 1; } if ('readerInfo' in pageData && pageData?.readerInfo?.htmlUrls?.json) { pageData.readerInfo.showPage = pageData.readerInfo.htmlUrls.json.length; } if ('appUniv' in pageData) { // 取消百度文库对谷歌、搜狗浏览器 referrer 的屏蔽 pageData.appUniv.blackBrowser = []; // 隐藏 APP 下载按钮 pageData.viewBiz.docInfo.needHideDownload = true; } return pageData } }) Object.defineProperty(unsafeWindow, 'pureViewPageData', { set: v => pureViewPageData = v, get() { if (!pureViewPageData) return pureViewPageData; // 去除水印,允许继续阅读 if('customParam' in pureViewPageData) { pureViewPageData.customParam.noWaterMark = 1; pureViewPageData.customParam.visibleFoldPage = 1; } if('readerInfo2019' in pureViewPageData) { pureViewPageData.readerInfo2019.freePage = pureViewPageData.readerInfo2019.page; } return pureViewPageData } }) } function sleep(time) { return new Promise(resolve => setTimeout(resolve, time)); } function AddBtn(){ function observeVueRoot(callbackVue) { const checkVue2Instance = (target) => { const vue = target && target.__vue__ return !!( vue && (typeof vue === 'object') && vue._isVue && (typeof vue.constructor === 'function') ) } const vue2RootSet = new WeakSet(); const observer = new MutationObserver( (mutations, observer) => { const disconnect = observer.disconnect.bind(observer); for (const { target } of mutations) { if (!target) { return } else if (checkVue2Instance(target)) { const inst = target.__vue__; const root = inst.$parent ? inst.$root : inst; if (vue2RootSet.has(root)) { // already callback, continue loop continue } vue2RootSet.add(root); callbackVue(root, disconnect); } } } ); observer.observe(document.documentElement, { attributes: true, subtree: true, childList: true }); return observer } observeVueRoot((el, disconnect) => { while (el.$parent) { // find base Vue el = el.$parent } const findCreader = (root, selector) => { if (!root) return null; if (root?.$el?.nodeType === Node.ELEMENT_NODE && root?.$el?.matches('#creader-app') && 'creader' in root) return root.creader; for (const child of root.$children) { let found = findCreader(child, selector); if (found) return found; } return null; } if (unsafeWindow['__creader__'] || (unsafeWindow['__creader__'] = findCreader(el))) disconnect(); }); /////////////////////////////////////////////////////////////////////////////////////////////// const loadScript = url => new Promise((resolve, reject) => { const removeWrap = (func, ...args) => { if (script.parentNode) script.parentNode.removeChild(script); return func(...args) } const script = document.createElement('script'); script.src = url; script.onload = removeWrap.bind(null, resolve); script.onerror = removeWrap.bind(null, reject); document.head.appendChild(script); }) const loadJsPDF = async () => { if (unsafeWindow.jspdf) return unsafeWindow.jspdf; await loadScript('https://cdn.staticfile.org/jspdf/2.5.1/jspdf.umd.min.js'); return unsafeWindow.jspdf; } window.addEventListener('DOMContentLoaded', async () => { await sleep("2000") const creader = unsafeWindow?.__creader__; console.log(creader) if (!creader) { console.error('[x] creader is undefined'); return } const showStatus = (text='', progress=-1) => { document.querySelector('.s-top.s-top-status').classList.add('show'); if(text) document.querySelector('.s-panel .s-text').innerHTML = text; if (progress >= 0) { progress = Math.min(progress, 100); document.querySelector('.s-panel .s-progress').style.width = `${Math.floor(progress)}%`; document.querySelector('.s-panel .s-progress-text').innerHTML = `${Math.floor(progress)}%`; } } const hideStatus = () => { document.querySelector('.s-top.s-top-status').classList.remove('show'); } let lastMessageTimer; const showMessage = (msg, time=3000) => { const msgEl = document.querySelector('.s-top.s-top-message'); msgEl.classList.add('show'); document.querySelector('.s-top.s-top-message .s-message').innerHTML = msg; clearTimeout(lastMessageTimer); lastMessageTimer = setTimeout(() => msgEl.classList.remove('show'), time); } const loadImage = (url) => new Promise(async (resolve, reject) => { if (!url) { resolve(null); return; } let img = await request('GET', url, null, 'blob'); let imgEl = document.createElement('img'); imgEl.onload = () => { resolve(imgEl); } imgEl.onabort = imgEl.onerror = reject; imgEl.src = URL.createObjectURL(img); }) const drawNode = async (doc, page, node) => { if (node.type == 'word') { for (let font of node.fontFamily) { font = /['"]?([^'"]+)['"]?/.exec(font) if (!font || page.customFonts.indexOf(font[1]) === -1) continue; doc.setFont(font[1], node.fontStyle); break; } doc.setTextColor(node.color); doc.setFontSize(node.fontSize); const options = { charSpace: node.letterSpacing, baseline: 'top' }; const transform = new doc.Matrix( node.matrix?.a ?? node.scaleX, node.matrix?.b ?? 0, node.matrix?.c ?? 0, node.matrix?.d ?? node.scaleY, node.matrix?.e ?? 0, node.matrix?.f ?? 0); if (node.useCharRender) { for (const char of node.chars) doc.text(char.text, char.rect.left, char.rect.top, options, transform); } else { doc.text(node.content, node.pos.x, node.pos.y, options, transform); } } else if (node.type == 'pic') { let img = page._pureImg; if (!img) { console.debug('[+] page._pureImg is undefined, loading...'); img = await loadImage(node.src); } if (!('x1' in node.pos)) { node.pos.x0 = node.pos.x1 = node.pos.x; node.pos.y1 = node.pos.y2 = node.pos.y; node.pos.x2 = node.pos.x3 = node.pos.x + node.pos.w; node.pos.y0 = node.pos.y3 = node.pos.y + node.pos.h; } const canvas = document.createElement('canvas'); const [w, h] = [canvas.width, canvas.height] = [node.pos.x2 - node.pos.x1, node.pos.y0 - node.pos.y1]; const ctx = canvas.getContext('2d'); if (node.pos.opacity && node.pos.opacity !== 1) ctx.globalAlpha = node.pos.opacity; if (node.scaleX && node.scaleX !== 1) ctx.scale(node.scaleX, node.scaleY); if (node.matrix) ctx.transform(node.matrix.a ?? 1, node.matrix.b ?? 0, node.matrix.c ?? 0, node.matrix.d ?? 1, node.matrix.e ?? 0, node.matrix.f ?? 0); ctx.drawImage(img, node.picPos.ix, node.picPos.iy, node.picPos.iw, node.picPos.ih, 0, 0, node.pos.w, node.pos.h); doc.addImage(canvas, 'PNG', node.pos.x1, node.pos.y1, w, h); canvas.remove(); } } const request = (method, url, data, responseType = 'text') => new Promise((resolve, reject) => { GM.xmlHttpRequest({ method, url, data, responseType, onerror: reject, ontimeout: reject, onload: (response) => { if (response.status >= 200 && response.status < 300) { resolve(responseType === 'text' ? response.responseText : response.response); } else { reject(new Error(response.statusText)); } } }); }); const loadFont = async (doc, page) => { const apiBase = 'https://wkretype.bdimg.com/retype'; let params = ["pn=" + page.index, "t=ttf", "rn=1", "v=" + page.readerInfo.pageInfo.version].join("&"); let ttf = page.readerInfo.ttfs.find(ttf => ttf.pageIndex === page.index) if (!ttf) return; let resp = await request('GET', apiBase + "/pipe/" + page.readerInfo.storeId + "?" + params + ttf.params) if (!resp) return; resp = resp.replace(/[\n\r ]/g, ''); let fonts = []; let blocks = resp.matchAll(/@font-face{[^{}]+}/g); for (const block of blocks) { const base64 = block[0].match(/url\(["']?([^"']+)["']?\)/); const name = block[0].match(/font-family:["']?([^;'"]+)["']?;/); const style = block[0].match(/font-style:([^;]+);/); const weight = block[0].match(/font-weight:([^;]+);/); if (!base64 || !name) throw new Error('failed to parse font'); fonts.push({ name: name[1], style: style ? style[1] : 'normal', weight: weight ? weight[1] : 'normal', base64: base64[1] }) } for (const font of fonts) { doc.addFileToVFS(`${font.name}.ttf`, font.base64.slice(font.base64.indexOf(',') + 1)); doc.addFont(`${font.name}.ttf`, font.name, font.style, font.weight); } } const generateArray = (start, end) => { return Array.from(new Array(end + 1).keys()).slice(start) } const downloadimgPDF = async(arr) =>{ let pageRangearr = [...Array(creader.readerDocData.showPage).keys()]; var pageRange; if(arr!=null){ pageRange = arr; }else{ pageRange = pageRangearr; } const version = 6; showStatus('正在加载', 0); const jspdf = await loadJsPDF(); let doc; for(let i = 0; i < pageRange.length; i++){ showStatus('正在准备', ((i + 1) / pageRange.length) * 100); let url = pageRange[i] const page = creader.renderPages[0]; console.log(page); // 缩放比例设为 1 // page.pageUnDamageScale = page.pageDamageScale = () => 1; if (page.readerInfo.pageInfo.version !== version) { throw new Error(`脚本已失效: 文库版本号=${page.readerInfo.pageInfo.version}, 脚本版本号=${version}`); } const pageSize = [page.pageWidth, page.pageHeight] if (!doc) { doc = new jspdf.jsPDF(pageSize[0] < pageSize[1] ? 'p' : 'l', 'pt', pageSize); } else { doc.addPage(pageSize); } showStatus('正在下载图片'); page._pureImg = await loadImage(page.readerInfo.urls[url]); showStatus('正在绘制'); for (const node of page.nodes) { await drawNode(doc, page, node); } if(page._pureImg?.src) URL.revokeObjectURL(page._pureImg.src); page._pureImg?.remove(); } doc.save(`${unsafeWindow?.pageData?.title?.replace(/ - 百度文库$/, '') ?? 'export'}.pdf`); } const downloadPDF = async (arr) => { let pageRangearr = [...Array(creader.readerDocData.showPage).keys()]; var pageRange; if(arr!=null){ pageRange = arr; }else{ pageRange = pageRangearr; } const version = 6; showStatus('正在加载', 0); // 强制加载所有页面 creader.loadNextPage(Infinity, true); console.debug('[+] pages:', creader.renderPages); const jspdf = await loadJsPDF(); let doc; for (let i = 0; i < pageRange.length; i++) { if(pageRange[i] >= creader.renderPages.length) { console.warn('[!] pageRange[i] >= creader.renderPages.length, skip...'); continue; } showStatus('正在准备', ((i + 1) / pageRange.length) * 100); const page = creader.renderPages[pageRange[i]]; // 缩放比例设为 1 page.pageUnDamageScale = page.pageDamageScale = () => 1; if (creader.readerDocData.readerType === 'html_view') await page.loadXreaderContent() if (creader.readerDocData.readerType === 'txt_view') await page.loadTxtContent() if (page.readerInfo.pageInfo.version !== version) { throw new Error(`脚本已失效: 文库版本号=${page.readerInfo.pageInfo.version}, 脚本版本号=${version}`); } const pageSize = [page.readerInfo.pageInfo.width, page.readerInfo.pageInfo.height] if (!doc) { doc = new jspdf.jsPDF(pageSize[0] < pageSize[1] ? 'p' : 'l', 'pt', pageSize); } else { doc.addPage(pageSize); } showStatus('正在下载图片'); page._pureImg = await loadImage(page.picSrc); showStatus('正在加载字体'); await loadFont(doc, page); showStatus('正在绘制'); for (const node of page.nodes) { await drawNode(doc, page, node); } if(page._pureImg?.src) URL.revokeObjectURL(page._pureImg.src); page._pureImg?.remove(); } doc.save(`${unsafeWindow?.pageData?.title?.replace(/ - 百度文库$/, '') ?? '百度文库文档'}.pdf`); } // 添加需要用到的样式 async function injectUI() { const pdfButton = `
手机' + qa + '扫码领取