更新脚本版本号,增加配置项支持,优化学习时长和视频倍速设置逻辑
This commit is contained in:
parent
47ebe38c30
commit
a901914794
|
|
@ -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、error;duration 是自动隐藏时间。
|
// type 可用 info、success、warning、error;duration 是自动隐藏时间。
|
||||||
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(
|
||||||
"视频正在播放,当前:" +
|
"视频正在播放,当前:" +
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue