您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
在智云课堂页面添加批量下载视频的功能
// ==UserScript== // @name 智云课堂批量下载 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 在智云课堂页面添加批量下载视频的功能 // @author Cold_Ink // @match https://classroom.zju.edu.cn/coursedetail* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; console.log("智云课堂批量下载脚本已启动"); // 获取URL中的参数 function getQueryVariable(variable) { const query = window.location.search.substring(1); const vars = query.split("&"); for (let i = 0; i < vars.length; i++) { const pair = vars[i].split("="); if (pair[0] === variable) { return decodeURIComponent(pair[1]); } if (decodeURIComponent(pair[0]) === variable) { return decodeURIComponent(pair[1]); } } return(false); } const course_id = getQueryVariable("course_id"); if (!course_id) { console.log("course_id not found"); return; } console.log(`课程ID: ${course_id}`); // CORS代理前缀(如果需要) const corsProxy = ''; // 例如 'https://cors-anywhere.herokuapp.com/' // 调用API获取课程目录 const apiUrl = `https://classroom.zju.edu.cn/courseapi/v2/course/catalogue?course_id=${course_id}`; console.log(`API URL: ${apiUrl}`); fetch(apiUrl, { method: 'GET', headers: { "Content-Type": "application/json" } // 根据需要添加 credentials: 'include' }) .then(response => { console.log("API响应状态:", response.status); return response.json(); }) .then(data => { console.log("API响应数据:", data); if (data.success && data.result && data.result.data) { const items = data.result.data; console.log(`获取到的课程目录项数量: ${items.length}`); // 处理每个视频项 const videos = []; for (let i = 0; i < items.length; i++) { const item = items[i]; let title = item.title; let videoUrl = null; let available = true; //if (item.pic) { try { const contentData = JSON.parse(item.content); console.log(`解析第${i + 1}项的content字段成功`); if (contentData.playback && contentData.playback.url) { videoUrl = contentData.playback.url; console.log(`第${i + 1}项视频URL: ${videoUrl}`); } else if (contentData.url) { // 处理直接在"url"字段中的情况 videoUrl = contentData.url; console.log(`第${i + 1}项视频URL: ${videoUrl}`); } else { available = false; console.log(`第${i + 1}项没有可用的视频URL`); } } catch (e) { console.error(`解析第${i + 1}项的content字段失败:`, e); available = false; } //} else { // available = false; // console.log(`第${i + 1}项的pic字段为空,标记为暂无回放`); //} // 如果pic为空或videoUrl未获取到,则标记为暂无回放 if (!available || !videoUrl) { title += "(暂无回放)"; } videos.push({title: title, videoUrl: videoUrl, available: available, originalIndex: i}); } console.log(`可下载的视频数量: ${videos.filter(v => v.available).length}`); // 添加批量下载界面 addDownloadUI(videos); } else { console.log("从API获取数据失败,数据结构不符合预期"); } }) .catch(error => { console.log("Error fetching API:", error); }); function addDownloadUI(videos) { console.log("正在添加批量下载的用户界面"); // 创建容器 const container = document.createElement("div"); container.style.position = "fixed"; container.style.bottom = "10px"; container.style.right = "10px"; container.style.backgroundColor = "white"; container.style.padding = "15px"; container.style.border = "1px solid #ccc"; container.style.zIndex = 9999; container.style.maxHeight = "80%"; container.style.overflowY = "auto"; container.style.fontSize = "14px"; container.style.lineHeight = "1.5"; container.style.boxShadow = "0 0 10px rgba(0,0,0,0.1)"; container.style.borderRadius = "5px"; container.style.width = "320px"; container.style.transition = "all 0.3s ease"; container.style.backgroundColor = "rgba(255, 255, 255, 0.95)"; container.style.display = "flex"; container.style.flexDirection = "column"; // 创建标题和最小化按钮 const header = document.createElement("div"); header.style.display = "flex"; header.style.justifyContent = "space-between"; header.style.alignItems = "center"; header.style.cursor = "default"; // 移除拖动功能 header.style.marginBottom = "10px"; const title = document.createElement("div"); title.style.fontWeight = "bold"; title.innerText = "批量下载视频"; header.appendChild(title); const minimizeButton = document.createElement("button"); minimizeButton.innerText = "—"; minimizeButton.style.border = "none"; minimizeButton.style.background = "none"; minimizeButton.style.cursor = "pointer"; minimizeButton.style.fontSize = "16px"; minimizeButton.style.lineHeight = "1"; minimizeButton.style.padding = "0"; minimizeButton.style.marginLeft = "10px"; minimizeButton.title = "最小化"; minimizeButton.addEventListener("click", () => { if (container.classList.contains("minimized")) { // 恢复窗口 container.classList.remove("minimized"); // 显示所有相关元素 selectAllContainer.style.display = "flex"; downloadButton.style.display = "block"; overallProgressContainer.style.display = "block"; status.style.display = "block"; list.style.display = "block"; minimizeButton.innerText = "—"; minimizeButton.title = "最小化"; console.log("恢复下载界面"); } else { // 最小化窗口 container.classList.add("minimized"); // 隐藏所有相关元素 selectAllContainer.style.display = "none"; downloadButton.style.display = "none"; overallProgressContainer.style.display = "none"; status.style.display = "none"; list.style.display = "none"; minimizeButton.innerText = "+"; minimizeButton.title = "恢复"; console.log("最小化下载界面"); } }); header.appendChild(minimizeButton); container.appendChild(header); // 创建全选复选框容器 const selectAllContainer = document.createElement("div"); selectAllContainer.style.display = "flex"; selectAllContainer.style.alignItems = "center"; selectAllContainer.style.marginBottom = "10px"; const selectAllCheckbox = document.createElement("input"); selectAllCheckbox.type = "checkbox"; selectAllCheckbox.id = "selectAllCheckbox"; const selectAllLabel = document.createElement("label"); selectAllLabel.htmlFor = "selectAllCheckbox"; selectAllLabel.innerText = " 全选"; selectAllContainer.appendChild(selectAllCheckbox); selectAllContainer.appendChild(selectAllLabel); container.appendChild(selectAllContainer); // 创建下载按钮 const downloadButton = document.createElement("button"); downloadButton.innerText = "下载选中视频"; downloadButton.style.display = "block"; downloadButton.style.marginTop = "10px"; downloadButton.style.width = "100%"; downloadButton.style.padding = "8px"; downloadButton.style.backgroundColor = "#4CAF50"; downloadButton.style.color = "white"; downloadButton.style.border = "none"; downloadButton.style.borderRadius = "3px"; downloadButton.style.cursor = "pointer"; downloadButton.style.fontSize = "14px"; downloadButton.addEventListener("mouseover", () => { if (!downloadButton.disabled) { downloadButton.style.backgroundColor = "#45a049"; } }); downloadButton.addEventListener("mouseout", () => { if (!downloadButton.disabled) { downloadButton.style.backgroundColor = "#4CAF50"; } }); container.appendChild(downloadButton); // 创建状态显示区域 const status = document.createElement("div"); status.style.marginTop = "10px"; status.style.fontSize = "12px"; status.style.color = "#555"; container.appendChild(status); // 创建整体进度条 const overallProgressContainer = document.createElement("div"); overallProgressContainer.style.width = "100%"; overallProgressContainer.style.backgroundColor = "#f3f3f3"; overallProgressContainer.style.borderRadius = "5px"; overallProgressContainer.style.marginTop = "10px"; overallProgressContainer.style.display = "none"; // 初始隐藏 container.appendChild(overallProgressContainer); const overallProgressBar = document.createElement("div"); overallProgressBar.style.width = "0%"; overallProgressBar.style.height = "20px"; overallProgressBar.style.backgroundColor = "#4CAF50"; overallProgressBar.style.borderRadius = "5px"; overallProgressContainer.appendChild(overallProgressBar); // 创建列表 const list = document.createElement("ul"); list.style.listStyle = "none"; list.style.padding = "0"; list.style.marginTop = "10px"; container.appendChild(list); // 添加视频项 videos.forEach((video, index) => { const listItem = document.createElement("li"); listItem.style.marginTop = "10px"; listItem.style.display = "block"; listItem.style.borderBottom = "1px solid #ddd"; listItem.style.paddingBottom = "10px"; const headerDiv = document.createElement("div"); headerDiv.style.display = "flex"; headerDiv.style.alignItems = "center"; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.value = video.originalIndex; // 保存视频在原始数组中的索引 checkbox.className = "videoCheckbox"; checkbox.style.marginRight = "10px"; if (!video.available) { checkbox.disabled = true; } const label = document.createElement("label"); label.style.flex = "1"; label.style.cursor = "pointer"; label.innerText = video.title; headerDiv.appendChild(checkbox); headerDiv.appendChild(label); listItem.appendChild(headerDiv); // 创建进度条 const progressContainer = document.createElement("div"); progressContainer.style.width = "100%"; progressContainer.style.backgroundColor = "#f3f3f3"; progressContainer.style.borderRadius = "5px"; progressContainer.style.marginTop = "5px"; progressContainer.style.display = "none"; // 初始隐藏 const progressBar = document.createElement("div"); progressBar.style.width = "0%"; progressBar.style.height = "10px"; progressBar.style.backgroundColor = "#4CAF50"; progressBar.style.borderRadius = "5px"; progressContainer.appendChild(progressBar); // 创建速度和时间信息 const infoDiv = document.createElement("div"); infoDiv.style.marginTop = "5px"; infoDiv.style.fontSize = "12px"; infoDiv.style.color = "#555"; infoDiv.style.display = "none"; // 初始隐藏 infoDiv.innerText = "速度: 0 KB/s | 预计剩余时间: 0 s"; listItem.appendChild(progressContainer); listItem.appendChild(infoDiv); list.appendChild(listItem); }); document.body.appendChild(container); console.log("批量下载界面已添加到页面"); // 事件监听 selectAllCheckbox.addEventListener("change", function() { const checkboxes = container.querySelectorAll(".videoCheckbox"); checkboxes.forEach(cb => { if (!cb.disabled) { cb.checked = this.checked; } }); console.log(`全选复选框状态改变为: ${this.checked}`); }); downloadButton.addEventListener("click", function() { console.log("下载按钮被点击"); status.innerText = "开始下载..."; const checkboxes = container.querySelectorAll(".videoCheckbox"); const selectedVideos = []; checkboxes.forEach(cb => { if (cb.checked) { const videoIndex = parseInt(cb.value); selectedVideos.push({ video: videos[videoIndex], index: videoIndex }); } }); if (selectedVideos.length === 0) { alert("请选择要下载的视频"); status.innerText = ""; console.log("未选择任何视频进行下载"); return; } console.log(`选中的视频数量: ${selectedVideos.length}`); selectedVideos.forEach((videoObj, idx) => { console.log(`准备下载 (${idx + 1}/${selectedVideos.length}): ${videoObj.video.title} - ${videoObj.video.videoUrl}`); }); // 禁用下载按钮并更改文本 downloadButton.disabled = true; downloadButton.innerText = "下载中..."; downloadButton.style.backgroundColor = "#888"; downloadButton.style.cursor = "not-allowed"; console.log("下载按钮已禁用,文本已更改为 '下载中...'"); // 显示整体进度条 overallProgressContainer.style.display = "block"; overallProgressBar.style.width = "0%"; console.log("显示整体进度条"); // 开始下载 let currentDownload = 0; let completed = 0; function downloadNext() { if (currentDownload < selectedVideos.length) { const videoObj = selectedVideos[currentDownload]; const video = videoObj.video; const videoIndex = videoObj.index; const listItem = list.children[videoIndex]; const progressContainer = listItem.querySelector("div:nth-child(2)"); const progressBar = progressContainer.querySelector("div"); const infoDiv = listItem.querySelector("div:nth-child(3)"); status.innerText = `正在下载 (${currentDownload + 1}/${selectedVideos.length}): ${video.title}`; console.log(`开始下载 (${currentDownload + 1}/${selectedVideos.length}): ${video.title} - ${video.videoUrl}`); progressContainer.style.display = "block"; infoDiv.style.display = "block"; // 创建 XHR 请求 const xhr = new XMLHttpRequest(); const downloadUrl = corsProxy + video.videoUrl; console.log(`下载链接: ${downloadUrl}`); xhr.open("GET", downloadUrl, true); xhr.responseType = "blob"; let startTime = Date.now(); // 监听进度 xhr.onprogress = function(event) { if (event.lengthComputable) { const percentComplete = ((event.loaded / event.total) * 100).toFixed(2); progressBar.style.width = percentComplete + "%"; // 计算下载速度和剩余时间 const currentTime = Date.now(); const elapsedTime = (currentTime - startTime) / 1000; // 秒 const bytesLoaded = event.loaded; const speed = elapsedTime > 0 ? (bytesLoaded / elapsedTime / 1024).toFixed(2) : '0'; // KB/s const remainingBytes = event.total - event.loaded; const estimatedTime = speed > 0 ? (remainingBytes / 1024 / speed).toFixed(2) : '0'; infoDiv.innerText = `速度: ${speed} KB/s | 预计剩余时间: ${estimatedTime} s`; //console.log(`下载进度 (${video.title}): ${percentComplete}% | 速度: ${speed} KB/s | 预计剩余时间: ${estimatedTime} s`); } }; // 监听完成 xhr.onload = function() { if (xhr.status === 200 || xhr.status === 206) { const blob = xhr.response; const url = window.URL.createObjectURL(blob); // 创建并点击隐藏的 <a> 标签 const a = document.createElement('a'); a.href = url; a.download = sanitizeFilename(video.title) + ".mp4"; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); // 释放 Blob URL window.URL.revokeObjectURL(url); console.log(`下载已启动 (${video.title}): ${a.download}`); } else { console.error(`下载失败 (${video.title}): 状态码 ${xhr.status}`); alert(`下载失败: ${video.title} (状态码 ${xhr.status})`); } completed++; console.log(`完成下载: ${video.title} (${completed}/${selectedVideos.length})`); // 更新整体进度条 const progress = ((completed / selectedVideos.length) * 100).toFixed(2); overallProgressBar.style.width = progress + "%"; console.log(`整体进度: ${progress}%`); currentDownload++; // 触发下一个下载 setTimeout(downloadNext, 1000); // 1秒延迟 }; // 监听错误 xhr.onerror = function() { console.error(`下载失败 (${video.title}): 网络错误`); alert(`下载失败: ${video.title} (网络错误)`); completed++; console.log(`下载错误处理: ${video.title} (${completed}/${selectedVideos.length})`); // 更新整体进度条 const progress = ((completed / selectedVideos.length) * 100).toFixed(2); overallProgressBar.style.width = progress + "%"; console.log(`整体进度: ${progress}%`); currentDownload++; // 触发下一个下载 setTimeout(downloadNext, 1000); // 1秒延迟 }; console.log(`发送XHR请求 (${video.title})`); xhr.send(); } else { status.innerText = "所有下载已完成!请查看浏览器的下载管理器。"; console.log("所有下载已完成"); // 隐藏整体进度条 setTimeout(() => { overallProgressContainer.style.display = "none"; console.log("隐藏整体进度条"); }, 5000); // 恢复下载按钮 downloadButton.disabled = false; downloadButton.innerText = "下载选中视频"; downloadButton.style.backgroundColor = "#4CAF50"; downloadButton.style.cursor = "pointer"; console.log("恢复下载按钮状态"); } } downloadNext(); }); } /** * 去除文件名中的非法字符 * @param {string} name - 原始文件名 * @returns {string} - 安全的文件名 */ function sanitizeFilename(name) { return name.replace(/[\/\\:*?"<>|]/g, '_'); } })();