// ==UserScript== // @name 昊誉信息|昊誉教师研训平台|免费版|使用说明:http://doc.zhanyc.cn/pages/hyxx/ // @namespace free // @version 1.0 // @description 当前是免费版本,只包含了视频页面自动播放、解除视频暂停限制以及视频进度采集功能。如需自动下一集、自动换课程、考试答题、全自动无人值守高级功能可升级付费版本|接各类脚本开发、代挂任务,微信:zhanyc_cn 个人网站:http://doc.zhanyc.cn // @include *://*.haoyuinfo.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_deleteValue // @run-at document-end // @require http://libs.baidu.com/jquery/2.0.0/jquery.min.js // @license Creative Commons // ==/UserScript== (function () { let $jq = $; unsafeWindow.$jq = $; let docUrl = "http://doc.zhanyc.cn/pages/hyxx/"; let FREEJS = { pageData: { waitOfIndexArr: [], video: { index: null, }, progressData: null, collectProgressFlag: false, }, // ==================== 工具方法 ==================== _waitTimeout(timeout) { return new Promise((resolve, reject) => { setTimeout(() => { return resolve(); }, timeout); }); }, _waitOf(fun, interval = 1000, timeout = 180) { return new Promise((resolve, reject) => { let _timeOut = timeout * 1000; if (fun()) { return resolve(); } let index = setInterval(() => { if (timeout != -1) { _timeOut -= interval; if (_timeOut < 0) { clearInterval(index); return reject(); } } if (fun()) { clearInterval(index); return resolve(); } }, interval); FREEJS.pageData.waitOfIndexArr.push(index); }); }, _abortWaitOf() { FREEJS.pageData.waitOfIndexArr.forEach((index) => { clearInterval(index); }); FREEJS.pageData.waitOfIndexArr = []; }, // ==================== 视频相关方法 ==================== getVideo() { return $("video")[0]; }, play() { let video = FREEJS.getVideo(); if (video) { video.volume = 0; setTimeout(() => { video.play(); }, 200); } }, setVideoVolume() { try { let video = FREEJS.getVideo(); if (video && video.volume != 0) { video.volume = 0; } } catch (e) { console.error(e); } }, setStep(time) { let video = FREEJS.getVideo(); if (video) { if (time >= 0) { video.currentTime = time; } else { video.currentTime = video.duration + time; } } }, isPlayFinish() { try { let video = FREEJS.getVideo(); return video && video.duration > 0 && video.currentTime + 5 >= video.duration; } catch (e) { return false; } }, async videoIsPlay() { return new Promise((resolve) => { try { let video = FREEJS.getVideo(); if (!video) return resolve(false); let curTime = video.currentTime; setTimeout(() => { let time1 = video.currentTime; let res = time1 > curTime; if (res) { setTimeout(() => { let time2 = video.currentTime; resolve(time2 > time1); }, 100); } else { return resolve(false); } }, 100); } catch (e) { resolve(false); } }); }, // ==================== 进度采集 ==================== async page_videoProgesss() { FREEJS._abortWaitOf(); await FREEJS._waitOf((a) => $("#scoreTb tbody tr").length > 0); FREEJS.floaterShowTips("正在采集视频进度..."); await FREEJS._waitTimeout(1000); let arr = []; $("#scoreTb tbody tr").each((i, el) => { let title = $(el).find("a:first").text().trim(); let isFinish = !$(el).find("td:last").text().includes("未完成"); arr.push({ title, isFinish }); }); let courseTitle = $("h3.title").text().trim(); let progressData = { title: courseTitle, video: arr }; // 持久化到 GM 存储,防止页面切换后数据丢失导致重复采集 FREEJS.setGMData("progressCache", progressData); FREEJS.pageData.progressData = progressData; FREEJS.floaterShowProgress(progressData); FREEJS.floaterShowTips("视频进度采集完成!"); // 返回到课程学习页面 FREEJS.getElByText($(".m-sxtstudy-nav a"), "课程学习").click(); }, // ==================== 视频页面处理 ==================== async page_video() { clearInterval(FREEJS.pageData.video.index); FREEJS.pageData.video.index = null; FREEJS._abortWaitOf(); await FREEJS._waitOf((a) => FREEJS.getElByText($("a"), "学习进度") != null); let title = $("h3.title").text().trim(); // 先从 GM 存储读取缓存的进度数据 if (!FREEJS.pageData.progressData) { FREEJS.pageData.progressData = FREEJS.getGMData("progressCache", null); } // 检查是否已有进度数据,没有则去采集(仅采集一次,采集后持久化到 GM 存储) if (!FREEJS.pageData.progressData || FREEJS.pageData.progressData.title != title) { FREEJS.pageData.collectProgressFlag = true; await FREEJS._waitTimeout(500); FREEJS.floaterShowTips("正在采集视频进度..."); FREEJS.getElByText($("a"), "学习进度")[0].click(); return; } // 在视频页面加载章节进度显示 FREEJS.floaterShowProgress(FREEJS.pageData.progressData); if (FREEJS.pageData.video.index != null) return; FREEJS.pageData.video.index = setInterval(async () => { try { // 解锁播放限制:处理暂停的视频 let video = FREEJS.getVideo(); if (!video) return; // 如果视频被暂停,自动播放 if (video.paused) { video.play(); FREEJS.floaterShowTips("检测到视频暂停,已自动恢复播放"); } // 静音 video.volume = 0; // 更新进度显示 let curTime = video.currentTime; let totalTime = video.duration; if (totalTime > 0) { let title = `进度:${curTime.toFixed(0)}/${totalTime.toFixed(0)}`; $("title").text(title); FREEJS.floaterUpdateProgress(curTime, totalTime); } // 更新当前播放的章节高亮 FREEJS.floaterUpdateCurrentChapter(); // 检测视频是否播放完成 if (FREEJS.isPlayFinish()) { FREEJS.floaterShowTips("当前视频播放完成"); clearInterval(FREEJS.pageData.video.index); FREEJS.pageData.video.index = null; return; } // 检查是否弹出完成提示 if ($(".g-study-prompt").text().includes("您已完成观看")) { FREEJS.floaterShowTips("您已完成观看!"); clearInterval(FREEJS.pageData.video.index); FREEJS.pageData.video.index = null; return; } } catch (e) { console.error("视频页面定时器出错", e); } }, 2000); }, // ==================== 页面路由 ==================== async runByUrl(url) { clearInterval(FREEJS.pageData.video.index); FREEJS.pageData.video.index = null; url = url.toLowerCase(); if (url.includes("/study/course/progess".toLowerCase())) { FREEJS.page_videoProgesss(); } else if (url.includes("/study/course/".toLowerCase())) { FREEJS.page_video(); } }, // ==================== 悬浮引流面板 ==================== addStyle() { GM_addStyle(` #FREEJS_floater { position: fixed; left: 10px; top: 10px; z-index: 999999; width: 320px; background: #fff; border: 2px solid #f1a325; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); font-family: "Microsoft YaHei", sans-serif; font-size: 13px; color: #333; line-height: 1.6; } #FREEJS_floater .floater-header { background: linear-gradient(135deg, #f1a325, #f7c948); color: #fff; padding: 8px 12px; border-radius: 6px 6px 0 0; font-weight: bold; font-size: 14px; display: flex; justify-content: space-between; align-items: center; } #FREEJS_floater .floater-header .floater-close { cursor: pointer; background: rgba(255,255,255,0.3); border: none; color: #fff; border-radius: 4px; padding: 0 8px; font-size: 16px; line-height: 22px; } #FREEJS_floater .floater-header .floater-close:hover { background: rgba(255,255,255,0.5); } #FREEJS_floater .floater-body { padding: 10px 12px; } #FREEJS_floater .floater-section { margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid #eee; } #FREEJS_floater .floater-section:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; } #FREEJS_floater .floater-status { display: flex; gap: 10px; flex-wrap: wrap; } #FREEJS_floater .floater-status-item { background: #e8f5e9; padding: 3px 8px; border-radius: 4px; font-size: 12px; color: #2e7d32; } #FREEJS_floater .floater-progress-bar { width: 100%; height: 8px; background: #e0e0e0; border-radius: 4px; overflow: hidden; margin: 4px 0; } #FREEJS_floater .floater-progress-bar .floater-progress-fill { height: 100%; background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 4px; transition: width 0.5s; } #FREEJS_floater .floater-progress-text { font-size: 12px; color: #666; text-align: center; } #FREEJS_floater .floater-progress-list { max-height: 150px; overflow-y: auto; font-size: 12px; } #FREEJS_floater .floater-progress-list .progress-item { padding: 2px 4px; border-bottom: 1px dashed #eee; } #FREEJS_floater .floater-progress-list .progress-item.finish { color: #4caf50; } #FREEJS_floater .floater-progress-list .progress-item.unfinish { color: #ff9800; } #FREEJS_floater .floater-tips { background: #fff3e0; padding: 8px; border-radius: 4px; font-size: 12px; color: #e65100; } #FREEJS_floater .floater-tips a { color: #1565c0; text-decoration: underline; cursor: pointer; } #FREEJS_floater .floater-tips a:hover { color: #0d47a1; } #FREEJS_floater .floater-contact { font-size: 11px; color: #888; text-align: center; padding-top: 4px; } #FREEJS_floater .floater-running { background: #e3f2fd; border: 1px solid #90caf9; padding: 4px 8px; border-radius: 4px; font-size: 12px; color: #1565c0; text-align: center; font-weight: bold; } `); }, createFloater() { if (document.getElementById("FREEJS_floater")) return; let html = `