您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download PDF from Tsinghua University Electronic Course Reserves Service Platform
// ==UserScript== // @name Tsinghua E-Reserves Lib Downloader // @namespace anyi.fan // @version 0.2.2 // @license GPL-3.0 License // @description Download PDF from Tsinghua University Electronic Course Reserves Service Platform // @author A1phaN // @match https://ereserves.lib.tsinghua.edu.cn/readkernel/ReadJPG/JPGJsNetPage/* // @grant none // ==/UserScript== const MAX_RETRY = 10; const QUERY_INTERVAL = 100; const sleep = time => new Promise(res => setTimeout(res, time)); const getImage = async (url, retry = MAX_RETRY) => { const img = new Image(); img.src = url; img.style.display = 'none'; const data = new Promise((res, rej) => { img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; canvas.getContext('2d')?.drawImage(img, 0, 0, img.width, img.height); res([img, canvas.toDataURL('image/jpeg')]); }; img.onerror = err => { retry > 0 ? res(getImage(url, retry - 1)) : rej(err); }; }); document.body.appendChild(img); return data; }; (async () => { const scanId = document.querySelector('#scanid')?.value; const bookId = location.href.split('/').at(-1); const bookNameElement = document.querySelector('#p_bookname'); const bookName = bookNameElement.innerText; const BotuReadKernel = document.cookie.split(';') .map(c => c.trim()) .find(c => c.startsWith('BotuReadKernel=')) ?.split('=')[1]; if (!scanId || !BotuReadKernel || !bookName) return; await new Promise(res => { if (window.jspdf) return res(); const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; script.onload = res; document.body.appendChild(script); }); if (!window.jspdf) return; const button = document.createElement('span'); button.className = 'fucBtn icon iconfont'; button.style = 'margin-left: 8px; position: relative; top: 2px;'; button.innerHTML = `<svg t="1703917701009" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5061" height="2rem" > <path d="M896 672c-17.066667 0-32 14.933333-32 32v128c0 6.4-4.266667 10.666667-10.666667 10.666667H170.666667c-6.4 0-10.666667-4.266667-10.666667-10.666667v-128c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v128c0 40.533333 34.133333 74.666667 74.666667 74.666667h682.666666c40.533333 0 74.666667-34.133333 74.666667-74.666667v-128c0-17.066667-14.933333-32-32-32z" fill="#ddd" p-id="5062" ></path> <path d="M488.533333 727.466667c6.4 6.4 14.933333 8.533333 23.466667 8.533333s17.066667-2.133333 23.466667-8.533333l213.333333-213.333334c12.8-12.8 12.8-32 0-44.8-12.8-12.8-32-12.8-44.8 0l-157.866667 157.866667V170.666667c0-17.066667-14.933333-32-32-32s-34.133333 14.933333-34.133333 32v456.533333L322.133333 469.333333c-12.8-12.8-32-12.8-44.8 0-12.8 12.8-12.8 32 0 44.8l211.2 213.333334z" fill="#ddd" p-id="5063" ></path> </svg>`; document.querySelector('.option-list').appendChild(button); const downloadPDF = async () => { try { button.onclick = null; let doc = null; let page = 1; const chapters = await ( await fetch( '/readkernel/KernelAPI/BookInfo/selectJgpBookChapters', { body: `SCANID=${scanId}`, headers: { Botureadkernel: BotuReadKernel, 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', }, method: 'POST', }, ) ).json(); if (chapters.code !== 1 || !Array.isArray(chapters.data)) { alert('Get chapters data failed!'); return; } for (let chap = 0; chap < chapters.data.length; ++chap) { bookNameElement.innerText = `${bookName}(正在获取第 ${chap + 1} 章...)`; const chapter = chapters.data[chap]; const chapterData = await ( await fetch( '/readkernel/KernelAPI/BookInfo/selectJgpBookChapter', { body: `EMID=${chapter.EMID}&BOOKID=${bookId}`, headers: { Botureadkernel: BotuReadKernel, 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', }, method: 'POST', }, ) ).json(); if (chapterData.code !== 1 || !Array.isArray(chapterData.data.JGPS)) { alert(`Get chapter ${chap + 1} ${chapter.EFRAGMENTNAME} data failed!`); return; } for (let i = 0; i < chapterData.data.JGPS.length; ++i) { const jpg = chapterData.data.JGPS[i]; try { const [img, dataURL] = await getImage(`/readkernel/JPGFile/DownJPGJsNetPage?filePath=${jpg.hfsKey}`); if (!doc) { doc = new jspdf.jsPDF({ format: [img.width, img.height], unit: 'px' }); } else { doc.addPage([img.width, img.height]); } doc.addImage(dataURL, 'JPEG', 0, 0, img.width, img.height); bookNameElement.innerText = `${bookName}(正在获取第 ${chap + 1} 章,已完成: ${i + 1} / ${chapterData.data.JGPS.length})`; await sleep(QUERY_INTERVAL); } catch(e) { alert(`Get page ${i + 1} of chapter ${chap + 1} ${chapter.EFRAGMENTNAME} failed!`); return; } } doc.outline.add(null, chapter.EFRAGMENTNAME, { pageNumber: page }); page += chapterData.data.JGPS.length; } if (doc) { const filename = `${bookName}.pdf`; try { doc.save(filename); } catch(e) { if (e instanceof RangeError && e.message === 'Invalid string length') { // jsPDF exceeds the maximum string length doc.__private__.resetCustomOutputDestination(); const content = doc.__private__.out(''); content.pop(); const blob = new Blob( content.map((line, idx) => { const str = idx === content.length - 1 ? line : line + '\n'; const arrayBuffer = new ArrayBuffer(str.length); const uint8Array = new Uint8Array(arrayBuffer); for (let i = 0; i < str.length; ++i) { uint8Array[i] = str.charCodeAt(i); } return arrayBuffer; }), { type: 'application/pdf' }, ); const a = document.createElement('a'); a.download = filename; a.rel = 'noopener'; a.href = URL.createObjectURL(blob); a.click(); } else { alert(`Unexpected Error when saving pdf: ${e?.message ?? e}`); } } } bookNameElement.innerText = `${bookName}(下载完成)`; } finally { button.onclick = downloadPDF; } }; button.onclick = downloadPDF; })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址