// ==UserScript==
// @name 乐课网课件下载
// @namespace http://tampermonkey.net/
// @version 2.4.3
// @description 本脚本将添加一个下载课件的按钮在查看课件的界面上,输出文件为PDF格式
// @author NWater
// @match https://homework.leke.cn/auth/student/homework/doWork.htm?homeworkDtlId=*&schoolId=*
// @match https://repository.leke.cn/ssr/page/pc/eBook
// @match https://webapp.leke.cn/interact-classroom/
// @icon https://static.leke.cn/images/common/favicon.ico
// @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
// @license GNU GPLv3
// @grant GM_download
// @grant unsafeWindow
// ==/UserScript==
// Some codes reference to <https://greasyfork.org/zh-CN/scripts/442310-pku-thesis-download> (GPLv3)
(function() {
const window = unsafeWindow;
// Begin of code block from https://juejin.cn/post/6956456779761303560
// Comment: Edited by nwater (2023)
// 下载
// @param {String} url 目标文件地址
// @param {String} filename 想要保存的文件名称
function courseDownload(url, filename, callback, progresscallpack) {
console.log('下载文件: ', url, ' > ', filename);
getBlob(url, function(blob) {
saveAs(blob, filename);
if(callback){
callback();
}
}, progresscallpack);
}
function getBlob(url,callback, progresscallpack) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.addEventListener('load', function() {
if (xhr.status === 200) {
callback(xhr.response);
}
});
if(progresscallpack){
xhr.addEventListener('progress',progresscallpack);
}
xhr.send();
}
// 保存
// @param {Blob} blob
// @param {String} filename 想要保存的文件名称
function saveAs(blob, filename) {
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, filename);
} else {
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = filename;
link.click();
window.URL.revokeObjectURL(link.href);
}
}
// End of code block from https://juejin.cn/post/6956456779761303560
async function initDownloader(){
'use strict';
let downloadButton;
if(document.querySelector("ul.c-sidebar li.mobile")){
console.log(`载入乐课网课件下载脚本:课件模式`);
downloadButton = document.querySelector("ul.c-sidebar li.mobile").cloneNode(true);
document.querySelector("ul.c-sidebar").appendChild(downloadButton);
let downloadRawButton = document.querySelector("ul.c-sidebar li.mobile").cloneNode(true);
downloadRawButton.innerHTML = `<a href="javascript:void(0);" style="background: lightblue; text-align: center; position: relative; z-index:99999999999;">下载源文件</a>`;
document.querySelector("ul.c-sidebar").appendChild(downloadRawButton);
downloadRawButton.addEventListener("click", async ()=>{
let fileId = false;
if(window.Csts && window.Csts.file && window.Csts.file.id){ fileId=window.Csts.file.id; }
if(window.INITSTATE && window.INITSTATE.setEBook && window.INITSTATE.setEBook.ebookInfo && window.INITSTATE.setEBook.ebookInfo.ebook && window.INITSTATE.setEBook.ebookInfo.ebook.fileId){
fileId = window.INITSTATE.setEBook.ebookInfo.ebook.fileId;
}
downloadRawButton.innerHTML = `<a href="javascript:void(0);" style="background: lightpink; text-align: center; position: relative; z-index:99999999999;">查询文件ID</a>`;
console.log(`获取文件ID:`, fileId)
if(fileId){
console.log(`正在请求文件信息: ${document.location.origin}/auth/global/fs/file/access/data.htm?id=${fileId}`);
let fileData = new XMLHttpRequest();
// fileData.open('GET', `https://repository.leke.cn/auth/global/fs/file/access/data.htm?id=${fileId}`);
fileData.open('GET', `/auth/global/fs/file/access/data.htm?id=${fileId}`);
fileData.addEventListener("load", ()=>{
const res = JSON.parse(fileData.response);
console.log(`获取到文件信息:`, res);
if(!res.success){ return; }
let outputfilename = res.datas.file.name+'.'+res.datas.file.ext;
if(document.querySelector('div.ebook-pc div.nav-container div.btn span.nowrap')){ // 如果是电子课本
outputfilename = document.querySelector('div.ebook-pc div.nav-container div.btn span.nowrap').textContent.replace(/\//,'_') + '.pdf';
}
courseDownload(res.datas.file.path, outputfilename, ()=>{
downloadRawButton.innerHTML = `<a href="javascript:void(0);" style="background: lightblue; text-align: center; position: relative; z-index:99999999999;">下载源文件</a>`;
}, (progressEv)=>{
downloadRawButton.innerHTML = `<a href="javascript:void(0);" style="background: lightpink; text-align: center; position: relative; z-index:99999999999;">${Math.round(progressEv.loaded / progressEv.total * 100)}%</a>`;
});
});
fileData.send();
}
});
}else if(document.querySelector("div.leftNav-item")){
console.log(`载入乐课网课件下载脚本:直播模式`);
downloadButton = document.querySelector("div.leftNav-item").cloneNode(true);
document.querySelector("div.leftNav div").appendChild(downloadButton);
}else{
setTimeout(initDownloader, 2000);
return;
}
downloadButtonModiff();
function downloadButtonModiff(text){
if(text){
downloadButton.innerHTML = `<a href="javascript:void(0);" style="background: lightpink; text-align: center; position: relative; z-index:99999999999;">${text}</a>`;
// downloadButton.removeEventListener("click", download);
return;
}
downloadButton.innerHTML = `<a href="javascript:void(0);" style="background: lightgreen; text-align: center; position: relative; z-index:99999999999;">下载</a>`;
downloadButton.removeEventListener("click", download);
downloadButton.addEventListener("click", download);
}
async function download(){
const selectors = [
"li.m-pdf__item > img",
"li.m-ppt__item > img:last-child", // 对于一页PPT,乐课网将其渲染成数帧,如果你需要下载所有帧而不是每页的最后一帧,删掉 ":last-child" 即可
"img.tileList-item--img", // 直播课堂
"li.doc-li > div > img",
"li.scroll-li div.body-img:last-child img"
// ……希望不会有更多类型了
];
selectors.forEach(async (selector)=>{
const images = selectImages(selector)
.then(makePDF);
images.then(downloadPDF).catch(e=>{
console.error(`下载课件失败:`, e);
downloadButtonModiff(`失败`);
setInterval(downloadButtonModiff, 2000);
});
images.catch(e=>{
console.error(`下载课件失败:`, e);
downloadButtonModiff(`失败`);
setInterval(downloadButtonModiff, 2000);
});
})
}
async function selectImages(selector){
if(!selector){ return false; }
downloadButtonModiff('读取中');
const images = document.querySelectorAll(selector);
let i=0;
downloadButtonModiff(`下载${i}/${images.length}`);
async function updateCounting(){
i++;
downloadButtonModiff(`下载${i}/${images.length}`);
console.debug(`下载课件 [${i+1}/${images.length}]`);
}
[...images].map(image=>{
if(image.attributes['data-src']){
image.src = image.attributes['data-src'].value;
}
image.onload = updateCounting;
return image;
});
return images;
}
async function makePDF(images){
if(!images || !images.length){ return false; }
// Optinion of image width and height to millimeters convertion
// Assume that 1 pixel = 0.264583 mm (96 DPI)
downloadButtonModiff(`导出0/${images.length}`);
const mmPerPx = 0.264583;
// Create a PDF document with jsPDF library (https://github.com/MrRio/jsPDF)
const pageWidth = images[0].naturalWidth ? images[0].naturalWidth * mmPerPx : images[0].width * mmPerPx;
const pageHeight = images[0].naturalHeight ? images[0].naturalHeight * mmPerPx : images[0].height * mmPerPx;
const pdf = images[0].naturalWidth > images[0].naturalHeight ? new jspdf.jsPDF({
orientation: "landscape",
format: [pageHeight, pageWidth]
}) : new jspdf.jsPDF({
format: [pageWidth, pageHeight]
});
async function addImage2PDF(i){
downloadButtonModiff(`导出${i}/${images.length}`);
console.debug(`导出课件 [${i}/${images.length}]`)
const image = images[i];
if (i > 0) { pdf.addPage(); }
pdf.addImage(image, "JPEG", 0, 0,
(image.naturalWidth ? image.naturalWidth : image.width) * mmPerPx,
(image.naturalHeight ? image.naturalHeight : image.height) * mmPerPx
);
if(i+1<images.length){
await addImage2PDF(i+1);
}
}
await addImage2PDF(0);
return pdf;
}
async function downloadPDF(pdf){
if(!pdf){ return false; }
downloadButtonModiff(`导出中`);
let outputfilename = 'downlaod.pdf';
if(document.querySelector('div.ebook-pc div.nav-container div.btn span.nowrap')){ // 如果是电子课本
outputfilename = document.querySelector('div.ebook-pc div.nav-container div.btn span.nowrap').textContent.replace(/\//,'_') + '.pdf';
}
if(document.querySelector('div.z-resource-detail div.briefintroduction div.info ul li.item span')){ // 如果是
outputfilename = document.querySelector('div.z-resource-detail div.briefintroduction div.info ul li.item span').attributes.title.value + '.pdf';
}
pdf.save(outputfilename);
downloadButtonModiff(`完成`);
setTimeout(downloadButtonModiff, 5000);
}
}
setTimeout(initDownloader, 500);
})();