/* globals jspdf UPNG */ // ==UserScript== // @name Rain Classroom PDF Direct Download // @name:zh-CN 雨课堂课件PDF下载工具 // @namespace https://www.pizyds.com/ // @version 1.1.0 // @description Automatic generation of direct download PDF on Rain Classroom // @description:zh-CN 在雨课堂页面自动生成PDF版本课件提供下载 // @author PillarsZhang // @homepage https://www.pizyds.com/rain-classroom-pdf-direct-download // @license MIT // @match https://*.yuketang.cn/* // @icon https://www.yuketang.cn/static/images/favicon.ico // @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.3.1/jspdf.umd.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.3/pako.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/upng-js/2.1.0/UPNG.min.js // @resource pizyds_iconfont_css https://at.alicdn.com/t/font_2448118_l5d66dc50k9.css // @grant GM_getResourceText // @grant GM_addStyle // @downloadURL none // ==/UserScript== (function() { 'use strict'; console.log("雨课堂课件PDF下载工具:已载入"); //对自动添加客观题答案到PPT页面的配置 var ans_config = { enabled: true, right: 30, up: 25, fontSize: 40, fontColor: "#000000" }; //jsPDF用于PDF生成,UPNG、pako用于PNG的反交错和压缩 const {jsPDF} = jspdf; //下载的图标,感谢iconfont const pizyds_iconfont_css = GM_getResourceText("pizyds_iconfont_css"); GM_addStyle(pizyds_iconfont_css); //实时查找PPT窗口 setInterval(()=>{ var el_dialog = find_basePPTDialog(); check_url && el_dialog && add_button_download(el_dialog); },200); //更改为内部校验链接,因为大量ajax页面跳转的存在 function check_url(){ return /https:\/\/.*\.yuketang\.cn\/v2\/web\/student.*/.test(window.location.href) } //按钮触发PDF生成逻辑 function download_process(el_dialog){ var url_slides = get_url_slides(el_dialog); if (url_slides.length > 0){ refreshProcessStatus("处理图片..."); console.groupCollapsed("雨课堂课件PDF下载工具:处理图片..."); image_process(url_slides) .then(img_list => { console.groupEnd(); refreshProcessStatus("生成PDF..."); var ppt_name = document.getElementsByClassName("ppt_name")[0].innerText; var filename = ppt_name + ".pdf"; var answer_list = ans_config.enabled ? get_answers(url_slides): []; pdf_process(img_list, filename, answer_list); refreshProcessStatus("下载课件"); }) } else{ alert("雨课堂课件PDF下载工具:没有提取到图片"); } } //第一步-借助UPNG,进行图片下载与反交错、压缩处理 function image_process(url_slides){ var promiseList = new Array(url_slides.length); var finished_num = 0; var count_finished_num = (index) => { var processStatus = `${++finished_num}/${url_slides.length}`; refreshProcessStatus(`处理图片(${processStatus})`); console.log(`雨课堂课件PDF下载工具:${processStatus} - 第${index+1}页 - ${url_slides[index]}`); } for (let i = 0; i < url_slides.length; i++){ promiseList[i] = fetch(url_slides[i]).then(response => { return response.arrayBuffer(); }).then(arrayBuffer_origin => { var img = UPNG.decode(arrayBuffer_origin); var rgba = UPNG.toRGBA8(img); var arrayBuffer_compress = UPNG.encode(rgba, img.width, img.height, 0); count_finished_num(i); return {unit8: new Uint8Array(arrayBuffer_compress), width: img.width, height: img.height}; }).catch(err => { console.error(err); alert("雨课堂课件PDF下载工具:图像处理出错"); }); } return Promise.all(promiseList); } //第二步-借助jsPDF,进行PDF的生成 function pdf_process(img_list, filename, answer_list){ console.groupCollapsed("雨课堂课件PDF下载工具:生成PDF..."); var doc = new jsPDF({ orientation: "landscape", unit: "px", format: [img_list[0].width, img_list[0].height], hotfixes: ["px_scaling"] }); var addPPT = (index) => { console.log(`雨课堂课件PDF下载工具:第 ${index+1} 页 - PPT`); doc.addImage(img_list[index].unit8, 'PNG', 0, 0, img_list[index].width, img_list[index].height); let answer_item = answer_list.find(obj => obj.index == index); if (answer_item && answer_item.ans != "") { let answer_img = text2img(answer_item.ans, ans_config.fontSize, ans_config.fontColor); console.log(`雨课堂课件PDF下载工具:第 ${index+1} 页 - 答案 - ${answer_item.ans}`); doc.addImage(answer_img.data, 'PNG', img_list[index].width - answer_img.width - ans_config.right, ans_config.up, answer_img.width, answer_img.height); }; }; addPPT(0); for (let i = 1; i < img_list.length; i++){ doc.addPage([img_list[i].width, img_list[i].height], "landscape"); addPPT(i); } console.groupEnd(); doc.save(filename); console.log(`雨课堂课件PDF下载工具:完成下载`); console.log(`雨课堂课件PDF下载工具:https://www.pizyds.com/rain-classroom-pdf-direct-download/`); } //按钮文本刷新 function refreshProcessStatus(processStatus){ var el_download = document.getElementsByClassName("pizyds_download")[0]; el_download.innerHTML = ` ${processStatus}`; } //查找PPT窗口 function find_basePPTDialog(){ var el_dialogs = document.getElementsByClassName("basePPTDialog"); if (el_dialogs.length == 1){ return el_dialogs[0]; } else{ return false; } } //客观题答案 function get_answers(url_slides){ var el_problem = document.getElementById("problem"); var answer_list = []; if (el_problem){ var el_exercises_info = el_problem.getElementsByClassName("exercises_info"); for (let i = 0; i < el_exercises_info.length; i++){ let el_url = el_exercises_info[i].querySelector(".img_box>img"); let el_ans = el_exercises_info[i].querySelector(".answer_info>.correct_answer"); var answer_item = { url: el_url ? el_url.src : "", ans: el_ans ? el_ans.innerText : "", index: -1 }; answer_item.index = url_slides.indexOf(answer_item.url); answer_list.push(answer_item); } } console.groupCollapsed(`雨课堂课件PDF下载工具:提取到 ${answer_list.length} 项答案`); console.table(answer_list); console.groupEnd(); return answer_list; } //对添加客观题答案到PPT的开关 function switch_answer(processStatus){ var el_switchAnswer = document.getElementsByClassName("pizyds_switchAnswer")[0]; if (ans_config.enabled){ el_switchAnswer.innerHTML = "[ 答案 ]"; ans_config.enabled = false; } else{ el_switchAnswer.innerHTML = "[ 答案 ]"; ans_config.enabled = true; } } //PPT图片链接提取 function get_url_slides(el_dialog){ try{ var el_swiper = el_dialog.getElementsByClassName("pptSwiper")[0]; var el_slides = el_swiper.getElementsByClassName("swiper-slide"); var url_slides = new Array(el_slides.length); for (let i = 0; i < el_slides.length; i++){ url_slides[i] = el_slides[i].getElementsByTagName("img")[0].src; } console.groupCollapsed(`雨课堂课件PDF下载工具:提取到 ${url_slides.length} 页 PPT`); console.table(url_slides); console.groupEnd(); return url_slides; } catch(err){ return new Array(); } } //按钮注入 function add_button_download(el_dialog){ var el_header = el_dialog.getElementsByClassName("layout_header")[0]; if (el_header.getElementsByClassName("pizyds_download").length == 0){ var el_download = create_node_from_html(` 下载课件`); el_download.onclick = () => download_process(el_dialog); el_header.appendChild(el_download); var el_switchAnswer = create_node_from_html(`[ 答案 ]`); el_switchAnswer.onclick = () => switch_answer(); el_header.appendChild(el_switchAnswer); console.log("雨课堂课件PDF下载工具:按钮注入成功"); return true; } else{ return false; console.log("雨课堂课件PDF下载工具:按钮注入失败"); } } //HTML字符串转节点 function create_node_from_html(html){ let tempNode = document.createElement('div'); tempNode.innerHTML = html; return tempNode.firstChild; } //修改自:http://www.jsfun.cn/#textBecomeImg //js使用canvas将文字转换成图像数据base64 function text2img(text, fontsize, fontcolor){ var canvas = document.createElement('canvas'); canvas.height = parseInt(fontsize * 1.2); var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = fontcolor; ctx.font = fontsize + "px Arial"; ctx.textBaseline = 'middle'; ctx.fillText(text, 0, fontsize/2); canvas.width = ctx.measureText(text).width; ctx.fillStyle = fontcolor; ctx.font = fontsize + "px Arial"; ctx.textBaseline = 'middle'; ctx.fillText(text, 0, fontsize/2); var dataUrl = canvas.toDataURL('image/png'); return {data: dataUrl, width: canvas.width, height: canvas.height}; } })();