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