您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
主页作业显示所属课程,使用Office 365预览课件,增加通知显示数量,去除悬浮窗,解除复制限制,课件自动下载,批量下载,资源页展示全部下载按钮
当前为
// ==UserScript== // @name ucloud-Evolved // @namespace http://tampermonkey.net/ // @version 0.24 // @description 主页作业显示所属课程,使用Office 365预览课件,增加通知显示数量,去除悬浮窗,解除复制限制,课件自动下载,批量下载,资源页展示全部下载按钮 // @author Quarix // @match https://ucloud.bupt.edu.cn/* // @match https://ucloud.bupt.edu.cn/uclass/course.html* // @match https://ucloud.bupt.edu.cn/uclass/* // @match https://ucloud.bupt.edu.cn/office/* // @icon https://ucloud.bupt.edu.cn/favicon.ico // @require https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/nprogress/0.2.0/nprogress.min.js#sha256-XWzSUJ+FIQ38dqC06/48sNRwU1Qh3/afjmJ080SneA8= // @resource NPROGRESS_CSS https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/nprogress/0.2.0/nprogress.min.css#sha256-pMhcV6/TBDtqH9E9PWKgS+P32PVguLG8IipkPyqMtfY= // @connect github.com // @grant GM_getResourceText // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_openInTab // @run-at document-start // @license MIT // ==/UserScript== (function () { // 拦截 Office 预览页面 if ( location.href.startsWith("https://ucloud.bupt.edu.cn/office/") && GM_getValue("autoSwitchOffice", false) ) { const url = new URLSearchParams(location.search).get("furl"); const filename = new URLSearchParams(location.search).get("fullfilename") || url; const viewURL = new URL(url); if (new URLSearchParams(location.search).get("oauthKey")) { const viewURLsearch = new URLSearchParams(viewURL.search); viewURLsearch.set( "oauthKey", new URLSearchParams(location.search).get("oauthKey") ); viewURL.search = viewURLsearch.toString(); } if ( filename.endsWith(".xls") || filename.endsWith(".xlsx") || filename.endsWith(".doc") || filename.endsWith(".docx") || filename.endsWith(".ppt") || filename.endsWith(".pptx") ) { if (window.stop) window.stop(); location.href = "https://view.officeapps.live.com/op/view.aspx?src=" + encodeURIComponent(viewURL.toString()); return; } else if (filename.endsWith(".pdf")) { if (window.stop) window.stop(); // 使用浏览器内置预览器,转blob避免出现下载动作 fetch(viewURL.toString()) .then((response) => response.blob()) .then((blob) => { const blobUrl = URL.createObjectURL(blob); location.href = blobUrl; }) .catch((err) => console.error("PDF加载失败:", err)); return; } return; } })(); (function interceptXHR() { const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function ( method, url, async, user, password ) { // hook XMR if (GM_getValue("showMoreNotification", true)) { if ( typeof url === "string" && url.includes("/ykt-basics/api/inform/news/list") ) { url = url.replace(/size=\d+/, "size=1000"); } else if ( typeof url === "string" && url.includes("/ykt-site/site/list/student/history") ) { url = url.replace(/size=\d+/, "size=15"); } } return originalOpen.call(this, method, url, async, user, password); }; })(); (function () { // 等待页面DOM加载完成 document.addEventListener("DOMContentLoaded", initializeExtension); // 用户设置 const settings = { autoDownload: GM_getValue("autoDownload", false), autoSwitchOffice: GM_getValue("autoSwitchOffice", false), autoClosePopup: GM_getValue("autoClosePopup", true), hideTimer: GM_getValue("hideTimer", true), unlockCopy: GM_getValue("unlockCopy", true), showMoreNotification: GM_getValue("showMoreNotification", true), useBiggerButton: GM_getValue("useBiggerButton", true), autoUpdate: GM_getValue("autoUpdate", false), showConfigButton: GM_getValue("showConfigButton", true), }; // 辅助变量 let jsp; let sumBytes = 0, loadedBytes = 0, downloading = false; let setClicked = false; let gpage = -1; let glist = null; let onlinePreview = null; // 初始化扩展功能 function initializeExtension() { // 注册菜单命令 registerMenuCommands(); const nprogressCSS = GM_getResourceText("NPROGRESS_CSS"); GM_addStyle(nprogressCSS); if (settings.showConfigButton) { loadui(); } addFunctionalCSS(); main(); if (settings.autoUpdate) { checkForUpdates(); } // 监听URL哈希变化 let hash = location.hash; setInterval(() => { if (location.hash != hash) { hash = location.hash; main(); } }, 50); } // 注册菜单命令 function registerMenuCommands() { GM_registerMenuCommand( (settings.showConfigButton ? "✅" : "❌") + "显示配置按钮:" + (settings.showConfigButton ? "已启用" : "已禁用"), () => { settings.showConfigButton = !settings.showConfigButton; GM_setValue("showConfigButton", settings.showConfigButton); location.reload(); } ); GM_registerMenuCommand( (settings.autoDownload ? "✅" : "❌") + "预览课件时自动下载:" + (settings.autoDownload ? "已启用" : "已禁用"), () => { settings.autoDownload = !settings.autoDownload; GM_setValue("autoDownload", settings.autoDownload); location.reload(); } ); GM_registerMenuCommand( (settings.autoSwitchOffice ? "✅" : "❌") + "使用 Office365 预览课件:" + (settings.autoSwitchOffice ? "已启用" : "已禁用"), () => { settings.autoSwitchOffice = !settings.autoSwitchOffice; GM_setValue("autoSwitchOffice", settings.autoSwitchOffice); location.reload(); } ); } /** * 通用标签页打开函数 * @param {string} url - 要打开的URL * @param {Object} options - 选项参数 * @param {boolean} [options.active=true] - 新标签页是否获得焦点 * @param {boolean} [options.insert=true] - 是否在当前标签页旁边插入新标签页 * @param {boolean} [options.setParent=true] - 新标签页是否将当前标签页设为父页面 * @param {string} [options.windowName="_blank"] - window.open的窗口名称 * @param {string} [options.windowFeatures=""] - window.open的窗口特性 * @returns {Object|Window|null} 打开的标签页对象 */ function openTab(url, options = {}) { const defaultOptions = { active: true, insert: true, setParent: true, windowName: "_blank", windowFeatures: "", }; const finalOptions = { ...defaultOptions, ...options }; if (typeof GM_openInTab === "function") { try { return GM_openInTab(url, { active: finalOptions.active, insert: finalOptions.insert, setParent: finalOptions.setParent, }); } catch (error) { return window.open( url, finalOptions.windowName, finalOptions.windowFeatures ); } } } function showUpdateNotification(newVersion) { const notification = document.createElement("div"); notification.style.cssText = ` position: fixed; bottom: 80px; right: 20px; background: #4a6cf7; color: white; padding: 15px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; max-width: 300px; `; notification.innerHTML = ` <div style="font-weight: bold; margin-bottom: 5px;">发现新版本 v${newVersion}</div> <div style="font-size: 14px; margin-bottom: 10px;">当前版本 v${GM_info.script.version}</div> <button id="updateNow" style="background: white; color: #4a6cf7; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; margin-right: 10px;">立即更新</button> <button id="updateLater" style="background: transparent; color: white; border: 1px solid white; padding: 5px 10px; border-radius: 4px; cursor: pointer;">稍后提醒</button> `; document.body.appendChild(notification); document.getElementById("updateNow").addEventListener("click", function () { openTab(GM_info.script.downloadURL, { active: true }); document.body.removeChild(notification); }); document .getElementById("updateLater") .addEventListener("click", function () { document.body.removeChild(notification); }); } function checkForUpdates() { const lastCheckTime = GM_getValue("lastUpdateCheck", 0); const now = Date.now(); const ONE_DAY = 24 * 60 * 60 * 1000; // 一天的毫秒数 if (now - lastCheckTime > ONE_DAY) { GM_setValue("lastUpdateCheck", now); GM_xmlhttpRequest({ method: "GET", url: GM_info.script.updateURL, onload: function (response) { const versionMatch = response.responseText.match( /@version\s+(\d+\.\d+)/ ); if (versionMatch && versionMatch[1]) { const latestVersion = versionMatch[1]; const currentVersion = GM_info.script.version; if (latestVersion > currentVersion) { showUpdateNotification(latestVersion); } } }, }); } } function loadui() { GM_addStyle(` #yzHelper-settings { position: fixed; bottom: 20px; right: 20px; background: #ffffff; box-shadow: 0 5px 25px rgba(0, 0, 0, 0.15); border-radius: 12px; padding: 20px; z-index: 9999; display: none; width: 300px; font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; transition: all 0.3s ease; opacity: 0; transform: translateY(10px); color: #333; } #yzHelper-settings.visible { opacity: 1; transform: translateY(0); } #yzHelper-settings h3 { margin-top: 0; margin-bottom: 15px; font-size: 18px; font-weight: 600; color: #2c3e50; padding-bottom: 10px; border-bottom: 1px solid #eee; } #yzHelper-settings .setting-item { margin-bottom: 16px; display: flex; align-items: center; } #yzHelper-settings .setting-item:last-of-type { margin-bottom: 20px; } #yzHelper-settings .switch { position: relative; display: inline-block; width: 44px; height: 24px; margin-right: 10px; } #yzHelper-settings .switch input { opacity: 0; width: 0; height: 0; } #yzHelper-settings .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .3s; border-radius: 24px; } #yzHelper-settings .slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; } #yzHelper-settings input:checked + .slider { background-color: #ffbe00; } #yzHelper-settings input:focus + .slider { box-shadow: 0 0 1px #ffbe00; } #yzHelper-settings input:checked + .slider:before { transform: translateX(20px); } #yzHelper-settings .setting-label { font-size: 14px; } #yzHelper-settings .buttons { display: flex; justify-content: flex-end; gap: 10px; } #yzHelper-settings button { background: #ffbe00; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: 500; color: #fff; transition: all 0.2s ease; outline: none; font-size: 14px; } #yzHelper-settings button:hover { background: #e9ad00; } #yzHelper-settings button.cancel { background: #f1f1f1; color: #666; } #yzHelper-settings button.cancel:hover { background: #e5e5e5; } #yzHelper-settings-toggle { position: fixed; bottom: 20px; right: 20px; background: #ffbe00; color: #fff; width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 24px; cursor: pointer; z-index: 9998; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); transition: all 0.3s ease; } #yzHelper-settings-toggle:hover { transform: rotate(30deg); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } #yzHelper-version { position: absolute; bottom: 15px; left: 20px; font-size: 12px; color: #999; } `); // 设置面板 const settingsToggle = document.createElement("div"); settingsToggle.id = "yzHelper-settings-toggle"; settingsToggle.innerHTML = "⚙️"; settingsToggle.title = "云邮助手设置"; document.body.appendChild(settingsToggle); const settingsPanel = document.createElement("div"); settingsPanel.id = "yzHelper-settings"; settingsPanel.innerHTML = ` <h3>云邮教学空间助手设置</h3> <div class="setting-item"> <label class="switch"> <input type="checkbox" id="autoDownload" ${ settings.autoDownload ? "checked" : "" }> <span class="slider"></span> </label> <span class="setting-label">预览课件时自动下载</span> </div> <div class="setting-item"> <label class="switch"> <input type="checkbox" id="autoSwitchOffice" ${ settings.autoSwitchOffice ? "checked" : "" }> <span class="slider"></span> </label> <span class="setting-label">使用 Office365 预览课件</span> </div> <div class="setting-item"> <label class="switch"> <input type="checkbox" id="autoClosePopup" ${ settings.autoClosePopup ? "checked" : "" }> <span class="slider"></span> </label> <span class="setting-label">自动关闭预览弹窗</span> </div> <div class="setting-item"> <label class="switch"> <input type="checkbox" id="hideTimer" ${ settings.hideTimer ? "checked" : "" }> <span class="slider"></span> </label> <span class="setting-label">隐藏预览界面倒计时</span> </div> <div class="setting-item"> <label class="switch"> <input type="checkbox" id="unlockCopy" ${ settings.unlockCopy ? "checked" : "" }> <span class="slider"></span> </label> <span class="setting-label">解除复制限制</span> </div> <div class="setting-item"> <label class="switch"> <input type="checkbox" id="showMoreNotification" ${ settings.showMoreNotification ? "checked" : "" }> <span class="slider"></span> </label> <span class="setting-label">显示更多的通知</span> </div> <div class="setting-item"> <label class="switch"> <input type="checkbox" id="useBiggerButton" ${ settings.useBiggerButton ? "checked" : "" }> <span class="slider"></span> </label> <span class="setting-label">加大翻页按钮尺寸</span> </div> <div class="setting-item"> <label class="switch"> <input type="checkbox" id="autoUpdate" ${ settings.autoUpdate ? "checked" : "" }> <span class="slider"></span> </label> <span class="setting-label">内置更新检查</span> </div> <div class="buttons"> <button id="cancelSettings" class="cancel">取消</button> <button id="saveSettings">保存设置</button> </div> <div id="yzHelper-version">当前版本:${GM_info.script.version}</div> `; document.body.appendChild(settingsPanel); // 面板交互 settingsToggle.addEventListener("click", () => { const isVisible = settingsPanel.classList.contains("visible"); if (isVisible) { settingsPanel.classList.remove("visible"); setTimeout(() => { settingsPanel.style.display = "none"; }, 300); } else { settingsPanel.style.display = "block"; void settingsPanel.offsetWidth; settingsPanel.classList.add("visible"); } }); document.getElementById("cancelSettings").addEventListener("click", () => { settingsPanel.classList.remove("visible"); setTimeout(() => { settingsPanel.style.display = "none"; }, 300); }); document.getElementById("saveSettings").addEventListener("click", () => { settings.autoDownload = document.getElementById("autoDownload").checked; settings.autoSwitchOffice = document.getElementById("autoSwitchOffice").checked; settings.autoClosePopup = document.getElementById("autoClosePopup").checked; settings.hideTimer = document.getElementById("hideTimer").checked; settings.unlockCopy = document.getElementById("unlockCopy").checked; settings.showMoreNotification = document.getElementById( "showMoreNotification" ).checked; settings.useBiggerButton = document.getElementById("useBiggerButton").checked; settings.autoUpdate = document.getElementById("autoUpdate").checked; GM_setValue("autoDownload", settings.autoDownload); GM_setValue("autoSwitchOffice", settings.autoSwitchOffice); GM_setValue("autoClosePopup", settings.autoClosePopup); GM_setValue("hideTimer", settings.hideTimer); GM_setValue("unlockCopy", settings.unlockCopy); GM_setValue("showMoreNotification", settings.showMoreNotification); GM_setValue("useBiggerButton", settings.useBiggerButton); GM_setValue("autoUpdate", settings.autoUpdate); settingsPanel.classList.remove("visible"); setTimeout(() => { settingsPanel.style.display = "none"; showNotification("设置已保存", "刷新页面后生效"); }, 300); }); // 通知函数 function showNotification(title, message) { const notification = document.createElement("div"); notification.style.cssText = ` position: fixed; bottom: 80px; right: 20px; background: #4CAF50; color: white; padding: 15px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; max-width: 300px; opacity: 0; transform: translateY(-10px); transition: all 0.3s ease; `; notification.innerHTML = ` <div style="font-weight: bold; margin-bottom: 5px;">${title}</div> <div style="font-size: 14px;">${message}</div> `; document.body.appendChild(notification); void notification.offsetWidth; notification.style.opacity = "1"; notification.style.transform = "translateY(0)"; setTimeout(() => { notification.style.opacity = "0"; notification.style.transform = "translateY(-10px)"; setTimeout(() => { document.body.removeChild(notification); }, 300); }, 3000); } } // 获取Token function getToken() { const cookieMap = new Map(); document.cookie.split("; ").forEach((cookie) => { const [key, value] = cookie.split("="); cookieMap.set(key, value); }); const token = cookieMap.get("iClass-token"); const userid = cookieMap.get("iClass-uuid"); return [userid, token]; } // 文件下载相关函数 async function downloadFile(url, filename) { console.log("Call download"); downloading = true; await jsp; NProgress.configure({ trickle: false, speed: 0 }); try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const contentLength = response.headers.get("content-length"); if (!contentLength) { throw new Error("Content-Length response header unavailable"); } const total = parseInt(contentLength, 10); sumBytes += total; const reader = response.body.getReader(); const chunks = []; while (true) { const { done, value } = await reader.read(); if (done) break; if (!downloading) { NProgress.done(); return; } chunks.push(value); loadedBytes += value.length; NProgress.set(loadedBytes / sumBytes); } NProgress.done(); sumBytes -= total; loadedBytes -= total; const blob = new Blob(chunks); const downloadUrl = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = downloadUrl; a.download = filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(downloadUrl); } catch (error) { console.error("Download failed:", error); } } // 任务搜索函数 async function searchTask(siteId, keyword, token) { const res = await fetch( "https://apiucloud.bupt.edu.cn/ykt-site/work/student/list", { headers: { authorization: "Basic cG9ydGFsOnBvcnRhbF9zZWNyZXQ=", "blade-auth": token, "content-type": "application/json;charset=UTF-8", }, body: JSON.stringify({ siteId, keyword, current: 1, size: 5, }), method: "POST", } ); const json = await res.json(); return json; } // 课程搜索函数 async function searchCourse(userId, id, keyword, token) { const res = await fetch( "https://apiucloud.bupt.edu.cn/ykt-site/site/list/student/current?size=999999¤t=1&userId=" + userId + "&siteRoleCode=2", { headers: { authorization: "Basic cG9ydGFsOnBvcnRhbF9zZWNyZXQ=", "blade-auth": token, }, body: null, method: "GET", } ); const json = await res.json(); const list = json.data.records.map((x) => ({ id: x.id, name: x.siteName, teachers: x.teachers.map((y) => y.name).join(", "), })); async function searchWithLimit(list, id, keyword, token, limit = 5) { for (let i = 0; i < list.length; i += limit) { const batch = list.slice(i, i + limit); const jobs = batch.map((x) => searchTask(x.id, keyword, token)); const ress = await Promise.all(jobs); for (let j = 0; j < ress.length; j++) { const res = ress[j]; if (res.data && res.data.records && res.data.records.length > 0) { for (const item of res.data.records) { if (item.id == id) { return batch[j]; } } } } } return null; } return await searchWithLimit(list, id, keyword, token); } // 获取任务列表 async function getTasks(siteId, token) { const res = await fetch( "https://apiucloud.bupt.edu.cn/ykt-site/work/student/list", { headers: { authorization: "Basic cG9ydGFsOnBvcnRhbF9zZWNyZXQ=", "blade-auth": token, "content-type": "application/json;charset=UTF-8", }, body: JSON.stringify({ siteId, current: 1, size: 9999, }), method: "POST", } ); const json = await res.json(); return json; } // 搜索课程 async function searchCourses(nids) { const result = {}; let ids = []; for (let id of nids) { const r = get(id); if (r) result[id] = r; else ids.push(id); } if (ids.length == 0) return result; const [userid, token] = getToken(); const res = await fetch( "https://apiucloud.bupt.edu.cn/ykt-site/site/list/student/current?size=999999¤t=1&userId=" + userid + "&siteRoleCode=2", { headers: { authorization: "Basic cG9ydGFsOnBvcnRhbF9zZWNyZXQ=", "blade-auth": token, }, body: null, method: "GET", } ); const json = await res.json(); const list = json.data.records.map((x) => ({ id: x.id, name: x.siteName, teachers: x.teachers.map((y) => y.name).join(", "), })); const hashMap = new Map(); let count = ids.length; for (let i = 0; i < ids.length; i++) { hashMap.set(ids[i], i); } async function searchWithLimit(list, limit = 5) { for (let i = 0; i < list.length; i += limit) { const batch = list.slice(i, i + limit); const jobs = batch.map((x) => getTasks(x.id, token)); const ress = await Promise.all(jobs); for (let j = 0; j < ress.length; j++) { const res = ress[j]; if (res.data && res.data.records && res.data.records.length > 0) { for (const item of res.data.records) { if (hashMap.has(item.id)) { result[item.id] = batch[j]; set(item.id, batch[j]); if (--count == 0) { return result; } } } } } } return result; } return await searchWithLimit(list); } // 获取未完成列表 async function getUndoneList() { const [userid, token] = getToken(); const res = await fetch( "https://apiucloud.bupt.edu.cn/ykt-site/site/student/undone?userId=" + userid, { headers: { authorization: "Basic cG9ydGFsOnBvcnRhbF9zZWNyZXQ=", "blade-auth": token, }, method: "GET", } ); const json = await res.json(); return json; } // 获取详情 async function getDetail(id) { const [userid, token] = getToken(); const res = await fetch( "https://apiucloud.bupt.edu.cn/ykt-site/work/detail?assignmentId=" + id, { headers: { authorization: "Basic cG9ydGFsOnBvcnRhbF9zZWNyZXQ=", "blade-auth": token, }, body: null, method: "GET", } ); const json = await res.json(); return json; } // 获取站点资源 async function getSiteResource(id) { const [userid, token] = getToken(); const res = await fetch( "https://apiucloud.bupt.edu.cn/ykt-site/site-resource/tree/student?siteId=" + id + "&userId=" + userid, { headers: { authorization: "Basic cG9ydGFsOnBvcnRhbF9zZWNyZXQ=", "blade-auth": token, }, body: null, method: "POST", } ); const json = await res.json(); const result = []; function foreach(data) { if (!data || !Array.isArray(data)) return; data.forEach((x) => { if (x.attachmentVOs && Array.isArray(x.attachmentVOs)) { x.attachmentVOs.forEach((y) => { if (y.type !== 2 && y.resource) result.push(y.resource); }); } if (x.children) foreach(x.children); }); } foreach(json.data); return result; } // 更新作业显示 async function updateAssignmentDisplay(list, page) { if (!list || list.length === 0) return; // 获取当前页的作业 const tlist = list.slice((page - 1) * 6, page * 6); if (tlist.length === 0) return; // 获取课程信息 const ids = tlist.map((x) => x.activityId); const infos = await searchCourses(ids); // 确保所有信息都已获取到 if (Object.keys(infos).length === 0) return; // 准备显示文本 const texts = tlist.map((x) => { const info = infos[x.activityId]; return info ? `${info.name}(${info.teachers})` : "加载中..."; }); // 等待作业元素显示 const timeout = 5000; // 5秒超时 const startTime = Date.now(); let nodes; while (Date.now() - startTime < timeout) { nodes = $x( '//*[@id="layout-container"]/div[2]/div[2]/div/div[2]/div[1]/div[3]/div[2]/div/div' ); if ( nodes.length > 0 && nodes.some((node) => node.children[0] && node.children[0].innerText) ) { break; } await sleep(100); } // 更新课程信息显示 for (let i = 0; i < Math.min(nodes.length, texts.length); i++) { if (nodes[i] && nodes[i].children[1]) { if (nodes[i].children[1].children.length === 0) { const p = document.createElement("div"); const t = document.createTextNode(texts[i]); p.appendChild(t); p.style.color = "#0066cc"; nodes[i].children[1].insertAdjacentElement("afterbegin", p); } else { nodes[i].children[1].children[0].innerHTML = texts[i]; nodes[i].children[1].children[0].style.color = "#0066cc"; } } } } // XPath选择器 function $x(xpath, context = document) { const iterator = document.evaluate( xpath, context, null, XPathResult.ANY_TYPE, null ); const results = []; let item; while ((item = iterator.iterateNext())) { results.push(item); } return results; } // 本地存储 function set(k, v) { const h = JSON.parse(localStorage.getItem("zzxw") || "{}"); h[k] = v; localStorage.setItem("zzxw", JSON.stringify(h)); } function get(k) { const h = JSON.parse(localStorage.getItem("zzxw") || "{}"); return h[k]; } // 插入课程信息 function insert(x) { if (!x) return; if ( $x( "/html/body/div[1]/div/div[2]/div[2]/div/div/div[2]/div/div[2]/div[1]/div/div/div[1]/div/p" ).length > 2 ) return; const d = $x( "/html/body/div[1]/div/div[2]/div[2]/div/div/div[2]/div/div[2]/div[1]/div/div/div[1]/div/p[1]" ); if (!d.length) { setTimeout(() => insert(x), 50); return; } // 检查是否已经插入过 const existingText = Array.from(d[0].parentNode.childNodes).some( (node) => node.textContent && node.textContent.includes(x.name) ); if (!existingText) { const p = document.createElement("p"); const t = document.createTextNode(x.name + "(" + x.teachers + ")"); p.appendChild(t); d[0].after(p); } } // 辅助函数 function sleep(n) { return new Promise((res) => setTimeout(res, n)); } async function wait(func) { let r = func(); if (r instanceof Promise) r = await r; if (r) return r; await sleep(50); return await wait(func); } async function waitChange(func, value) { const r = value; while (1) { let t = func(); if (t instanceof Promise) t = await t; if (t != r) return t; await sleep(50); } } // 预览URL相关 async function getPreviewURL(storageId) { const res = await fetch( "https://apiucloud.bupt.edu.cn/blade-source/resource/preview-url?resourceId=" + storageId ); const json = await res.json(); onlinePreview = json.data.onlinePreview; return json.data.previewUrl; } // 启用文本选择 修改按钮尺寸 function addFunctionalCSS() { GM_addStyle(` .teacher-home-page .home-left-container .in-progress-section .in-progress-body .in-progress-item .activity-box .activity-title { height: auto !important; } `); if (settings.enableTextSelection) { GM_addStyle(` .el-checkbox, .el-checkbox-button__inner, .el-empty__image img, .el-radio, div, span, p, a, h1, h2, h3, h4, h5, h6, li, td, th { -webkit-user-select: auto !important; -moz-user-select: auto !important; -ms-user-select: auto !important; user-select: auto !important; } `); document.addEventListener( "copy", function (e) { e.stopImmediatePropagation(); }, true ); document.addEventListener( "selectstart", function (e) { e.stopImmediatePropagation(); }, true ); } if (settings.useBiggerButton) { GM_addStyle(` .teacher-home-page .home-left-container .my-lesson-section .my-lesson-header .header-control .banner-control-btn, .teacher-home-page .home-left-container .in-progress-section .in-progress-header .header-control .banner-control-btn { width: 60px !important; height: 30px !important; background: #f2f2f2 !important; line-height: auto !important; } .teacher-home-page .home-left-container .my-lesson-section .my-lesson-header .header-control .banner-control-btn span,.teacher-home-page .home-left-container .in-progress-section .in-progress-header .header-control .banner-control-btn span { font-size: 22px !important; } `); } } // 主函数 async function main() { "use strict"; // ticket跳转 if (new URLSearchParams(location.search).get("ticket")?.length) { setTimeout(() => { location.href = "https://ucloud.bupt.edu.cn/uclass/#/student/homePage"; }, 500); return; } // 课件预览页面 if ( location.href.startsWith( "https://ucloud.bupt.edu.cn/uclass/course.html#/resourceLearn" ) ) { if (settings.autoClosePopup) { const dialogBox = document.querySelector("div.el-message-box__wrapper"); if ( dialogBox && window.getComputedStyle(dialogBox).display !== "none" ) { const messageElement = dialogBox.querySelector( ".el-message-box__message p" ); if ( messageElement && (messageElement.textContent.includes("您正在学习其他课件") || messageElement.textContent.includes("您已经在学习此课件了")) ) { const confirmButton = dialogBox.querySelector( ".el-button--primary" ); if (confirmButton) { confirmButton.click(); } else { console.log("未找到确认按钮"); } } } } if (settings.hideTimer) { GM_addStyle(` .preview-container .time { display: none !important; } `); } } // 作业详情页面 if ( location.href.startsWith( "https://ucloud.bupt.edu.cn/uclass/course.html#/student/assignmentDetails_fullpage" ) ) { const q = new URLSearchParams(location.href); const id = q.get("assignmentId"); const r = get(id); const [userid, token] = getToken(); // 显示相关课程信息 if (r) { insert(r); } else { const title = q.get("assignmentTitle"); if (!id || !title) return; try { const courseInfo = await searchCourse(userid, id, title, token); if (courseInfo) { insert(courseInfo); set(id, courseInfo); } } catch (e) { console.error("获取课程信息失败", e); } } // 处理资源预览和下载 try { const detail = (await getDetail(id)).data; if (!detail || !detail.assignmentResource) return; const filenames = detail.assignmentResource.map((x) => x.resourceName); const urls = await Promise.all( detail.assignmentResource.map((x) => { return getPreviewURL(x.resourceId); }) ); await wait( () => $x('//*[@id="assignment-info"]/div[2]/div[2]/div[2]/div').length > 0 ); $x('//*[@id="assignment-info"]/div[2]/div[2]/div[2]/div').forEach( (x, index) => { if ( x.querySelector(".by-icon-eye-grey") || x.querySelector(".by-icon-yundown-grey") ) { x.querySelector(".by-icon-eye-grey").remove(); x.querySelector(".by-icon-yundown-grey").remove(); } // 添加预览按钮 const i = document.createElement("i"); i.title = "预览"; i.classList.add("by-icon-eye-grey"); i.addEventListener("click", () => { const url = urls[index]; const filename = filenames[index]; if (settings.autoDownload) { downloadFile(url, filename); console.log("Autodownload"); } if ( filename.endsWith(".xls") || filename.endsWith(".xlsx") || url.endsWith(".doc") || url.endsWith(".docx") || url.endsWith(".ppt") || url.endsWith(".pptx") ) openTab( "https://view.officeapps.live.com/op/view.aspx?src=" + encodeURIComponent(url), { active: true, insert: true } ); else if (onlinePreview !== null) openTab(onlinePreview + encodeURIComponent(url), { active: true, insert: true, }); }); // 添加下载按钮 const i2 = document.createElement("i"); i2.title = "下载"; i2.classList.add("by-icon-yundown-grey"); i2.addEventListener("click", () => { downloadFile(urls[index], filenames[index]); }); // 插入按钮 if (x.children.length >= 3) { x.children[3]?.remove(); x.children[2]?.insertAdjacentElement("afterend", i); x.children[2]?.remove(); x.children[1]?.insertAdjacentElement("afterend", i2); } else { x.appendChild(i2); x.appendChild(i); } } ); } catch (e) { console.error("处理资源失败", e); } } // 主页面 else if ( location.href.startsWith( "https://ucloud.bupt.edu.cn/uclass/#/student/homePage" ) || location.href.startsWith( "https://ucloud.bupt.edu.cn/uclass/index.html#/student/homePage" ) ) { try { // 未完成任务列表 const list = glist || (await getUndoneList()).data.undoneList; if (!list || !Array.isArray(list)) return; glist = list; const observer = new MutationObserver(async (mutations) => { // 当前页码 const pageElement = document.querySelector( "#layout-container > div.main-content > div.router-container > div > div.teacher-home-page > div.home-left-container.home-inline-block > div.in-progress-section.home-card > div.in-progress-header > div > div:nth-child(2) > div > div.banner-indicator.home-inline-block" ); if (!pageElement) return; // 解析页码 const currentPage = parseInt( pageElement.innerHTML.trim().split("/")[0] ); if (isNaN(currentPage)) return; // 页码变化则更新显示 if (currentPage !== gpage) { gpage = currentPage; await updateAssignmentDisplay(list, currentPage); } }); observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: true, }); // 初始化页码 let page = 1; const pageElement = document.querySelector( "#layout-container > div.main-content > div.router-container > div > div.teacher-home-page > div.home-left-container.home-inline-block > div.in-progress-section.home-card > div.in-progress-header > div > div:nth-child(2) > div > div.banner-indicator.home-inline-block" ); if (pageElement) { page = parseInt(pageElement.innerHTML.trim().split("/")[0]); gpage = page; } // 更新作业显示 await updateAssignmentDisplay(list, page); // 本学期课程点击事件 document.querySelectorAll('div[class="header-label"]').forEach((el) => { if (el.textContent.includes("本学期课程")) { el.style.cursor = "pointer"; el.addEventListener("click", (e) => { e.preventDefault(); window.location.href = "https://ucloud.bupt.edu.cn/uclass/index.html#/student/myCourse"; }); } }); } catch (e) { console.error("主页处理失败", e); } } // 课程主页 else if ( location.href.startsWith( "https://ucloud.bupt.edu.cn/uclass/course.html#/student/courseHomePage" ) ) { try { const site = JSON.parse(localStorage.getItem("site")); if (!site || !site.id) return; const id = site.id; const resources = await getSiteResource(id); // 添加下载按钮到每个资源 const resourceItems = $x( '//div[@class="resource-item"]/div[@class="right"]' ); const previewItems = $x( '//div[@class="resource-item"]/div[@class="left"]' ); if (resourceItems.length > 0) { resourceItems.forEach((x, index) => { if (index >= resources.length) return; if (settings.autoDownload) { previewItems[index].addEventListener( "click", async (e) => { const url = await getPreviewURL(resources[index].id); downloadFile(url, resources[index].name); console.log("Autodownload"); }, false ); } const i = document.createElement("i"); i.title = "下载"; i.classList.add("by-icon-download"); i.classList.add("btn-icon"); i.classList.add("visible"); i.style.cssText = ` display: inline-block !important; visibility: visible !important; cursor: pointer !important; `; // 获取data-v属性 const dataAttr = Array.from(x.attributes).find((attr) => attr.localName.startsWith("data-v") ); if (dataAttr) { i.setAttribute(dataAttr.localName, ""); } i.addEventListener( "click", async (e) => { e.stopPropagation(); const url = await getPreviewURL(resources[index].id); downloadFile(url, resources[index].name); }, false ); if (x.children.length) x.children[0].remove(); x.insertAdjacentElement("afterbegin", i); }); // "下载全部"按钮 if ( !document.getElementById("downloadAllButton") && resources.length > 0 ) { const downloadAllButton = `<div style="display: flex;flex-direction: row;justify-content: end;margin-right: 24px;margin-top: 20px;"> <button type="button" class="el-button submit-btn el-button--primary" id="downloadAllButton"> 下载全部 </button> </div>`; const resourceList = $x( "/html/body/div/div/div[2]/div[2]/div/div/div" ); if (resourceList.length > 0) { const containerElement = document.createElement("div"); containerElement.innerHTML = downloadAllButton; resourceList[0].before(containerElement); document.getElementById("downloadAllButton").onclick = async () => { downloading = !downloading; if (downloading) { document.getElementById("downloadAllButton").innerHTML = "取消下载"; for (let file of resources) { if (!downloading) return; await downloadFile( await getPreviewURL(file.id), file.name ); } // 下载完成后重置按钮 if (downloading) { downloading = false; document.getElementById("downloadAllButton").innerHTML = "下载全部"; } } else { document.getElementById("downloadAllButton").innerHTML = "下载全部"; } }; } } } } catch (e) { console.error("课程主页处理失败", e); } } } })();