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