// ==UserScript== // @name 百度网盘极速下载助手、百度文库免会员下载|全文阅读|开启右键复制 // @namespace http://zhihupe.com/ // @version 1.3.14 // @author zhihu // @antifeature membership 为防止接口被盗!该脚本需要输入验证码之后才能使用完整功能,感谢理解 // @description 【本脚本功能】百度网盘免会员满速下载、百度文库免会员下载,可全文阅读,文字可复制 // @match https://pan.baidu.com/* // @icon https://www.zhihupe.com/favicon.ico // @require https://cdn.staticfile.org/limonte-sweetalert2/11.1.9/sweetalert2.all.min.js // @require https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js // @require https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/crypto-js.min.js // @match *.baidu.com/* // @match wenku.baidu.com/view/* // @grant GM_xmlhttpRequest // @grant GM.xmlHttpRequest // @grant GM_openInTab // @grant GM_addStyle // @grant GM_setClipboard // @grant unsafeWindow // @run-at document-start // @connect pan.10zv.com // @connect wp.nanmu.cool // @connect tool.zhihupe.com // @connect bdimg.com // @license AGPL // @downloadURL none // ==/UserScript== (function() { 'use strict'; //公共方法 const zhurl = "http://tool.zhihupe.com/"; const servers = [ "http://wp.nanmu.cool/", "http://pan.10zv.com/", ]; var website = ""; var ua ="" const scriptInfo = GM_info.script; const author = scriptInfo.author; var Page = ""; var url = window.location.href; var copyurl=url.replace('view','share'); var InterfaceList = [ {"name":"wkdownload1","url":"http://www.html22.com/d/?url="}]; var type = ""; function sleep(time) { return new Promise(resolve => setTimeout(resolve, time)); } //加载定时 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'; setTimeout(() => { document.body.removeChild(m) }, d * 1000); }, duration); } let zhihu = { message: { success(text) { toast.fire({title: text, icon: 'success'}); }, error(text) { toast.fire({title: text, icon: 'error'}); }, warning(text) { toast.fire({title: text, icon: 'warning'}); }, info(text) { toast.fire({title: text, icon: 'info'}); }, question(text) { toast.fire({title: text, icon: 'question'}); } } } let toast = Swal.mixin({ toast: true, showConfirmButton: false, timer: 3500, timerProgressBar: false, didOpen: (toast) => { toast.addEventListener('mouseenter', Swal.stopTimer); toast.addEventListener('mouseleave', Swal.resumeTimer); } }); //弹窗提示 function getPage(){ if (url.indexOf(".baidu.com/disk/main") > 0) { Page = "main" } else if (url.indexOf(".baidu.com/disk/home") > 0) { Page = "home" } else if (url.indexOf(".baidu.com/view/") > 0) { Page = "wenku" } } addbtn(); async function addbtn() { await sleep(1500); getPage(); if (Page === 'home'){ let button = ` 智狐下载助手 `; $('#layoutMain div:has(span.g-new-create)>span.g-dropdown-button:first').before(button); } if (Page === 'main'){ let button = ` `; $('.nd-main-layout__body div:has(a.nd-upload-button)>a.nd-upload-button:first').before(button); } if (Page === 'wenku'){ let botton = `
文库下载助手
`; $("body").append(botton); } $('#zhihuDown').on('click', async e => { let file = getSelectedfileList(), pwd = getPwd(4); if (!file) return; zhihu.message.success('正在获取百度分享链接...'); let surl = await getShortUrl(file.fs_id, pwd); if (!surl) { return zhihu.message.error('百度分享链接获取失败'); } showMain(surl, pwd, file.server_filename) console.log(surl,pwd); }); $('#wenkuDown').on('click', async e => { showWenku(); }); } //百度文库 function showWenku(){ let defaultpassword = ""; detectType(); if (localStorage.password && (Date.now() - localStorage.passwordTime) < 17280000) { defaultpassword = localStorage.password; } else { localStorage.password = ""; } let fileName = $('h3.doc-title').text(); var downtit ="下载PDF"; console.log(fileName); if(type =="ppt"){ downtit ="下载word(PDF)"; } else{ downtit ="下载PDF"; } let html = `
正在获取 ${fileName}
微信扫描上方二维码关注公众号
回复"3"获取验证码
${downtit}
手动复制
`; Swal.fire({ html:html, width: 380, allowOutsideClick: false, showCancelButton: true, confirmButtonText: '交流反馈', cancelButtonText: '关闭', reverseButtons: true }).then(r => { if (r.isConfirmed) GM_openInTab('https://www.zhihupe.com/ask/list_21_9.html'); }); $('#dowmBtn').off().on("click", function () { let passwordCode = $("#passwordCode").val(); if (passwordCode) { GM_xmlhttpRequest({ method: "GET", url: "http://tool.zhihupe.com/bdwp.php?m=WENKU&author="+author+"&PWD="+passwordCode, headers: { "Content-Type": "text/html; charset=utf-8" }, onload: function(res){ var json=JSON.parse(res.responseText); if(json.error == 1){ if (passwordCode != localStorage.password) { localStorage.password = passwordCode; localStorage.passwordTime = Date.now(); } if(type =="ppt"){ window.open(InterfaceList[0].url + url); } else{ $(".swal2-cancel").click(); $(".pdfbtn").click(); } }else if(json.error == -2){ Toast('验证码错误!'); }else { Toast('服务器请求失败,请重试!'); } }, onerror: function(err){ Toast(err); } }); }else { Toast('请输入验证码!'); } }); $('#copyBtn').off().on("click", function () { let passwordCode = $("#passwordCode").val(); if (passwordCode) { GM_xmlhttpRequest({ method: "GET", url: "http://tool.zhihupe.com/bdwp.php?m=WENKU&author="+author+"&PWD="+passwordCode, headers: { "Content-Type": "text/html; charset=utf-8" }, onload: function(res){ var json=JSON.parse(res.responseText); if(json.error == 1){ if (passwordCode != localStorage.password) { localStorage.password = passwordCode; localStorage.passwordTime = Date.now(); } $(".pure-tool-btn").click(); $(".swal2-cancel").click(); }else if(json.error == -2){ Toast('验证码错误!'); }else { Toast('服务器请求失败,请重试!'); } }, onerror: function(err){ Toast(err); } }); }else { Toast('请输入验证码!'); } }); } function printDeal(){ } function detectType() { // 获取文档类型名称 if($('div').is('.doc-title-wrap')){ let doc_title_wrap = document.getElementsByClassName("doc-title-wrap")[0]; let file_type = doc_title_wrap.children[0].className; if (file_type.search("word") !== -1) { type = "word"; console.log(type); } else if (file_type.search("ppt") !== -1) { type = "ppt"; } else if (file_type.search("excel") !== -1) { type = "excel"; } else if (file_type.search("pdf") !== -1) { type = "pdf"; } else if (file_type.search("txt" !== -1)) { type = "txt"; } else { type = file_type; } }else{ type = "word"; } console.log(type); // 判断文档类型 } function getVip() { let pageData, pureViewPageData; Object.defineProperty(unsafeWindow, 'pageData', { set: v=>pageData = v, get() { if(!pageData) return pageData; // 启用 VIP pageData.vipInfo.global_svip_status = 1; pageData.vipInfo.global_vip_status = 1; pageData.vipInfo.isVip = 1; pageData.vipInfo.isWenkuVip = 1; 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; // 去除水印,允许继续阅读 pureViewPageData.customParam.noWaterMark = 1; pureViewPageData.customParam.visibleFoldPage = 1; pureViewPageData.readerInfo2019.freePage = pureViewPageData.readerInfo2019.page; return pureViewPageData } }) // 注册个 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} 个弹窗~`); }); } getVip(); //百度网盘 function showMain(surl, pwd, fileName) { let defaultpassword = ""; if (localStorage.password && (Date.now() - localStorage.passwordTime) < 17280000) { defaultpassword = localStorage.password; } else { localStorage.password = ""; } let html = `
正在获取 ${fileName} 的直链
方式一:IDM用户代理(UA)必须设置为:
软件下载及教程
方式二:Aria2/Motrix 无需配置,请看下方使用教程
软件下载及教程
为防止接口被滥用,需要输入验证码
点击获取直链
微信扫描上方二维码获取验证码

解析步骤

1.关注公众号【智狐百宝箱】
2.回复‘解析’获取验证码
3.将验证码输入左边输入框中,点击获取高速直链!
每晚23点到凌晨30分维护服务器,脚本暂停使用大家有问题点击下方的交流反馈进行反应,脚本的问题也会第一时间交流区公布
`; Swal.fire({ html:html, width: 780, allowOutsideClick: false, showCancelButton: true, confirmButtonText: '交流反馈', cancelButtonText: '关闭', reverseButtons: true }).then(r => { if (r.isConfirmed) GM_openInTab('https://www.zhihupe.com/ask/list_21_9.html'); }); $('#dowmBtn').off().on("click", function () { website = servers[Math.floor(Math.random()*servers.length)]; console.log(website) let passwordCode = $("#passwordCode").val(); if (passwordCode) { GM_xmlhttpRequest({ method: "GET", url: "http://tool.zhihupe.com/bdwpcs.php?m=WANPAN&author="+author+"&PWD="+passwordCode+"&website="+website, headers: { "Content-Type": "text/html; charset=utf-8" }, onload: function(res){ console.log(res.responseText) var json=JSON.parse(res.responseText); if(json.error == 1){ if (passwordCode != localStorage.password) { localStorage.password = passwordCode; localStorage.passwordTime = Date.now(); } let password = json.code; ua = json.ua; getLink(password); $("#tip").html("正在获取链接,请稍等!"); }else if(json.error == -2){ let msg =json.msg Toast(msg); }else { Toast('服务器请求失败,请重试!'); } }, onerror: function(err){ Toast(err); } }); }else { Toast('请输入验证码!'); } }); function getLink(passwordCode) { (async () => { let exception = null; try { let str = await getFileInfo(surl, pwd, passwordCode, website); console.log(surl, pwd, passwordCode, website); return await getLinkCommon(str, website); } catch (e) { exception = e; } throw exception; })().then(link => { $("#tip").html("高速链接获取成功!!!"); $("#title").html(`获取 ${fileName} 的高速直链成功`); $("#ua").html(`${ua}`); $("#copyIDM").html(`
复制IDM链接到剪贴板
`) $('#copyIDM').off().on('click', e => { GM_setClipboard(link); Toast('已复制IDM链接到剪贴板'); }); $("#sendAria").html(`
发送到Aria2(motix)
`); $('#sendAria').off().on('click', e => showAria(link, fileName)); }).catch(e => { $("#title").html(`获取 ${fileName} 的高速直链失败`) $("#tip").html(`获取高速链接失败!!!,原因是${e}`) }); } } function getPwd(len) { len = len || 4; let $char = 'abcdefhijkmnprstwxyz123456789'; let l = $char.length; let pwd = ''; for (let i = 0; i < len; i++) { pwd += $char.charAt(Math.floor(Math.random() * l)); } return pwd; } function getList() { try { return require('system-core:context/context.js').instanceForSystem.list.getSelected(); } catch (e) { return document.querySelector('.nd-main-list').__vue__.selectedList; } } function getSelectedfileList() { let list = getList(); if (list && list.length === 1) { if (list[0].isdir === 1) { return zhihu.message.error('提示:请打开文件夹后勾选文件!'); } return list[0]; }else if(list.length > 1){ return zhihu.message.error('提示:不要同时勾选多个文件'); }else{ return zhihu.message.error('提示:请先勾选要下载的文件!'); } } function getShortUrl(fs_id, pwd) { let bdstoken = ''; return fetch(`https://pan.baidu.com/share/set?channel=chunlei&clienttype=0&web=1&channel=chunlei&web=1&app_id=250528&bdstoken=${bdstoken}&clienttype=0`, { "headers": { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9", "content-type": "text/plain;charset=UTF-8", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "none" }, "referrerPolicy": "no-referrer-when-downgrade", "body": `fid_list=[${fs_id}]&schannel=4&channel_list=[]&period=1&pwd=` + pwd, "method": "POST", "mode": "cors", "credentials": "include" }).then(r => r.json()).then(r => r.shorturl.replace(/^.+\//, '')).catch(e => null); } function getFileInfo(surl, pwd, passwordCode, website) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', data: `surl=${surl}&pwd=${pwd}&Password=` + passwordCode, url: website, headers: { "content-type": "application/x-www-form-urlencoded", }, onload: res => { if (res.status != 200) return reject(res); resolve(res.responseText); console.log(res.responseText) }, onerror: err => reject(err) }); }).then(r => { let m = r.match(/javascript:confirmdl\((.+)\);/); console.log(m); if (m) return m[1]; return Promise.reject($(r).find('div.alert.alert-danger').text().trim() || `获取下载信息失败`); }); } function getParam(str) { function fetch_token(fs_id, timestamp, sign, randsk, share_id, uk, bdstoken, filesize) { let base64 = btoa(fs_id + sign + uk); let base642 = btoa("nbest" + base64 + fs_id + "Yuan_Tuo" + share_id + sign + base64 + "baiduwp-php-donate"); let md5 = CryptoJS.MD5(base642 + timestamp + base64).toString() return md5; } function urlEncode(obj) { return Array.isArray(obj) ? obj.map(o => urlEncode(o)).join('&') : Object.keys(obj).map(key => key + '=' + obj[key]).join('&'); } let arr = str.replace(/'/g,'').split(','); arr.push(fetch_token(...arr)); return urlEncode(['fs_id', 'time', 'sign', 'randsk', 'share_id', 'uk', 'bdstoken', 'filesize', 'token'].reduce((t, v, i) => (t[v] = arr[i]) && t, {})); } function getLinkCommon(str, website) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', data: getParam(str), url: website + "/?download", headers: { "content-type": "application/x-www-form-urlencoded", }, onload: res => { if (res.status != 200) return reject(res); resolve(res.responseText); }, onerror: err => reject(err) }); }).then(r => { let link = $(r).find('#https').attr('href'); if (link) return link; return Promise.reject($(r).find('div.alert.alert-danger').text().trim() || '获取直链失败'); }); } function showAria(url, filename) { Swal.fire({ title: '发送到 Aria2 Json-RPC', html: `
推送aria2默认配置:ws://localhost:6800/jsonrpc
推送Motrix默认配置:ws://localhost:16800/jsonrpc
没有token的话,留空
`, allowOutsideClick: false, focusConfirm: false, confirmButtonText: '发送', showCancelButton: true, cancelButtonText: '取消', reverseButtons: true, preConfirm: () => { let wsurl = $('#wsurl').val(); if (!wsurl) { Swal.showValidationMessage('RPC地址必填'); return; } } }).then(r => r.isConfirmed && addUri(url, filename)); } function addUri(url, filename) { var wsurl = localStorage.wsurl = $('#wsurl').val(); var uris = [url.replace('https:', 'http:'), url]; var token = localStorage.wsToken = $('#token').val(); var options = { "max-connection-per-server": "16", "user-agent": ua }; if (filename != "") { options.out = filename; } let json = { "id": "baiduwp-php", "jsonrpc": '2.0', "method": 'aria2.addUri', "params": [uris, options], }; if (token != "") { json.params.unshift("token:" + token); } let patt = /^wss?\:\/\/(((([A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+)|([A-Za-z0-9]+))(\.(([A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+)|([A-Za-z0-9]+)))*(\.[A-Za-z0-9]{2,10}))|(localhost)|((([01]?\d?\d)|(2[0-4]\d)|(25[0-5]))(\.([01]?\d?\d)|(2[0-4]\d)|(25[0-5])){3})|((\[[A-Za-z0-9:]{2,39}\])|([A-Za-z0-9:]{2,39})))(\:\d{1,5})?(\/.*)?$/; if (!patt.test(wsurl)) { Swal.fire('地址错误', 'WebSocket 地址不符合验证规则,请检查是否填写正确!', 'error'); return; } var ws = new WebSocket(wsurl); ws.onerror = event => { console.log(event); Swal.fire('连接错误', 'Aria2 连接错误,请打开控制台查看详情!', 'error'); }; ws.onopen = () => { ws.send(JSON.stringify(json)); } ws.onmessage = event => { console.log(event); let received_msg = JSON.parse(event.data); if (received_msg.error !== undefined) { if (received_msg.error.code === 1) Swal.fire('通过RPC连接失败', '请打开控制台查看详细错误信息,返回信息:' + received_msg.error.message, 'error'); } switch (received_msg.method) { case "aria2.onDownloadStart": Swal.fire('Aria2 发送成功', 'Aria2 已经开始下载!' + filename, 'success'); localStorage.setItem('aria2wsurl', wsurl); // add aria2 config to SessionStorage if (token != "" && token != null) localStorage.setItem('aria2token', token); break; case "aria2.onDownloadError": ; Swal.fire('下载错误', 'Aria2 下载错误!', 'error'); break; case "aria2.onDownloadComplete": Swal.fire('下载完成', 'Aria2 下载完成!', 'success'); break; default: break; } }; } })(); //本段代码取自:https://greasyfork.org/scripts/438420 (function () { 'use strict'; // 拿到阅读器的 Vue 实例 // https://github.com/EHfive/userscripts/tree/master/userscripts/enbale-vue-devtools 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 () => { const creader = unsafeWindow?.__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 downloadPDF = async (pageRange = [...Array(creader.readerDocData.page).keys()]) => { 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(/ - 百度文库$/, '') ?? 'export'}.pdf`); } // 添加需要用到的样式 async function injectUI() { const pdfButton = `
` const statusOverlay = `
正在加载...
0%
`; const messageOverlay = `
testtest
`; document.body.insertAdjacentHTML('afterbegin', statusOverlay); document.body.insertAdjacentHTML('afterbegin', messageOverlay); document.querySelector('.tool-bar-wrapper')?.insertAdjacentHTML('afterbegin', pdfButton); document.head.appendChild(document.createElement('style')).innerHTML = ` .pdfbtn { display:none } .s-btn-pdf:hover { background-color: #6c32bc; cursor: pointer; } .s-top { position: fixed; top: 0; left: 0; bottom: 0; right: 0; z-index: 2000; padding-top: 40vh; display: none; } .s-top.s-top-message { text-align: center; } .s-message { background-color: #000000aa; color: white; padding: 8px 14px; text-align: center; font-size: 18px; border-radius: 6px; display: inline-block; } .s-top.s-top-status { z-index: 1000; cursor: wait; background-color: rgba(0, 0, 0, 0.4); backdrop-filter: blur(10px) saturate(1.8); } .s-top.show { display: block; } .s-panel { background: white; width: 90%; max-width: 480px; border-radius: 12px; padding: 14px 24px; margin: 0 auto; } .s-progress-wrapper { height: 24px; border-radius: 12px; width: 100%; background-color: #eeeff3; overflow: hidden; margin-bottom: 12px; } .s-progress { background-color: #f7603e; height: 24px; width: 0; transition: width 0.2s ease; } .s-status { display: flex; font-size: 14px; } .s-text { flex-grow: 1; color: #5f5f5f; } .s-progress-text { color: #f7603e; font-weight: bold; } .s-message { } `; } injectUI(); const exportPDF = async (...args) => { try { await downloadPDF(...args); showMessage(`已成功导出,共计 ${creader.readerDocData.page} 页~`); } catch (error) { console.error('[x] failed to export:', error); showMessage('导出失败:'+error?.message ?? error); } finally { hideStatus(); } } document.querySelector('.pdfbtn').onclick = ()=>exportPDF(); unsafeWindow['downloadPDF'] = exportPDF; }); })();