// ==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(); } })();