博看网PDF下载助手
// ==UserScript==
// @name 博看网PDF下载助手
// @namespace http://tampermonkey.net/
// @version 2025.3.4
// @description 在博看网页面直接下载书籍/期刊为PDF
// @author Shuaima
// @match *://new.bookan.com.cn/page/detail.html?*
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @connect api.bookan.com.cn
// @connect img1-qn.bookan.com.cn
// @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.4.0/jspdf.umd.min.js
// ==/UserScript==
(function() {
'use strict';
const { jsPDF } = window.jspdf;
// 添加下载按钮
function addDownloadButton() {
const btn = document.createElement('button');
btn.innerHTML = '下载PDF';
btn.style.cssText = `
padding: 7px 15px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
z-index: 9999;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
`;
btn.addEventListener('click', initDownload);
// 查找mt20类名的元素并添加按钮
const mt20Elements = document.querySelectorAll('.mt20');
if (mt20Elements.length > 0) {
mt20Elements[0].appendChild(btn);
} else {
console.error('未找到类名为mt20的元素');
}
return btn;
}
// 获取URL参数
function getUrlParams() {
const params = new URLSearchParams(window.location.search);
return {
type: params.get('type'),
id: params.get('id')
};
}
// API请求封装
function apiRequest(url, params) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `${url}?${new URLSearchParams(params)}`,
responseType: 'json',
onload: (res) => {
if (res.status === 200 && res.response?.code === 0) {
resolve(res.response.data);
} else {
reject(new Error('API请求失败'));
}
},
onerror: (err) => reject(err)
});
});
}
// 获取期刊信息
async function getJournalInfo(id, type) {
return apiRequest('https://api.bookan.com.cn/resource/issueInfoList', {
instanceId: 12696,
isDetail: 1,
issueIds: id,
resourceType: type
}).then(data => data[0]);
}
// 获取图片哈希列表
async function getImageHashes(resourceId, issueId, pageCount, type) {
return apiRequest('https://api.bookan.com.cn/resource/getHash', {
resourceId: resourceId,
issueId: issueId,
start: 1,
end: pageCount,
resourceType: type
});
}
// 下载单张图片
function downloadImage(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'blob',
onload: (res) => res.status === 200 ? resolve(res.response) : reject(),
onerror: reject
});
});
}
// 生成PDF
async function createPDF(images, filename) {
const doc = new jsPDF('p', 'mm', 'a4');
const pageWidth = doc.internal.pageSize.getWidth();
const pageHeight = doc.internal.pageSize.getHeight();
for (let i = 0; i < images.length; i++) {
if (i > 0) doc.addPage();
const imgUrl = URL.createObjectURL(images[i]);
doc.addImage(imgUrl, 'JPEG', 0, 0, pageWidth, pageHeight);
URL.revokeObjectURL(imgUrl);
}
doc.save(filename);
}
// 主下载流程
async function initDownload() {
const downloadButton = document.querySelector('button');
try {
const { type, id } = getUrlParams();
if (!type || !id) throw new Error('未找到有效参数');
// 获取期刊信息
const info = await getJournalInfo(id, type);
const { resourceId, issueId, count: pageCount } = info;
// 获取哈希列表
const hashes = await getImageHashes(resourceId, issueId, pageCount, type);
// 下载所有图片
const images = [];
const totalImages = hashes.length;
let downloadedImages = 0;
for (const hashItem of hashes) {
const imgUrl = `http://img1-qn.bookan.com.cn/jpage8/${resourceId}/${resourceId}-${issueId}/${hashItem.hash}_big.jpg`;
const blob = await downloadImage(imgUrl);
images.push({ blob, page: hashItem.page });
downloadedImages++;
const progress = Math.round((downloadedImages / totalImages) * 100);
downloadButton.innerHTML = `下载中 ${progress}%`;
}
// 按页码排序
images.sort((a, b) => a.page - b.page);
// 生成PDF
const filename = `${info.resourceName}_${info.issueName}.pdf`.replace(/[\\/]/g, '_');
await createPDF(images.map(i => i.blob), filename);
downloadButton.innerHTML = '下载完成';
GM_notification({
title: '下载完成',
text: `文件已保存为:${filename}`,
timeout: 5000
});
} catch (error) {
downloadButton.innerHTML = '下载失败';
GM_notification({
title: '下载失败',
text: error.message,
timeout: 5000
});
}
}
// 监听页面加载完成事件
window.addEventListener('load', function() {
// 等待两秒后初始化
setTimeout(function() {
addDownloadButton();
}, 1000);
});
})();