您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
教材列表页与预览页添加了PDF按钮,免登录查看或下载电子课本与课外书籍,可批量下载
当前为
// ==UserScript== // @name 国家中小学智慧教育平台教材PDF电子课本链接与下载工具 // @namespace https://greasyfork.org/zh-CN/scripts/466598 // @version 1.2.8 // @description 教材列表页与预览页添加了PDF按钮,免登录查看或下载电子课本与课外书籍,可批量下载 // @match *://basic.smartedu.cn/* // @match *://www.zxx.edu.cn/* // @grant none // ==/UserScript== (function() { 'use strict'; let link; const pattern = /contentId=([a-zA-Z0-9_-]+)/; // 匹配 contentId 的正则表达式 const contentId = window.location.href.match(pattern)?.[1]; // 获取 contentId 的数值 link = `https://r3-ndr.ykt.cbern.com.cn/edu_product/esp/assets_document/${contentId}.pkg/pdf.pdf`; // window.location.href = link; // 直接重定向到该网址,经测试可用 const maxTimeToCheck = 5000; // 最多检查 5000 毫秒,即 5 秒 let elapsedTime = 0; function checkLoginModal() { const loginModal = document.querySelector(".fish-modal-root"); if (loginModal) { clearInterval(intervalId1); // 清除定时器 // 去除阻碍 loginModal.remove(); // 去除遮罩 const body = document.querySelector("html > body"); // 去除 body 属性 body.removeAttribute("class"); body.removeAttribute("style"); // 添加阅读器 const name = document.querySelectorAll(".fish-breadcrumb-link")[2]?.textContent; const indexModuleWrapperECeCo = document.querySelector(".index-module_wrapper_ECeCo"); if (indexModuleWrapperECeCo) { indexModuleWrapperECeCo.innerHTML = ` <div class="index-module_wrapper_ECeCo"> <div class="index-module_header_tG-zz"> <h3 class="index-module_title_bnE9V">${name}</h3> <div class="index-module_info_evO1d"> <span class="index-module_origin_nuihE"> <svg class="index-module_icon_dwVZ4"> <use xlink:href="#icon_hotel_fill"> </use> </svg> <span class="index-module_department_ewVZW">智慧中小学</span> </span> <div class="index-module_assesment-detail_jhGLz"> <div class="index-module_assessment-content_06v6Z"> <div class="fish-dropdown-trigger index-module_assessment-rate_euPTQ"> <ul class="fish-rate fish-rate-disabled" tabindex="-1" role="radiogroup"> <li class="fish-rate-star fish-rate-star-full"> <div role="radio" aria-checked="true" aria-posinset="1" aria-setsize="5" tabindex="-1"> <div class="fish-rate-star-first"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> <div class="fish-rate-star-second"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> </div> </li> <li class="fish-rate-star fish-rate-star-full"> <div role="radio" aria-checked="true" aria-posinset="2" aria-setsize="5" tabindex="-1"> <div class="fish-rate-star-first"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> <div class="fish-rate-star-second"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> </div> </li> <li class="fish-rate-star fish-rate-star-full"> <div role="radio" aria-checked="true" aria-posinset="3" aria-setsize="5" tabindex="-1"> <div class="fish-rate-star-first"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> <div class="fish-rate-star-second"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> </div> </li> <li class="fish-rate-star fish-rate-star-full"> <div role="radio" aria-checked="true" aria-posinset="4" aria-setsize="5" tabindex="-1"> <div class="fish-rate-star-first"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> <div class="fish-rate-star-second"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> </div> </li> <li class="fish-rate-star fish-rate-star-zero"> <div role="radio" aria-checked="true" aria-posinset="5" aria-setsize="5" tabindex="-1"> <div class="fish-rate-star-first"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> <div class="fish-rate-star-second"> <div class="custom-star-wrap" style="width: 100%;"> <div class="custom-star"> </div> </div> </div> </div> </li> </ul> <span class="index-module_text_rukZW">5.0分</span> </div> </div> <span class="fish-dropdown-trigger index-module_assessment-btn_6imdF "> <button type="button" class="fish-btn fish-btn-round"> <svg class="index-module_rate-icon_YM1Lc"> <use xlink:href="#icon_evaluate_fill"> </use> </svg> <span class="index-module_assessment-btn-text_fw-+Z">评价</span> </button> </span> </div> <div class="index-module_extra_tUQog"> <div class="index-module_like-wrap_NbyLe "> <svg class="index-module_like_qOb9K"> <use xlink:href="#icon_thumb_fill"> </use> </svg> <div class="index-module_like-count_GXOGd">100w+点赞</div> </div> <div class="index-module_suggestion-wrap_s+Ii+ "> <svg class="index-module_suggestion-icon_IrRxU"> <use xlink:href="#icon_feedback_fill"> </use> </svg> <div>建议</div> </div> </div> </div> </div> <div class="index-module_divider_rI-lg"> </div> <div class="imageList-module_special-edu-image-list-wrapper_18zfs"> <div class="imageList-module_special-edu-image-list_+ywag" style="max-width: unset;"> <div class="course-document"> <div class="document-context" style="overflow: hidden; height: 95%;"> <embed src="${link}" width="100%" height="100%"> </div> <div class="doc-toolbar"> <div class="left"> </div> <div class="center"> </div> <div class="right"> <div class="split-line"> </div> <div class="tool-btn fullscreen" title="全屏"> </div> </div> </div> </div> </div> </div> </div> `; } // 全屏切换 const fullscreenBtn = document.querySelector(".tool-btn.fullscreen"); const courseDocument = document.querySelector(".course-document"); const htmlElement = document.querySelector("html"); fullscreenBtn.addEventListener("click", () => { if (courseDocument.classList.contains("full-screen")) { // 当前处于"全屏状态",切换为"非全屏状态" courseDocument.classList.remove("full-screen"); htmlElement.style.overflow = ""; } else { // 当前处于"非全屏状态",切换为"全屏状态" courseDocument.classList.add("full-screen"); htmlElement.style.overflow = "hidden"; } }); // 重定向至登录页 const toLogin = [ document.querySelector(".fish-dropdown-trigger.index-module_assessment-btn_6imdF"), document.querySelector(".index-module_like-wrap_NbyLe"), document.querySelector(".index-module_suggestion-wrap_s\\+Ii\\+") ]; toLogin.forEach(element => { element.addEventListener("click", () => { window.open("https://auth.smartedu.cn/uias/login/"); }); }); } else { elapsedTime += 100; if (elapsedTime >= maxTimeToCheck) { clearInterval(intervalId1); // 清除定时器,停止检查 } } } const intervalId1 = setInterval(checkLoginModal, 100); // 每隔 100 毫秒检查一次 .fish-modal-root 元素是否存在 function checkIndexModule() { console.log("生成链接: " + link); fetch(link) .then(response => { if (response.ok) { console.log("生成链接有效"); // 有效链接 } else { console.log("生成链接无效"); // 无效链接 link = `https://r3-ndr.ykt.cbern.com.cn/edu_product/esp/assets/${contentId}.pkg/pdf.pdf`; } }) .catch(() => { console.log("检测生成链接发生错误"); // 网络错误或请求被拒绝等 }); const container = document.querySelector(".index-module_extra_tUQog"); // 找到要添加按钮的容器元素 if (container && !document.querySelector(".Btns")) { clearInterval(intervalId2); // 清除定时器 const div = document.createElement("div"); div.className = "Btns"; div.innerHTML = `<a class="link" href="${link}" style="margin-left: 24px; color: rgb(136, 136, 136);">📓 查看PDF</a><a class="download" style="margin-left: 24px; color: rgb(136, 136, 136);">📓 下载PDF</a>`; container.appendChild(div); // 将按钮添加到网页中 const element1 = document.querySelector("div.Btns > a.link"); if (element1) { element1.addEventListener("mouseover", function() { this.innerHTML = "📘 查看PDF"; this.style.color = "#226dec"; // 鼠标移入时修改元素的样式 }); element1.addEventListener("mouseout", function() { this.innerHTML = "📓 查看PDF"; this.style.color = "#888"; // 鼠标移出时恢复原来的样式 }); element1.addEventListener("mousedown", function() { this.innerHTML = "📘 查看PDF"; this.style.color = "#226dec"; // 鼠标按下时修改元素的样式 }); } const element2 = document.querySelector("div.Btns > a.download"); if (element2) { element2.addEventListener("click", function(event) { // 点击下载 var fileUrl = link; var xhr = new XMLHttpRequest(); xhr.open("GET", fileUrl); xhr.responseType = "blob"; xhr.onloadstart = function() { // 初始化进度 element2.innerText = "0%"; }; xhr.onprogress = function(event) { if (event.lengthComputable) { var progress = Math.round((event.loaded / event.total) * 100); element2.innerText = "📓 下载中 (" + progress + "%)"; } }; xhr.onload = function() { if (xhr.status === 200) { var blob = xhr.response; var downloadLink = document.createElement("a"); downloadLink.href = URL.createObjectURL(blob); downloadLink.download = document.querySelector(".index-module_title_bnE9V").innerText; // 文件名 downloadLink.dispatchEvent(new MouseEvent("click")); element2.innerText = "📓 下载PDF"; // 下载完成后恢复按钮文字 } }; xhr.send(); // 发送请求 }); } } else { elapsedTime += 100; if (elapsedTime >= maxTimeToCheck) { clearInterval(intervalId2); // 清除定时器,停止检查 } } } const intervalId2 = setInterval(checkIndexModule, 100); // 每隔 100 毫秒检查一次 index-module_extra_tUQog 元素是否存在 const originalFetch = window.fetch; // 纠正课后阅读、备课和自学页面链接 window.fetch = function (url, options) { return originalFetch(url, options).then(function(response) { if (response.ok && url.endsWith(".pdf")) { let modifiedURL = url.replace("-private", ""); console.log("Fetched PDF (modified valid URL): " + modifiedURL); const element1 = document.querySelector("a.link"); // 按钮 element1 if (element1) { element1.href = modifiedURL; // 修改 element1 的链接 } link = modifiedURL; // 修改 element2 的链接 const prepContainer = document.querySelector(".index-module_resource-info__fav_3xNjm"); // 备课页面添加按钮 if (prepContainer && !document.querySelector(".index-module_resource-info__fav_3xNjm > div > .link")) { const div = document.createElement("div"); div.setAttribute("style", "display: flex; align-items: center;"); div.innerHTML = `<a class="link" href="${modifiedURL}" target="_blank" style="font-size: 14px; color: rgb(136, 136, 136);">📓 查看PDF</a>`; // 待纠正时更新链接 prepContainer.appendChild(div); } const syncClassSuggestion = document.getElementsByClassName("index-module_suggestion-wrap_s+Ii+ suggestion")[0]; // 自学页面添加按钮 if (syncClassSuggestion && !document.querySelector("div > .link")) { const div = document.createElement("div"); div.setAttribute("style", "display: flex; align-items: center; margin-left: 30px"); div.innerHTML = `<a class="link" href="${modifiedURL}" target="_blank" style="font-size: 14px; color: rgb(136, 136, 136);">📓 查看PDF</a>`; // 待纠正时更新链接 syncClassSuggestion.insertAdjacentElement("afterend", div); } } return response; }); }; function checkIndexModules() { const old_added_PDF_btns = document.querySelectorAll(".PDF-btns"); if (old_added_PDF_btns) { old_added_PDF_btns.forEach(element => { element.remove(); }); } const item = document.querySelectorAll("li.index-module_item_GfOnF"); if (item) { const img = document.querySelectorAll("div.index-module_cover_DGT6P > img"); let pdfArray = []; // 声明PDF空数组 for (let i = 0; i < img.length; i++) { if (img[i]) { clearInterval(intervalId3); // 查找到 img,清除定时器停止检查 const src = img[i].getAttribute("src"); let pdf; if (src.includes("esp/assets/")) { let extractedText = src.match(/assets\/(.*?)\./)[1]; pdf = `https://r3-ndr.ykt.cbern.com.cn/edu_product/esp/assets/${extractedText}.pkg/pdf.pdf`; } else if (src.includes("esp/assets_document/")) { let extractedText = src.match(/assets_document\/(.*?)\./)[1]; pdf = `https://r3-ndr.ykt.cbern.com.cn/edu_product/esp/assets_document/${extractedText}.pkg/pdf.pdf`; } else if (src.includes("65/document/")) { let extractedText = src.match(/document\/(.*?)\/image/)[1]; pdf = `https://v3.ykt.cbern.com.cn/65/document/${extractedText}/pdf.pdf`; } else { console.log("未识别链接"); pdf = `undefined`; } if (pdf) { pdfArray.push(pdf); // 将 pdf 添加到数组中 console.log(`pdf${i + 1}: ${pdf}`); } } } const content = document.querySelectorAll("div.index-module_content_KmLzG"); for (let i = 0; i < content.length; i++) { if (content[i]) { content[i].setAttribute("style", "width: 500px;"); // 统一 content 宽度 } } const contentName = document.querySelectorAll("div.index-module_line_LgJAC"); const added_PDF_btns = document.querySelectorAll("li.index-module_item_GfOnF > div.PDF-btns"); for (let i = 0; i < item.length; i++) { if (item[i] && !added_PDF_btns[i] && pdfArray[i]) { const PDF_btns = document.createElement("div"); // 创建 PDF_btns PDF_btns.setAttribute("class", "PDF-btns"); PDF_btns.innerHTML = ` <a type="button" class="fish-btn" style="margin-left: 24px; margin-bottom: 5px;" href="${pdfArray[i]}" target="_blank">查看PDF</a> <a type="button" class="fish-btn fish-btn-primary" style="margin-left: 24px; margin-bottom: 5px;">下载PDF</a> `; item[i].appendChild(PDF_btns); // 将 PDF_btns 添加到父元素中 if (pdfArray[i] !== "undefined") { PDF_btns.addEventListener("click", function(event) { // 停止 PDF_btns 的点击事件向更上一层元素传播 event.stopPropagation(); }); } else { PDF_btns.addEventListener('click', function(event) { // 链接 undefined 则阻止默认点击行为 event.preventDefault(); }); } const element2 = document.querySelectorAll("div.PDF-btns > a.fish-btn.fish-btn-primary")[i]; if (element2) { element2.addEventListener("click", function(event) { // 点击下载 var fileUrl = pdfArray[i]; var xhr = new XMLHttpRequest(); xhr.open("GET", fileUrl); xhr.responseType = "blob"; xhr.onloadstart = function() { // 初始化进度 element2.innerText = "下载中0%"; }; xhr.onprogress = function(event) { if (event.lengthComputable) { var progress = Math.round((event.loaded / event.total) * 100); element2.innerText = "下载中 (" + progress + "%)"; } }; xhr.onload = function() { if (xhr.status === 200) { var blob = xhr.response; var downloadLink = document.createElement("a"); downloadLink.href = URL.createObjectURL(blob); downloadLink.download = contentName[i].querySelector("span").getAttribute("title"); // 文件名 downloadLink.dispatchEvent(new MouseEvent("click")); element2.innerText = "下载PDF"; // 下载完成后恢复按钮文字 } }; xhr.send(); // 发送请求 }); } } } } else { elapsedTime += 100; if (elapsedTime >= maxTimeToCheck) { clearInterval(intervalId3); // 清除定时器,停止检查 } } } let intervalId3 = setInterval(checkIndexModules, 100); // 每隔 100 毫秒检查一次 function doItAgain() { clearInterval(intervalId3); // 防止多次启动定时器 elapsedTime = 0; // 逝去时间归零 intervalId3 = setInterval(checkIndexModules, 100); // 创建新的定时器以待调用 } const observer = new MutationObserver(function(mutations) { // 创建一个MutationObserver mutations.forEach(function(mutation) { if (mutation.addedNodes && mutation.addedNodes.length > 0) { // 检查是否有新增节点 for (let i = 0; i < mutation.addedNodes.length; i++) { const addedNode = mutation.addedNodes[i]; if (addedNode.classList && addedNode.classList.contains("PDF-btns")) { // 判断节点的 classList 中是否存在 PDF-btns 类名 console.log("PDF-btns已添加"); } else { console.log("DOM树已变化"); doItAgain(); // 执行新的定时器 } } } }); }); const config = { // 配置需要观察的内容 childList: true, // 监听子节点的变化 subtree: true // 监听所有后代节点的变化 }; observer.observe(document.body, config); })();