// ==UserScript== // @name 江西省执业药师管理平台刷课助手(zhjg.mpa.jiangxi.gov.cn) // @version 1.0 // @description 江西省执业药师管理平台刷课助手,倍速+静音+自动播放+进度记忆,自动遍历iframe/shadow DOM,客服微信wkds995 // @author 晓晓 // @match *://zhjg.mpa.jiangxi.gov.cn/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; var SETTINGS_KEY = 'speed_settings'; var defaults = { speed: 2, muted: true, autoplay: true, remember: true }; var S = Object.assign({}, defaults, GM_getValue(SETTINGS_KEY, {})); function save() { GM_setValue(SETTINGS_KEY, S); } // ==================== 工具 ==================== function $1(s, ctx) { return (ctx || document).querySelector(s); } function $A(s, ctx) { return Array.from((ctx || document).querySelectorAll(s)); } function log(msg) { var el = $1('#sp-log'); if (el) { el.value += '[' + new Date().toLocaleTimeString('zh-CN', { hour12: false }) + '] ' + msg + '\n'; el.scrollTop = el.scrollHeight; } console.log('[倍速]', msg); } // ==================== Shadow DOM ==================== function* getShadowRoots(root) { if (!root) return; var walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); var node; while (node = walker.nextNode()) { if (node.shadowRoot) { yield node.shadowRoot; yield* getShadowRoots(node.shadowRoot); } } } function qsa(selector, ctx) { ctx = ctx || document; var r = $A(selector, ctx); for (var sr of getShadowRoots(ctx)) { r = r.concat($A(selector, sr)); } return r; } // ==================== 视频控制 ==================== function applyToAllVideos(action) { qsa('video').forEach(function(v) { try { action(v); } catch (e) {} }); $A('iframe').forEach(function(frame) { try { qsa('video', frame.contentDocument || frame.contentWindow.document).forEach(action); } catch (e) {} }); } function applySpeed(v) { if (v.playbackRate !== S.speed) v.playbackRate = S.speed; } function applyMuted(v) { if (v.muted !== S.muted) v.muted = S.muted; } function applyAutoplay(v) { if (S.autoplay && v.paused && v.readyState >= 3) v.play().catch(function(){}); } function applyAll() { applyToAllVideos(function(v) { applySpeed(v); applyMuted(v); applyAutoplay(v); }); } // ==================== 进度记忆 ==================== function saveProgress() { if (!S.remember) return; qsa('video').forEach(function(v) { if (v.duration > 5 && v.currentTime > 1) { var key = 'sp_progress_' + location.host + '_' + v.src; GM_setValue(key, v.currentTime); } }); } function restoreProgress() { if (!S.remember) return; applyToAllVideos(function(v) { var key = 'sp_progress_' + location.host + '_' + v.src; var t = GM_getValue(key, 0); if (t > 1 && t < v.duration - 3) { v.currentTime = t; log('进度已恢复: ' + Math.floor(t/60) + '分' + (t%60).toFixed(0) + '秒'); } }); } // ==================== UI面板 ==================== function createPanel() { if ($1('#sp-panel')) return; var el = document.createElement('div'); el.id = 'sp-panel'; el.style.cssText = [ 'position:fixed;top:20px;right:20px;z-index:999999;', 'background:linear-gradient(135deg,#1a1a2e,#16213e);', 'color:#e0e0e0;padding:14px 18px;border-radius:12px;', 'min-width:300px;box-shadow:0 8px 32px rgba(0,0,0,0.5);', 'font-family:"PingFang SC","Microsoft YaHei",sans-serif;', 'border:1px solid rgba(255,255,255,0.08);font-size:13px;' ].join(''); el.innerHTML = '
' + '▶' + '
江西省药师管理平台刷课助手
' + '
● 运行中
' + '
' + '
' + '
倍速: ' + S.speed + 'x
' + '' + '
' + '0.5x16x
' + '
' + '
' + '' + '' + '
' + '
' + '' + '
' + '
未检测到视频
' + '
' + '' + '
' + '' + '
' + '客服微信:wkds995
'; document.body.appendChild(el); // 倍速滑块 $1('#sp-speed').addEventListener('input', function () { S.speed = parseFloat(this.value); $1('#sp-speed-val').textContent = S.speed + 'x'; save(); applyToAllVideos(applySpeed); log('倍速调节: ' + S.speed + 'x'); }); // 静音 $1('#sp-muted-btn').addEventListener('click', function () { S.muted = !S.muted; this.style.background = S.muted ? '#ef4444' : '#374151'; this.innerHTML = S.muted ? '☕ 已静音' : '🔊 开启声音'; save(); applyToAllVideos(applyMuted); log(S.muted ? '已静音' : '开启声音'); }); // 自动播放 $1('#sp-ap-btn').addEventListener('click', function () { S.autoplay = !S.autoplay; this.style.background = S.autoplay ? '#10b981' : '#374151'; this.innerHTML = S.autoplay ? '▶ 已自动播放' : '⏸ 手动播放'; save(); log(S.autoplay ? '自动播放开启' : '手动播放'); }); // 记忆进度 $1('#sp-rem-btn').addEventListener('click', function () { S.remember = !S.remember; this.style.background = S.remember ? '#3b82f6' : '#374151'; this.innerHTML = S.remember ? '📋 记忆进度ON' : '📋 记忆进度OFF'; save(); log(S.remember ? '记忆进度开启' : '记忆进度关闭'); }); // 打赏按钮 $1('#sp-donate-btn').addEventListener('click', function () { window.__popupTemplate.donate('https://i.postimg.cc/QdhkjkFG/wxpay.png', '自愿打赏,您的支持是持续更新的动力'); }); } // ==================== 进度显示 ==================== function updateProgress() { var el = $1('#sp-progress'); if (!el) return; var videos = qsa('video'); if (videos.length === 0) { el.textContent = '未检测到视频'; return; } var parts = []; videos.forEach(function(v, i) { if (v.duration > 0) { var pct = Math.round((v.currentTime / v.duration) * 100); var cm = Math.floor(v.currentTime / 60), cs = Math.floor(v.currentTime % 60); var tm = Math.floor(v.duration / 60), ts = Math.floor(v.duration % 60); parts.push('视频' + (i + 1) + ': ' + pct + '% (' + cm + ':' + String(cs).padStart(2, '0') + '/' + tm + ':' + String(ts).padStart(2, '0') + ') ' + S.speed + 'x'); } }); el.textContent = parts.length > 0 ? parts.join(' | ') : '未检测到视频'; } // ==================== 主循环 ==================== var counter = 0; var saved = false; function loop() { counter++; applyAll(); if (S.remember && counter % 20 === 0) saveProgress(); if (counter % 10 === 0) updateProgress(); if (!saved && counter === 5) { saved = true; restoreProgress(); log('进度已恢复'); } } // ==================== 弹窗模板(标准4条须知,打赏需点按钮)==================== (function(){ function injectStyles() { if (document.getElementById('xx-popup-styles')) return; var s = document.createElement('style'); s.id = 'xx-popup-styles'; s.textContent = [ '.xx-mask{position:fixed;inset:0;background:rgba(0,0,0,0.75);z-index:99999998;display:flex;align-items:center;justify-content:center;}', '.xx-box{background:#181c2b;border-radius:16px;border:1px solid rgba(255,255,255,0.12);padding:28px;color:#fff;font-family:system-ui,-apple-system,sans-serif;}', '.xx-box h2{margin:0 0 18px;font-size:18px;text-align:center;border-bottom:1px solid rgba(255,255,255,0.1);padding-bottom:14px;color:#fff;}', '.xx-content{line-height:1.8;font-size:14px;color:#e2e8f0;}', '.xx-content p{margin:6px 0;}', '.xx-footer{margin-top:22px;text-align:center;}', '.xx-btn{padding:11px 36px;border:none;border-radius:10px;cursor:pointer;font-size:14px;font-weight:600;transition:opacity .2s;}', '.xx-btn:hover{opacity:0.88;}', '.xx-btn-primary{background:linear-gradient(135deg,#6a11cb,#2575fc);color:#fff;}', '.xx-btn-secondary{background:rgba(255,255,255,0.08);color:#aaa;border:1px solid rgba(255,255,255,0.15);}', '.xx-donate{width:360px;}', '.xx-donate h3{margin:0 0 14px;font-size:16px;text-align:center;letter-spacing:.5px;color:#fff;}', '.xx-wechat-label{text-align:center;font-size:13px;color:#c9d1d9;margin-bottom:10px;letter-spacing:1px;}', '.xx-qr-wrap{width:220px;height:220px;background:#fff;border-radius:12px;margin:0 auto 12px;display:flex;align-items:center;justify-content:center;overflow:hidden;}', '.xx-qr-wrap img{width:200px;height:200px;border-radius:8px;object-fit:cover;}', '.xx-donate-desc{font-size:12px;color:#c9d1d9;margin-bottom:16px;text-align:center;line-height:1.6;}' ].join(''); document.head.appendChild(s); } function createGuidePopup(items, btnText, onClose) { if (document.querySelector('.xx-guide-mask')) return; btnText = btnText || '我已知晓'; onClose = onClose || function(){}; injectStyles(); var lines = items.map(function(item){ var icon = item.ok !== false ? '\u2705' : '\u26a0\ufe0f'; return '

' + icon + ' ' + item.text + '

'; }).join(''); var mask = document.createElement('div'); mask.className = 'xx-mask xx-guide-mask'; mask.innerHTML = '

📖 脚本使用须知

' + lines + '
'; document.body.appendChild(mask); function close(){ mask.remove(); onClose(); } mask.querySelector('.guide-close-btn').onclick = close; mask.onclick = function(e){ if(e.target === mask) close(); }; } function createDonatePopup(wxQrUrl, desc, onClose) { if (document.querySelector('.xx-donate-mask')) return; onClose = onClose || function(){}; injectStyles(); var descHtml = desc ? '
' + desc + '
' : '
自愿打赏,您的支持是持续更新的动力
'; var mask = document.createElement('div'); mask.className = 'xx-mask xx-donate-mask'; mask.innerHTML = '

💰 感谢支持脚本持续维护更新

微信支付
微信收款码
' + descHtml + '
'; document.body.appendChild(mask); function close(){ mask.remove(); onClose(); } mask.querySelector('.donate-close-btn').onclick = close; mask.onclick = function(e){ if(e.target === mask) close(); }; } window.__popupTemplate = { guide: createGuidePopup, donate: createDonatePopup }; })(); // ==================== 入口 ==================== function main() { createPanel(); var T = window.__popupTemplate; T.guide([ { ok: true, text: '脚本说明:本脚本仅作为辅助,自行承担全部使用风险' }, { ok: true, text: '速度控制:需自行判断,合理控制速度,规避风控' }, { ok: true, text: '💰打赏按钮:自愿支持,不影响脚本任何功能' }, { ok: false, text: '请保持页面前台运行,不要频繁刷新页面' } ], '我已知晓'); log('江西省药师管理刷课助手已启动'); log('当前倍速: ' + S.speed + 'x | 静音: ' + (S.muted ? '是' : '否') + ' | 自动: ' + (S.autoplay ? '是' : '否') + ' | 记忆: ' + (S.remember ? '是' : '否')); log('请点上方演示区滑块调节倍速'); setInterval(loop, 500); window.addEventListener('beforeunload', saveProgress); } if (document.readyState === 'complete') { main(); } else { window.addEventListener('load', main); } })();