// ==UserScript==
// @name 昊誉网课全自动学习(全功能整合版)
// @namespace http://tampermonkey.net/
// @version 45.0
// @description 面板显示、自动播放、弹窗关闭、达标跳转、快退补偿、暂停恢复、精准选课
// @author 助手
// @match https://train.haoyuinfo.com/*/study/course/*
// @match https://train.haoyuinfo.com/study/course/*
// @match https://train.haoyuinfo.com/userCenter*
// @grant none
// ==/UserScript==
(function() {
'use strict';
function log(msg) {
console.log('[昊誉助手] ' + msg);
}
// ========== 学时面板 ==========
let studyPanel = null;
let hasReachedRequired = false;
let lastRequired = 0;
let lastViewed = 0;
function createStudyPanel() {
if (studyPanel) return;
studyPanel = document.createElement('div');
studyPanel.id = 'haoyu-study-panel';
studyPanel.innerHTML = `
📊 学习进度
要求观看: -- 分钟
已观看: -- 分钟
`;
document.body.appendChild(studyPanel);
}
function getStudyData() {
const viewSpan = document.getElementById('viewTimeTxt');
let viewedText = '--';
let requiredText = '--';
let requiredNum = 0;
let viewedNum = 0;
if (viewSpan) {
viewedText = viewSpan.textContent.trim();
viewedNum = parseInt(viewedText) || 0;
const timeParagraph = viewSpan.closest('p');
if (timeParagraph) {
const spans = timeParagraph.querySelectorAll('span');
if (spans.length > 0) {
requiredText = spans[0].textContent.trim();
requiredNum = parseInt(requiredText) || 0;
}
}
}
lastRequired = requiredNum;
lastViewed = viewedNum;
return { viewedText, requiredText, viewedNum: viewedNum, requiredNum: requiredNum };
}
function updateStudyPanel() {
if (!document.getElementById('haoyu-study-panel')) {
studyPanel = null;
createStudyPanel();
}
const data = getStudyData();
const reqElem = document.getElementById('haoyu-required');
const viewElem = document.getElementById('haoyu-viewed');
const statusElem = document.getElementById('haoyu-status');
if (reqElem) reqElem.textContent = data.requiredText;
if (viewElem) viewElem.textContent = data.viewedText;
if (statusElem) {
if (data.requiredNum > 0 && data.viewedNum >= data.requiredNum) {
statusElem.textContent = '✅ 学时已达标';
statusElem.style.color = '#4caf50';
} else if (data.requiredNum > 0 && data.viewedNum < data.requiredNum) {
statusElem.textContent = '⏳ 还需观看 ' + (data.requiredNum - data.viewedNum) + ' 分钟';
statusElem.style.color = '#ff9800';
} else {
statusElem.textContent = '📹 正在播放...';
statusElem.style.color = '#2196f3';
}
}
}
function checkCompletedLabel() {
const completedIns = document.querySelector('ins.tip');
return completedIns && completedIns.textContent.trim() === '已完成' && completedIns.offsetParent;
}
function checkNumberReached() {
const data = getStudyData();
return data.requiredNum > 0 && data.viewedNum >= data.requiredNum;
}
// ========== 用户中心 ==========
function findUnfinishedCourse() {
const allIns = document.querySelectorAll('ins');
for (let ins of allIns) {
if (ins.textContent.trim() !== '去学习' || !ins.offsetParent) continue;
const li = ins.closest('li');
if (!li) continue;
if (li.querySelector('img[src*="ico-sxtpass"]')) continue;
log('找到未完成课程');
return ins.closest('a') || ins;
}
return null;
}
function autoEnterCourse() {
const entry = findUnfinishedCourse();
if (entry) entry.click();
}
// ========== 课程页面:弹窗处理 ==========
function closePopup() {
// 继续学习弹窗
const continueBtn = document.querySelector('button.mylayer-btn.mylayer-btn3.type0');
if (continueBtn && continueBtn.offsetParent && continueBtn.textContent.includes('继续学习')) {
log('点击继续学习');
continueBtn.click();
return true;
}
// 完成弹窗确定按钮
const confirmBtn = document.querySelector('button.mylayer-btn.type1');
if (confirmBtn && confirmBtn.offsetParent && !/观看|学时|分钟|已观看/i.test(confirmBtn.textContent.trim())) {
log('关闭完成弹窗');
confirmBtn.click();
return true;
}
// 关闭图标
const closeIcon = document.querySelector('.mylayer-close, .mylayer-close-btn, [class*="close"]');
if (closeIcon && closeIcon.offsetParent) {
closeIcon.click();
return true;
}
// 通用文字按钮
const all = document.querySelectorAll('button, a, span, div');
for (let el of all) {
if (el.offsetParent && /确定|知道了|关闭|ok|close|完成/i.test(el.textContent.trim()) && !/观看|学时|分钟|已观看/i.test(el.textContent.trim())) {
el.click();
return true;
}
}
return false;
}
// ========== 视频处理 ==========
function getActiveVideo() {
const videos = document.querySelectorAll('video');
for (let video of videos) {
if (video.offsetParent && video.offsetWidth > 0 && video.offsetHeight > 0) return video;
}
const iframes = document.querySelectorAll('iframe');
for (let iframe of iframes) {
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
const video = doc.querySelector('video');
if (video && video.offsetWidth > 0 && video.offsetHeight > 0) return video;
} catch(e) {}
}
return document.querySelector('video');
}
function forceAutoPlay(video, attempt = 0) {
if (!video || !video.paused || video.ended || attempt > 5 || hasReachedRequired) return;
video.play().catch(() => {
if (attempt === 2) video.muted = true;
setTimeout(() => forceAutoPlay(video, attempt + 1), 1500);
});
}
// ★ 快退补偿:视频快播完但学时不够,往回拉进度条
function checkAndRewind(video) {
if (!video || video.paused || video.ended) return;
if (!isFinite(video.duration) || video.duration <= 0) return;
const remaining = video.duration - video.currentTime;
// 剩余不到60秒,且学时不够
if (remaining < 60 && lastRequired > lastViewed) {
const minutesNeeded = lastRequired - lastViewed;
const rewindSeconds = Math.min(minutesNeeded * 60, video.duration * 0.8);
const targetTime = Math.max(0, video.duration - rewindSeconds - 10);
log('学时不足(' + lastViewed + '/' + lastRequired + '),视频剩余' + Math.floor(remaining) + '秒,快退至' + Math.floor(targetTime) + '秒');
video.currentTime = targetTime;
if (video.paused) forceAutoPlay(video);
}
}
function isNextButtonActive() {
const btn = document.querySelector('a.btn.next');
if (!btn || btn.classList.contains('disable')) return false;
const s = getComputedStyle(btn);
return !(s.cursor === 'not-allowed' || parseFloat(s.opacity) < 0.5);
}
function performGoToNext() {
if (!isNextButtonActive()) { checkCourseFinished(); return; }
if (typeof goNext === 'function') goNext();
else { const btn = document.querySelector('a.btn.next'); if (btn) btn.click(); }
hasReachedRequired = false;
}
function checkCourseFinished() {
if (!isNextButtonActive()) {
log('课程结束,返回用户中心');
window.location.href = 'https://train.haoyuinfo.com/userCenter';
} else {
performGoToNext();
}
}
// ========== 主循环 ==========
function setupCoursePage() {
// 弹窗监控
new MutationObserver(closePopup).observe(document.body, { childList: true, subtree: true });
setInterval(closePopup, 1500);
// 创建面板
createStudyPanel();
// 核心守护循环
setInterval(() => {
updateStudyPanel();
if (hasReachedRequired) return;
// 跳转条件1:已完成标签
if (checkCompletedLabel()) {
hasReachedRequired = true;
log('检测到已完成标签,跳转');
const video = getActiveVideo();
if (video && !video.paused) video.pause();
closePopup();
setTimeout(performGoToNext, 300);
return;
}
// 跳转条件2:学时数字达标
if (checkNumberReached()) {
hasReachedRequired = true;
log('学时数字达标,跳转');
const video = getActiveVideo();
if (video && !video.paused) video.pause();
closePopup();
setTimeout(performGoToNext, 300);
return;
}
// 视频守护
const video = getActiveVideo();
if (!video) return;
// 暂停恢复
if (video.paused && !video.ended) {
forceAutoPlay(video);
}
// 快退补偿
if (!video.paused && !video.ended) {
checkAndRewind(video);
}
}, 1000);
// 初始视频
const video = getActiveVideo();
if (video) {
if (video.readyState >= 2) forceAutoPlay(video);
else video.addEventListener('canplay', () => forceAutoPlay(video), { once: true });
video.addEventListener('ended', () => {
log('视频播放结束');
closePopup();
setTimeout(performGoToNext, 2000);
});
}
}
// ========== 路由 ==========
const url = window.location.href;
if (url.includes('/userCenter')) {
setTimeout(autoEnterCourse, 1500);
} else if (url.includes('/study/course')) {
setupCoursePage();
}
})();