// ==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); }); })();