// ==UserScript== // @name 学习通下载器 Chaoxing Downloader // @namespace https://github.com/lcandy2/user.js/tree/main/websites/chaoxing.com/chaoxing-downloader // @version 1.2 // @author 甜檸Cirtron (lcandy2) // @description 一键下载资料文件,无视系统限制。 // @license AGPL-3.0-or-later // @copyright lcandy2 All Rights Reserved // @homepage https://greasyfork.org/scripts/499214 // @homepageURL https://greasyfork.org/scripts/499214 // @source https://github.com/lcandy2/user.js/tree/main/websites/chaoxing.com/chaoxing-downloader // @match *://*.chaoxing.com/mooc2-ans/coursedata/stu-datalist* // @require https://registry.npmmirror.com/vue/3.4.27/files/dist/vue.global.prod.js // @require data:application/javascript,%3Bwindow.Vue%3DVue%3B // @require https://registry.npmmirror.com/vuetify/3.6.6/files/dist/vuetify.min.js // @require data:application/javascript,%3B // @resource VuetifyStyle https://registry.npmmirror.com/vuetify/3.6.6/files/dist/vuetify.min.css // @connect pan-yz.chaoxing.com // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_xmlhttpRequest // @run-at document-end // ==/UserScript== (function (vue, vuetify) { 'use strict'; var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); const backgroundFetch = async (url) => { return new Promise((resolve, reject) => { _GM_xmlhttpRequest({ method: "GET", url, onload: (response) => { resolve(response.responseText); }, onerror: (error) => { reject(error); } }); }); }; const _sfc_main = /* @__PURE__ */ vue.defineComponent({ __name: "App", setup(__props) { const buttonRef = vue.ref(null); const downloadLink = vue.ref(""); const isDownloading = vue.ref(false); const isFinished = vue.ref(false); const isErrored = vue.ref(false); const fileInfo = vue.ref({ fileName: "", objectId: "", dataId: "", t: "", courseId: "", clazzId: "", cpi: "", puid: "" }); const handleClick = async (event) => { !isFinished.value && event.preventDefault(); isDownloading.value = true; if (buttonRef.value && !isFinished.value) { try { const tokenReq = await backgroundFetch("https://pan-yz.chaoxing.com/api/token/uservalid"); const tokenJson = JSON.parse(tokenReq); const token = tokenJson._token; const fileInfoApi = new URL( "https://pan-yz.chaoxing.com/api/share/downloadUrl" ); fileInfoApi.searchParams.set("puid", fileInfo.value.puid); fileInfoApi.searchParams.set("_token", token); fileInfoApi.searchParams.set("sarepuid", fileInfo.value.puid); fileInfoApi.searchParams.set("objectid", fileInfo.value.objectId); const fileInfoReq = await backgroundFetch(fileInfoApi.toString()); const fileInfoJson = JSON.parse(fileInfoReq); const fileInfoUrl = fileInfoJson.url; downloadLink.value = fileInfoUrl.toString(); isFinished.value = true; vue.nextTick(() => { buttonRef.value.$el.click(); }); } catch (e) { isErrored.value = true; console.error(e); } } else { isDownloading.value = false; } }; vue.onMounted(() => { var _a; if (buttonRef.value) { try { const fileElement = (_a = buttonRef.value.$el.parentElement) == null ? void 0 : _a.parentElement.parentElement.parentElement; fileInfo.value.objectId = fileElement == null ? void 0 : fileElement.getAttribute("objectid"); fileInfo.value.fileName = fileElement == null ? void 0 : fileElement.getAttribute("dataname"); fileInfo.value.dataId = (fileElement == null ? void 0 : fileElement.getAttribute("id")) || (fileElement == null ? void 0 : fileElement.getAttribute("order")); fileInfo.value.t = fileElement == null ? void 0 : fileElement.getAttribute("t"); const userIdElement = document.querySelector("input#userId"); if (userIdElement) { fileInfo.value.puid = userIdElement.getAttribute("value") || ""; } const href = new URL(window.location.href); fileInfo.value.courseId = href.searchParams.get("courseid") || ""; fileInfo.value.clazzId = href.searchParams.get("clazzid") || ""; fileInfo.value.cpi = href.searchParams.get("cpi") || ""; const downloadUrl = new URL( "https://mooc1.chaoxing.com/coursedata/downloadData?ut=s" ); downloadUrl.searchParams.set("dataId", fileInfo.value.dataId); downloadUrl.searchParams.set("classId", fileInfo.value.clazzId); downloadUrl.searchParams.set("cpi", fileInfo.value.cpi); downloadUrl.searchParams.set("courseId", fileInfo.value.courseId); downloadLink.value = downloadUrl.toString(); } catch (e) { isErrored.value = true; console.error(e); } } }); return (_ctx, _cache) => { const _component_v_btn = vue.resolveComponent("v-btn"); return vue.openBlock(), vue.createBlock(_component_v_btn, { ref_key: "buttonRef", ref: buttonRef, disabled: isErrored.value, loading: isDownloading.value && !isErrored.value, onClick: handleClick, href: downloadLink.value, density: "compact", variant: "plain", color: "primary" }, { default: vue.withCtx(() => [ vue.createTextVNode(vue.toDisplayString(isErrored.value ? "下载器错误,请刷新重试" : isDownloading.value ? "正在下载中" : isFinished.value ? "下载完成" : "下载器下载"), 1) ]), _: 1 }, 8, ["disabled", "loading", "href"]); }; } }); const appendApp = (element) => { const vuetify$1 = vuetify.createVuetify({}); const app = vue.createApp(_sfc_main); app.use(vuetify$1); app.mount(element); }; const cssLoader = (e) => { const t = GM_getResourceText(e); return GM_addStyle(t), t; }; cssLoader("VuetifyStyle"); function addElement() { const uls = document.querySelectorAll("ul.operate"); uls.forEach((ul) => { var _a; const file = (_a = ul.parentElement) == null ? void 0 : _a.parentElement; if (!file) return; const isBook = file.getAttribute("type") === "book"; const isAFolder = file.getAttribute("type") === "afolder"; const hasObjectID = file.getAttribute("objectid") !== ""; if (isBook || isAFolder || !hasObjectID) return; if (!ul.querySelector("[chaoxing-downloader]")) { const li = document.createElement("li"); li.className = "operate_down"; li.setAttribute("chaoxing-downloader", ""); ul.appendChild(li); appendApp(li); } }); } addElement(); const observer = new MutationObserver((mutationsList, observer2) => { for (let mutation of mutationsList) { if (mutation.type === "childList") { addElement(); } } }); const config = { childList: true, subtree: true }; observer.observe(document, config); })(Vue, Vuetify);