// ==UserScript==
// @name 江西干部网络学院学习助手(www.jxgbwlxy.gov.cn)
// @version 1.0.0
// @description study.jxgbwlxy.gov.cn 江西干部网络学院专用版
// @author 晓晓
// @match https://study.jxgbwlxy.gov.cn/study/courseMine*
// @match https://study.jxgbwlxy.gov.cn/courseDetailsNew?*
// @match https://study.jxgbwlxy.gov.cn/videoChoose?*
// @match https://study.jxgbwlxy.gov.cn/video?*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addValueChangeListener
// @run-at document-end
// @license MIT
// ==/UserScript==
(function () {
'use strict';
var SETTINGS_KEY = 'jxgbwlxy_settings';
var VAL_KEY = 'JXGBWLXY_AUTO_STATUS';
var defaults = { speed: 2, muted: true, autoplay: true, autonext: true };
var S = Object.assign({}, defaults, GM_getValue(SETTINGS_KEY, {}));
function save() { GM_setValue(SETTINGS_KEY, S); }
var myId = Math.random().toString(36).substring(2);
var lastHeartbeatTime = 0;
var otherTabIsPlaying = false;
var isRunning = true;
// ==================== 跨Tab协调 ====================
GM_addValueChangeListener(VAL_KEY, function (key, oldValue, newValue, remote) {
var parts = (newValue || '').split(':');
var timestamp = parts[0];
var msg = parts[1];
var senderId = parts[2];
if (msg === 'playing' && senderId !== myId) {
lastHeartbeatTime = parseInt(timestamp);
otherTabIsPlaying = true;
} else if (msg === 'finished') {
otherTabIsPlaying = false;
}
});
function sendMessage(msg) {
GM_setValue(VAL_KEY, Date.now() + ':' + msg + ':' + myId);
}
function checkIsPlaying() {
return otherTabIsPlaying && (Date.now() - lastHeartbeatTime < 60000);
}
function tryClose() {
console.log('[江西干部] 检测到冲突,切换标签...');
window.open('', '_self');
window.close();
setTimeout(function () {
if (!window.closed) window.location.href = 'https://study.jxgbwlxy.gov.cn/study/courseMine';
}, 500);
}
function log(msg, type) {
type = type || 'info';
var prefix = { error: '❌', success: '✅', warning: '⚠️' }[type] || '🔍';
console.log('[江西干部]', prefix, msg);
var el = document.querySelector('#jxgbwlxy-log');
if (el) {
var t = new Date().toLocaleTimeString('zh-CN', { hour12: false });
el.value += '[' + t + '] ' + msg + '\n';
el.scrollTop = el.scrollHeight;
}
}
// ==================== UI面板 ====================
function $1(s) { return document.querySelector(s); }
function createPanel() {
if ($1('#jxgbwlxy-panel')) return;
var el = document.createElement('div');
el.id = 'jxgbwlxy-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('#jxgbwlxy-speed').addEventListener('input', function () {
S.speed = parseFloat(this.value);
$1('#jxgbwlxy-speed-val').textContent = S.speed + 'x';
save();
applySpeedAll();
log('\u500d\u901f: ' + S.speed + 'x', 'success');
});
// 静音
$1('#jxgbwlxy-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 ? '\u5df2\u9759\u97f3' : '\u5f00\u542f\u58f0\u97f3');
});
// 自动播放
$1('#jxgbwlxy-ap-btn').addEventListener('click', function () {
S.autoplay = !S.autoplay;
this.style.background = S.autoplay ? '#10b981' : '#374151';
this.innerHTML = S.autoplay ? '▶ 自动' : '⏸ 手动';
save();
log(S.autoplay ? '\u81ea\u52a8\u64ad\u653e\u5f00\u542f' : '\u624b\u52a8\u64ad\u653e');
});
// 自动下一集
$1('#jxgbwlxy-next-btn').addEventListener('click', function () {
S.autonext = !S.autonext;
this.style.background = S.autonext ? '#3b82f6' : '#374151';
this.innerHTML = S.autonext ? '➤ 下一集' : '⏮ 手动';
save();
log(S.autonext ? '\u81ea\u52a8\u4e0b\u4e00\u96c6\u5f00\u542f' : '\u624b\u52a8\u4e0b\u4e00\u96c6');
});
}
function updateStatus(text, type) {
var el = $1('#jxgbwlxy-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('#jxgbwlxy-progress');
if (el) el.textContent = text;
}
// ==================== 视频控制 ====================
function findVideos() {
var result = [];
var frames = document.querySelectorAll('iframe');
for (var fi = 0; fi < frames.length; fi++) {
try {
var vs = frames[fi].contentDocument.querySelectorAll('video');
for (var vi = 0; vi < vs.length; vi++) result.push(vs[vi]);
} catch (e) {}
}
var vs2 = document.querySelectorAll('video');
for (var vi2 = 0; vi2 < vs2.length; vi2++) result.push(vs2[vi2]);
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;
});
}
function isVideoComplete(v) {
if (!v || !v.duration || isNaN(v.duration) || v.duration === 0) return false;
return v.currentTime / v.duration >= 0.95;
}
// ==================== 自动下一集 ====================
function autoNext() {
log('\u89c6\u9891\u5b8c\u6210\uff0c\u5c1d\u8bd5\u81ea\u52a8\u4e0b\u4e00\u96c6...', 'warning');
// 方法1: 找"继续学习"按钮
var btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
var t = (btns[i].textContent || '').trim();
if (t.includes('\u7ee7\u7eed\u5b66\u4e60') || t.includes('\u4e0b\u4e00\u96c6') || t.includes('\u7ee7\u7eed')) {
log('\u627e\u5230\u7ee7\u7eed\u6309\u94ae: ' + t, 'success');
btns[i].click();
setTimeout(function () { location.reload(); }, 2000);
return true;
}
}
// 方法2: 找 el-button 类型
var elBtns = document.querySelectorAll('.el-button');
for (var j = 0; j < elBtns.length; j++) {
var t2 = (elBtns[j].textContent || '').trim();
if (t2.includes('\u7ee7\u7eed') || t2.includes('\u4e0b\u4e00') || t2.includes('\u63a5\u4e0b')) {
log('\u627e\u5230 el-button: ' + t2, 'success');
elBtns[j].click();
setTimeout(function () { location.reload(); }, 2000);
return true;
}
}
// 方法3: 回到课程列表
log('\u672a\u627e\u5230\u4e0b\u4e00\u96c6\u6309\u94ae\uff0c\u8df3\u56de\u8bfe\u7a0b\u5217\u8868', 'warning');
setTimeout(function () {
window.location.href = 'https://study.jxgbwlxy.gov.cn/study/courseMine';
}, 3000);
return false;
}
// ==================== 页面分流 ====================
var url = window.location.href;
// 1. 列表页
if (url.indexOf('/study/courseMine') !== -1) {
createPanel();
log('\u8bfe\u7a0b\u5217\u8868\u9875\uff0c\u5f00\u59cb\u81ea\u52a8\u68c0\u6d4b...', 'success');
function listLoop() {
if (!isRunning) return;
if (checkIsPlaying()) {
setTimeout(listLoop, 30000);
return;
}
var cards = document.querySelectorAll('div.courseCard');
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
var btn = card.querySelector('button');
if (btn && btn.textContent && btn.textContent.trim()) {
var btnText = btn.textContent.trim();
// 跳过已完成
if (btnText.includes('\u5df2\u5b8c\u6210') || btnText.includes('100')) continue;
log('\u70b9\u51fb\u8bfe\u7a0b\u5377: ' + (card.textContent || '').substring(0, 30), 'success');
card.click();
return; // 点了一个就等下一次
}
}
setTimeout(listLoop, 60000); // 每分钟检查一次
}
setTimeout(listLoop, 5000);
// 定时刷新防卡死
setInterval(function () { location.reload(); }, 30 * 60 * 1000);
}
// 2. 详情页 / 选集页
else if (url.indexOf('/courseDetailsNew?') !== -1 || url.indexOf('/videoChoose?') !== -1) {
createPanel();
log('\u8bfe\u7a0b\u8be6\u60c5\u9875\uff0c\u5f00\u59cb\u68c0\u6d4b\u5f00\u59cb\u6309\u94ae...', 'success');
function detailLoop() {
if (!isRunning) return;
if (checkIsPlaying()) {
tryClose();
return;
}
var btn = document.querySelector('button.myBtn.selected');
if (btn) {
log('\u627e\u5230\u5f00\u59cb\u6309\u94ae\uff0c\u70b9\u51fb\u5e76\u5237\u65b0...', 'success');
btn.click();
setTimeout(function () { location.reload(); }, 2000);
} else {
setTimeout(detailLoop, 2000);
}
}
setTimeout(detailLoop, 3000);
}
// 3. 视频播放页
else if (url.indexOf('/video?') !== -1) {
createPanel();
log('\u89c6\u9891\u64ad\u653e\u9875\uff0c\u542f\u52a8\u5237\u8bfe...', 'success');
var videoLoopTimer = null;
var progressTimer = null;
var videoCompleteFired = false;
var nextCooldown = false;
function updateVideoProgress() {
var vs = findVideos();
if (vs.length === 0) return;
var v = vs[0];
if (v.duration && !isNaN(v.duration) && 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);
updateProgress('\u2705 ' + pct + '% | ' + cm + ':' + String(cs).padStart(2, '0') + ' / ' + tm + ':' + String(ts).padStart(2, '0') + ' | ' + S.speed + 'x');
updateStatus('\u6b63\u5728\u64ad\u653e', 'success');
}
}
function videoLoop() {
if (!isRunning) return;
if (checkIsPlaying()) {
tryClose();
return;
}
sendMessage('playing');
var vs = findVideos();
if (vs.length === 0) {
setTimeout(videoLoop, 5000);
return;
}
var v = vs[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 () {});
}
// 进度更新
if (v.duration && !isNaN(v.duration) && 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);
updateProgress('\u2705 ' + pct + '% | ' + cm + ':' + String(cs).padStart(2, '0') + ' / ' + tm + ':' + String(ts).padStart(2, '0') + ' | ' + S.speed + 'x');
updateStatus('\u6b63\u5728\u64ad\u653e', 'success');
// 完成检测
if (isVideoComplete(v) && !videoCompleteFired && !nextCooldown) {
videoCompleteFired = true;
if (S.autonext) {
nextCooldown = true;
updateStatus('\u89c6\u9891\u5b8c\u6210\uff0c\u51c6\u5907\u4e0b\u4e00\u96c6...', 'warning');
sendMessage('finished');
var ok = autoNext();
setTimeout(function () { nextCooldown = false; }, 20000);
if (!ok) {
updateStatus('\u5df2\u5b8c\u6210\uff0c\u8bf7\u624b\u52a8\u5207\u6362', 'success');
}
} else {
sendMessage('finished');
updateStatus('\u89c6\u9891\u5b8c\u6210\uff0c\u81ea\u52a8\u4e0b\u4e00\u96c6\u5df2\u5173\u95ed', 'success');
}
}
}
// 弹窗处理
var popupBtn = document.querySelector('body > div.el-message-box__wrapper > div > div.el-message-box__btns > button');
if (popupBtn) {
log('\u5f39\u51fa\u5f39\u7a97\uff0c\u81ea\u52a8\u5173\u95ed', 'warning');
popupBtn.click();
}
setTimeout(videoLoop, 5000);
}
setTimeout(videoLoop, 2000);
setInterval(updateVideoProgress, 1000);
}
})();