您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
course helper for HKU Moodle with new design
// ==UserScript== // @name HKU-New-Moodle-Helper // @include http://moodle.hku.hk/* // @include https://moodle.hku.hk/* // @include https://moodle.hku.hk/ // @match https://moodle.hku.hk/my/courses.php // @run-at document-end // @version 2024-08-26.05 // @description course helper for HKU Moodle with new design // @author ArcaLunar // @resource mystyle https://cdn.jsdelivr.net/gh/ArcaLunar/hku-new-moodle-helper@578d6aa/website.css // @resource fontawesome https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css // @icon https://www.google.com/s2/favicons?sz=64&domain=hku.hk // @grant GM_getResourceText // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @namespace https://github.com/ArcaLunar/hku-new-moodle-helper // @license CC BY-NC 4.0 // ==/UserScript== // #region 检查是否有本地数据,没有则初始化 (() => { if ( !( GM_getValue("selectedCoursesList", { src: undefined }).src instanceof Array ) ) { GM_setValue("selectedCoursesList", { src: [] }); } })(); // GM_setValue("selectedCoursesList", { src: [] }); // #endregion const request = (obj) => { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open(obj.method || "GET", obj.url); if (obj.headers) { Object.keys(obj.headers).forEach((key) => { xhr.setRequestHeader(key, obj.headers[key]); }); } xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response); } else { reject(xhr.statusText); } }; xhr.onerror = () => reject(xhr.statusText); xhr.send(JSON.stringify(obj.body)); }); }; (function () { // 加载样式 GM_addStyle(GM_getResourceText("mystyle")); // var url = window.location.href; // ===================== // #region 监听 my/courses 页面变化 const targetNode = document.getElementById("page-content"); const config = { attributes: true, childList: true, subtree: true }; const url = window.location.href; const callback = function (mutationsList, observer) { let url = window.location.href; if (url.includes("my/courses.php")) { var viewPage = document.getElementsByClassName( "paged-content-page-container" ); if (viewPage.length > 0) { console.log("View Page Detected"); console.log(viewPage[0]); var coursePageList = viewPage[0].children; console.log("Course Page List Detected"); console.log(coursePageList); // 检查是否加载完成 if (coursePageList.length > 0) { var containsDivOnly = true; for (var i = 0; i < coursePageList.length; i++) { if (coursePageList[i].tagName != "DIV") { containsDivOnly = false; break; } } if (containsDivOnly) { console.log("Contains Div"); initButton(coursePageList); // 初始化按钮 initButtonAction(coursePageList); // 初始化按钮事件 } } } } }; const observer = new MutationObserver(callback); observer.observe(targetNode, config); window.addEventListener("load", function () { const url = window.location.href; if (url == "https://moodle.hku.hk/") { // 主页 console.log("rendering"); renderMainPage(); } else if (url.includes("course/search.php")) { // Search 界面 let allCourses = document.getElementsByClassName("coursebox"); if (allCourses && allCourses.length > 0) { console.log("Search Page Detected. Start initializing"); console.log(allCourses); initButtonSearchPage(allCourses); } } }); // #endregion // ===================== // #region 添加按钮 // coursePageList: 分页的课程列表 function initButton(coursePageList) { observer.disconnect(); // 不观测,防止递归 // 提取已选课程列表 let selectedCoursesList = GM_getValue("selectedCoursesList", { src: [], }).src; console.log("Selected Courses List: "); console.log(selectedCoursesList); // 删掉所有 Button (() => { let allButtons = document.getElementsByClassName("moodle-helper"); for (let i = allButtons.length - 1; i >= 0; i--) { allButtons[i].remove(); } })(); // 在哪一页 var activeIndex = (() => { if (url.includes("my/courses.php")) { for (let i = 0; i < coursePageList.length; i++) { if (!coursePageList[i].classList.contains("hidden")) { return i; } } } })(); // 什么视图? var view = (() => { if (window.location.href.includes("my/courses.php")) { let button = document.getElementById("displaydropdown"); return button.children[0].textContent.trim(); } })(); console.log("Active Index: " + activeIndex); // 遍历当前页面的课程 if (activeIndex != undefined && activeIndex != null) { var currentPage = coursePageList[activeIndex].children[0].children; // 课程 HTML 代码 if (view == "Card") { if (!currentPage[0].classList.contains("card")) { observer.observe(targetNode, config); // 重新激活 return; } } else { if (!currentPage[0].classList.contains("course-listitem")) { observer.observe(targetNode, config); // 重新激活 return; } } console.log("Current Page: "); console.log(currentPage); for (var i = 0; i < currentPage.length; i++) { let hasBeenAdded = false; var currentCourseId = currentPage[i].dataset.courseId; console.log(currentCourseId); // 检查是否已添加 for (var j = 0; j < selectedCoursesList.length; j++) { if (currentCourseId == selectedCoursesList[j].courseId) { hasBeenAdded = true; break; } } let displayedInfo; if (view != "Card") { displayedInfo = currentPage[i].children[0].children[1]; // div.row // flex-column } else { displayedInfo = currentPage[i].children[1].children[0].children[0]; // div.card-body } // 新增的按钮 // 检查按钮是否已被添加 var getButton = document.getElementById(`course${currentCourseId}`); // 判断是 remove 还是 add var initState = getButton == null || getButton == undefined; // 初始化按钮 let newButton = document.createElement("button"); newButton.id = `course${currentCourseId}`; newButton.classList.add("moodle-helper"); newButton.classList.add("btn"); newButton.classList.add("btn-primary"); newButton.relatedCourseId = currentCourseId; if (initState) { if (hasBeenAdded) { newButton.textContent = "Remove from this semester"; newButton.action = "to-remove"; newButton.classList.add("helper-remove-button"); displayedInfo.appendChild(newButton); } else { newButton.textContent = "Add to this semester"; newButton.action = "to-add"; newButton.classList.add("helper-add-button"); displayedInfo.appendChild(newButton); } currentPage[i].initComplete = true; } } } console.log("Button Init Complete"); observer.observe(targetNode, config); // 重新激活 } function initButtonSearchPage(allCourses) { // 提取已选课程列表 let selectedCoursesList = GM_getValue("selectedCoursesList", { src: [], }).src; console.log("Selected Courses List: "); console.log(selectedCoursesList); // 删掉所有 button for (let i = 0; i < allCourses.length; i++) { let hasBeenAdded = false; let currentCourseId = allCourses[i].dataset.courseid; (() => { let allButtons = allCourses[i].getElementsByClassName("moodle-helper"); for (let i = allButtons.length - 1; i >= 0; i--) { allButtons[i].remove(); } })(); // 检查是否已添加 for (let j = 0; j < selectedCoursesList.length; j++) { if (currentCourseId == selectedCoursesList[j].courseId) { hasBeenAdded = true; break; } } let getButton = document.getElementById(`course${currentCourseId}`); let initState = getButton == null || getButton == undefined; // 初始化按钮 let newButton = document.createElement("button"); newButton.classList.add("moodle-helper"); newButton.classList.add("btn"); newButton.classList.add("btn-primary"); newButton.id = `course${currentCourseId}`; newButton.relatedCourseId = currentCourseId; if (hasBeenAdded) { newButton.textContent = "Remove from this semester"; newButton.action = "to-remove"; newButton.classList.add("helper-remove-button"); } else { newButton.textContent = "Add to this semester"; newButton.action = "to-add"; newButton.classList.add("helper-add-button"); } newButton.addEventListener("click", function () { if (this.action == "to-add") { this.classList.remove("helper-add-button"); this.classList.add("helper-remove-button"); this.textContent = "Remove from this semester"; addToSemester( this.relatedCourseId, GM_getValue("selectedCoursesList", { src: [] }).src ); this.action = "to-remove"; } else if (this.action == "to-remove") { this.classList.remove("helper-remove-button"); this.classList.add("helper-add-button"); this.textContent = "Add to this semester"; removeFromSem( this.relatedCourseId, GM_getValue("selectedCoursesList", { src: [] }).src ); this.action = "to-add"; } }); let whereToInsert = allCourses[i].getElementsByClassName("course-btn")[0]; whereToInsert.insertBefore(newButton, whereToInsert.firstChild); } } // #endregion /* #region 初始化按钮监听 */ function initButtonAction(coursePageList) { observer.disconnect(); // 不观测,防止递归 // 获取所有按钮 let allButtons = document.getElementsByClassName("moodle-helper"); let selectedCoursesList = GM_getValue("selectedCoursesList", { src: [], }).src; for (let i = 0; i < allButtons.length; i++) { allButtons[i].addEventListener("click", function () { if (this.action == "to-add") { addToSemester(this.relatedCourseId, selectedCoursesList); this.classList.remove("helper-add-button"); this.classList.add("helper-remove-button"); this.textContent = "Remove from this semester"; this.action = "to-remove"; } else if (this.action == "to-remove") { removeFromSem(this.relatedCourseId, selectedCoursesList); this.classList.remove("helper-remove-button"); this.classList.add("helper-add-button"); this.textContent = "Add to this semester"; this.action = "to-add"; } // 重新刷新 let url = window.location.href; if (url == "https://moodle.hku.hk/") renderMainPage(); }); } observer.observe(targetNode, config); // 重新激活 } /* #endregion */ function removeFromSem(currentCourseId, selectedCoursesList) { console.log("removed"); // 从课表删除 var filteredList = []; for (var i = 0; i < selectedCoursesList.length; i++) { if (selectedCoursesList[i].courseId != currentCourseId) { filteredList.push(selectedCoursesList[i]); } } GM_setValue("selectedCoursesList", { src: filteredList }); console.log(filteredList); } function addToSemester(currentCourseId, selectedCoursesList) { console.log("added"); // 添加到课表 var info; if (window.location.href == "https://moodle.hku.hk/my/courses.php") { info = parseCourseInfo(currentCourseId); } else if (window.location.href == "https://moodle.hku.hk/") { info = parseCourseInfoMainPage(currentCourseId); } else if (window.location.href.includes("course/search.php")) { info = parseCourseInfoSearchPage(currentCourseId); } var currentCourse = { courseId: currentCourseId, courseInfoPack: info, }; selectedCoursesList.push(currentCourse); GM_setValue("selectedCoursesList", { src: selectedCoursesList }); console.log(selectedCoursesList); } /* #region 主页渲染 */ function renderMainPage() { const selectedCoursesList = GM_getValue("selectedCoursesList", { src: [], }).src; // 先全部清除 (() => { let allCards = document.getElementsByClassName("moodle-helper-card"); for (let i = allCards.length - 1; i >= 0; i--) { allCards[i].remove(); } })(); let mainPage = document.getElementById("frontpage-course-list"); let checkCourseOfSemWrapper = document.getElementById( "course-of-sem-wrapper" ); if (checkCourseOfSemWrapper != null) { checkCourseOfSemWrapper.remove(); } // 插入 H2 标题 let courseOfSemWrapper = document.createElement("div"); courseOfSemWrapper.classList.add("course-of-sem-wrapper"); courseOfSemWrapper.id = "course-of-sem-wrapper"; let courseOfSemTitle = document.createElement("h2"); courseOfSemTitle.textContent = "Courses of the Semester"; courseOfSemTitle.id = "course-of-sem-title"; courseOfSemWrapper.appendChild(courseOfSemTitle); // 插入选择的课程 for (let i = 0; i < selectedCoursesList.length; i++) { let course = createCard(selectedCoursesList[i]); courseOfSemWrapper.appendChild(course); } // 插入到主页 mainPage.insertBefore(courseOfSemWrapper, mainPage.firstChild); injectMainPageButton(); } /* #endregion */ /* #region 解析 my/courses.php 界面的课程信息 */ function parseCourseInfo(courseId) { // 什么视图? var view = (() => { if (window.location.href.includes("my/courses.php")) { let button = document.getElementById("displaydropdown"); return button.children[0].textContent.trim(); } })(); var viewPage = document.getElementsByClassName( "paged-content-page-container" ); var coursePageList = viewPage[0].children; if (view != "Card") { for (let i = 0; i < coursePageList.length; i++) { // 第一级 for ( let c = 0; c < coursePageList[i].children[0].children.length; c++ ) { // 第二级 let currentCourse = coursePageList[i].children[0].children[c]; if (currentCourse.dataset.courseId == courseId) { let ret = {}; let row = currentCourse.children[0]; // Image var courseImg = row.children[0].children[0].children[0].attributes["style"].value; ret.courseImg = courseImg; // Info let courseInfo = row.children[1]; // Course Name var courseName = courseInfo.children[0]; ret.courseName = courseName.outerHTML; // Category ret.courseCategory = courseInfo.children[1].outerHTML; // Year ret.courseYear = courseInfo.children[2].outerHTML; // Summary var courseSummary = courseInfo.children[3]; ret.courseSummary = courseSummary.outerHTML; console.log(ret); return ret; } } } } else { for (let i = 0; i < coursePageList.length; i++) { // 第一级 for ( let j = 0; j < coursePageList[i].children[0].children.length; j++ ) { let course = coursePageList[i].children[0].children[j]; let currentCourseId = course.dataset.courseId; if (currentCourseId == courseId) { let ret = {}; // Image let courseImg = course.children[0].children[0].attributes["style"].value; ret.courseImg = courseImg; let courseInfo = course.children[1].children[0].children[0]; // Course Name ret.courseName = courseInfo.children[0].outerHTML; // Category ret.courseCategory = courseInfo.children[1].children[0].outerHTML; // Year ret.courseYear = courseInfo.children[1].children[1].outerHTML; // Summary ret.courseSummary = document.createElement("div").outerHTML; return ret; } } } } } function parseCourseInfoMainPage(courseId) { let page = document.getElementsByClassName( "frontpage-course-list-enrolled" )[0]; let courses = page.getElementsByClassName("coursebox"); console.log(courses); for (let i = 0; i < courses.length; i++) { if (courseId == courses[i].dataset.courseid) { let course = courses[i]; let ret = {}; // class name let courseName = course.getElementsByClassName("coursename")[0]; ret.courseName = courseName.outerHTML; // Image let courseImg = 'background-image: url("' + course.getElementsByClassName("courseimage")[0].dataset.src + '");'; ret.courseImg = courseImg; // Category let coursecat = course.getElementsByClassName("coursecat")[0].children; let category = coursecat[0].outerHTML; let year = coursecat[1].outerHTML; ret.courseCategory = category; ret.courseYear = year; // summary let summary = course.getElementsByClassName("no-overflow")[0]; if (summary) { summary.classList.remove("no-overflow"); summary.classList.add("summary"); } else { summary = document.createElement("div"); } ret.courseSummary = summary.outerHTML; console.log(ret); return ret; } } } function parseCourseInfoSearchPage(courseId) { console.log("Parsing Course Info"); let allCourses = document.getElementsByClassName("coursebox"); // 定位到课程 for (let i = 0; i < allCourses.length; i++) { if (courseId == allCourses[i].dataset.courseid) { let course = allCourses[i]; let content = course.getElementsByClassName("content")[0]; let ret = {}; // courseImage let courseImg = content.getElementsByClassName("courseimage")[0].attributes["style"] .value; ret.courseImg = courseImg; // courseName let courseName = content.getElementsByClassName("coursename")[0]; ret.courseName = courseName.outerHTML; // courseCategory let courseCategory = content.getElementsByClassName("coursecat")[0].children[0]; ret.courseCategory = courseCategory.outerHTML; // courseYear let courseYear = content.getElementsByClassName("coursecat")[0].children[1]; ret.courseYear = courseYear.outerHTML; // courseSummary let courseSummary = content.getElementsByClassName("no-overflow")[0]; if (courseSummary) { courseSummary.classList.remove("no-overflow"); courseSummary.classList.add("summary"); } else { courseSummary = document.createElement("div"); } ret.courseSummary = courseSummary.outerHTML; console.log(ret); return ret; } } } /* #endregion */ /* #region 主页的课程卡片 */ function createCard(courseInfo) { let card = document.createElement("div"); card.classList.add("moodle-helper-card"); card.classList.add("coursebox"); card.classList.add("list"); card.classList.add("clearfix"); card.courseId = courseInfo.courseId; card.id = `courseCard${courseInfo.courseId}`; card.type = "1"; let content = document.createElement("div"); content.classList.add("content"); // 缩略图 let alink = document.createElement("a"); alink.href = `https://moodle.hku.hk/course/view.php?id=${courseInfo.courseId}`; let img = document.createElement("div"); img.classList.add("courseimage"); img.setAttribute("style", courseInfo.courseInfoPack.courseImg); img.dataset.src = courseInfo.courseInfoPack.courseImg .replace('background-image: url("', "") .replace('");', ""); alink.appendChild(img); // Summary 部分 let summary = document.createElement("div"); summary.classList.add("summary"); // 课程名称 let h3 = document.createElement("h3"); h3.classList.add("coursename"); let h3a = document.createElement("a"); h3a.innerHTML = courseInfo.courseInfoPack.courseName; h3.appendChild(h3a); // Category let category = document.createElement("div"); category.classList.add("coursecat"); category.classList.add("text-muted"); let csp1 = document.createElement("span"); csp1.innerHTML = courseInfo.courseInfoPack.courseCategory; category.appendChild(csp1); let csp2 = document.createElement("span"); csp2.innerHTML = courseInfo.courseInfoPack.courseYear; category.appendChild(csp2); // CourseSummary let courseSummary = document.createElement("div"); courseSummary.innerHTML = courseInfo.courseInfoPack.courseSummary; summary.appendChild(h3); summary.appendChild(category); summary.appendChild(courseSummary); // 移除课程 let removeCourse = document.createElement("button"); removeCourse.classList.add("btn"); removeCourse.classList.add("btn-primary"); removeCourse.classList.add("moodle-helper"); removeCourse.classList.add("helper-remove-button"); removeCourse.textContent = "Remove from this semester"; removeCourse.action = "to-remove"; removeCourse.relatedCourseId = courseInfo.courseId; removeCourse.addEventListener("click", function () { removeFromSem( this.relatedCourseId, GM_getValue("selectedCoursesList", { src: [] }).src ); let thiscard = document.getElementById( `courseCard${this.relatedCourseId}` ); thiscard.remove(); renderMainPage(); }); // enter course let enterCourse = document.createElement("div"); enterCourse.classList.add("course-btn"); let p1 = document.createElement("p"); p1.appendChild(removeCourse); enterCourse.appendChild(p1); let p = document.createElement("p"); let pa = document.createElement("a"); pa.classList.add("btn"); pa.classList.add("btn-primary"); pa.href = `https://moodle.hku.hk/course/view.php?id=${courseInfo.courseId}`; pa.textContent = "Click to enter this course"; p.appendChild(pa); enterCourse.appendChild(p); content.appendChild(alink); content.appendChild(summary); content.appendChild(enterCourse); card.appendChild(content); return card; } /* #endregion */ /* #region 在主页插入按钮 */ function injectMainPageButton() { let courseBoxes = document.getElementsByClassName("coursebox"); let selectedCoursesList = GM_getValue("selectedCoursesList", { src: [], }).src; for (let i = 0; i < courseBoxes.length; i++) { if (courseBoxes[i].classList.contains("moodle-helper-card")) continue; let courseId = courseBoxes[i].dataset.courseid; let courseButton = courseBoxes[i].getElementsByClassName("course-btn")[0]; // 移除已有按钮 let buttonElement = courseBoxes[i].getElementsByClassName("moodle-helper"); for (let j = buttonElement.length - 1; j >= 0; j--) { buttonElement[j].remove(); } // 检查是否已添加 var hasBeenAdded = false; for (let j = 0; j < selectedCoursesList.length; j++) { if (selectedCoursesList[j].courseId == courseId) { hasBeenAdded = true; break; } } // 如果已添加 if (hasBeenAdded) { let removeButton = document.createElement("button"); removeButton.classList.add("btn"); removeButton.classList.add("btn-primary"); removeButton.classList.add("moodle-helper"); removeButton.classList.add("helper-remove-button"); removeButton.textContent = "Remove from this semester"; removeButton.action = "to-remove"; removeButton.relatedCourseId = courseId; removeButton.addEventListener("click", function () { if (this.action == "to-add") { addToSemester(this.relatedCourseId, selectedCoursesList); this.classList.remove("helper-add-button"); this.classList.add("helper-remove-button"); this.textContent = "Remove from this semester"; this.action = "to-remove"; } else if (this.action == "to-remove") { removeFromSem(this.relatedCourseId, selectedCoursesList); this.classList.remove("helper-remove-button"); this.classList.add("helper-add-button"); this.textContent = "Add to this semester"; this.action = "to-add"; } // 重新刷新 let url = window.location.href; if (url == "https://moodle.hku.hk/") renderMainPage(); }); courseButton.insertBefore(removeButton, courseButton.firstChild); } else { let addButton = document.createElement("button"); addButton.classList.add("btn"); addButton.classList.add("btn-primary"); addButton.classList.add("moodle-helper"); addButton.classList.add("helper-add-button"); addButton.textContent = "Add to this semester"; addButton.action = "to-add"; addButton.relatedCourseId = courseId; addButton.addEventListener("click", function () { if (this.action == "to-add") { addToSemester(this.relatedCourseId, selectedCoursesList); this.classList.remove("helper-add-button"); this.classList.add("helper-remove-button"); this.textContent = "Remove from this semester"; this.action = "to-remove"; } else if (this.action == "to-remove") { removeFromSem(this.relatedCourseId, selectedCoursesList); this.classList.remove("helper-remove-button"); this.classList.add("helper-add-button"); this.textContent = "Add to this semester"; this.action = "to-add"; } // 重新刷新 let url = window.location.href; if (url == "https://moodle.hku.hk/") renderMainPage(); }); courseButton.insertBefore(addButton, courseButton.firstChild); } } } /* #endregion */ })();