// ==UserScript== // @name 📚 国开智能刷课助手 // @namespace http://tampermonkey.net/ // @version 2.3.0.1 // @description 国开自动刷课 - 云端授权版 本地智能恢复,q反馈群:612441267 // @author lakay666 // @match *://lms.ouchn.cn/course/* // @match *://lms.ouchn.cn/user/courses* // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_notification // @grant unsafeWindow // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js // @run-at document-end // ==/UserScript== (function() { 'use strict'; const SERVER = 'http://150.158.119.55:3000'; const SK = 'gkbk_activation_code'; // ==================== 本地智能恢复 ==================== var lastUrl = location.href; var lastProgress = 0; var lastChangeTime = Date.now(); var recoveryTimer = null; function startLocalRecovery() { recoveryTimer = setInterval(function() { var now = Date.now(); var currentUrl = location.href; var currentProgress = 0; try { var el = document.querySelector('.progress-text, [class*="progress"], .study-progress'); if (el) { var m = el.textContent.match(/(\d+)%/); if (m) currentProgress = parseInt(m[1]); } } catch(e) {} // 检测:同URL超过15分钟没变化 if (currentUrl === lastUrl && currentProgress === lastProgress) { var stuckMinutes = Math.floor((now - lastChangeTime) / 60000); if (stuckMinutes >= 15) { console.warn('[国开助手] 检测到卡住 ' + stuckMinutes + ' 分钟,自动恢复'); if (currentUrl.includes('full-screen')) { var backBtn = document.querySelector('a.full-screen-mode-back'); if (backBtn) backBtn.click(); else history.back(); } else { location.reload(); } lastChangeTime = now; } } else { lastUrl = currentUrl; lastProgress = currentProgress; lastChangeTime = now; } }, 60000); } // ==================== 等待 globalData ==================== function waitForGlobalData(maxRetries, callback) { maxRetries = maxRetries || 20; var retries = 0; function tryGet() { retries++; var gd = (typeof unsafeWindow !== 'undefined' ? unsafeWindow.globalData : null) || window.globalData; if (gd && gd.user && gd.user.id) { callback(gd); return; } if (retries < maxRetries) { setTimeout(tryGet, 1000); } else { console.warn('[国开助手] 无法获取 globalData(已重试' + maxRetries + '次)'); } } tryGet(); } function generateMachineCode(gd) { if (!gd || !gd.user || !gd.user.id) return null; var h = 0; var u = String(gd.user.userNo || gd.user.id); for (var i = 0; i < u.length; i++) { h = ((h << 5) - h) + u.charCodeAt(i); h |= 0; } return Math.abs(h).toString(16).toUpperCase(); } var heartbeatTimer = null; function startHeartbeat(mc, un) { if (heartbeatTimer) return; reportStatus(mc, un); heartbeatTimer = setInterval(function() { reportStatus(mc, un); }, 60000); } function reportStatus(mc, un) { var courseId = ''; try { var gd = window.globalData || (typeof unsafeWindow !== 'undefined' ? unsafeWindow.globalData : null); if (gd && gd.course && gd.course.id) courseId = String(gd.course.id); } catch(e) {} var progress = 0; try { var el = document.querySelector('.progress-text, [class*="progress"], .study-progress'); if (el) { var m = el.textContent.match(/(\d+)%/); if (m) progress = parseInt(m[1]); } } catch(e) {} GM_xmlhttpRequest({ method: 'POST', url: SERVER + '/api/client-status', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ machineCode: mc, userName: un, status: { url: location.href, courseId: courseId, progress: progress, paused: (window.GKBK_PAUSED === true), timestamp: Date.now() } }), onload: function(r) { try { var res = JSON.parse(r.responseText); if (res.command === 'recover') { console.warn('[国开助手] 服务器下发恢复指令: ' + res.action); if (res.action === 'return_course') { var backBtn = document.querySelector('a.full-screen-mode-back'); if (backBtn) backBtn.click(); else history.back(); } else { location.reload(); } } } catch(e) {} } }); } function showLoading(msg) { var el = document.getElementById('gkbk-loading'); if (!el) { el = document.createElement('div'); el.id = 'gkbk-loading'; el.style.cssText = 'position:fixed;top:20px;right:20px;padding:12px 20px;background:#2d8cf0;color:#fff;border-radius:8px;z-index:2147483647;font-size:14px;box-shadow:0 4px 12px rgba(0,0,0,0.15);'; document.body.appendChild(el); } el.textContent = '📚 ' + msg; } function hideLoading() { var el = document.getElementById('gkbk-loading'); if (el) el.remove(); } function showAuthPanel(mc, un) { if (document.querySelector('#gkbk-auth-panel')) return; var panel = document.createElement('div'); panel.id = 'gkbk-auth-panel'; panel.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:360px;background:#fff;border-radius:16px;box-shadow:0 8px 32px rgba(0,0,0,0.2);z-index:2147483647;padding:24px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;'; panel.innerHTML = '

🔐 授权验证

'+ '
'+un+'
'+ '
'+ '
'+ '
'+ '
'+ '
'+ ''+ '
🛰️ 本地智能恢复 | 15分钟无变化自动刷新
'; document.body.appendChild(panel); document.getElementById('gkbk-copy').onclick = function() { GM_setClipboard(mc); showLoading('已复制'); setTimeout(hideLoading, 1500); }; document.getElementById('gkbk-verify').onclick = function() { var code = document.getElementById('gkbk-code').value.trim(); if (!code) { alert('请输入激活码'); return; } GM_setValue(SK, code); panel.remove(); verifyAndLoad(code, mc, un); }; } function decryptAES(enc, key, iv) { var k = CryptoJS.enc.Utf8.parse(key), i = CryptoJS.enc.Utf8.parse(iv); return CryptoJS.AES.decrypt(enc, k, { iv: i, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString(CryptoJS.enc.Utf8); } function verifyAndLoad(activationCode, mc, un) { showLoading('正在验证...'); GM_xmlhttpRequest({ method: 'POST', url: SERVER + '/api/verify', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ machineCode: mc, activationCode: activationCode, userName: un, encrypt: false }), onload: function(r) { try { var res = JSON.parse(r.responseText); if (!res.success) { hideLoading(); alert('验证失败:' + res.message); GM_deleteValue(SK); showAuthPanel(mc, un); return; } showLoading('加载中...'); var script = res.coreScript; if (res.encrypted && res.decryptKey && res.decryptIv) script = decryptAES(script, res.decryptKey, res.decryptIv); if (!script) { hideLoading(); alert('加载失败'); return; } window.GKBK_MACHINE_CODE = mc; window.GKBK_USER_NAME = un; eval(script); hideLoading(); startHeartbeat(mc, un); startLocalRecovery(); // ← 启动本地智能恢复 var daysLeft = Math.ceil((new Date(res.expire) - Date.now()) / 86400000); if (daysLeft <= 7) GM_notification({ title: '国开刷课助手', text: '激活码将在 ' + daysLeft + ' 天后到期', timeout: 10000 }); } catch (e) { hideLoading(); alert('加载失败:' + e.message); } }, onerror: function() { hideLoading(); alert('无法连接服务器'); } }); } GM_registerMenuCommand('🔄 重置授权', function() { if (confirm('确定重置?')) { GM_deleteValue(SK); location.reload(); } }); waitForGlobalData(20, function(gd) { var mc = generateMachineCode(gd), un = gd.user.name || '未知'; if (!mc) return; window.GKBK_MACHINE_CODE = mc; window.GKBK_USER_NAME = un; var savedCode = GM_getValue(SK, ''); if (savedCode) verifyAndLoad(savedCode, mc, un); else showAuthPanel(mc, un); }); })();