// ==UserScript== // @name 华医网-广西基层培训自动播放助手(纯免费) // @namespace https://local.hyw-assist/ // @version 1.0.1 // @description 自动关闭华医网课程播放页常见弹窗;视频正常播放结束后自动进入下一课。不读取登录凭据,不伪造学习进度。 // @match *://*.91huayi.com/* // @run-at document-start // @noframes // @grant none // ==/UserScript== (function () { 'use strict'; const CONFIG = { autoClosePopup: true, autoNext: true, autoPlay: true, nextDelayMs: 2500, popupIntervalMs: 800 }; const state = { courseList: [], currentCourseId: getQueryParam('courseware_id'), lastEndedAt: 0 }; const COURSE_PLAY_PAGE = /\/exercise\/exercisecourse\/courseplay/i; function getQueryParam(name) { try { return new URLSearchParams(location.search).get(name) || ''; } catch (error) { return ''; } } function log(...args) { console.log('[华医网学习助手]', ...args); } function isCoursePlayPage() { return COURSE_PLAY_PAGE.test(location.pathname); } function normalizeCourseList(data) { if (Array.isArray(data)) return data; if (data && Array.isArray(data.data)) return data.data; if (data && Array.isArray(data.list)) return data.list; return []; } function hookCourseListRequest() { const origOpen = XMLHttpRequest.prototype.open; const origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url) { this.__hywUrl = String(url || ''); return origOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function () { this.addEventListener('load', function () { try { const url = this.__hywUrl || ''; if (url.indexOf('/OtherCourserList') === -1) return; const list = normalizeCourseList(JSON.parse(this.responseText)); if (list.length) { state.courseList = list; log('已读取课程列表:', list.length); } } catch (error) { // 忽略解析失败,不影响页面原有逻辑 } }); return origSend.apply(this, arguments); }; if (window.fetch) { const origFetch = window.fetch; window.fetch = function (input, init) { return origFetch.call(this, input, init).then(function (response) { try { const url = typeof input === 'string' ? input : (input && input.url) || ''; if (url.indexOf('/OtherCourserList') === -1) return response; response.clone().text().then(function (text) { try { const list = normalizeCourseList(JSON.parse(text)); if (list.length) { state.courseList = list; log('已读取课程列表:', list.length); } } catch (error) { // 忽略解析失败 } }); } catch (error) { // 忽略钩子异常 } return response; }); }; } } function getCurrentCourseItem() { const key = state.currentCourseId; return state.courseList.find(function (item) { return item.LearningPlan_Courseware_Id === key || item.Courseware_Id === key; }) || null; } function getNextCourseItem() { const current = getCurrentCourseItem(); if (!current || !state.courseList.length) return null; const planId = current.LearningPlan_Id; const pool = planId ? state.courseList.filter(function (item) { return item.LearningPlan_Id === planId; }) : state.courseList; const sorted = pool.slice().sort(function (a, b) { return (a.List_Order || 0) - (b.List_Order || 0); }); const index = sorted.findIndex(function (item) { return item.LearningPlan_Courseware_Id === current.LearningPlan_Courseware_Id || item.Courseware_Id === current.Courseware_Id; }); if (index >= 0 && index + 1 < sorted.length) return sorted[index + 1]; return null; } function isVisible(element) { if (!element) return false; const style = window.getComputedStyle(element); if (!style) return false; if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false; const rect = element.getBoundingClientRect(); return rect.width > 0 || rect.height > 0; } function findVisible(selector) { const nodes = document.querySelectorAll(selector); for (const node of nodes) { if (isVisible(node)) return node; } return null; } function clickTextButton(root, texts) { const candidates = root.querySelectorAll('button, a, label, div, span'); for (const candidate of candidates) { const text = (candidate.innerText || candidate.textContent || '').trim(); if (texts.indexOf(text) !== -1 && isVisible(candidate)) { candidate.click(); return true; } } return false; } function looksLikeOverlay(element) { const style = window.getComputedStyle(element); const rect = element.getBoundingClientRect(); return (style.position === 'fixed' || style.position === 'absolute') && rect.width > 0 && rect.height > 0 && (parseInt(style.zIndex, 10) || 0) >= 1000; } function closePopups() { if (!CONFIG.autoClosePopup) return; const facePopup = findVisible('.popup_box'); if (facePopup) { if (clickTextButton(facePopup, ['进入课程学习', '继续学习'])) return; const faceClose = facePopup.querySelector('.content_close img, .content_close'); if (faceClose && isVisible(faceClose)) { faceClose.click(); return; } facePopup.style.display = 'none'; } const reviewPopup = findVisible('.dialog_box_pj'); if (reviewPopup) { const reviewClose = reviewPopup.querySelector('.diaheader_box_pj img, .diaheader_box_pj'); if (reviewClose && isVisible(reviewClose)) { reviewClose.click(); return; } reviewPopup.style.display = 'none'; } const mask = findVisible('.mask'); if (mask) mask.style.display = 'none'; const tipOk = document.getElementById('TipMsgOk'); if (tipOk && isVisible(tipOk)) { const tipBox = tipOk.closest('[class*="dialog"],[class*="popup"],.mask'); if (tipBox && isVisible(tipBox)) tipOk.click(); } const overlays = document.querySelectorAll( '[class*="popup"],[class*="dialog"],[class*="modal"],[class*="layer"],[class*="mask"]' ); for (const overlay of overlays) { if (!isVisible(overlay) || !looksLikeOverlay(overlay)) continue; if (overlay.classList.contains('popup_box') || overlay.classList.contains('dialog_box_pj') || overlay.classList.contains('mask')) continue; const closeButton = overlay.querySelector('[class*="close"], .layui-layer-close'); if (closeButton && isVisible(closeButton)) { closeButton.click(); continue; } overlay.style.display = 'none'; } } function clickCourseById(courseId) { if (!courseId) return false; const nodes = document.querySelectorAll('[onclick]'); for (const node of nodes) { const onclick = node.getAttribute('onclick') || ''; if (onclick.indexOf('VideoPlay') !== -1 && onclick.indexOf(courseId) !== -1) { node.click(); return true; } } return false; } function clickNextFromDom() { const nodes = Array.prototype.slice.call(document.querySelectorAll('[onclick*="VideoPlay"]')); if (!nodes.length || !state.currentCourseId) return false; const index = nodes.findIndex(function (node) { return (node.getAttribute('onclick') || '').indexOf(state.currentCourseId) !== -1; }); if (index < 0 || index + 1 >= nodes.length) return false; nodes[index + 1].click(); return true; } function navigateToCourse(courseId) { if (!courseId) return; location.href = '/exercise/ExerciseCourse/CoursePlay?courseware_id=' + encodeURIComponent(courseId); } function goToNextCourse() { if (!CONFIG.autoNext) return; const next = getNextCourseItem(); if (next && clickCourseById(next.LearningPlan_Courseware_Id || next.Courseware_Id)) { log('已点击下一课:', next.Courseware_Name || next.LearningPlan_Courseware_Id); return; } if (clickNextFromDom()) { log('已点击下一课(DOM)'); return; } if (next) { log('正在跳转下一课:', next.Courseware_Name || next.LearningPlan_Courseware_Id); navigateToCourse(next.LearningPlan_Courseware_Id || next.Courseware_Id); } else { log('未找到下一课,可能需要先刷新课程列表'); } } function tryPlay(video) { if (!video || !video.paused) return; const promise = video.play(); if (promise && promise.catch) promise.catch(function () {}); } function handleVideoEnded() { const now = Date.now(); if (now - state.lastEndedAt < 5000) return; state.lastEndedAt = now; log('当前视频播放完成,稍后进入下一课'); setTimeout(goToNextCourse, CONFIG.nextDelayMs); } function watchVideoPlayer() { let currentVideo = null; function bindVideo(video) { video.addEventListener('ended', handleVideoEnded); video.addEventListener('loadedmetadata', function () { if (CONFIG.autoPlay) tryPlay(video); }); video.addEventListener('timeupdate', function () { if (video.duration && video.currentTime >= video.duration - 1 && video.readyState >= 3) { handleVideoEnded(); } }); } function scan() { const playerBox = document.getElementById('ccVideo-box') || document; const video = playerBox.querySelector('video'); if (video && video !== currentVideo) { currentVideo = video; bindVideo(video); if (CONFIG.autoPlay) tryPlay(video); } } scan(); new MutationObserver(scan).observe(document.body, { childList: true, subtree: true }); } function createUI() { const box = document.createElement('div'); box.id = 'hyw-course-assist'; box.innerHTML = '