更新脚本版本号,增加配置项支持,优化学习时长和视频倍速设置逻辑

This commit is contained in:
竟康 2026-06-05 14:17:30 +08:00
parent 47ebe38c30
commit a901914794
1 changed files with 167 additions and 37 deletions

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name 平顶山学院(青书学堂)自动学习脚本 // @name 平顶山学院(青书学堂)自动学习脚本
// @namespace http://tampermonkey.net/ // @namespace http://tampermonkey.net/
// @version 2026-06-05.2 // @version 2026-06-05.3
// @description try to take over the world! // @description try to take over the world!
// @author You // @author You
// @match https://degree.qingshuxuetang.com/pdsu/Student/Course/CourseStudy* // @match https://degree.qingshuxuetang.com/pdsu/Student/Course/CourseStudy*
@ -9,7 +9,10 @@
// @icon https://www.google.com/s2/favicons?sz=64&domain=qingshuxuetang.com // @icon https://www.google.com/s2/favicons?sz=64&domain=qingshuxuetang.com
// @updateURL https://git.zaobai.com/public/tampermonkey_script/raw/branch/master/qingshuxuetang_auto_play.user.js // @updateURL https://git.zaobai.com/public/tampermonkey_script/raw/branch/master/qingshuxuetang_auto_play.user.js
// @downloadURL https://git.zaobai.com/public/tampermonkey_script/raw/branch/master/qingshuxuetang_auto_play.user.js // @downloadURL https://git.zaobai.com/public/tampermonkey_script/raw/branch/master/qingshuxuetang_auto_play.user.js
// @grant none // @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// ==/UserScript== // ==/UserScript==
(function () { (function () {
@ -18,34 +21,48 @@
// 当前页面地址,用来判断是在课程列表页还是课件播放页。 // 当前页面地址,用来判断是在课程列表页还是课件播放页。
const url = window.location.href; const url = window.location.href;
// 课件至少需要学习多少秒;默认 5 分钟。
const MIN_STUDY_SECONDS = 5 * 60;
// 视频课件的 video 标签选择器;如果网站改版导致找不到视频,优先检查这里。 // 视频课件的 video 标签选择器;如果网站改版导致找不到视频,优先检查这里。
const VIDEO_SELECTOR = "#vjs_video_3_html5_api"; const VIDEO_SELECTOR = "#vjs_video_3_html5_api";
// 视频默认播放倍速;想改成 1.5 倍、3 倍就改这个值。
const VIDEO_PLAYBACK_RATE = 2;
// 文本课件容器选择器easyXDM 中间的数字会变,所以这里用开头和结尾匹配。 // 文本课件容器选择器easyXDM 中间的数字会变,所以这里用开头和结尾匹配。
const TEXT_SELECTOR = "[id^='easyXDM_default'][id$='_provider']"; const TEXT_SELECTOR = "[id^='easyXDM_default'][id$='_provider']";
// Tampermonkey 持久化配置的存储 key。
// 配置会存在脚本自己的存储区里,更新脚本代码不会覆盖这里的值。
const CONFIG_STORAGE_KEY = "qingshu_auto_config";
// 默认配置。第一次安装或重置配置后,会使用这些值。
const DEFAULT_CONFIG = {
// 课件至少需要学习多少秒;默认 5 分钟。
minStudySeconds: 5 * 60,
// 视频默认播放倍速;想改成 1.5 倍、3 倍可以从 Tampermonkey 菜单配置。
videoPlaybackRate: 2,
// toast 默认显示时长单位毫秒4000 表示 4 秒后自动隐藏。
toastDefaultDuration: 4000,
// 运行心跳提示间隔,单位秒;视频播放中、文本课件等待中都会按这个间隔提醒。
heartbeatSeconds: 10,
// 课程列表最多等待多少秒;超过后认为可能请求失败,触发刷新。
courseListWaitSeconds: 60,
// 课程列表加载失败时最多自动刷新几次。
maxCourseListRefreshCount: 3,
};
// 读取 Tampermonkey 里保存过的配置,并和默认配置合并。
// 以后脚本新增配置项时,旧配置也能自动补上默认值。
const CONFIG = {
...DEFAULT_CONFIG,
...GM_getValue(CONFIG_STORAGE_KEY, {}),
};
// toast 容器和样式标签的 id通常不用改除非和页面元素冲突。 // toast 容器和样式标签的 id通常不用改除非和页面元素冲突。
const TOAST_CONTAINER_ID = "qingshu-auto-toast-container"; const TOAST_CONTAINER_ID = "qingshu-auto-toast-container";
const TOAST_STYLE_ID = "qingshu-auto-toast-style"; const TOAST_STYLE_ID = "qingshu-auto-toast-style";
// toast 默认显示时长单位毫秒4000 表示 4 秒后自动隐藏。
const TOAST_DEFAULT_DURATION = 4000;
// 运行心跳提示间隔,单位秒;视频播放中、文本课件等待中都会按这个间隔提醒。
const HEARTBEAT_SECONDS = 10;
// 课程列表最多等待多少秒;超过后认为可能请求失败,触发刷新。
const COURSE_LIST_WAIT_SECONDS = 60;
// 课程列表加载失败时最多自动刷新几次。
const MAX_COURSE_LIST_REFRESH_COUNT = 3;
// 记录课程列表自动刷新次数的 sessionStorage key。 // 记录课程列表自动刷新次数的 sessionStorage key。
// 带上 pathname/search避免不同课程页面之间互相影响。 // 带上 pathname/search避免不同课程页面之间互相影响。
const COURSE_LIST_REFRESH_KEY = const COURSE_LIST_REFRESH_KEY =
@ -125,7 +142,11 @@
// 显示一条 toast同时保留 console 输出。 // 显示一条 toast同时保留 console 输出。
// type 可用 info、success、warning、errorduration 是自动隐藏时间。 // type 可用 info、success、warning、errorduration 是自动隐藏时间。
const notify = (message, type = "info", duration = TOAST_DEFAULT_DURATION) => { const notify = (
message,
type = "info",
duration = CONFIG.toastDefaultDuration
) => {
const log = type === "error" ? console.error : console.log; const log = type === "error" ? console.error : console.log;
log(message); log(message);
@ -150,6 +171,99 @@
setTimeout(removeToast, duration); setTimeout(removeToast, duration);
}; };
const saveConfig = (patch) => {
Object.assign(CONFIG, patch);
GM_setValue(CONFIG_STORAGE_KEY, CONFIG);
notify("配置已保存,刷新页面后完全生效", "success", 6000);
};
const promptNumberConfig = (label, key, unit, currentValue, toStoredValue) => {
const input = prompt(label + "(当前:" + currentValue + unit + "", currentValue);
if (input === null) {
return;
}
const value = Number(input);
if (!Number.isFinite(value) || value <= 0) {
notify("配置失败请输入大于0的数字", "error", 6000);
return;
}
saveConfig({
[key]: toStoredValue ? toStoredValue(value) : value,
});
};
// 在 Tampermonkey 菜单里提供配置入口。
// 这些值保存在脚本存储区,脚本更新不会覆盖它们。
const registerConfigMenus = () => {
GM_registerMenuCommand("设置视频倍速", () => {
promptNumberConfig(
"设置视频倍速",
"videoPlaybackRate",
"倍",
CONFIG.videoPlaybackRate
);
});
GM_registerMenuCommand("设置最小学习时间", () => {
promptNumberConfig(
"设置最小学习时间",
"minStudySeconds",
"分钟",
CONFIG.minStudySeconds / 60,
(value) => value * 60
);
});
GM_registerMenuCommand("设置心跳提示间隔", () => {
promptNumberConfig(
"设置心跳提示间隔",
"heartbeatSeconds",
"秒",
CONFIG.heartbeatSeconds
);
});
GM_registerMenuCommand("设置课程列表等待时间", () => {
promptNumberConfig(
"设置课程列表等待时间",
"courseListWaitSeconds",
"秒",
CONFIG.courseListWaitSeconds
);
});
GM_registerMenuCommand("设置课程列表最大刷新次数", () => {
promptNumberConfig(
"设置课程列表最大刷新次数",
"maxCourseListRefreshCount",
"次",
CONFIG.maxCourseListRefreshCount,
Math.floor
);
});
GM_registerMenuCommand("设置 toast 显示时长", () => {
promptNumberConfig(
"设置 toast 显示时长",
"toastDefaultDuration",
"秒",
CONFIG.toastDefaultDuration / 1000,
(value) => value * 1000
);
});
GM_registerMenuCommand("重置脚本配置", () => {
GM_deleteValue(CONFIG_STORAGE_KEY);
Object.assign(CONFIG, DEFAULT_CONFIG);
notify("配置已重置,刷新页面后完全生效", "success", 6000);
});
};
registerConfigMenus();
notify("插件加载完成,等待界面渲染", "info"); notify("插件加载完成,等待界面渲染", "info");
// 等待某个 DOM 条件成立。 // 等待某个 DOM 条件成立。
@ -201,9 +315,11 @@
const refreshCourseStudyIfNeeded = () => { const refreshCourseStudyIfNeeded = () => {
const refreshCount = Number(sessionStorage.getItem(COURSE_LIST_REFRESH_KEY) || 0); const refreshCount = Number(sessionStorage.getItem(COURSE_LIST_REFRESH_KEY) || 0);
if (refreshCount >= MAX_COURSE_LIST_REFRESH_COUNT) { if (refreshCount >= CONFIG.maxCourseListRefreshCount) {
notify( notify(
"课程列表等待超时已刷新3次仍未加载停止自动刷新", "课程列表等待超时,已刷新" +
CONFIG.maxCourseListRefreshCount +
"次仍未加载,停止自动刷新",
"error", "error",
8000 8000
); );
@ -213,10 +329,12 @@
const nextRefreshCount = refreshCount + 1; const nextRefreshCount = refreshCount + 1;
sessionStorage.setItem(COURSE_LIST_REFRESH_KEY, String(nextRefreshCount)); sessionStorage.setItem(COURSE_LIST_REFRESH_KEY, String(nextRefreshCount));
notify( notify(
"课程列表等待超过1分钟准备刷新页面第" + "课程列表等待超过" +
formatSeconds(CONFIG.courseListWaitSeconds) +
",准备刷新页面(第" +
nextRefreshCount + nextRefreshCount +
"/" + "/" +
MAX_COURSE_LIST_REFRESH_COUNT + CONFIG.maxCourseListRefreshCount +
"次)", "次)",
"warning", "warning",
8000 8000
@ -254,7 +372,7 @@
} }
const studiedSeconds = parseStudySeconds(mark.innerText); const studiedSeconds = parseStudySeconds(mark.innerText);
return studiedSeconds < MIN_STUDY_SECONDS; return studiedSeconds < CONFIG.minStudySeconds;
}; };
// 返回课程列表页。 // 返回课程列表页。
@ -305,7 +423,7 @@
return items.length > 0 ? items : null; return items.length > 0 ? items : null;
}, },
COURSE_LIST_WAIT_SECONDS * 1000 CONFIG.courseListWaitSeconds * 1000
); );
if (!items) { if (!items) {
@ -317,7 +435,7 @@
notify("获取到课程数量:" + items.length, "success"); notify("获取到课程数量:" + items.length, "success");
// 从上到下找到第一个“没学过”或“学习不足5分钟”的课件并进入。 // 从上到下找到第一个“没学过”或“学习不足配置时长”的课件并进入。
for (const item of items) { for (const item of items) {
if (shouldEnterCourseware(item)) { if (shouldEnterCourseware(item)) {
const mark = item.querySelector(".study_being"); const mark = item.querySelector(".study_being");
@ -342,16 +460,22 @@
// 等到播放页真正渲染出视频或文本课件,再决定如何处理。 // 等到播放页真正渲染出视频或文本课件,再决定如何处理。
const coursewareElement = await waitForCoursewareElement(); const coursewareElement = await waitForCoursewareElement();
// 文本课件没有 video 标签,不需要播放;停留 5 分钟后返回列表页。 // 文本课件没有 video 标签,不需要播放;停留到配置的学习时长后返回列表页。
if (coursewareElement.matches(TEXT_SELECTOR)) { if (coursewareElement.matches(TEXT_SELECTOR)) {
notify("检测到文本课件等待5分钟后返回准备页面", "warning", 6000); notify(
"检测到文本课件,等待" +
formatSeconds(CONFIG.minStudySeconds) +
"后返回准备页面",
"warning",
6000
);
const textStartedAt = Date.now(); const textStartedAt = Date.now();
let textInterval; let textInterval;
const notifyTextProgress = () => { const notifyTextProgress = () => {
const studiedSeconds = Math.floor((Date.now() - textStartedAt) / 1000); const studiedSeconds = Math.floor((Date.now() - textStartedAt) / 1000);
if (studiedSeconds >= MIN_STUDY_SECONDS) { if (studiedSeconds >= CONFIG.minStudySeconds) {
clearInterval(textInterval); clearInterval(textInterval);
notify("文本课件等待完成,返回准备页面", "success"); notify("文本课件等待完成,返回准备页面", "success");
returnToStudyPage(); returnToStudyPage();
@ -362,14 +486,14 @@
"文本课件学习中,已等待:" + "文本课件学习中,已等待:" +
formatSeconds(studiedSeconds) + formatSeconds(studiedSeconds) +
",剩余:" + ",剩余:" +
formatSeconds(MIN_STUDY_SECONDS - studiedSeconds), formatSeconds(CONFIG.minStudySeconds - studiedSeconds),
"info" "info"
); );
}; };
textInterval = setInterval( textInterval = setInterval(
notifyTextProgress, notifyTextProgress,
HEARTBEAT_SECONDS * 1000 CONFIG.heartbeatSeconds * 1000
); );
notifyTextProgress(); notifyTextProgress();
return; return;
@ -403,12 +527,18 @@
returnToStudyPage(); returnToStudyPage();
} }
if (video.playbackRate !== VIDEO_PLAYBACK_RATE) { if (video.playbackRate !== CONFIG.videoPlaybackRate) {
notify("将视频倍速设置为" + VIDEO_PLAYBACK_RATE + "倍速", "success"); notify(
video.playbackRate = VIDEO_PLAYBACK_RATE; "将视频倍速设置为" + CONFIG.videoPlaybackRate + "倍速",
"success"
);
video.playbackRate = CONFIG.videoPlaybackRate;
} }
if (Date.now() - lastVideoHeartbeatAt >= HEARTBEAT_SECONDS * 1000) { if (
Date.now() - lastVideoHeartbeatAt >=
CONFIG.heartbeatSeconds * 1000
) {
lastVideoHeartbeatAt = Date.now(); lastVideoHeartbeatAt = Date.now();
notify( notify(
"视频正在播放,当前:" + "视频正在播放,当前:" +