// ==UserScript==
// @name 百度网盘极速下载助手、百度文库免会员下载|全文阅读|开启右键复制
// @namespace http://zhihupe.com/
// @version 1.3.13
// @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 = `
`;
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} 的直链
方式二: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: ``,
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 = ``;
const messageOverlay = ``;
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;
});
})();