// ==UserScript==
// @name BNU-Thesis-Download 北师大论文平台下载工具
// @supportURL https://github.com/xiaotianxt/PKU-Thesis-Download
// @homepageURL https://github.com/xiaotianxt/PKU-Thesis-Download
// @version 0.1
// @description 北师大论文平台下载工具,请勿传播下载的文件,否则后果自负。
// @author xiaotianxt
// @match https://etdlib.bnu.edu.cn/read/pdfindex1.jsp?fid=*
// @icon https://www.bnu.edu.cn/images/favicon.ico
// @require https://cdnjs.cloudflare.com/ajax/libs/notify/0.4.2/notify.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
// @license GNU GPLv3
// @namespace https://greasyfork.org/users/576672
// @downloadURL https://update.greasyfork.icu/scripts/484052/BNU-Thesis-Download%20%E5%8C%97%E5%B8%88%E5%A4%A7%E8%AE%BA%E6%96%87%E5%B9%B3%E5%8F%B0%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7.user.js
// @updateURL https://update.greasyfork.icu/scripts/484052/BNU-Thesis-Download%20%E5%8C%97%E5%B8%88%E5%A4%A7%E8%AE%BA%E6%96%87%E5%B9%B3%E5%8F%B0%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7.meta.js
// ==/UserScript==
(function () {
"use strict";
const RESOLUTION = "bnu_thesis_download.resolution";
const RESOLUTIONS = ["3f", "4f", "5f"];
const fid = $("#fid").val();
const totalPage = parseInt($("#totalPages").html().replace(/ \/ /, ""));
const fileName = $("#fileName").val();
const baseUrl = `https://etdlib.bnu.edu.cn/read/jumpServlet?fid=${fid}&filename=${fileName}&visitid=undefined`;
const msgBox = initUI();
initMonitor();
function initUI() {
// 下载按钮
const downloadButton = document.querySelector("#thumbtab").cloneNode(true);
downloadButton.innerHTML = `
下载
`;
document.querySelector("#btnList").appendChild(downloadButton);
downloadButton.addEventListener("click", download);
// 清晰度
const resolution = localStorage.getItem(RESOLUTION) || RESOLUTIONS[0];
const resolutionRadioGroup = document
.querySelector("#thumbtab")
.cloneNode(true);
resolutionRadioGroup.innerHTML = `
`;
document.querySelector("#btnList").appendChild(resolutionRadioGroup);
$("input[name='resolution'][value='" + resolution + "']").prop(
"checked",
true
);
$("input[name='resolution']").on("click", (e) => {
localStorage.setItem(RESOLUTION, e.target.value);
$("#jspPane_scroll img").each((i, elem) => {
elem.src = elem.src.replace(
new RegExp(`scale=(${RESOLUTIONS.join("|")})`),
`scale=${e.target.value}`
);
});
$.notify("清晰度已调整", "success");
});
$("input[name='resolution'][value='5f']").on("click", (e) => {
$("[resolution]").notify("图片尺寸过大,加载速度会比较缓慢。", "warn");
});
// msgBox
const msgBox = downloadButton.querySelector("span");
return msgBox;
}
function initMonitor() {
const targetNode = document.getElementById("jspPane_scroll");
const config = { childList: true, subtree: true };
const callback = (mutationList, observer) => {
const resolution = document.querySelector(
'input[name="resolution"]:checked'
).value;
for (const mutation of mutationList) {
if (mutation.type === "childList") {
const target = mutation.target.querySelector("img");
if (target)
target.src = target.src
.replace(
new RegExp(`scale=${RESOLUTIONS[0]}`),
`scale=${resolution}`
)
.replace(/watermark=[^&]+$/, `watermark=`);
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
}
async function download(e) {
e.preventDefault();
e.target.disabled = true;
await solveSrc().then(solveImg).then(solvePDF);
e.target.disabled = false;
}
/**
* 解析pdf图片链接
*/
async function solveSrc() {
async function downloadSrcInfo(url) {
return fetch(url)
.then((res) => res.json())
.then((json) => {
finished++;
msgBox.innerHTML = finished + "/" + page;
return json.list;
});
}
let urlPromise = [];
let page = 0;
let finished = 0;
for (; page < totalPage; page++) {
const url = baseUrl + "&page=" + page;
urlPromise.push(downloadSrcInfo(url));
msgBox.innerHTML = finished + "/" + page;
}
return Promise.all(urlPromise);
}
/**
* 下载图片
*/
async function solveImg(urls) {
async function downloadPdf(url, i) {
return fetch(url)
.then((res) => res.blob())
.then((blob) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
return new Promise((resolve) => {
reader.onloadend = () => {
resolve(reader.result);
numFinished++;
msgBox.innerHTML = numFinished + "/" + numTotal;
};
});
});
}
// remove duplicated
const map = new Map();
const resolution = localStorage.getItem(RESOLUTION) || RESOLUTIONS[0];
urls.forEach((triple) => {
triple.forEach((item) => {
map.set(
item.id,
item.src
.replace(
new RegExp(`scale=${RESOLUTIONS[0]}`),
`scale=${resolution}`
)
.replace(/watermark=[^&]+$/, "watermark=")
);
});
});
// sort and clear index
urls = [...map.entries()]
.sort((a, b) => a[0] - b[0])
.map((item) => item[1]);
// download images
const base64Promise = [];
let numFinished = 0;
let numTotal = 0;
urls.forEach((url) => {
base64Promise.push(downloadPdf(url));
numTotal++;
msgBox.innerHTML = numFinished + "/" + numTotal;
});
return Promise.all(base64Promise);
}
/**
* 拼接为pdf
* @param {*} base64s
*/
async function solvePDF(base64s) {
msgBox.innerHTML = "拼接中";
const doc = new jspdf.jsPDF();
base64s.forEach((base64, index) => {
doc.addImage(base64, "JPEG", 0, 0, 210, 297);
index + 1 == base64s.length || doc.addPage();
});
msgBox.innerHTML = "保存中";
doc.save(document.title + ".pdf");
msgBox.innerHTML = "完成!";
}
})();