您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
直接下载课程资源
// ==UserScript== // @name Easy云课堂下载助手 // @namespace https://www.easyketang.com/ // @version 1.5 // @description 直接下载课程资源 // @author Quarix // @include https://www.easyketang.com/* // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @run-at document-start // @license MIT // ==/UserScript== (function () { "use strict"; const STORAGE_KEY_SHOW = "EasyKetangDLHelper_Show"; const STORAGE_KEY_COLLAPSE = "EasyKetangDLHelper_Collapsed"; let isShown = localStorage.getItem(STORAGE_KEY_SHOW); if (isShown === null) isShown = "true"; isShown = isShown === "true"; let isCollapsed = localStorage.getItem(STORAGE_KEY_COLLAPSE); if (isCollapsed === null) isCollapsed = "false"; isCollapsed = isCollapsed === "true"; let floatBox, header, toggleBtn, resourceList, statusText, dragInfo; function createUI() { if (floatBox) return; floatBox = document.createElement("div"); Object.assign(floatBox.style, { position: "fixed", top: "10px", right: "10px", width: "350px", maxHeight: "500px", backgroundColor: "#fff", border: "1px solid #ccc", boxShadow: "0 4px 12px rgba(0,0,0,0.25)", zIndex: 999999, fontSize: "14px", padding: "0", fontFamily: "Helvetica, Arial, sans-serif", borderRadius: "6px", color: "#333", userSelect: "none", display: isShown ? "block" : "none", overflow: "hidden", boxSizing: "border-box", }); document.body.appendChild(floatBox); header = document.createElement("div"); Object.assign(header.style, { cursor: "move", backgroundColor: "#0084ff", color: "white", padding: "8px 12px", fontWeight: "600", display: "flex", justifyContent: "space-between", alignItems: "center", userSelect: "none", }); floatBox.appendChild(header); const title = document.createElement("span"); title.textContent = "资源下载助手"; header.appendChild(title); const controls = document.createElement("div"); header.appendChild(controls); toggleBtn = document.createElement("button"); toggleBtn.textContent = isCollapsed ? "+" : "−"; styleControlButton(toggleBtn); controls.appendChild(toggleBtn); const closeBtn = document.createElement("button"); closeBtn.textContent = "×"; closeBtn.title = "隐藏悬浮窗 (通过GM菜单重新打开)"; styleControlButton(closeBtn); controls.appendChild(closeBtn); toggleBtn.onclick = () => { isCollapsed = !isCollapsed; localStorage.setItem(STORAGE_KEY_COLLAPSE, isCollapsed); updateCollapse(); }; closeBtn.onclick = () => { setShown(false); }; resourceList = document.createElement("div"); Object.assign(resourceList.style, { maxHeight: "440px", overflowY: "auto", padding: "10px 15px", display: isCollapsed ? "none" : "block", userSelect: "text", }); floatBox.appendChild(resourceList); statusText = document.createElement("div"); Object.assign(statusText.style, { fontSize: "12px", color: "#888", margin: "8px 15px 10px", textAlign: "center", display: isCollapsed ? "none" : "block", }); statusText.textContent = "尚未获取到资源"; floatBox.appendChild(statusText); dragInfo = { dragging: false, offsetX: 0, offsetY: 0 }; header.addEventListener("mousedown", onDragStart); document.addEventListener("mousemove", onDragMove); document.addEventListener("mouseup", onDragEnd); } function styleControlButton(btn) { Object.assign(btn.style, { marginLeft: "8px", background: "transparent", border: "none", color: "white", fontSize: "18px", lineHeight: "18px", cursor: "pointer", userSelect: "none", padding: "0 4px", }); btn.onmouseenter = () => (btn.style.color = "#cce4ff"); btn.onmouseleave = () => (btn.style.color = "white"); } function updateCollapse() { if (!resourceList || !statusText || !toggleBtn) return; if (isCollapsed) { resourceList.style.display = "none"; statusText.style.display = "none"; toggleBtn.textContent = "+"; floatBox.style.height = "auto"; floatBox.style.maxHeight = ""; } else { resourceList.style.display = "block"; statusText.style.display = "block"; toggleBtn.textContent = "−"; floatBox.style.maxHeight = "500px"; } } function setShown(show) { isShown = show; if (floatBox) floatBox.style.display = show ? "block" : "none"; localStorage.setItem(STORAGE_KEY_SHOW, show ? "true" : "false"); updateMenu(); } let menuId = null; function updateMenu() { if (menuId) GM_unregisterMenuCommand(menuId); menuId = GM_registerMenuCommand( (isShown ? "隐藏" : "显示") + "资源下载窗", () => setShown(!isShown) ); } updateMenu(); function onDragStart(e) { dragInfo.dragging = true; const rect = floatBox.getBoundingClientRect(); dragInfo.offsetX = e.clientX - rect.left; dragInfo.offsetY = e.clientY - rect.top; e.preventDefault(); } function onDragMove(e) { if (!dragInfo.dragging) return; const left = e.clientX - dragInfo.offsetX; const top = e.clientY - dragInfo.offsetY; floatBox.style.left = Math.min( window.innerWidth - floatBox.offsetWidth - 10, Math.max(10, left) ) + "px"; floatBox.style.top = Math.min( window.innerHeight - floatBox.offsetHeight - 10, Math.max(10, top) ) + "px"; floatBox.style.right = "auto"; e.preventDefault(); } function onDragEnd(e) { dragInfo.dragging = false; } // 直接写content字符串为txt下载 function downloadTextFile(text, filename) { const blob = new Blob([text], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); downloadFileWithProgress(url, filename); setTimeout(() => URL.revokeObjectURL(url), 15000); } async function downloadFileWithProgress(url, filename) { try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP错误,状态码 ${response.status}`); const contentLength = response.headers.get("content-length"); const total = contentLength ? parseInt(contentLength, 10) : null; const reader = response.body.getReader(); const chunks = []; let receivedLength = 0; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); receivedLength += value.length; } const blob = new Blob(chunks); const blobUrl = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = blobUrl; a.download = filename || ""; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(blobUrl); } catch (error) { console.warn("下载出错,尝试在新标签打开链接:", error); window.open(url, "_blank", "noopener"); } } // 主体处理函数:task_item,过滤空content function handleTaskItem(taskItem) { createUI(); resourceList.innerHTML = ""; statusText.textContent = "已获取资源"; for (const category in taskItem) { if (!Array.isArray(taskItem[category])) continue; const list = taskItem[category]; list.forEach((section) => { if (!section.resource || !section.resource.length) return; // 过滤content为空的资源 const filteredResources = section.resource.filter((r) => { if (r.content === null || r.content === undefined) return false; const c = String(r.content).trim(); return c.length > 0; }); if (!filteredResources.length) return; const sectionTitle = document.createElement("h3"); sectionTitle.textContent = section.name || "未命名分类"; Object.assign(sectionTitle.style, { margin: "15px 0 8px", fontWeight: "700", borderBottom: "1px solid #ddd", paddingBottom: "2px", fontSize: "15px", }); resourceList.appendChild(sectionTitle); filteredResources.forEach((res) => { const btn = document.createElement("button"); btn.textContent = res.filename || `资源-${res.id}`; Object.assign(btn.style, { display: "block", width: "100%", marginBottom: "8px", cursor: "pointer", padding: "8px 12px", backgroundColor: "#0084ff", color: "#fff", border: "none", borderRadius: "4px", textAlign: "left", fontWeight: "500", userSelect: "none", transition: "background-color 0.2s ease", }); btn.onmouseenter = () => (btn.style.backgroundColor = "#005ecb"); btn.onmouseleave = () => (btn.style.backgroundColor = "#0084ff"); btn.onclick = () => { const content = String(res.content).trim(); if (/^https?:\/\//i.test(content)) { let url = String(res.content).trim(); if (url.startsWith("http://")) { url = url.replace(/^http:\/\//i, "https://"); } downloadFileWithProgress(url, res.filename); } else { downloadTextFile(content, (res.filename || "文件") + ".txt"); } }; resourceList.appendChild(btn); }); }); } } (function () { const origOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url) { this._url = url + ""; return origOpen.apply(this, arguments); }; const origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function (body) { this.addEventListener("load", () => { if (!this._url) return; if ( this._url.includes( "https://cloud-api.easyketang.com/admin/task_item/get_nnew_list_item" ) ) { try { const text = this.responseText; const json = JSON.parse(text); if (json && json.data && json.data.task_item) { handleTaskItem(json.data.task_item); } } catch (e) { console.warn("EasyKetang 下载助手 XHR 解析异常:", e); } } }); return origSend.apply(this, arguments); }; })(); let lastHash = location.hash; window.addEventListener("hashchange", () => { if (location.hash !== lastHash) { lastHash = location.hash; if (resourceList) resourceList.innerHTML = ""; if (statusText) statusText.textContent = "等待接口请求资源..."; } }); function start() { createUI(); updateCollapse(); } if ( document.readyState === "complete" || document.readyState === "interactive" ) { start(); } else { window.addEventListener("DOMContentLoaded", start, false); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址