// ==UserScript== // @name 江西省专业技术人员继续教育网络平台-江西现代专技继续教育网-自动刷课助手(jiangxi.chinamde.cn) // @version 1.0.0 // @description 江西省专业技术人员继续教育网络平台(jiangxi.chinamde.cn)专用版:解除视频限制+自动下一集+自动切换课程+倍速+防暂停 // @author 晓晓 // @match *://jiangxi.chinamde.cn/* // @exclude *://jiangxi.chinamde.cn/play* // @grant GM_getValue // @grant GM_setValue // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; // ==================== 站名映射 ==================== var SITE_HOST = location.hostname.replace(/^www\./, '').toLowerCase(); var SITE_NAME = '江西省专业技术人员继续教育网络平台'; // ==================== 配置 ==================== var SETTINGS_KEY = 'mde_settings'; var defaults = { speed: 2, muted: true, autoplay: true, autonext: true, autopark: true, freeunlock: 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, type) { type = type || 'info'; var prefix = { error: '❌', success: '✅', warning: '⚠️' }[type] || '🔍'; console.log('[' + SITE_NAME + ']', prefix, msg); var el = $1('#mde-log'); if (el) { var t = new Date().toLocaleTimeString('zh-CN', { hour12: false }); el.value += '[' + t + '] ' + msg + '\n'; el.scrollTop = el.scrollHeight; } } // ==================== UI面板 ==================== function createPanel() { if ($1('#mde-panel')) return; var el = document.createElement('div'); el.id = 'mde-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 = '
' + '🌍' + '
' + SITE_NAME + '
' + '
● 运行中
' + '
' + '
倍速: ' + S.speed + 'x
' + '' + '
0.5x16x
' + '
' + '' + '' + '' + '
' + '
' + '' + '
' + '
请点击启动
' + '
' + '
' + '
' + '' + '
' + '' + '
' + '💬 ' + 'wkds995' + ' 客服微信' + '
'; document.body.appendChild(el); // 启动/暂停 $1('#mde-btn-toggle').addEventListener('click', function () { if (getRunning()) { setRunning(false); $1('#mde-badge').textContent = '○ 已暂停'; $1('#mde-badge').style.background = '#6b7280'; this.textContent = '▶ 开始刷课'; this.style.background = '#1890ff'; updateStatus('已暂停', 'warning'); } else { setRunning(true); $1('#mde-badge').textContent = '● 运行中'; $1('#mde-badge').style.background = '#10b981'; this.textContent = '⏸ 暂停刷课'; this.style.background = '#ef4444'; updateStatus('开始刷课...', 'success'); } }); // 倍速滑块 $1('#mde-speed').addEventListener('input', function () { S.speed = parseFloat(this.value); $1('#mde-speed-val').textContent = S.speed + 'x'; save(); applySpeedAll(); log('倍速: ' + S.speed + 'x', 'success'); }); // 静音 $1('#mde-muted-btn').addEventListener('click', function () { S.muted = !S.muted; this.style.background = S.muted ? '#ef4444' : '#374151'; this.innerHTML = S.muted ? '☕ 静音' : '🔊 有声'; save(); applyMutedAll(); log(S.muted ? '已静音' : '开启声音'); }); // 自动播放 $1('#mde-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('#mde-next-btn').addEventListener('click', function () { S.autonext = !S.autonext; this.style.background = S.autonext ? '#3b82f6' : '#374151'; this.innerHTML = S.autonext ? '➤ 下一集' : '⏮ 手动'; save(); log(S.autonext ? '自动下一集开启' : '手动下一集'); }); // 防暂停 $1('#mde-park-btn').addEventListener('click', function () { S.autopark = !S.autopark; this.style.background = S.autopark ? '#f59e0b' : '#374151'; var cb = this.querySelector('input'); if (cb) cb.checked = S.autopark; save(); log(S.autopark ? '防暂停开启' : '防暂停关闭'); if (S.autopark) hookPause(); }); } var STATUS_KEY = 'mde_running'; function getRunning() { return GM_getValue(STATUS_KEY, false); } function setRunning(v) { GM_setValue(STATUS_KEY, v); } function updateStatus(text, type) { var el = $1('#mde-status'); if (!el) return; el.textContent = text; var colors = { success: '#4ade80', warning: '#fbbf24', error: '#ef4444', info: '#60a5fa' }; el.style.color = colors[type] || '#4ade80'; } function updateProgress(text) { var el = $1('#mde-progress'); if (el) el.textContent = text; } function updateInfo(text) { var el = $1('#mde-info'); if (el) el.textContent = text; } // ==================== 视频控制 ==================== function findVideos() { var result = []; // 主文档 $A('video').forEach(function(v) { result.push(v); }); // iframe穿透 $A('iframe').forEach(function(frame) { try { $A('video', frame.contentDocument || frame.contentWindow.document).forEach(function(v) { result.push(v); }); } catch (e) {} }); return result; } function applySpeedAll() { findVideos().forEach(function(v) { if (Math.abs(v.playbackRate - S.speed) > 0.01) v.playbackRate = S.speed; }); } function applyMutedAll() { findVideos().forEach(function(v) { if (v.muted !== S.muted) v.muted = S.muted; }); } // ==================== 防暂停Hook ==================== function hookPause() { if (!S.autopark) return; // 西瓜播放器 (xgplayer) if (unsafeWindow.on_spark_player_pause !== undefined) { var _orig_pause = unsafeWindow.on_spark_player_pause; unsafeWindow.on_spark_player_pause = function() { log('xgplayer暂停事件拦截,重新播放', 'warning'); try { var v = findVideos()[0]; if (v && v.paused) v.play().catch(function(){}); } catch(e) {} if (typeof _orig_pause === 'function') _orig_pause.apply(this, arguments); }; } else { unsafeWindow.on_spark_player_pause = function() { log('xgplayer暂停事件,重新播放', 'warning'); try { var v = findVideos()[0]; if (v && v.paused) v.play().catch(function(){}); } catch(e) {} }; } if (unsafeWindow.on_spark_player_resume !== undefined) { var _orig_resume = unsafeWindow.on_spark_player_resume; unsafeWindow.on_spark_player_resume = function() { try { var v = findVideos()[0]; if (v && Math.abs(v.playbackRate - S.speed) > 0.01) v.playbackRate = S.speed; } catch(e) {} if (typeof _orig_resume === 'function') _orig_resume.apply(this, arguments); }; } else { unsafeWindow.on_spark_player_resume = function() { try { var v = findVideos()[0]; if (v && Math.abs(v.playbackRate - S.speed) > 0.01) v.playbackRate = S.speed; } catch(e) {} }; } // 通用 pause 事件 findVideos().forEach(function(v) { v.onpause = function() { if (!S.autopark || !getRunning()) return; log('视频暂停,自动恢复播放', 'warning'); setTimeout(function() { var vv = findVideos()[0]; if (vv && vv.paused) vv.play().catch(function(){}); }, 500); }; }); log('防暂停Hook已安装', 'success'); } // ==================== 视频完成检测 ==================== function isVideoComplete(v) { if (!v || !v.duration || isNaN(v.duration) || v.duration === 0) return false; return v.currentTime / v.duration >= 0.95; } // ==================== 自动下一集(xgplayer标准结构)==================== function autoNextVideo() { log('视频即将完成,尝试自动下一集...', 'warning'); // 方法1: 找"下一节"按钮 var nextBtns = $A('button'); for (var i = 0; i < nextBtns.length; i++) { var t = (nextBtns[i].textContent || '').trim(); if (t.includes('下一节') || t.includes('下一章') || t.includes('下一集') || t.includes('继续') || t.includes('下一')) { log('找到下一节按钮: ' + t, 'success'); nextBtns[i].click(); return true; } } // 方法2: 找 xgplayer 下一集按钮 var xgNext = $1('.xgplayer-next') || $1('.xgplayer-sideslip-next') || $1('[class*="next"]'); if (xgNext) { log('找到xgplayer下一集按钮', 'success'); xgNext.click(); return true; } // 方法3: 找课程目录中的下一个未完成项 var items = $A('li[class*="chapter"], li[class*="section"], li[class*="item"]'); for (var j = 0; j < items.length; j++) { var progress = items[j].querySelector('[class*="progress"], [class*="percent"], [class*="bar"]'); var text = items[j].textContent || ''; if (progress) { var pctText = progress.textContent || ''; if (!pctText.includes('100') && !pctText.includes('完成')) { var links = items[j].querySelectorAll('a'); for (var k = 0; k < links.length; k++) { var href = links[k].href; if (href && !href.includes('#') && href !== location.href) { log('跳转下一节: ' + (links[k].textContent || '').trim().substring(0, 30), 'success'); location.href = href; return true; } } } } } // 方法4: 刷新当前页(有些平台刷新自动进下一节) log('未找到下一节按钮,刷新尝试...', 'warning'); setTimeout(function() { location.reload(); }, 3000); return false; } // ==================== 自动换课程(课程列表页)==================== function autoSwitchCourse() { log('检测课程列表页,准备自动切换...', 'info'); // 找"开始学习"或"继续学习"按钮 var startBtns = $A('button, a'); for (var i = 0; i < startBtns.length; i++) { var t = (startBtns[i].textContent || '').trim(); if (t.includes('开始学习') || t.includes('继续学习')) { log('找到开始/继续按钮: ' + t, 'success'); startBtns[i].click(); return true; } } return false; } // ==================== 主循环 ==================== var counter = 0; var lastCompleteState = false; var nextCooldown = false; function mainLoop() { counter++; if (!getRunning()) { if (counter % 120 === 0) updateStatus('已暂停', 'warning'); return; } var videos = findVideos(); if (videos.length === 0) { // 无视频,检测是否是课程列表页 if (counter % 60 === 0) { updateStatus('等待视频页面...', 'info'); updateProgress(''); // 检查URL是否在课程列表 if (!location.href.includes('/play') && !location.href.includes('/xuexi')) { updateInfo('提示: 请进入视频学习页面'); } } return; } var v = videos[0]; // 倍速+静音 if (Math.abs(v.playbackRate - S.speed) > 0.01) v.playbackRate = S.speed; if (v.muted !== S.muted) v.muted = S.muted; // 自动播放 if (S.autoplay && v.paused && !v.ended && v.readyState >= 3) { v.play().catch(function(e) {}); } // 进度显示 if (v.duration && !isNaN(v.duration) && v.duration > 0) { var cur = Math.floor(v.currentTime); var total = Math.floor(v.duration); var pct = Math.round((cur / total) * 100); var cm = Math.floor(cur / 60), cs = cur % 60; var tm = Math.floor(total / 60), ts = total % 60; updateProgress('📊 ' + pct + '% | ' + cm + ':' + String(cs).padStart(2, '0') + ' / ' + tm + ':' + String(ts).padStart(2, '0') + ' | ' + S.speed + 'x'); updateStatus('正在播放', 'success'); // 完成检测 + 自动下一集 if (isVideoComplete(v) && !lastCompleteState && !nextCooldown) { lastCompleteState = true; if (S.autonext) { nextCooldown = true; var ok = autoNextVideo(); setTimeout(function() { nextCooldown = false; }, 15000); if (!ok) { updateStatus('已完成,请手动切换', 'warning'); } } else { updateStatus('视频已完成(自动下一集已关闭)', 'success'); } } else if (!isVideoComplete(v)) { lastCompleteState = false; } } else { updateProgress('视频加载中...'); } } // ==================== 入口 ==================== function main() { log(SITE_NAME + ' 刷课助手 v1.0 已启动', 'success'); log('当前站点: ' + SITE_NAME + ' (' + SITE_HOST + ')', 'info'); log('倍速: ' + S.speed + 'x | 静音: ' + (S.muted?'是':'否') + ' | 自动: ' + (S.autoplay?'是':'否') + ' | 下一集: ' + (S.autonext?'是':'否') + ' | 防暂停: ' + (S.autopark?'是':'否'), 'info'); log('注: 已解除3视频限制,不限章节数', 'success'); createPanel(); // 安装防暂停Hook if (S.autopark) { setTimeout(hookPause, 2000); } setInterval(mainLoop, 500); // 每5秒重装Hook(应对SPA页面切换) setInterval(function() { if (S.autopark && getRunning()) hookPause(); }, 5000); } if (document.readyState === 'complete') { main(); } else { window.addEventListener('load', main); } })();