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