// ==UserScript== // @name 全新国开免费自动刷课 // @namespace 小石头 // @description 支持自动访问线上链接、查看资料附件、观看视频、自动查看页面、自动参与发帖回帖。 // @version 2.7.4 // @author 小石头 // @icon data:image/png;base64,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 // @match *://lms.ouchn.cn/course/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @connect keyt.cn // @original-author 小石头 // @original-license GPL-1.0 // @original-script http://one.ouchn.cn/ // @source http://one.ouchn.cn/ // @note 2.7.2:修复刷新后授权码不自动恢复的问题 // @license 请联系作者3365137745 // ==/UserScript== (() => { "use strict"; // ============================================================ // 配置区 // ============================================================ const CONFIG = { videoSpeed: 2, randomDelayMin: 5000, randomDelayMax: 8000, autoPlayVideo: true, muteVideo: true, autoNext: false, nextDelay: 3000, enablePanel: true, repeatLearned: false, activityInterval: 0, unlocked: false }; const ACTIVITY_INTERVAL_STEPS = [ { label: '0秒', sec: 0 }, { label: '3秒', sec: 3 }, { label: '5秒', sec: 5 }, { label: '10秒', sec: 10 }, { label: '30秒', sec: 30 }, { label: '1分钟', sec: 60 }, { label: '2分钟', sec: 120 } ]; const REPEAT_SKIP_TYPES = ['forum']; // ============================================================ // 卡密通网络验证配置 // ============================================================ const KEYT_USER = 'cjtfky123'; const KEYT_APP = 'a'; const KEYT_SECRET = '1cc35a6aa4b3b60f82100301574ca9a9'; const KEYT_BASE = `https://www.keyt.cn/kami/${KEYT_USER}/check.php`; const AUTH_CACHE_KEY = 'ouchn_auth_cache'; const MACHINE_ID_KEY = 'ouchn_machine_id'; const HEARTBEAT_MS = 50000; const HEARTBEAT_MAX_FAIL = 5; const TIME_TOLERANCE = 120; // ============================================================ // MD5 纯 JS 实现 // ============================================================ const MD5 = (function () { function safeAdd(x, y) { const lsw = (x & 0xFFFF) + (y & 0xFFFF); const msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } function bitRotateLeft(num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); } function md5cmn(q, a, b, x, s, t) { return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b); } function md5ff(a, b, c, d, x, s, t) { return md5cmn((b & c) | (~b & d), a, b, x, s, t); } function md5gg(a, b, c, d, x, s, t) { return md5cmn((b & d) | (c & ~d), a, b, x, s, t); } function md5hh(a, b, c, d, x, s, t) { return md5cmn(b ^ c ^ d, a, b, x, s, t); } function md5ii(a, b, c, d, x, s, t) { return md5cmn(c ^ (b | ~d), a, b, x, s, t); } function binlMD5(x, len) { x[len >> 5] |= 0x80 << (len % 32); x[(((len + 64) >>> 9) << 4) + 14] = len; let a = 1732584193, b = -271733879, c = -1732584194, d = 271733878; for (let i = 0; i < x.length; i += 16) { const olda = a, oldb = b, oldc = c, oldd = d; a = md5ff(a, b, c, d, x[i], 7, -680876936); d = md5ff(d, a, b, c, x[i + 1], 12, -389564586); c = md5ff(c, d, a, b, x[i + 2], 17, 606105819); b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330); a = md5ff(a, b, c, d, x[i + 4], 7, -176418897); d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426); c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341); b = md5ff(b, c, d, a, x[i + 7], 22, -45705983); a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416); d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417); c = md5ff(c, d, a, b, x[i + 10], 17, -42063); b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162); a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682); d = md5ff(d, a, b, c, x[i + 13], 12, -40341101); c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290); b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329); a = md5gg(a, b, c, d, x[i + 1], 5, -165796510); d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632); c = md5gg(c, d, a, b, x[i + 11], 14, 643717713); b = md5gg(b, c, d, a, x[i], 20, -373897302); a = md5gg(a, b, c, d, x[i + 5], 5, -701558691); d = md5gg(d, a, b, c, x[i + 10], 9, 38016083); c = md5gg(c, d, a, b, x[i + 15], 14, -660478335); b = md5gg(b, c, d, a, x[i + 4], 20, -405537848); a = md5gg(a, b, c, d, x[i + 9], 5, 568446438); d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690); c = md5gg(c, d, a, b, x[i + 3], 14, -187363961); b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501); a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467); d = md5gg(d, a, b, c, x[i + 2], 9, -51403784); c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473); b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734); a = md5hh(a, b, c, d, x[i + 5], 4, -378558); d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463); c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562); b = md5hh(b, c, d, a, x[i + 14], 23, -35309556); a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060); d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353); c = md5hh(c, d, a, b, x[i + 7], 16, -155497632); b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640); a = md5hh(a, b, c, d, x[i + 13], 4, 681279174); d = md5hh(d, a, b, c, x[i], 11, -358537222); c = md5hh(c, d, a, b, x[i + 3], 16, -722521979); b = md5hh(b, c, d, a, x[i + 6], 23, 76029189); a = md5hh(a, b, c, d, x[i + 9], 4, -640364487); d = md5hh(d, a, b, c, x[i + 12], 11, -421815835); c = md5hh(c, d, a, b, x[i + 15], 16, 530742520); b = md5hh(b, c, d, a, x[i + 2], 23, -995338651); a = md5ii(a, b, c, d, x[i], 6, -198630844); d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415); c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905); b = md5ii(b, c, d, a, x[i + 5], 21, -57434055); a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571); d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606); c = md5ii(c, d, a, b, x[i + 10], 15, -1051523); b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799); a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359); d = md5ii(d, a, b, c, x[i + 15], 10, -30611744); c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380); b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649); a = md5ii(a, b, c, d, x[i + 4], 6, -145523070); d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379); c = md5ii(c, d, a, b, x[i + 2], 15, 718787259); b = md5ii(b, c, d, a, x[i + 9], 21, -343485551); a = safeAdd(a, olda); b = safeAdd(b, oldb); c = safeAdd(c, oldc); d = safeAdd(d, oldd); } return [a, b, c, d]; } function binl2rstr(input) { let output = ''; for (let i = 0; i < input.length * 32; i += 8) { output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF); } return output; } function rstr2binl(input) { const output = []; output[(input.length >> 2) - 1] = undefined; for (let i = 0; i < output.length; i++) output[i] = 0; for (let i = 0; i < input.length * 8; i += 8) { output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32); } return output; } function rstrMD5(s) { return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)); } function rstr2hex(input) { const hexTab = '0123456789abcdef'; let output = ''; for (let i = 0; i < input.length; i++) { const x = input.charCodeAt(i); output += hexTab.charAt((x >>> 4) & 0x0F) + hexTab.charAt(x & 0x0F); } return output; } function str2rstrUTF8(input) { return unescape(encodeURIComponent(input)); } return function (s) { return rstr2hex(rstrMD5(str2rstrUTF8(s))); }; })(); // ============================================================ // 卡密通验证:工具函数 // ============================================================ const nowTs = () => Math.floor(Date.now() / 1000); function getMachineId() { let id = GM_getValue(MACHINE_ID_KEY, ''); if (!id) { id = 'OUCHN-' + Date.now().toString(36).toUpperCase() + '-' + Math.random().toString(36).slice(2, 10).toUpperCase(); GM_setValue(MACHINE_ID_KEY, id); } return id + 'MAC'; } function verifyResponse(raw) { if (!raw) return null; const signIndex = raw.indexOf('|sign='); if (signIndex === -1) return null; const body = raw.substring(0, signIndex); const sign = raw.substring(signIndex + 6); if (MD5(body + KEYT_SECRET) !== sign) return null; const lastPipe = body.lastIndexOf('|'); if (lastPipe === -1) return null; const serverTs = parseInt(body.substring(lastPipe + 1), 10); if (Math.abs(nowTs() - serverTs) > TIME_TOLERANCE) return null; return body.substring(0, lastPipe); } function gmGet(url) { return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url, headers: { 'Cache-Control': 'no-cache' }, timeout: 12000, onload: (res) => resolve(res.status === 200 ? res.responseText : null), onerror: () => resolve(null), ontimeout: () => resolve(null) }); }); } async function fetchCardSwitch() { for (let i = 0; i < 3; i++) { const url = `${KEYT_BASE}?act=get_switch&app=${KEYT_APP}&t=${nowTs()}`; const raw = await gmGet(url); if (raw) { const biz = verifyResponse(raw); if (biz && biz.indexOf('CARD_ON') !== -1) return 'CARD_ON'; if (biz && biz.indexOf('CARD_OFF') !== -1) return 'CARD_OFF'; } await new Promise(r => setTimeout(r, 500)); } return 'CARD_ON'; } async function verifyCard(card, mac) { const url = `${KEYT_BASE}?card=${encodeURIComponent(card)}&mac=${encodeURIComponent(mac)}&app=${KEYT_APP}&heart=1&t=${nowTs()}`; const raw = await gmGet(url); return verifyResponse(raw); } function parseRemain(biz) { if (!biz) return null; const parts = biz.split('|'); if (parts.length >= 3) { return { days: parseInt(parts[2], 10) || 0, minutes: parts.length >= 4 ? (parseInt(parts[3], 10) || 0) : 0 }; } if (parts.length === 2 && parts[1] === 'permanent') { return { days: 99999, minutes: 0 }; } return null; } let heartbeatTimer = null; let heartbeatFail = 0; let currentCard = ''; let currentMac = ''; function stopHeartbeat() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } } function startHeartbeat(panel) { stopHeartbeat(); heartbeatFail = 0; heartbeatTimer = setInterval(async () => { if (!currentCard || !currentMac) return; const biz = await verifyCard(currentCard, currentMac); if (biz && biz.startsWith('ok|')) { heartbeatFail = 0; const info = parseRemain(biz); if (info && panel) { panel.log(`💓 心跳正常 · 剩余 ${info.days}天 ${info.minutes}分钟`, 'info'); } } else { heartbeatFail++; if (panel) panel.log(`⚠️ 心跳异常 ${heartbeatFail}/${HEARTBEAT_MAX_FAIL}`, 'warning'); if (heartbeatFail >= HEARTBEAT_MAX_FAIL) { stopHeartbeat(); if (panel) panel.log('❌ 心跳连续失败,已锁定功能', 'error'); CONFIG.unlocked = false; } } }, HEARTBEAT_MS); } // ============================================================ // 从缓存自动解锁(多时机重试) // ============================================================ function tryUnlockFromCache(reason = '') { try { const cached = GM_getValue(AUTH_CACHE_KEY, null); console.log(`[刷课] tryUnlockFromCache(${reason}) cached =`, cached); if (!cached || !cached.card || !cached.mac) { console.log('[刷课] 无缓存,跳过自动解锁'); return false; } currentCard = cached.card; currentMac = cached.mac; CONFIG.unlocked = true; // 直接操作当前 DOM(防止实例引用失效) const btn = document.getElementById('donateBtn'); if (btn) { btn.innerHTML = '已解锁'; console.log('[刷课] 按钮已改为已解锁'); } const range = document.getElementById('activityIntervalRange'); if (range) range.disabled = false; const labels = document.querySelectorAll('#intervalLabels span'); labels.forEach(s => s.classList.remove('disabled')); const repeat = document.getElementById('repeatLearnedCheck'); if (repeat) repeat.disabled = false; // 如果当前实例的 panel 可用,也打个日志 if (currentAutoLearn && currentAutoLearn.panel && typeof currentAutoLearn.panel.log === 'function') { currentAutoLearn.panel.log('✅ 已用缓存卡密自动解锁,正在恢复心跳…', 'success'); } // 启动心跳(如果尚未启动) if (typeof startHeartbeat === 'function' && currentAutoLearn && currentAutoLearn.panel) { startHeartbeat(currentAutoLearn.panel); } console.log('[刷课] 自动解锁完成'); return true; } catch (e) { console.error('[刷课] tryUnlockFromCache 报错:', e); return false; } } // ============================================================ // LMS API // ============================================================ const API = { getGlobalData() { return { course: window.globalData?.course || {}, user: window.globalData?.user || {}, dept: window.globalData?.dept || {}, isOpenUniversity: window.globalData?.isOpenUniversity || true, courseRoles: window.globalData?.courseRoles || ["student"], deliveryOrg: window.globalData?.deliveryOrg || "ouchn", useSinglePage: window.globalData?.useSinglePage ?? true, expandActivityInfo: window.globalData?.expandActivityInfo ?? false }; }, addVideoLearningRecords({ start_at, end_at, syllabus_id, activity_id, upload_id }) { const data = this.getGlobalData(); const duration = Math.ceil(300 * Math.random() + 40); const payload = JSON.stringify({ syllabus_id, activity_id, upload_id, start_at, end_at, duration, user_id: data.user.id, org_id: data.user.orgId, course_id: data.course.id, is_teacher: false, is_student: true, ts: Date.now(), user_agent: navigator.userAgent, meeting_type: "online_video", org_name: data.user.orgName, org_code: data.user.orgCode, user_no: data.user.userNo, user_name: data.user.name, course_code: data.course.courseCode, course_name: data.course.name }); return new Promise((resolve, reject) => { $.ajax({ url: `https://lms.ouchn.cn/statistics/api/online-videos`, data: payload, type: "POST", cache: false, contentType: "text/plain;charset=UTF-8", complete: resolve, error: reject }); }); }, postLearningActiVities(activityId, activityType, isOpen, activityName = null) { const data = this.getGlobalData(); const payload = JSON.stringify({ org_id: data.user.orgId, user_id: data.user.id, course_id: data.course.id, enrollment_role: data.courseRoles[0], is_teacher: false, activity_id: activityId, activity_type: activityType, activity_name: activityName, module: null, action: isOpen ? "open" : "close", ts: new Date().getTime(), user_agent: navigator.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", mode: "normal", channel: "web", target_info: {}, master_course_id: data.course.id, org_name: data.user.orgName, org_code: data.user.orgCode, user_no: data.user.userNo, user_name: data.user.name, course_code: data.course.courseCode, course_name: data.course.name, dep_id: data.dept.id, dep_name: data.dept.name, dep_code: data.dept.code }); return new Promise((resolve, reject) => { $.ajax({ url: `https://lms.ouchn.cn/statistics/api/learning-activity`, data: payload, type: "POST", contentType: "application/json", dataType: "JSON", success: resolve, error: reject }); }); }, postActivitiesRead(activityId, extraData = {}) { return new Promise((resolve, reject) => { $.ajax({ type: "POST", url: `https://lms.ouchn.cn/api/course/activities-read/${activityId}`, contentType: "application/json", dataType: "JSON", data: JSON.stringify(extraData), success: resolve, error: reject }); }); }, getCategoryId(activityId) { return new Promise((resolve) => { $.get(`https://lms.ouchn.cn/api/forum/${activityId}/category?fields=id`, {}) .done((data) => resolve(data)) .fail(() => resolve(null)); }); }, postForum(categoryId, { title, content } = {}) { const defaultTitle = `好好学习${Date.now()}`; const defaultContent = `

好好学习,天天向上。${Date.now()}

`; return new Promise((resolve, reject) => { $.ajax({ type: "POST", url: `https://lms.ouchn.cn/api/topics`, contentType: "application/json", dataType: "JSON", data: JSON.stringify({ title: title || defaultTitle, content: content || defaultContent, category_id: categoryId, uploads: [] }), success: resolve, error: reject }); }); } }; const notificationTypes = { material: "参考资料", web_link: "线上链接", online_video: "音视频教材", slide: "微课", lesson: "录播教材", homework: "作业", forum: "讨论区", chatroom: "iSlide 直播", questionnaire: "调查问卷", page: "页面", course_invite: "課程邀請", scorm: "SCORM" }; const VideoPlayer = { findVideoElement() { const selectors = ['video', '.video-player video', '.play-video video', 'video_html5', 'object video', 'iframe video']; for (const selector of selectors) { const video = document.querySelector(selector); if (video && video.tagName === 'VIDEO') return video; } const embeds = document.querySelectorAll('embed, object'); for (const embed of embeds) { const video = embed.contentDocument?.querySelector('video'); if (video) return video; } return null; }, async waitForVideo(maxWaitTime = 15000) { const startTime = Date.now(); while (Date.now() - startTime < maxWaitTime) { const video = this.findVideoElement(); if (video) return video; await new Promise(resolve => setTimeout(resolve, 500)); } return null; } }; // ============================================================ // 面板 // ============================================================ class LogPanel { constructor() { this.panelHtml = `
🎬 国开学习 v2.0 正式版
⚙️参数设置
视频倍速
自动播放
静音播放
学习间隔 0秒
0s3s5s 10s30s1m 2m
📋运行日志
清空
重复刷课
❤️ 感谢支持

如果这个脚本帮到了你,欢迎捐赠支持作者~

联系方式: QQ:3365137745
⚠️ 开启重复刷课?

重复刷课会将【已学过的活动】全部重新执行一遍:

  • 视频会重新播放
  • 学习记录会重复上报

确定要开启吗?

`; this.init(); } init() { const wrapper = document.querySelector(".wrapper") || document.body; wrapper.insertAdjacentHTML('beforeend', this.panelHtml); this.panel = document.getElementById("autoLearnPanel"); this.panelHeader = document.getElementById("panelHeader"); this.panelBody = document.getElementById("panelBody"); this.logContainer = document.getElementById("logContainer"); this.startBtn = document.getElementById("startBtn"); this.pauseBtn = document.getElementById("pauseBtn"); this.restartBtn = document.getElementById("restartBtn"); this.speedSelector = document.getElementById("speedSelector"); this.autoPlayCheck = document.getElementById("autoPlayCheck"); this.muteVideoCheck = document.getElementById("muteVideoCheck"); this.btnClose = document.getElementById("btnClose"); this.btnMinimize = document.getElementById("btnMinimize"); this.btnClearLog = document.getElementById("btnClearLog"); this.progressFill = document.getElementById("progressFill"); this.activityIntervalRange = document.getElementById("activityIntervalRange"); this.intervalValue = document.getElementById("intervalValue"); this.intervalLabels = document.querySelectorAll("#intervalLabels span"); this.repeatLearnedCheck = document.getElementById("repeatLearnedCheck"); this.donateBtn = document.getElementById("donateBtn"); this.donateModalMask = document.getElementById("donateModalMask"); this.donateModalOk = document.getElementById("donateModalOk"); this.donateModalCancel = document.getElementById("donateModalCancel"); this.repeatModalMask = document.getElementById("repeatModalMask"); this.repeatModalOk = document.getElementById("repeatModalOk"); this.repeatModalCancel = document.getElementById("repeatModalCancel"); this.authCodeInput = document.getElementById("authCodeInput"); this.authCodeTip = document.getElementById("authCodeTip"); this.bindEvents(); } setRunningState(running, paused = false) { if (running) { this.startBtn.disabled = true; this.pauseBtn.disabled = false; this.restartBtn.disabled = false; this.pauseBtn.innerHTML = paused ? '继续' : '暂停'; } else { this.startBtn.disabled = false; this.pauseBtn.disabled = true; this.restartBtn.disabled = true; this.pauseBtn.innerHTML = '暂停'; } } showRepeatModal() { this.repeatModalMask.classList.add('show'); } hideRepeatModal() { this.repeatModalMask.classList.remove('show'); } unlockAll() { CONFIG.unlocked = true; this.activityIntervalRange.disabled = false; this.intervalLabels.forEach(s => s.classList.remove('disabled')); this.repeatLearnedCheck.disabled = false; this.donateBtn.innerHTML = '已解锁'; this.log('🔓 已解锁:学习间隔 / 重复刷课', 'success'); } bindEvents() { this.btnClose.addEventListener('click', () => { this.panel.style.transform = 'translateX(120%)'; setTimeout(() => { this.panel.style.display = 'none'; }, 300); }); this.btnMinimize.addEventListener('click', () => { this.panelBody.style.display = this.panelBody.style.display === 'none' ? 'flex' : 'none'; this.panel.style.height = this.panelBody.style.display === 'none' ? '56px' : 'auto'; }); this.btnClearLog.addEventListener('click', () => { this.logContainer.innerHTML = ''; }); let isDragging = false, offsetX, offsetY; this.panelHeader.addEventListener('mousedown', (e) => { if (e.target.classList.contains('ctrl-btn')) return; isDragging = true; offsetX = e.clientX - this.panel.offsetLeft; offsetY = e.clientY - this.panel.offsetTop; this.panel.style.zIndex = '9999999999'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const x = e.clientX - offsetX, y = e.clientY - offsetY; this.panel.style.left = Math.max(0, Math.min(window.innerWidth - this.panel.offsetWidth, x)) + 'px'; this.panel.style.top = Math.max(0, Math.min(window.innerHeight - this.panel.offsetHeight, y)) + 'px'; this.panel.style.right = 'auto'; }); document.addEventListener('mouseup', () => { isDragging = false; }); this.speedSelector.addEventListener('click', (e) => { const target = e.target.closest('.speed-btn'); if (!target) return; const speed = parseFloat(target.dataset.speed); CONFIG.videoSpeed = speed; this.speedSelector.querySelectorAll('.speed-btn').forEach(btn => btn.classList.remove('active')); target.classList.add('active'); this.log(`倍速已设置为 ${speed}x`, 'info'); }); this.autoPlayCheck.addEventListener('change', (e) => { CONFIG.autoPlayVideo = e.target.checked; this.log(`自动播放: ${CONFIG.autoPlayVideo ? '开启' : '关闭'}`, 'info'); }); this.muteVideoCheck.addEventListener('change', (e) => { CONFIG.muteVideo = e.target.checked; this.log(`静音播放: ${CONFIG.muteVideo ? '开启' : '关闭'}`, 'info'); }); // ============== 卡密通验证 ============== const resetDonateModal = () => { if (this.authCodeInput) this.authCodeInput.value = ''; if (this.authCodeTip) { this.authCodeTip.textContent = ''; this.authCodeTip.classList.remove('show'); } if (this.authCodeInput) this.authCodeInput.classList.remove('is-error'); }; const showAuthError = (msg) => { if (!this.authCodeInput || !this.authCodeTip) return; this.authCodeInput.classList.add('is-error'); this.authCodeTip.textContent = msg; this.authCodeTip.classList.add('show'); }; const tryUnlock = async () => { if (!this.authCodeInput) return; const code = (this.authCodeInput.value || '').trim(); if (!code) { showAuthError('请输入授权码'); return; } this.donateModalOk.disabled = true; this.donateModalOk.textContent = '验证中...'; const sw = await fetchCardSwitch(); if (sw === 'CARD_OFF') { this.donateModalOk.disabled = false; this.donateModalOk.textContent = '确认'; this.donateModalMask.classList.remove('show'); resetDonateModal(); this.unlockAll(); this.log('🔓 卡密验证已关闭,直接放行', 'success'); return; } const mac = getMachineId(); const biz = await verifyCard(code, mac); this.donateModalOk.disabled = false; this.donateModalOk.textContent = '确认'; if (!biz) { showAuthError('验证失败:签名错误或网络异常'); return; } if (biz.startsWith('ok|')) { const info = parseRemain(biz); currentCard = code; currentMac = mac; GM_setValue(AUTH_CACHE_KEY, { card: code, mac, ts: Date.now() }); this.donateModalMask.classList.remove('show'); resetDonateModal(); this.unlockAll(); if (info) { this.log(`✅ 验证成功 · 剩余 ${info.days}天 ${info.minutes}分钟`, 'success'); } else { this.log('✅ 验证成功(终身有效)', 'success'); } startHeartbeat(this); } else if (biz.startsWith('error|')) { showAuthError(`验证失败:${biz.split('|')[1]}`); } else { showAuthError(`验证失败:${biz}`); } }; this.donateBtn.addEventListener('click', () => { if (CONFIG.unlocked) { this.log('已解锁,无需重复捐赠 ❤️', 'info'); return; } resetDonateModal(); this.donateModalMask.classList.add('show'); setTimeout(() => this.authCodeInput?.focus(), 100); }); this.donateModalCancel.addEventListener('click', () => { this.donateModalMask.classList.remove('show'); resetDonateModal(); }); this.donateModalOk.addEventListener('click', () => { tryUnlock(); }); this.authCodeInput?.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); tryUnlock(); } }); this.authCodeInput?.addEventListener('input', () => { this.authCodeInput.classList.remove('is-error'); this.authCodeTip?.classList.remove('show'); }); this.donateModalMask.addEventListener('click', (e) => { if (e.target === this.donateModalMask) { this.donateModalMask.classList.remove('show'); resetDonateModal(); } }); // ============== 重复刷课开关 ============== this.repeatLearnedCheck.checked = CONFIG.repeatLearned; this.repeatLearnedCheck.disabled = true; CONFIG.repeatLearned = false; this.repeatLearnedCheck.addEventListener('change', (e) => { if (!CONFIG.unlocked) return; if (e.target.checked) { e.target.checked = false; this.showRepeatModal(); } else { CONFIG.repeatLearned = false; this.log('重复刷课: 关闭', 'info'); } }); this.repeatModalCancel.addEventListener('click', () => { this.hideRepeatModal(); this.log('重复刷课: 已取消开启', 'warning'); }); this.repeatModalOk.addEventListener('click', () => { this.hideRepeatModal(); this.repeatLearnedCheck.checked = true; CONFIG.repeatLearned = true; this.log('重复刷课: 开启', 'info'); }); this.repeatModalMask.addEventListener('click', (e) => { if (e.target === this.repeatModalMask) { this.hideRepeatModal(); this.log('重复刷课: 已取消开启', 'warning'); } }); // ============== 学习间隔 ============== const updateIntervalUI = () => { const idx = parseInt(this.activityIntervalRange.value, 10) || 0; const step = ACTIVITY_INTERVAL_STEPS[idx]; CONFIG.activityInterval = step.sec; this.intervalValue.textContent = step.label; this.intervalLabels.forEach(s => { s.classList.toggle('active', parseInt(s.dataset.idx, 10) === idx); }); }; this.activityIntervalRange.value = 0; this.activityIntervalRange.disabled = true; this.intervalLabels.forEach(s => s.classList.add('disabled')); this.activityIntervalRange.addEventListener('input', () => { if (!CONFIG.unlocked) return; updateIntervalUI(); }); this.intervalLabels.forEach(span => { span.addEventListener('click', () => { if (!CONFIG.unlocked) return; this.activityIntervalRange.value = span.dataset.idx; updateIntervalUI(); }); }); updateIntervalUI(); } log(message, type = 'info') { const now = new Date(); const timeStr = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); const icons = { info: 'ℹ️', success: '✓', warning: '⚠️', error: '✗' }; const item = document.createElement('div'); item.className = `log-item ${type}`; item.innerHTML = `${timeStr}${icons[type] || '📝'}${message}`; this.logContainer.appendChild(item); this.logContainer.scrollTop = this.logContainer.scrollHeight; } setProgress(percent) { this.progressFill.style.width = `${Math.min(100, Math.max(0, percent))}%`; } onStart(callback) { this.startBtn.addEventListener('click', () => { callback(); this.startBtn.innerHTML = '刷课中'; this.setRunningState(true, false); }); } onPause(callback) { this.pauseBtn.addEventListener('click', () => callback()); } onRestart(callback) { this.restartBtn.addEventListener('click', () => callback()); } } const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const randomDelay = () => { const ms = Math.floor(Math.random() * (CONFIG.randomDelayMax - CONFIG.randomDelayMin)) + CONFIG.randomDelayMin; return delay(ms); }; function findNextButton() { try { const $btn = $('button, a').filter((i, el) => { const text = $(el).text().trim().replace(/\s+/g, ''); return text === '下一个' || text === '下一个>'; }); if ($btn.length > 0) return $btn[0]; } catch (e) {} const allButtons = document.querySelectorAll('button, a, [role="button"], [class*="next"]'); for (const btn of allButtons) { const text = (btn.textContent || '').trim().replace(/\s+/g, ''); if (text === '下一个' || text === '下一个>' || /下一个/.test(text)) { const rect = btn.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) return btn; } } try { const xpath = '//*[contains(text(), "下一个") and (self::button or self::a or self::div or self::span)]'; const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); if (result.singleNodeValue) { const node = result.singleNodeValue; const rect = node.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) return node; } } catch (e) {} return null; } function goNext(panel) { const btn = findNextButton(); if (!btn) { if (panel) panel.log('⚠️ 未找到"下一个"按钮', 'warning'); return false; } if (panel) panel.log(`➡️ 点击下一个...`, 'info'); try { btn.click(); } catch (e) { const event = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); btn.dispatchEvent(event); } return true; } class CourseAutoLearn { constructor(courseId) { this.courseId = courseId; this.panel = new LogPanel(); this.isRunning = false; this.isPaused = false; this.totalActivities = 0; this.completedActivities = 0; } async waitIfPaused() { while (this.isPaused && this.isRunning) await delay(300); } pause() { if (!this.isRunning || this.isPaused) return; this.isPaused = true; this.panel.setRunningState(true, true); this.panel.log('⏸ 已暂停,点击"继续"恢复', 'warning'); } resume() { if (!this.isRunning || !this.isPaused) return; this.isPaused = false; this.panel.setRunningState(true, false); this.panel.log('▶ 已恢复运行', 'info'); } restart() { this.panel.log('🔄 正在重新开始…', 'warning'); this.isRunning = false; this.isPaused = false; setTimeout(() => { this.completedActivities = 0; this.totalActivities = 0; this.panel.setProgress(0); this.panel.startBtn.innerHTML = '刷课中'; this.panel.setRunningState(true, false); this.start(); }, 400); } async start() { this.isRunning = true; this.isPaused = false; this.completedActivities = 0; this.totalActivities = 0; this.panel.log('===== 智能刷课开始 =====', 'info'); this.panel.log(`当前配置: 倍速=${CONFIG.videoSpeed}x, 自动播放=${CONFIG.autoPlayVideo ? '开启' : '关闭'}, 自动下一个=${CONFIG.autoNext ? '开启' : '关闭'}, 重复学习=${CONFIG.repeatLearned ? '开启' : '关闭'}, 学习间隔=${CONFIG.activityInterval}秒(视频除外)`, 'info'); this.panel.setProgress(0); try { await this.processCourse(); } catch (err) { this.panel.log(`执行出错: ${err.message}`, 'error'); console.error(err); } this.panel.log('===== 刷课完成 =====', 'success'); this.panel.setProgress(100); this.isRunning = false; this.isPaused = false; this.panel.setRunningState(false); this.panel.startBtn.innerHTML = '🚀开始'; if (CONFIG.autoNext) { this.panel.log(`⏳ ${CONFIG.nextDelay / 1000}秒后切换到下一个任务...`, 'info'); setTimeout(() => { const success = goNext(this.panel); if (success) this.panel.log('✅ 已切换到下一个任务,请等待页面加载...', 'success'); else this.panel.log('⚠️ 无法自动切换,请手动点击"下一个"', 'warning'); }, CONFIG.nextDelay); } } async processCourse() { const courseId = this.courseId; const completenessData = await this.getCompleteness(courseId); const startProgress = completenessData?.study_completeness || 0; this.panel.log(`当前课程进度: ${startProgress}%`, 'info'); const modulesData = await this.getModules(courseId); const modules = modulesData?.modules || []; this.panel.log(`课程共 ${modules.length} 个模块`, 'info'); const completedActivities = completenessData?.completed_result?.completed?.learning_activity || []; this.totalActivities = modules.reduce((sum, m) => sum + (m.activities_count || 0), 0) || modules.length * 3; for (const module of modules) { if (!this.isRunning) break; await this.waitIfPaused(); await randomDelay(); this.panel.log(`━━━━ ${module.name} ━━━━`, 'info'); const activitiesData = await this.getModuleActivities(courseId, module.id); const activities = activitiesData?.learning_activities || []; for (const activity of activities) { if (!this.isRunning) break; await this.waitIfPaused(); const { title, id, type } = activity; const isCompleted = completedActivities.includes(parseInt(id)); if (isCompleted && !CONFIG.repeatLearned) { this.panel.log(`[跳过] ${title} (${notificationTypes[type] || type})`, 'warning'); this.completedActivities++; this.updateProgress(); continue; } this.panel.log( isCompleted ? `[已学过·重刷] ${title} (${notificationTypes[type] || type})` : `开始处理: ${title}`, 'info' ); try { await this.handleActivity(activity, isCompleted && CONFIG.repeatLearned); this.panel.log(`✅ 完成: ${title}`, 'success'); } catch (err) { this.panel.log(`❌ 失败: ${title} - ${err.message}`, 'error'); } this.completedActivities++; this.updateProgress(); if (type !== 'online_video' && CONFIG.activityInterval > 0) { this.panel.log(`⏱️ 学习间隔等待 ${CONFIG.activityInterval} 秒...`, 'info'); await delay(CONFIG.activityInterval * 1000); } await randomDelay(); } } const endProgress = (await this.getCompleteness(courseId))?.study_completeness || 0; this.panel.log(`🎉 刷课完成! 进度: ${startProgress}% → ${endProgress}%`, 'success'); } updateProgress() { if (this.totalActivities > 0) { const percent = (this.completedActivities / this.totalActivities) * 100; this.panel.setProgress(percent); } } async handleActivity(activity, isRepeat = false) { const { id, title, type, is_open, uploads, syllabus_id } = activity; if (isRepeat && REPEAT_SKIP_TYPES.includes(type)) { this.panel.log(`ℹ️ 重刷模式:跳过${notificationTypes[type] || type} (${title})`, 'info'); return; } await API.postLearningActiVities(id, type, is_open, title); await delay(500); switch (type) { case 'page': await API.postActivitiesRead(id); break; case 'online_video': await this.handleVideoActivity(id, title, uploads, syllabus_id); break; case 'material': for (const upload of (uploads || [])) { await API.postActivitiesRead(id, { upload_id: upload.id }); } break; case 'forum': { const categoryData = await API.getCategoryId(id); const categoryId = categoryData?.topic_category?.id; if (categoryId) await API.postForum(categoryId); else this.panel.log(`⚠️ 讨论区未获取到 category_id: ${title}`, 'warning'); break; } case 'web_link': await API.postActivitiesRead(id); break; default: this.panel.log(`⚠️ 不支持的活动类型: ${type}`, 'warning'); } } async handleVideoActivity(activityId, title, uploads, syllabusId) { const panel = this.panel; const videoUploads = Array.isArray(uploads) ? uploads : []; let actualDuration = 300; panel.log(`📋 开始处理视频: ${title}`, 'info'); await API.postLearningActiVities(activityId, 'online_video', true, title); await API.postActivitiesRead(activityId); if (CONFIG.autoPlayVideo) { const video = await VideoPlayer.waitForVideo(); // 修法 2:识别"已播完的旧视频",视为未找到 const isStaleVideo = video && video.duration > 0 && video.currentTime >= video.duration * 0.98; const usableVideo = (video && !isStaleVideo) ? video : null; if (usableVideo) { usableVideo.playbackRate = CONFIG.videoSpeed; usableVideo.muted = CONFIG.muteVideo; try { await usableVideo.play(); actualDuration = usableVideo.duration || 300; panel.log(`🎬 视频播放中, 时长: ${Math.round(actualDuration)}秒, 倍速: ${CONFIG.videoSpeed}x, ${CONFIG.muteVideo ? '已静音' : '有声'}`, 'info'); const maxWaitMs = Math.min(120000, Math.max(actualDuration * 1000 / CONFIG.videoSpeed, 15000)); const startTime = Date.now(); let lastProgressReport = 0; let playedSuccessfully = false; while (Date.now() - startTime < maxWaitMs) { if (!this.isRunning) { try { usableVideo.pause(); } catch (e) {} return; } await this.waitIfPaused(); if (usableVideo.ended || usableVideo.currentTime >= (usableVideo.duration || actualDuration) * 0.95) { playedSuccessfully = true; break; } const current = usableVideo.currentTime || 0; const total = usableVideo.duration || actualDuration; const progress = total > 0 ? Math.round((current / total) * 100) : 0; if (progress >= lastProgressReport + 20) { panel.log(` 播放进度: ${progress}% (${Math.round(current)}/${Math.round(total)}秒)`, 'info'); lastProgressReport = progress; } await new Promise(resolve => setTimeout(resolve, 3000 / CONFIG.videoSpeed)); if (usableVideo.paused && !usableVideo.ended && usableVideo.currentTime < (usableVideo.duration || actualDuration) * 0.95) { try { await usableVideo.play(); } catch (e) {} } } if (playedSuccessfully) { panel.log(`✅ 视频播放完成`, 'success'); actualDuration = Math.max(usableVideo.duration || actualDuration, usableVideo.currentTime || actualDuration); } else { panel.log(`⏱️ 播放超时,强制结束 (已播放 ${Math.round(usableVideo.currentTime || 0)}秒)`, 'warning'); actualDuration = Math.max(usableVideo.currentTime || actualDuration, actualDuration * 0.95); } } catch (err) { panel.log(`📺 视频播放失败: ${err.message}`, 'error'); try { actualDuration = usableVideo.duration || 300; } catch (e) { actualDuration = 300; } } } else { if (isStaleVideo) { panel.log(`📺 检测到的是已播完的旧视频,使用元数据时长`, 'warning'); } else { panel.log('📺 未找到视频元素,使用元数据时长', 'warning'); } if (videoUploads[0]?.videos?.[0]?.duration) actualDuration = videoUploads[0].videos[0].duration; } } else { if (videoUploads[0]?.videos?.[0]?.duration) actualDuration = videoUploads[0].videos[0].duration; panel.log(`📺 跳过自动播放,使用时长: ${actualDuration}秒`, 'info'); } if (!this.isRunning) return; for (const upload of videoUploads) { for (const videoInfo of upload.videos || []) { const duration = videoInfo.duration || actualDuration; await API.addVideoLearningRecords({ syllabus_id: syllabusId, activity_id: activityId, upload_id: upload.id, start_at: 0, end_at: Math.round(duration) }); await API.postActivitiesRead(activityId, { start: 0, end: Math.round(duration) }); } } panel.log(`📊 已报告观看进度: 0 - ${Math.round(actualDuration)}秒`, 'success'); } getCompleteness(courseId) { return new Promise((resolve, reject) => { $.get(`https://lms.ouchn.cn/api/course/${courseId}/my-completeness`) .done((data) => resolve(data)) .fail((xhr, status, err) => reject(new Error(`my-completeness 请求失败: ${status || err}`))); }); } getModules(courseId) { return new Promise((resolve, reject) => { $.get(`https://lms.ouchn.cn/api/courses/${courseId}/modules`) .done((data) => resolve(data)) .fail((xhr, status, err) => reject(new Error(`modules 请求失败: ${status || err}`))); }); } getModuleActivities(courseId, moduleId) { return new Promise((resolve, reject) => { $.get(`https://lms.ouchn.cn/api/course/${courseId}/all-activities?module_ids=[${moduleId}]&activity_types=learning_activities,exams,classrooms`) .done((data) => resolve(data)) .fail((xhr, status, err) => reject(new Error(`all-activities 请求失败: ${status || err}`))); }); } } let currentCourseId = null; let currentAutoLearn = null; function cleanupOldPanel() { const oldPanel = document.getElementById('autoLearnPanel'); if (oldPanel) oldPanel.remove(); } function init() { cleanupOldPanel(); const courseIdInput = document.querySelector("#courseId"); if (!courseIdInput) { setTimeout(init, 2000); return; } const courseId = courseIdInput.value; if (!courseId) { setTimeout(init, 2000); return; } if (currentCourseId === courseId && currentAutoLearn && document.getElementById('autoLearnPanel')) { return; } currentCourseId = courseId; currentAutoLearn = new CourseAutoLearn(courseId); currentAutoLearn.panel.log(`🎓 课程ID: ${courseId}`, 'info'); currentAutoLearn.panel.log(`💡 点击"开始"按钮开始自动学习;首次使用请点"捐赠"`, 'info'); currentAutoLearn.panel.onStart(() => { currentAutoLearn.start(); }); currentAutoLearn.panel.onPause(() => { if (currentAutoLearn.isPaused) currentAutoLearn.resume(); else currentAutoLearn.pause(); }); currentAutoLearn.panel.onRestart(() => { currentAutoLearn.restart(); }); // ============ 立即 + 多次延迟尝试解锁 ============ tryUnlockFromCache('immediate'); setTimeout(() => tryUnlockFromCache('delay-500ms'), 500); setTimeout(() => tryUnlockFromCache('delay-3s'), 3000); // ============ init 后检测补解锁(防止面板重建) ============ setTimeout(() => { try { const cached = GM_getValue(AUTH_CACHE_KEY, null); if (cached && cached.card && cached.mac) { const btn = document.getElementById('donateBtn'); if (btn && !btn.innerHTML.includes('已解锁')) { console.log('[刷课] init 后检测到未解锁,重新解锁'); currentCard = cached.card; currentMac = cached.mac; CONFIG.unlocked = true; btn.innerHTML = '已解锁'; const range = document.getElementById('activityIntervalRange'); if (range) range.disabled = false; const labels = document.querySelectorAll('#intervalLabels span'); labels.forEach(s => s.classList.remove('disabled')); const repeat = document.getElementById('repeatLearnedCheck'); if (repeat) repeat.disabled = false; if (currentAutoLearn?.panel?.log) { currentAutoLearn.panel.log('✅ 已用缓存卡密自动解锁(面板重建后恢复)', 'success'); } if (typeof startHeartbeat === 'function' && currentAutoLearn?.panel) { startHeartbeat(currentAutoLearn.panel); } } } } catch (e) { console.error('[刷课] init 后自动解锁失败:', e); } }, 300); } // ============================================================ // 全局守护:每 1.5 秒检查面板是否被重建后忘记解锁 // ============================================================ setInterval(() => { try { const btn = document.getElementById('donateBtn'); if (!btn) return; if (btn.innerHTML.includes('已解锁')) return; const cached = GM_getValue(AUTH_CACHE_KEY, null); if (!cached || !cached.card || !cached.mac) return; console.log('[刷课-守护] 检测到未解锁面板,自动解锁'); currentCard = cached.card; currentMac = cached.mac; CONFIG.unlocked = true; btn.innerHTML = '已解锁'; const range = document.getElementById('activityIntervalRange'); if (range) range.disabled = false; const labels = document.querySelectorAll('#intervalLabels span'); labels.forEach(s => s.classList.remove('disabled')); const repeat = document.getElementById('repeatLearnedCheck'); if (repeat) repeat.disabled = false; if (currentAutoLearn?.panel?.log) { currentAutoLearn.panel.log('✅ 已用缓存卡密自动解锁(守护进程)', 'success'); } if (typeof startHeartbeat === 'function' && currentAutoLearn?.panel) { startHeartbeat(currentAutoLearn.panel); } } catch (e) { console.error('[刷课-守护] 报错:', e); } }, 1500); let lastUrl = location.href; function handleUrlChange(newUrl) { console.log(`[刷课脚本] 页面变化: ${lastUrl} -> ${newUrl}`); lastUrl = newUrl; setTimeout(init, 1500); } const originalPushState = history.pushState; history.pushState = function () { originalPushState.apply(this, arguments); if (location.href !== lastUrl) handleUrlChange(location.href); }; const originalReplaceState = history.replaceState; history.replaceState = function () { originalReplaceState.apply(this, arguments); if (location.href !== lastUrl) handleUrlChange(location.href); }; window.addEventListener('popstate', () => { if (location.href !== lastUrl) handleUrlChange(location.href); }); setInterval(() => { if (location.href !== lastUrl) { handleUrlChange(location.href); return; } const courseIdInput = document.querySelector("#courseId"); const liveCourseId = courseIdInput?.value; if (liveCourseId && currentCourseId && liveCourseId !== currentCourseId) { setTimeout(init, 1500); return; } if (!document.getElementById('autoLearnPanel') && liveCourseId) { init(); } }, 2000); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();