diff --git a/qingshuxuetang_auto_play.user.js b/qingshuxuetang_auto_play.user.js index 78f2bdc..593951e 100644 --- a/qingshuxuetang_auto_play.user.js +++ b/qingshuxuetang_auto_play.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name 平顶山学院(青书学堂)自动学习脚本 // @namespace http://tampermonkey.net/ -// @version 2026-06-03.2 +// @version 2026-06-04.1 // @description try to take over the world! // @author You // @match https://degree.qingshuxuetang.com/pdsu/Student/Course/CourseStudy* @@ -23,6 +23,12 @@ const TOAST_STYLE_ID = "qingshu-auto-toast-style"; const TOAST_DEFAULT_DURATION = 4000; const HEARTBEAT_SECONDS = 10; + const COURSE_LIST_WAIT_SECONDS = 60; + const MAX_COURSE_LIST_REFRESH_COUNT = 3; + const COURSE_LIST_REFRESH_KEY = + "qingshu_auto_course_list_refresh_count:" + + location.pathname + + location.search; // 创建右下角 toast 容器和样式。 // 每次通知都会创建一个新的 toast,所以多个通知会一起堆叠显示。 @@ -126,7 +132,7 @@ // 等待某个 DOM 条件成立。 // 页面里的课程列表、视频、文本课件经常是后加载的,所以不用固定延迟, // 而是监听 DOM 变化,直到 getResult() 返回有效结果。 - const waitForCondition = (getResult) => { + const waitForCondition = (getResult, timeoutMs) => { const result = getResult(); if (result) { @@ -134,12 +140,23 @@ } return new Promise((resolve) => { + let timeoutId; + + const finish = (result) => { + observer.disconnect(); + + if (timeoutId) { + clearTimeout(timeoutId); + } + + resolve(result); + }; + const observer = new MutationObserver(() => { const result = getResult(); if (result) { - observer.disconnect(); - resolve(result); + finish(result); } }); @@ -147,9 +164,47 @@ childList: true, subtree: true, }); + + if (timeoutMs) { + timeoutId = setTimeout(() => { + finish(null); + }, timeoutMs); + } }); }; + // CourseStudy 偶尔会因为请求失败(例如 429)导致课程列表一直不出现。 + // 这里记录刷新次数:等 1 分钟还没有列表就刷新,最多刷新 3 次。 + const refreshCourseStudyIfNeeded = () => { + const refreshCount = Number(sessionStorage.getItem(COURSE_LIST_REFRESH_KEY) || 0); + + if (refreshCount >= MAX_COURSE_LIST_REFRESH_COUNT) { + notify( + "课程列表等待超时,已刷新3次仍未加载,停止自动刷新", + "error", + 8000 + ); + return; + } + + const nextRefreshCount = refreshCount + 1; + sessionStorage.setItem(COURSE_LIST_REFRESH_KEY, String(nextRefreshCount)); + notify( + "课程列表等待超过1分钟,准备刷新页面(第" + + nextRefreshCount + + "/" + + MAX_COURSE_LIST_REFRESH_COUNT + + "次)", + "warning", + 8000 + ); + location.reload(); + }; + + const clearCourseStudyRefreshCount = () => { + sessionStorage.removeItem(COURSE_LIST_REFRESH_KEY); + }; + // 把“共4分钟49秒”“共1小时2分钟3秒”这类文字换算成秒数, // 方便判断是否已经学满 5 分钟。 const parseStudySeconds = (text) => { @@ -220,12 +275,22 @@ notify("进入准备阶段,等待课程列表", "info"); // 课程列表是异步渲染的,等到至少有一个课程节点后再开始遍历。 - const items = await waitForCondition(() => { - const list = document.querySelector("#list"); - const items = list ? list.querySelectorAll("li > a.node") : []; + const items = await waitForCondition( + () => { + const list = document.querySelector("#list"); + const items = list ? list.querySelectorAll("li > a.node") : []; - return items.length > 0 ? items : null; - }); + return items.length > 0 ? items : null; + }, + COURSE_LIST_WAIT_SECONDS * 1000 + ); + + if (!items) { + refreshCourseStudyIfNeeded(); + return; + } + + clearCourseStudyRefreshCount(); notify("获取到课程数量:" + items.length, "success");