您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动播放、跳过弹窗、精准跳转下一课(加强鲁棒性,适配多种DOM与文案)
// ==UserScript== // @name 华医网助手(自动下一课增强版)v3.2.0 // @namespace http://tampermonkey.net/ // @version 3.2.0 // @description 自动播放、跳过弹窗、精准跳转下一课(加强鲁棒性,适配多种DOM与文案) // @author Yik Liu (Enhanced) // @match *://*.91huayi.com/course_ware/course_ware_polyv.aspx?* // @match *://*.91huayi.com/course_ware/course_list.aspx?* // @match https://cme28.91huayi.com/pages/exam_result.aspx?cwid=* // @grant none // @run-at document-idle // ==/UserScript== (function () { 'use strict'; let examErrorCount = 0; const CONTACT_IMG_SIZE = 180; // ===== 限刷计数:每刷三节就停止 ===== const LIMIT_KEY = 'HY_CLASS_LIMIT_COUNT'; const LIMIT_MAX = 3; const Limit = { get() { try { return parseInt(sessionStorage.getItem(LIMIT_KEY) || '0', 10); } catch { return 0; } }, set(v) { try { sessionStorage.setItem(LIMIT_KEY, String(v)); } catch {} }, inc() { const n = this.get() + 1; this.set(n); updateLimitDisplay(); return n; }, reached() { return this.get() >= LIMIT_MAX; }, reset() { this.set(0); updateLimitDisplay(); } }; // 如果是用户刷新(reload),则重置计数;正常页面跳转(navigate)不重置 try { const nav = (performance.getEntriesByType && performance.getEntriesByType('navigation')[0]) || null; if (nav && nav.type === 'reload') { Limit.reset(); } } catch {} function updateLimitDisplay() { const el = document.getElementById('h-limit'); if (el) el.innerText = `限刷: ${Limit.get()}/${LIMIT_MAX}`; } function createStatusPanel() { if (document.getElementById('huayi-status')) return; const panel = document.createElement('div'); panel.id = 'huayi-status'; panel.style.cssText = ` position: fixed; right: 10px; bottom: 10px; z-index: 999999; background: rgba(0,0,0,0.75); color: #fff; padding: 10px 12px; border-radius: 10px; font-size: 14px; max-width: 300px; line-height: 1.4; backdrop-filter: blur(2px); `; panel.innerHTML = ` <div><b>华医网助手状态 (v3.2.0)</b></div> <div id="h-status">状态: 启动中</div> <div id="h-action">操作: -</div> <div id="h-exam">考试按钮: -</div> <div id="h-error">异常检测: 0 次</div> <div id="h-title">当前视频: -</div> <div id="h-limit">限刷: 0/${LIMIT_MAX}</div> <div style="margin-top:6px; display:flex; gap:6px;"> <button id="h-next" style="padding:4px 8px;border-radius:6px;border:0;background:#00a0f6;color:#fff;cursor:pointer;">强制下一课</button> <button id="h-hide" style="padding:4px 8px;border-radius:6px;border:0;background:#666;color:#fff;cursor:pointer;">隐藏面板</button> <button id="h-reset" style="padding:4px 8px;border-radius:6px;border:0;background:#999;color:#fff;cursor:pointer;">清零</button> </div> `; document.body.appendChild(panel); document.getElementById('h-next').onclick = () => autoJumpToLearningVideo(); document.getElementById('h-hide').onclick = () => panel.remove(); document.getElementById('h-reset').onclick = () => Limit.reset(); updateLimitDisplay(); } function updateStatusPanel(status, action, exam, errors, title) { const set = (id, text) => { const el = document.getElementById(id); if (el) el.innerText = text; }; set('h-status', `状态: ${status}`); set('h-action', `操作: ${action}`); set('h-exam', `考试按钮: ${exam}`); set('h-error', `异常检测: ${errors} 次`); set('h-title', `当前视频: ${title}`); } function autoSkipPopup() { const tryClose = () => { try { document.querySelector('.pv-ask-skip')?.click(); document.querySelector('.signBtn')?.click(); document.querySelector("button[onclick='closeProcessbarTip()']")?.click(); document.querySelector('button.btn_sign')?.click(); if (document.querySelector('#floatTips')?.style.display !== 'none') { window.closeFloatTips?.(); } document.querySelector('.el-message-box__btns .el-button--primary')?.click(); document.querySelector('.el-dialog__footer .el-button--primary')?.click(); } catch (e) {} }; tryClose(); setInterval(tryClose, 2000); } function autoPlayVideo() { const video = document.querySelector('video'); if (!video) return; video.muted = true; video.volume = 0; const ensurePlay = () => { if (video.paused) video.play().catch(() => {}); }; ensurePlay(); const playInterval = setInterval(ensurePlay, 1000); video.addEventListener('play', () => { updateStatusPanel('监控中', '播放中', '检测中', examErrorCount, document.title); }); document.addEventListener('visibilitychange', () => { if (!document.hidden) ensurePlay(); }); attachEndedFallback(); } function attachEndedFallback() { const v = document.querySelector('video'); if (!v) return; v.addEventListener( 'ended', () => { setTimeout(autoJumpToLearningVideo, 1000); }, { once: true } ); } function toAbsoluteUrl(maybeUrl) { if (!maybeUrl) return ''; if (/^https?:\/\//i.test(maybeUrl)) return maybeUrl; if (/^javascript:/i.test(maybeUrl)) return ''; const a = document.createElement('a'); a.href = maybeUrl; return a.href; } function extractUrlFromOnclick(onclickStr) { if (!onclickStr) return ''; let m = onclickStr.match(/(['"])(https?:\/\/[^'"]*course_ware[^'"]*\.aspx\?[^'"]*)\1/i) || onclickStr.match(/(['"])(https?:\/\/[^'"]*course_ware[^'"]*\.aspx)\1/i); if (m) return m[2] || m[1]; m = onclickStr.match(/(course_ware[^'"]*\.aspx\?[^'"]*)/i) || onclickStr.match(/(course_ware[^'"]*\.aspx)/i); if (m) return m[1]; return ''; } function autoJumpToLearningVideo() { if (Limit.reached()) { updateStatusPanel('已暂停', '已达到限刷上限', '-', examErrorCount, document.title); return; } updateStatusPanel('跳转中', '查找下一个课程...', '-', examErrorCount, document.title); const docs = [document]; try { if (window.top && window.top !== window && window.top.document) { docs.push(window.top.document); } } catch (e) {} const itemSelectors = [ 'li.lis-inside-content', 'li.lis_content', '.lis-inside-content li', '.lis-content li', 'li[class*="lis"]' ]; const nextableTexts = ['学习中', '继续学习', '立即学习']; const doneOrExamTexts = ['待考试', '已完成', '考试', '合格']; const currentCwid = new URLSearchParams(window.location.search).get('cwid'); let items = []; for (const d of docs) { for (const sel of itemSelectors) { const found = Array.from(d.querySelectorAll(sel)); if (found.length) { items = found; break; } } if (items.length) break; } if (!items.length) { updateStatusPanel('错误', '未找到课程列表', '-', examErrorCount, document.title); return; } const isNextable = (el) => { const btn = el.querySelector('button, a, .state_btn, .state_lis_btn, [role="button"], input[type="button"], input[type="submit"]'); const text = (btn?.innerText || btn?.value || el.innerText || '').replace(/\s/g, ''); if (nextableTexts.some((t) => text.includes(t))) return true; if (btn) { const cs = getComputedStyle(btn); const bg = cs.backgroundColor || cs.background; if (/rgb\s*\(\s*0\s*,\s*160\s*,\s*246\s*\)/i.test(bg)) return true; if (/#00a0f6/i.test(cs.background || '')) return true; } return false; }; let currentIndex = -1; if (currentCwid) { currentIndex = items.findIndex((item) => { const cand = [ item.querySelector('h2[onclick]'), item.querySelector('[onclick*="cwid="]'), item.querySelector('a[href*="cwid="]') ].filter(Boolean); return cand.some((el) => { const s = el.getAttribute('onclick') || el.getAttribute('href') || ''; return s.includes(`cwid=${currentCwid}`); }); }); } const start = currentIndex !== -1 ? currentIndex + 1 : 0; let nextTarget = null; for (let i = start; i < items.length; i++) { const rawText = (items[i].innerText || '').replace(/\s/g, ''); if (doneOrExamTexts.some((t) => rawText.includes(t))) continue; if (isNextable(items[i])) { nextTarget = items[i]; break; } } if (!nextTarget) { for (let i = start; i < items.length; i++) { const rawText = (items[i].innerText || '').replace(/\s/g, ''); if (/未学习|去学习|开始学习/.test(rawText)) { nextTarget = items[i]; break; } } } if (!nextTarget) { updateStatusPanel('待命', '无更多课程可学习', '-', examErrorCount, document.title); return; } const urlCands = [ nextTarget.querySelector('a[href*="course_ware_polyv.aspx"]'), nextTarget.querySelector('[onclick*="course_ware_polyv.aspx"]'), nextTarget.querySelector('a[href*="course_ware.aspx"]'), nextTarget.querySelector('[onclick*="course_ware.aspx"]') ].filter(Boolean); let targetUrl = ''; for (const el of urlCands) { const href = el.getAttribute('href') || ''; const onclick = el.getAttribute('onclick') || ''; if (/course_ware.*\.aspx/i.test(href)) { targetUrl = href; } else { const u = extractUrlFromOnclick(onclick); if (u) targetUrl = u; } if (targetUrl) break; } if (targetUrl) { targetUrl = toAbsoluteUrl(targetUrl); if (targetUrl) { const count = Limit.inc(); updateStatusPanel('跳转中', `第 ${count}/${LIMIT_MAX} 节,准备跳转`, '-', examErrorCount, document.title); location.href = targetUrl; return; } } const clickable = nextTarget.querySelector('h2[onclick], a, button, [role="button"], input[type="button"], input[type="submit"]'); if (clickable) { const count = Limit.inc(); updateStatusPanel('跳转中', `第 ${count}/${LIMIT_MAX} 节,未抓到URL,尝试点击元素`, '-', examErrorCount, document.title); try { clickable.dispatchEvent(new MouseEvent('click', { bubbles: true })); } catch (e) { clickable.click?.(); } } else { updateStatusPanel('错误', '找到课程但无法点击或提取URL', '-', examErrorCount, document.title); } } function observeStatusChange() { const targetNode = document.querySelector('.lis-content') || document.querySelector('.lis-inside-content') || document.body; if (!targetNode) return; const observer = new MutationObserver((mutationsList) => { for (const m of mutationsList) { if (m.type === 'characterData') { const btn = m.target?.parentElement; if (btn && /button|a/i.test(btn.tagName)) { const t = (btn.innerText || '').replace(/\s/g, ''); if (t.includes('待考试') || t.includes('已完成')) { updateStatusPanel('监控中', '课程完成,准备跳转', '检测中', examErrorCount, document.title); setTimeout(autoJumpToLearningVideo, 1500); observer.disconnect(); break; } } } if (m.type === 'attributes' && m.attributeName === 'style') { const el = m.target; if (el && /button|a/i.test(el.tagName)) { const cs = getComputedStyle(el); const bg = cs.backgroundColor || ''; if (/rgb\s*\(\s*25\d\s*,\s*10\d\s*,\s*10\d\s*\)/.test(bg) || /rgb\s*\(\s*253\s*,\s*103\s*,\s*103\s*\)/.test(bg)) { updateStatusPanel('监控中', '状态更新,准备跳转', '检测中', examErrorCount, document.title); setTimeout(autoJumpToLearningVideo, 1500); observer.disconnect(); break; } } } } }); observer.observe(targetNode, { childList: true, subtree: true, characterData: true, attributes: true }); } function clickFirstImmediateLearn() { const btns = [ ...document.querySelectorAll('input.state_lis_btn, input[type="button"], input[type="submit"]'), ...document.querySelectorAll('button, a[role="button"], a.el-button') ]; for (const btn of btns) { const val = (btn.value || btn.innerText || '').trim().replace(/\s/g, ''); if (val.includes('立即学习') || val.includes('继续学习') || val.includes('去学习')) { btn.click(); return; } } } function createContactPanel() { if (document.getElementById('hy-contact-panel')) return; const panel = document.createElement('div'); panel.id = 'hy-contact-panel'; panel.style.cssText = ` position: fixed; left: 10px; bottom: 10px; z-index: 999998; background: rgba(255,255,255,0.92); color: #000; padding: 10px; border-radius: 10px; font-size: 14px; box-shadow: 0 0 8px rgba(0,0,0,0.2); text-align: center; `; panel.innerHTML = ` <div style="font-weight: bold; color: red; font-size: 16px;">如果没有时间可加v:Yyyyylaj</div> <div style="display:flex; gap:8px; align-items:center; justify-content:center; margin-top:5px;"> <img src="https://i.ibb.co/d0fRsHkY/20250814140655-55-266.jpg" alt="打款码1" width="180" style="border-radius:10px;" /> <img src="https://i.ibb.co/FbmyTqhC/20250814140916-56-266.jpg" alt="打款码2" width="180" style="border-radius:10px;" /> </div> <div style="margin-top:5px; font-size: 12px;">创作不易,感谢支持 ☕</div> `; document.body.appendChild(panel); } function init() { const url = location.href; createStatusPanel(); createContactPanel(); if (/course_ware\/course_ware_polyv\.aspx/i.test(url)) { setTimeout(() => { const originalOnPlayOver = window.s2j_onPlayOver; window.s2j_onPlayOver = function () { try { if (typeof originalOnPlayOver === 'function') { originalOnPlayOver.apply(this, arguments); } } catch (e) {} setTimeout(() => autoJumpToLearningVideo(), 1000); }; autoSkipPopup(); if (document.querySelector('video')) { autoPlayVideo(); updateStatusPanel('运行中', '初始化完成', '检测中', examErrorCount, document.title); } else { updateStatusPanel('运行中', '未检测到视频,等待中', '检测中', examErrorCount, document.title); } observeStatusChange(); }, 1500); } if (/course_ware\/course_list\.aspx/i.test(url)) { setTimeout(autoJumpToLearningVideo, 2000); } if (/\/exam_result\.aspx/i.test(url)) { setTimeout(clickFirstImmediateLearn, 1500); } } init(); })();