// ==UserScript== // @name 国家中小学智慧教育平台电子课本教材下载 最新版[直接下载pdf 跳过浏览器默认预览] 2023-07-07更新 // @namespace https://greasyfork.org/zh-CN/scripts/469898-smartedutextbookdownloader // @version 1.7 // @description 在国家中小学智慧教育平台网站中添加电子课本下载按钮,在列表中无需跳转,无需登录,批量下载 // @author @topjohncian // @require https://unpkg.com/idb@7/build/umd.js // @require https://unpkg.com/coco-message@2.0.3/coco-message.min.js // @match *://basic.smartedu.cn/* // @connect r1-ndr.ykt.cbern.com.cn // @connect r2-ndr.ykt.cbern.com.cn // @connect r3-ndr.ykt.cbern.com.cn // @license MIT // @grant window.onurlchange // @downloadURL none // ==/UserScript== window.materialInfo = []; function ramdomItem(arr) { return arr[Math.floor(Math.random() * arr.length)]; } async function onDownloadClick(event) { event.preventDefault(); event.stopPropagation(); const target = event.target; const detailHost = [ "//s-file-1.ykt.cbern.com.cn", "//s-file-2.ykt.cbern.com.cn", ]; const downloadHost = "https://r1-ndr.ykt.cbern.com.cn,https://r2-ndr.ykt.cbern.com.cn,https://r3-ndr.ykt.cbern.com.cn".split( "," ); const materialId = target.dataset.materialId; const fileName = target.dataset.materialTitle + ".pdf"; // alert("下载" + fileName + "\n" + target.dataset.materialId); const cancel = window.cocoMessage.loading(`正在下载 ${fileName}`); const detail = await ( await fetch( ramdomItem(detailHost) + `/zxx/ndrv2/resources/tch_material/details/${materialId}.json` ) ).json(); const pdfDetail = detail.ti_items.find((item) => item.ti_format === "pdf") ?? null; if (pdfDetail === null) { throw new Error("未找到pdf文件"); } const downloadURL = pdfDetail.ti_storage.replace( /^cs_path:\${ref-path}/, ramdomItem(downloadHost) ); const blob = await ( await fetch(downloadURL, { referrer: "https://basic.smartedu.cn/", referrerPolicy: "strict-origin-when-cross-origin", body: null, method: "GET", mode: "cors", }) ).blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = fileName; a.target = "_blank"; a.click(); URL.revokeObjectURL(url); cancel(); window.cocoMessage.success(`下载完成 ${fileName}`); } let retryTimes = 0; function tchMaterialHook() { // var script = document.createElement("script"); // script.src = "https://cdn.jsdelivr.net/npm/idb@7/build/umd.js"; // document.head.appendChild(script); // const { unProxy } = window.ah.proxy({ // onResponse: (response, handler) => { // if ( // new URL(response.config.url.replace(/^(\/\/)/, "https://")).pathname === // "/proxy/cloud/v1/res_stats/actions/query" // ) { // window.materialInfo = JSON.parse(response.response); // } // handler.next(response); // }, // }); const materialUlElement = document.querySelector( "#main-content > div.content > div.fish-spin-nested-loading.x-edu-nested-loading > div > div:nth-child(2) > div > div:nth-child(2) > div:nth-child(2) > ul" ); if (materialUlElement === null) { if (retryTimes >= 60) { return; } else { retryTimes += 1; setTimeout(() => tchMaterialHook(), 1000); } return; } retryTimes = 0; const config = { attributes: true, childList: true, subtree: true }; // Callback function to execute when mutations are observed const callback = (mutationList, observer) => { console.log( "[SMARTEDU-DOWNLOADER] Mutation Observer Updated", mutationList, observer ); const needUpdate = mutationList .flatMap((mutation) => [...mutation.addedNodes, ...mutation.removedNodes]) .every((element) => !(element instanceof HTMLButtonElement)); if (needUpdate) { hook().then(); } }; const observer = new MutationObserver(callback); observer.observe(materialUlElement, config); hook().then(); async function hook() { if (window.materialInfo.length === 0) { await getMaterialInfo(); } const materialSpanDivs = materialUlElement.querySelectorAll( "li > div:nth-child(2) > div:nth-child(1)" ); const versionLabelSpan = document.querySelector( "#main-content > div.content > div.fish-spin-nested-loading.x-edu-nested-loading > div > div:nth-child(2) > div > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(2) > label.fish-radio-tag-wrapper-checked > span:nth-child(2)" ); const versionLabel = versionLabelSpan?.innerText ?? ""; materialSpanDivs.forEach((materialSpanDiv) => { materialSpanDiv.querySelector("button")?.remove(); }); for (const materialSpanDiv of materialSpanDivs) { const materialName = materialSpanDiv.querySelector("span").innerText; const materials = window.materialInfo.filter( (m) => m.title === materialName ); const material = versionLabel !== "" ? materials.find((m) => m.tag_list.some((tag) => tag.tag_name === versionLabel) ) ?? materials[0] : materials[0]; const button = document.createElement("button"); button.dataset.materialId = material.id; button.dataset.materialTitle = material.title; button.innerText = `下载 ${material.title}.pdf`; button.setAttribute("style", "z-index: 999;"); button.onclick = onDownloadClick; materialSpanDiv.appendChild(button); } } } function tchMaterialDetailHook() { const contentId = new URLSearchParams(location.search).get("contentId"); if (contentId === null) { return; } const materialSpanDiv = document.querySelector( `#main-content > div.content > div:last-child > div > div > div:nth-child(1)` ); const materialTitle = materialSpanDiv?.querySelector("h3")?.innerText ?? ""; if (materialSpanDiv === null || materialTitle === "") { if (retryTimes >= 60) { return; } else { retryTimes += 1; setTimeout(() => tchMaterialDetailHook(), 1000); } return; } retryTimes = 0; // const material = window.materialInfo.find((m) => m.id === contentId); const button = document.createElement("button"); button.dataset.materialId = contentId; button.dataset.materialTitle = materialTitle; button.innerText = `下载 ${materialTitle}.pdf`; button.setAttribute("style", "z-index: 999;"); button.onclick = onDownloadClick; materialSpanDiv.appendChild(button); } async function getMaterialInfo() { const db = await idb.openDB("content-library_ncet-xedu"); window.materialInfo = await db .transaction("NDR_TchMaterial", "readonly") .objectStore("NDR_TchMaterial") .getAll(); } async function main() { "use strict"; if (new URL(location.href).pathname === "/tchMaterial") { tchMaterialHook(); } else if (new URL(location.href).pathname === "/tchMaterial/detail") { tchMaterialDetailHook(); } } (async function () { // @ts-expect-error if (window.onurlchange === null) { // feature is supported window.addEventListener("urlchange", (info) => { console.log(info); main(); }); } await main(); })();