// ==UserScript== // @name 🔥【免费】中国大学Mooc答题助手(HappyMooc) // @icon https://i.postimg.cc/YqDVMYRC/Happy-Mooc-icon-128x128.png // @namespace http://tampermonkey.net/ // @version 1.2 // @description 免费答题助手(不需要购买题库),挂机刷课,真免费! // @author YZG // @match https://www.icourse163.org/learn/* // @match http://www.icourse163.org/learn/* // @match http://www.icourse163.org/spoc/learn/* // @match https://www.icourse163.org/spoc/learn/* // @match https://www.icourse163.org/mooc/* // @grant GM.xmlHttpRequest // @grant unsafeWindow // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function () { 'use strict'; const debug = false const API_CONFIG = { BASE_URL: debug?'http://localhost:5001':"https://mooc.gxx.cool", ENDPOINTS: { AID_CHANGE: '/api/aidChange', USER_STATUS: '/api/user/status' } }; // QRCode库加载状态 let qrcodeLoaded = false; let qrcodeLoadCallbacks = []; let qrcode = document.createElement('script'); qrcode.src = "https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"; // 监听QRCode库加载完成 qrcode.onload = function() { console.log('QRCode库加载完成'); qrcodeLoaded = true; // 执行所有等待的回调函数 qrcodeLoadCallbacks.forEach(callback => callback()); qrcodeLoadCallbacks = []; }; qrcode.onerror = function() { console.error('QRCode库加载失败'); qrcodeLoaded = true; // 即使加载失败也标记为完成,让程序继续运行 // 执行所有等待的回调函数 qrcodeLoadCallbacks.forEach(callback => callback()); qrcodeLoadCallbacks = []; }; document.head.appendChild(qrcode); const buildUrl = (endpoint, params = {}) => { let url = API_CONFIG.BASE_URL + endpoint; Object.keys(params).forEach(key => { url = url.replace(`{${key}}`, params[key]); }); return url; }; const user_info = unsafeWindow.webUser || GM_getValue('webUser'); const global_data = { webUser: { ...user_info }, courseDto: { ...unsafeWindow.courseDto }, curseData: {} }; console.log(global_data); class ToastManager { constructor() { this.container = null; this.toasts = []; this.init(); } init() { this.addStyles(); this.createContainer(); } addStyles() { const style = document.createElement('style'); style.textContent = ` .toast-container { position: fixed; top: 20px; right: 20px; z-index: 10000; pointer-events: none; } .toast { background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); margin-bottom: 10px; padding: 16px 20px; min-width: 300px; max-width: 400px; pointer-events: auto; transform: translateX(100%); transition: all 0.3s ease; border-left: 4px solid #007bff; display: flex; align-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; line-height: 1.4; } .toast.show { transform: translateX(0); } .toast.success { border-left-color: #28a745; } .toast.error { border-left-color: #dc3545; } .toast.warning { border-left-color: #ffc107; } .toast.info { border-left-color: #17a2b8; } .toast-icon { margin-right: 12px; font-size: 18px; flex-shrink: 0; } .toast.success .toast-icon::before { content: '✓'; color: #28a745; } .toast.error .toast-icon::before { content: '✕'; color: #dc3545; } .toast.warning .toast-icon::before { content: '⚠'; color: #ffc107; } .toast.info .toast-icon::before { content: 'ℹ'; color: #17a2b8; } .toast-content { flex: 1; } .toast-title { font-weight: 600; margin-bottom: 4px; color: #333; } .toast-message { color: #666; } .toast-close { margin-left: 12px; background: none; border: none; font-size: 18px; color: #999; cursor: pointer; padding: 0; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s ease; } .toast-close:hover { background: #f0f0f0; color: #666; } .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10001; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s ease; } .modal-overlay.show { opacity: 1; } .modal { background: #fff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); max-width: 500px; width: 90%; max-height: 80vh; overflow: hidden; transform: scale(0.9); transition: transform 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .modal-overlay.show .modal { transform: scale(1); } .modal-header { padding: 20px 24px 16px; border-bottom: 1px solid #eee; display: flex; align-items: center; justify-content: space-between; } .modal-title { font-size: 18px; font-weight: 600; color: #333; margin: 0; } .modal-close { background: none; border: none; font-size: 24px; color: #999; cursor: pointer; padding: 0; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s ease; } .modal-close:hover { background: #f0f0f0; color: #666; } .modal-body { padding: 20px 24px; color: #666; line-height: 1.5; } .modal-footer { padding: 16px 24px 20px; border-top: 1px solid #eee; display: flex; justify-content: flex-end; gap: 12px; } .btn { padding: 8px 16px; border-radius: 6px; border: 1px solid #ddd; background: #fff; color: #333; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s ease; } .btn:hover { background: #f8f9fa; } .btn-primary { background: #007bff; border-color: #007bff; color: #fff; } .btn-primary:hover { background: #0056b3; border-color: #0056b3; } .btn-danger { background: #dc3545; border-color: #dc3545; color: #fff; } .btn-danger:hover { background: #c82333; border-color: #c82333; } `; document.head.appendChild(style); } createContainer() { this.container = document.createElement('div'); this.container.className = 'toast-container'; document.body.appendChild(this.container); } show(type, title, message, duration = 4000) { const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.innerHTML = `
${title}
${message}
`; this.container.appendChild(toast); this.toasts.push(toast); setTimeout(() => toast.classList.add('show'), 10); const closeBtn = toast.querySelector('.toast-close'); closeBtn.addEventListener('click', () => this.remove(toast)); if (duration > 0) { setTimeout(() => this.remove(toast), duration); } return toast; } remove(toast) { toast.classList.remove('show'); setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } const index = this.toasts.indexOf(toast); if (index > -1) { this.toasts.splice(index, 1); } }, 300); } success(title, message, duration) { return this.show('success', title, message, duration); } error(title, message, duration) { return this.show('error', title, message, duration); } warning(title, message, duration) { return this.show('warning', title, message, duration); } info(title, message, duration) { return this.show('info', title, message, duration); } confirm(title, message, onConfirm, onCancel) { const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); setTimeout(() => overlay.classList.add('show'), 10); const closeModal = () => { overlay.classList.remove('show'); setTimeout(() => { if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } }, 300); }; overlay.querySelector('.modal-close').addEventListener('click', () => { closeModal(); if (onCancel) onCancel(); }); overlay.querySelector('.btn-cancel').addEventListener('click', () => { closeModal(); if (onCancel) onCancel(); }); overlay.querySelector('.btn-confirm').addEventListener('click', () => { closeModal(); if (onConfirm) onConfirm(); }); overlay.addEventListener('click', (e) => { if (e.target === overlay) { closeModal(); if (onCancel) onCancel(); } }); } } const base64Encode = (str) => { return btoa(unescape(encodeURIComponent(str))); }; const toast = new ToastManager(); // ================ 慢刷功能相关变量 ================ let studyTime = 0; global_data.slow = false; global_data.check_handle = null; global_data.interval_check = null; global_data.current_unit_index = 0; global_data.last_url = ''; global_data.video_id = ''; global_data.current_video = null; global_data.pause_listener = null; global_data.units = []; global_data.lesson_id = null; global_data.current_unit = null; // ================ 慢刷功能实现 ================ function save_moc_ccontent_learn(data, csrfkey, index, url = "https://www.icourse163.org/web/j/courseBean.saveMocContentLearn.rpc?csrfKey=") { return new Promise((resolve, reject) => { let [Auth_Signature, timestamp, o] = get_Auth_Signature(data); GM.xmlHttpRequest({ url: url + csrfkey, method: 'post', headers: { 'Content-Type': 'application/json;charset=UTF-8', 'timestamp': timestamp, 'Auth-Signature': Auth_Signature, 'Nonce': o, 'System': 'v1', 'Edu-Script-Token': global_data.csrfkey, 'Origin': 'null' }, data: data, onload: function (response) { try { let res = JSON.parse(response.responseText); if (res.code !== 0) { unsafeWindow.fail_list.push({'params': [data, csrfkey, url]}); } } catch (e) { // 忽略解析错误 } resolve(); } }); }); } function get_Auth_Signature(data) { let nonce = global_data.webUser.nonce || '12345678901234567890123456789012'; let timestamp = new Date().getTime() + studyTime; let o = nonce.slice(-4); nonce = nonce.slice(0, -4); let Auth_Signature = unsafeWindow.CryptoJS.MD5(data + o + timestamp + nonce).toString().toLocaleUpperCase(); return [Auth_Signature, timestamp, o]; } function load_lesson() { let lesson_id = global_data.lesson_id; if (!lesson_id) return; let lession_url = 'https://www.icourse163.org/web/j/courseBean.getLastLearnedMocTermDto.rpc?csrfKey=' + global_data.csrfkey; let lession_data = { termId: lesson_id, }; try { GM.xmlHttpRequest({ url: lession_url, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Origin': 'null' }, data: new URLSearchParams(lession_data).toString(), onload: function (response) { try { const result = JSON.parse(response.responseText); if (result.result && result.result.mocTermDto && result.result.mocTermDto.chapters) { const chapters = result.result.mocTermDto.chapters; global_data.chapter_storage = chapters; let units = []; for (let chapter of chapters) { if (!chapter.lessons) continue; for (let lesson of chapter.lessons) { if (!lesson.units) continue; units = units.concat(lesson.units); } } global_data.units = units; toast.success('课程加载成功', '已加载 ' + units.length + ' 个学习单元'); } } catch (parseError) { console.error('解析课程数据失败:', parseError); toast.error('课程数据解析失败', '请刷新页面重试'); } }, onerror: function (error) { console.error('加载课程失败:', error); toast.error('加载课程失败', '请检查网络连接'); } }); } catch (error) { console.error('加载课程失败:', error); toast.error('加载课程失败', '请刷新页面重试'); } } function curremt_unit_index() { // 优先从URL中获取当前单元ID const currentUrl = unsafeWindow.location.href; const urlMatch = currentUrl.match(/cid=(\d+)/); if (urlMatch) { const currentUnitId = urlMatch[1]; for (let i = 0; i < global_data.units.length; i++) { if (global_data.units[i].id.toString() === currentUnitId) { return i; } } } // 如果URL中没有cid,则从DOM中获取 const li = document.querySelector(".f-fl.current"); if (!li) return 0; const li_id = li.getAttribute("data-id"); let current_unit_index = 0; for (let i = 0; i < global_data.units.length; i++) { if (global_data.units[i].id.toString() === li_id) { current_unit_index = i; break; } } return current_unit_index; } function get_lesson() { let next_unit = null; for (let i = global_data.current_unit_index + 1; i < global_data.units.length; i++) { // 只跳过讨论类型,保留其他所有类型(包括文档、视频等) if (global_data.units[i].contentType !== 6) { global_data.current_unit_index = i; next_unit = global_data.units[i]; break; } } return next_unit; } function goto_next_href() { if (!global_data.slow) { return; } const loc = unsafeWindow.location.href; const next_unit = get_lesson(); console.log('当前单元索引:', global_data.current_unit_index); console.log('下一个单元:', next_unit); unsafeWindow.location.href = loc.split('#')[0] + '#/learn/content'; const progressElement = document.getElementById('lesson_progress'); if (progressElement) { progressElement.textContent = '首页'; } if (next_unit === null) { clearInterval(global_data.check_handle); clearInterval(global_data.interval_check); global_data.slow = false; if (progressElement) { progressElement.style.color = 'red'; progressElement.textContent = '未开始'; } toast.success('刷课完成', '慢刷任务已全部完成!'); return; } global_data.current_unit = next_unit; const lessonId = next_unit.lessonId; const id = next_unit.id; const contentTypeName = { 1: '视频', 3: '文档', 4: '富文本', 5: '测验', 6: '讨论' }[next_unit.contentType] || '未知'; console.log(`准备跳转到: ${contentTypeName} (ID: ${id})`); const url = loc.split('#')[0] + `#/learn/content?type=detail&id=${lessonId}&cid=${id}`; setTimeout(function () { unsafeWindow.location.href = url; }, 2000); } function handle_discuss() { if (!global_data.slow) { return; } global_data.last_url = unsafeWindow.location.href; goto_next_href(); } function handle_exam() { if (!global_data.slow) { return; } global_data.last_url = unsafeWindow.location.href; goto_next_href(); } function _C(element) { element.id = 'globalScoreLockBtn'; element.click(); element.removeAttribute("id"); } function handle_ppt() { global_data.last_url = unsafeWindow.location.href; if (!global_data.slow) { return; } const ppt_slider = document.querySelector(".ux-edu-pdfthumbnailviewer.f-pa.j-body"); if (!ppt_slider) return; const a_list = ppt_slider.querySelectorAll('a'); if (a_list.length === 0) return; function click_a(index) { const currentSlider = document.querySelector(".ux-edu-pdfthumbnailviewer.f-pa.j-body"); if (!currentSlider) { global_data.slow = false; const progressElement = document.getElementById('lesson_progress'); if (progressElement) { progressElement.style.color = 'red'; progressElement.textContent = '未开始'; } return; } _C(a_list[index]); const random_time = Math.floor(Math.random() * 1000) + 2500; setTimeout(function () { if (index < a_list.length - 1) { click_a(++index); } else { goto_next_href(); } }, random_time); } const pageInput = document.querySelector(".ux-h5pdfreader_container_footer_pages_in"); let current_page = pageInput ? parseInt(pageInput.value) || 1 : 1; click_a(current_page - 1); } function handle_txt() { if (!global_data.slow) { return; } global_data.last_url = unsafeWindow.location.href; goto_next_href(); } function slow_skip_lesson(tip = true) { if (global_data.slow || !global_data.webUser) { return; } if (unsafeWindow.location.href.indexOf('type=detail') === -1) { toast.warning('请到课程页面', '请到课程页面后再开始挂机刷课!', 5000); return; } // 获取课程信息 if (!global_data.lesson_id) { if (unsafeWindow.moocTermDto) { global_data.lesson_id = unsafeWindow.moocTermDto.id; } else if (unsafeWindow.termDto) { global_data.lesson_id = unsafeWindow.termDto.id; } } // 获取CSRF Key if (!global_data.csrfkey) { global_data.csrfkey = document.cookie.match(/NTESSTUDYSI=(.*?);/) ? document.cookie.match(/NTESSTUDYSI=(.*?);/)[1] : undefined; } // 加载课程单元 if (global_data.units.length === 0) { load_lesson(); } global_data.current_unit_index = curremt_unit_index(); global_data.current_unit = global_data.units[global_data.current_unit_index]; global_data.slow = true; global_data.check_handle = null; global_data.check_handle = setInterval(function () { const current_url = unsafeWindow.location.href; const last_url = global_data.last_url; if (current_url !== last_url && current_url.indexOf('type=detail') !== -1 && document.querySelector(".u-learnBCUI.f-cb")) { // 重新获取当前单元 global_data.current_unit_index = curremt_unit_index(); global_data.current_unit = global_data.units[global_data.current_unit_index]; if (!global_data.current_unit || global_data.current_unit.contentType === undefined) { return; // 等待课程单元数据加载 } switch (global_data.current_unit.contentType) { case 1: //视频 const progressElement = document.getElementById('lesson_progress'); if (progressElement) progressElement.textContent = '视频'; break; case 3: //ppt文档 const progressElement3 = document.getElementById('lesson_progress'); if (progressElement3) progressElement3.textContent = '文档'; break; case 4: //富文本 const progressElement4 = document.getElementById('lesson_progress'); if (progressElement4) progressElement4.textContent = '富文本'; handle_txt(); break; case 5: //测验 const progressElement5 = document.getElementById('lesson_progress'); if (progressElement5) progressElement5.textContent = '测验'; handle_exam(); break; case 6: //讨论 const progressElement6 = document.getElementById('lesson_progress'); if (progressElement6) progressElement6.textContent = '讨论'; handle_discuss(); break; } } const video_elem = document.querySelector('video'); if (video_elem && video_elem !== global_data.current_video) { global_data.last_url = unsafeWindow.location.href; video_elem.play(); video_elem.muted = true; // 移除旧视频的暂停监听器 if (global_data.current_video && global_data.pause_listener) { global_data.current_video.removeEventListener('pause', global_data.pause_listener); } // 创建新的暂停监听器 global_data.pause_listener = function() { if (global_data.slow) { video_elem.play(); } }; video_elem.addEventListener('pause', global_data.pause_listener); global_data.current_video = video_elem; global_data.video_id = video_elem.id || 'video_' + Date.now(); } if (document.querySelector(".ux-edu-pdfthumbnailviewer.f-pa.j-body") && document.querySelector(".ux-edu-pdfthumbnailviewer.f-pa.j-body").querySelectorAll('a').length > 0 && current_url !== last_url) { handle_ppt(); global_data.last_url = unsafeWindow.location.href; } if (document.querySelector(".playEnd.f-f0.f-pa")) { goto_next_href(); } }, 1000); global_data.interval_check = null; global_data.interval_check = setInterval(function () { const loc = unsafeWindow.location.href; if (loc.indexOf("learn/content") === -1) { global_data.slow = false; const progressElement = document.getElementById('lesson_progress'); if (progressElement) { progressElement.style.color = 'red'; progressElement.textContent = '未开始'; } clearInterval(global_data.interval_check); clearInterval(global_data.check_handle); } }, 1000); if (tip) { toast.info('正在挂机刷课', '请勿手动切换页面!', 5000); } const progressElement = document.getElementById('lesson_progress'); if (progressElement) { progressElement.style.color = 'blue'; progressElement.textContent = '挂机中'; } } // 添加隐藏 j-anchorContainer 的CSS const hideAnchorStyle = document.createElement('style'); hideAnchorStyle.textContent = '#j-anchorContainer { display: none !important; }'; document.head.appendChild(hideAnchorStyle); const createUI = () => { const sidePanel = document.createElement('div'); Object.assign(sidePanel.style, { position: 'fixed', top: '15%', right: '20px', width: '280px', backgroundColor: '#fff', borderRadius: '12px', boxShadow: '0 4px 20px rgba(0, 0, 0, 0.08)', zIndex: '10000', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', overflow: 'hidden' }); sidePanel.visible = true; const tabContainer = document.createElement('div'); Object.assign(tabContainer.style, { height: '48px', backgroundColor: '#fff', display: 'flex', alignItems: 'stretch', borderBottom: '1px solid #f0f0f0' }); const tab1 = document.createElement('div'); Object.assign(tab1.style, { flex: '1', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', fontSize: '14px', fontWeight: '500', color: '#007AFF', borderBottom: '2px solid #007AFF', transition: 'all 0.2s ease' }); tab1.textContent = '小程序码'; tab1.setAttribute('data-tab', 'miniprogram'); const tab2 = document.createElement('div'); Object.assign(tab2.style, { flex: '1', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', fontSize: '14px', fontWeight: '500', color: '#8e8e93', borderBottom: '2px solid transparent', transition: 'all 0.2s ease' }); tab2.textContent = '绑定页面'; tab2.setAttribute('data-tab', 'binding'); const tab3 = document.createElement('div'); Object.assign(tab3.style, { flex: '1', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', fontSize: '14px', fontWeight: '500', color: '#8e8e93', borderBottom: '2px solid transparent', transition: 'all 0.2s ease' }); tab3.textContent = '挂机刷课'; tab3.setAttribute('data-tab', 'slowlearn'); const contentArea = document.createElement('div'); Object.assign(contentArea.style, { padding: '20px', backgroundColor: '#fff', minHeight: '220px' }); const miniprogramContent = document.createElement('div'); Object.assign(miniprogramContent.style, { textAlign: 'center' }); const qrDescription = document.createElement('div'); Object.assign(qrDescription.style, { fontSize: '12px', color: '#666', marginBottom: '16px', lineHeight: '1.4' }); qrDescription.textContent = '扫码进入小程序使用完整功能'; const miniprogramQRContainer = document.createElement('div'); Object.assign(miniprogramQRContainer.style, { display: 'flex', justifyContent: 'center', alignItems: 'center', backgroundColor: '#f8f9fa', borderRadius: '8px', border: '1px solid #e9ecef' }); const qrImage = document.createElement('img'); Object.assign(qrImage.style, { width: '150px', height: '150px', borderRadius: '8px', backgroundColor: 'white', objectFit: 'contain' }); qrImage.src = 'https://i.postimg.cc/K8pSzjtH/suncode.jpg'; qrImage.alt = '小程序码'; qrImage.onerror = function() { const placeholder = document.createElement('div'); Object.assign(placeholder.style, { width: '200px', height: '200px', backgroundColor: '#e9ecef', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#999', fontSize: '12px', textAlign: 'center', flexDirection: 'column' }); placeholder.innerHTML = '
小程序码加载失败
请检查网络连接
'; miniprogramQRContainer.replaceChild(placeholder, qrImage); }; miniprogramQRContainer.appendChild(qrImage); miniprogramContent.appendChild(qrDescription); miniprogramContent.appendChild(miniprogramQRContainer); const bindingContent = document.createElement('div'); Object.assign(bindingContent.style, { display: 'none' }); const bindingTitle = document.createElement('div'); Object.assign(bindingTitle.style, { fontSize: '16px', fontWeight: '600', color: '#333', marginBottom: '12px', textAlign: 'center' }); bindingTitle.textContent = '绑定账户'; const bindingDescription = document.createElement('div'); Object.assign(bindingDescription.style, { fontSize: '12px', color: '#666', textAlign: 'center', marginBottom: '16px', lineHeight: '1.4' }); bindingDescription.textContent = '请使用微信小程序扫描下方二维码完成账户绑定'; const bindingQRContainer = document.createElement('div'); Object.assign(bindingQRContainer.style, { display: 'flex', justifyContent: 'center', alignItems: 'center', backgroundColor: '#f8f9fa', borderRadius: '8px', border: '1px solid #e9ecef' }); const encodedUserId = base64Encode(global_data.webUser.id); console.log('生成的Base64编码userid:', encodedUserId); const qrElement = document.createElement('div'); qrElement.style.display = 'inline-block'; const loadingHint = document.createElement('div'); Object.assign(loadingHint.style, { padding: '30px', fontSize: '12px', color: '#666', textAlign: 'center' }); loadingHint.textContent = '正在生成二维码...'; qrElement.appendChild(loadingHint); generateQRCode(qrElement, encodedUserId).then(() => { bindingQRContainer.appendChild(qrElement); }); bindingContent.appendChild(bindingTitle); bindingContent.appendChild(bindingDescription); bindingContent.appendChild(bindingQRContainer); const slowlearnContent = document.createElement('div'); Object.assign(slowlearnContent.style, { display: 'none' }); // 挂机刷课界面 const slowlearnTitle = document.createElement('div'); Object.assign(slowlearnTitle.style, { fontSize: '17px', fontWeight: '600', color: '#000', marginBottom: '6px', textAlign: 'center' }); const slowlearnDescription = document.createElement('div'); Object.assign(slowlearnDescription.style, { fontSize: '13px', color: '#8e8e93', marginBottom: '16px', lineHeight: '1.4', textAlign: 'center' }); const startButton = document.createElement('button'); Object.assign(startButton.style, { width: '85%', padding: '12px 16px', backgroundColor: '#007AFF', color: '#fff', border: 'none', borderRadius: '8px', fontSize: '15px', fontWeight: '500', cursor: 'pointer', margin: '0 auto 10px auto', display: 'block', transition: 'opacity 0.2s ease' }); startButton.textContent = '开始挂机'; startButton.onmouseover = () => startButton.style.opacity = '0.8'; startButton.onmouseout = () => startButton.style.opacity = '1'; startButton.onclick = () => { slow_skip_lesson(true); toast.info('慢刷启动', '慢刷任务已开始,请勿手动切换页面!', 3000); }; const stopButton = document.createElement('button'); Object.assign(stopButton.style, { width: '85%', padding: '12px 16px', backgroundColor: '#ff3b30', color: '#fff', border: 'none', borderRadius: '8px', fontSize: '15px', fontWeight: '500', cursor: 'pointer', margin: '0 auto 16px auto', display: 'block', transition: 'opacity 0.2s ease' }); stopButton.textContent = '停止挂机'; stopButton.onmouseover = () => stopButton.style.opacity = '0.8'; stopButton.onmouseout = () => stopButton.style.opacity = '1'; stopButton.onclick = () => { global_data.slow = false; if (global_data.check_handle) clearInterval(global_data.check_handle); if (global_data.interval_check) clearInterval(global_data.interval_check); const progressElement = document.getElementById('lesson_progress'); if (progressElement) { progressElement.style.color = '#8e8e93'; progressElement.textContent = '未开始'; } toast.info('慢刷已停止', '慢刷任务已停止'); }; const progressContainer = document.createElement('div'); Object.assign(progressContainer.style, { backgroundColor: '#f2f2f7', borderRadius: '10px', padding: '14px', textAlign: 'center', marginBottom: '14px' }); const progressLabel = document.createElement('div'); Object.assign(progressLabel.style, { fontSize: '12px', color: '#8e8e93', marginBottom: '6px', fontWeight: '500' }); progressLabel.textContent = '刷课进度'; const progressElement = document.createElement('div'); progressElement.id = 'lesson_progress'; Object.assign(progressElement.style, { fontSize: '20px', fontWeight: '600', color: '#000', }); progressElement.textContent = '未开始'; progressContainer.appendChild(progressLabel); progressContainer.appendChild(progressElement); slowlearnContent.appendChild(slowlearnDescription); slowlearnContent.appendChild(startButton); slowlearnContent.appendChild(stopButton); slowlearnContent.appendChild(progressContainer); const switchTab = (activeTab) => { [tab1, tab2, tab3].forEach(tab => { const isActive = tab.getAttribute('data-tab') === activeTab; if (isActive) { tab.style.color = '#007AFF'; tab.style.borderBottomColor = '#007AFF'; } else { tab.style.color = '#8e8e93'; tab.style.borderBottomColor = 'transparent'; } }); miniprogramContent.style.display = activeTab === 'miniprogram' ? 'block' : 'none'; bindingContent.style.display = activeTab === 'binding' ? 'block' : 'none'; slowlearnContent.style.display = activeTab === 'slowlearn' ? 'block' : 'none'; }; tab1.addEventListener('click', (e) => { e.stopPropagation(); switchTab('miniprogram'); }); tab2.addEventListener('click', (e) => { e.stopPropagation(); switchTab('binding'); }); tab3.addEventListener('click', (e) => { e.stopPropagation(); switchTab('slowlearn'); }); tabContainer.appendChild(tab1); tabContainer.appendChild(tab2); tabContainer.appendChild(tab3); contentArea.appendChild(miniprogramContent); contentArea.appendChild(bindingContent); contentArea.appendChild(slowlearnContent); sidePanel.appendChild(tabContainer); sidePanel.appendChild(contentArea); document.body.appendChild(sidePanel); addDragFunctionality(sidePanel); // 添加ESC键监听器 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' || e.keyCode === 27) { sidePanel.visible = !sidePanel.visible; sidePanel.style.display = sidePanel.visible ? 'block' : 'none'; GM_setValue('windowVisible', sidePanel.visible); } }); const savedWindowVisible = GM_getValue('windowVisible', true); sidePanel.visible = savedWindowVisible; sidePanel.style.display = savedWindowVisible ? 'block' : 'none'; }; const createBindingUI = () => { const sidePanel = document.createElement('div'); Object.assign(sidePanel.style, { position: 'fixed', top: '40%', right: '20px', width: '320px', backgroundColor: 'white', border: '1px solid #e1e5e9', borderRadius: '12px', boxShadow: '0 8px 32px rgba(0, 0, 0, 0.12)', zIndex: '10000', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', overflow: 'hidden' }); sidePanel.visible = true; const tabContainer = document.createElement('div'); Object.assign(tabContainer.style, { height: '60px', backgroundColor: '#f8f9fa', borderBottom: '1px solid #e9ecef', display: 'flex', alignItems: 'stretch' }); const tab1 = document.createElement('div'); Object.assign(tab1.style, { flex: '1', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', fontSize: '14px', fontWeight: '500', color: '#666', backgroundColor: '#f8f9fa', borderBottom: '2px solid transparent', transition: 'all 0.3s ease' }); tab1.textContent = '小程序码'; tab1.setAttribute('data-tab', 'miniprogram'); const tab2 = document.createElement('div'); Object.assign(tab2.style, { flex: '1', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', fontSize: '14px', fontWeight: '500', color: '#007bff', backgroundColor: 'white', borderBottom: '2px solid #007bff', transition: 'all 0.3s ease' }); tab2.textContent = '绑定页面'; tab2.setAttribute('data-tab', 'binding'); const contentArea = document.createElement('div'); Object.assign(contentArea.style, { padding: '16px', backgroundColor: 'white', minHeight: '200px' }); const miniprogramContent = document.createElement('div'); Object.assign(miniprogramContent.style, { textAlign: 'center', display: 'none' }); const qrTitle = document.createElement('div'); Object.assign(qrTitle.style, { fontSize: '16px', fontWeight: '600', color: '#333', marginBottom: '12px' }); qrTitle.textContent = '小程序码'; const qrDescription = document.createElement('div'); Object.assign(qrDescription.style, { fontSize: '12px', color: '#666', marginBottom: '16px', lineHeight: '1.4' }); qrDescription.textContent = '扫码进入小程序使用更多功能'; const miniprogramQRContainer = document.createElement('div'); Object.assign(miniprogramQRContainer.style, { display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px', border: '1px solid #e9ecef' }); const qrImage = document.createElement('img'); Object.assign(qrImage.style, { width: '150px', height: '150px', borderRadius: '8px', backgroundColor: 'white', border: '1px solid #ddd', objectFit: 'contain' }); qrImage.src = 'https://i.postimg.cc/K8pSzjtH/suncode.jpg'; qrImage.alt = '小程序码'; qrImage.onerror = () => { qrImage.style.display = 'none'; const errorPlaceholder = document.createElement('div'); Object.assign(errorPlaceholder.style, { width: '150px', height: '150px', backgroundColor: '#f8f9fa', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#999', fontSize: '12px', textAlign: 'center', border: '1px solid #e9ecef' }); errorPlaceholder.textContent = '图片加载失败'; qrImage.parentNode.insertBefore(errorPlaceholder, qrImage.nextSibling); }; miniprogramQRContainer.appendChild(qrImage); miniprogramContent.appendChild(qrTitle); miniprogramContent.appendChild(qrDescription); miniprogramContent.appendChild(miniprogramQRContainer); const bindingContent = document.createElement('div'); const bindingDescription = document.createElement('div'); Object.assign(bindingDescription.style, { fontSize: '12px', color: '#666', textAlign: 'center', marginBottom: '16px', lineHeight: '1.4' }); bindingDescription.textContent = '请使用微信小程序扫描下方二维码完成账户绑定'; const bindingQRContainer = document.createElement('div'); Object.assign(bindingQRContainer.style, { display: 'flex', justifyContent: 'center', alignItems: 'center', backgroundColor: '#f8f9fa', borderRadius: '8px', border: '1px solid #e9ecef' }); const encodedUserId = base64Encode(global_data.webUser.id); console.log('生成的Base64编码userid:', encodedUserId); const qrElement = document.createElement('div'); qrElement.style.display = 'inline-block'; const loadingHint = document.createElement('div'); Object.assign(loadingHint.style, { padding: '30px', fontSize: '12px', color: '#666', textAlign: 'center' }); loadingHint.textContent = '正在生成二维码...'; qrElement.appendChild(loadingHint); generateQRCode(qrElement, encodedUserId).then(() => { bindingQRContainer.appendChild(qrElement); }); bindingContent.appendChild(bindingDescription); bindingContent.appendChild(bindingQRContainer); const switchTab = (activeTab) => { [tab1, tab2].forEach(tab => { const isActive = tab.getAttribute('data-tab') === activeTab; Object.assign(tab.style, { color: isActive ? '#007bff' : '#666', backgroundColor: isActive ? 'white' : '#f8f9fa', borderBottomColor: isActive ? '#007bff' : 'transparent' }); }); miniprogramContent.style.display = activeTab === 'miniprogram' ? 'block' : 'none'; bindingContent.style.display = activeTab === 'binding' ? 'block' : 'none'; }; tab1.addEventListener('click', (e) => { e.stopPropagation(); switchTab('miniprogram'); }); tab2.addEventListener('click', (e) => { e.stopPropagation(); switchTab('binding'); }); tabContainer.appendChild(tab1); tabContainer.appendChild(tab2); contentArea.appendChild(miniprogramContent); contentArea.appendChild(bindingContent); sidePanel.appendChild(tabContainer); sidePanel.appendChild(contentArea); document.body.appendChild(sidePanel); addDragFunctionality(sidePanel); // 添加ESC键监听器 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' || e.keyCode === 27) { sidePanel.visible = !sidePanel.visible; sidePanel.style.display = sidePanel.visible ? 'block' : 'none'; GM_setValue('windowVisible', sidePanel.visible); } }); const savedWindowVisible = GM_getValue('windowVisible', true); sidePanel.visible = savedWindowVisible; sidePanel.style.display = savedWindowVisible ? 'block' : 'none'; }; const generateQRCode = (container, text) => { return new Promise((resolve) => { const waitForQRCode = (callback) => { if (qrcodeLoaded) { callback(); } else { qrcodeLoadCallbacks.push(callback); } }; waitForQRCode(() => { try { if (typeof QRCode === 'undefined') { throw new Error('QRCode 库未加载,请确保已正确引入 qrcodejs'); } console.log('检测到 QRCode 库,类型:', typeof QRCode); container.innerHTML = ''; const qrContainer = document.createElement('div'); qrContainer.style.display = 'inline-block'; qrContainer.style.padding = '10px'; qrContainer.style.backgroundColor = '#ffffff'; qrContainer.style.borderRadius = '8px'; const qr = new QRCode(qrContainer, { text: text, width: 150, height: 150, colorDark: '#000000', colorLight: '#ffffff', correctLevel: QRCode.CorrectLevel.M }); console.log('使用 qrcodejs 生成二维码成功'); container.appendChild(qrContainer); resolve(); } catch (error) { console.error('二维码生成失败:', error); console.log('切换到备用方案'); generateFallbackQRCode(container, text); resolve(); } }); }); }; const generateFallbackQRCode = (container, text) => { console.log('使用备用方案生成二维码'); container.innerHTML = ''; const canvas = document.createElement('canvas'); const size = 150; canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, size, size); ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, size, 20); ctx.fillRect(0, 0, 20, size); ctx.fillRect(size-20, 0, 20, size); ctx.fillRect(0, size-20, size, 20); const gridSize = 16; const cellSize = Math.floor((size - 40) / gridSize); const startX = 20; const startY = 20; ctx.fillStyle = '#000000'; for (let i = 0; i < gridSize; i++) { for (let j = 0; j < gridSize; j++) { const seed = text.charCodeAt((i * gridSize + j) % text.length); if ((seed + i + j) % 3 === 0) { ctx.fillRect( startX + j * cellSize, startY + i * cellSize, cellSize - 1, cellSize - 1 ); } } } const cornerSize = cellSize * 3; ctx.fillRect(startX, startY, cornerSize, cornerSize); ctx.fillStyle = '#ffffff'; ctx.fillRect(startX + cellSize, startY + cellSize, cellSize, cellSize); ctx.fillStyle = '#000000'; ctx.fillRect(startX + (gridSize - 3) * cellSize, startY, cornerSize, cornerSize); ctx.fillStyle = '#ffffff'; ctx.fillRect(startX + (gridSize - 2) * cellSize, startY + cellSize, cellSize, cellSize); ctx.fillStyle = '#000000'; ctx.fillRect(startX, startY + (gridSize - 3) * cellSize, cornerSize, cornerSize); ctx.fillStyle = '#ffffff'; ctx.fillRect(startX + cellSize, startY + (gridSize - 2) * cellSize, cellSize, cellSize); const qrContainer = document.createElement('div'); qrContainer.style.display = 'inline-block'; qrContainer.style.padding = '10px'; qrContainer.style.backgroundColor = '#ffffff'; qrContainer.style.borderRadius = '8px'; qrContainer.style.border = '1px solid #ddd'; qrContainer.appendChild(canvas); container.appendChild(qrContainer); const hint = document.createElement('div'); hint.style.marginTop = '10px'; hint.style.fontSize = '12px'; hint.style.color = '#999'; hint.innerHTML = '请使用微信扫一扫 ⚠️ 备用显示'; container.appendChild(hint); }; const checkUserStatus = () => { return new Promise((resolve) => { if (!global_data.webUser?.id) { console.log('用户未登录,无法检查状态'); return resolve(false); } GM.xmlHttpRequest({ method: 'GET', url: `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.USER_STATUS}?userid=${global_data.webUser.id}`, headers: { 'Content-Type': 'application/json' }, timeout: 3000, onload: (response) => { console.log(response); const isEnabled = handleStatusResponse(response); resolve(isEnabled); }, onerror: (error) => { console.error('检查用户状态网络错误:', error); resolve(true); } }); }); }; const handleStatusResponse = (response) => { if (response.status !== 200) { console.error('检查用户状态请求失败:', response.status); return false; } try { const result = JSON.parse(response.responseText); if (result.status !== 0) { console.error('检查用户状态失败:', result.msg); return false; } const userData = result.data; console.log('用户状态检查结果:', userData); global_data.webUser.is_banned = userData.is_banned; global_data.webUser.is_bound = userData.is_bound; global_data.webUser.is_new_user = userData.is_new_user; if (userData.is_banned) { console.log('用户已被封禁,禁止使用脚本'); return false; } console.log('用户状态正常,允许使用脚本'); return true; } catch (error) { console.error('解析用户状态响应失败:', error); return false; } }; const reportAidChange = () => { GM.xmlHttpRequest({ method: 'POST', url: buildUrl(API_CONFIG.ENDPOINTS.AID_CHANGE), headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ userID: global_data.webUser.id, aid: global_data.curseData.aid, tid: global_data.curseData.tid, courseName: global_data.courseDto.name, testName: global_data.curseData.tname, isExam: global_data.curseData.isExam }), onload: (response) => { if (response.status === 200) { console.log('AID change reported successfully:', response.responseText); toast.success('记录成功', '已同步打开试卷信息'); } else { console.error('Failed to report AID change:', response); toast.error('记录失败', ''); } }, onerror: (error) => { console.error('Error reporting AID change:', error); toast.error('网络错误', ''); } }); }; const hookXHR = () => { const originalXHR = unsafeWindow.XMLHttpRequest; unsafeWindow.XMLHttpRequest = class extends originalXHR { open(method, url, ...rest) { this._intercept = url.includes('getOpenQuizPaperDto')||url.includes('getOpenHomeworkPaperDto'); super.open(method, url, ...rest); } set onreadystatechange(callback) { super.onreadystatechange = () => { if (this._intercept && this.readyState === 4) { try { const jsonResponse = JSON.parse(this.responseText); if (jsonResponse.result && jsonResponse.result.aid) { global_data.curseData.aid = jsonResponse.result.aid; global_data.curseData.tid = jsonResponse.result.tid; global_data.curseData.tname = jsonResponse.result.tname; global_data.curseData.isExam = !!jsonResponse.result.examId && jsonResponse.result.examId !== -1; console.log('提取到的数据:', { aid: global_data.curseData.aid, tid: global_data.curseData.tid, courseName: global_data.courseDto.name, testName: global_data.curseData.tname, examId: jsonResponse.result.examId, isExam: global_data.curseData.isExam }); reportAidChange(); } } catch (error) { console.error('Failed to parse response as JSON:', error); } } callback?.call(this); }; } }; }; const addDragFunctionality = (element) => { let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; const dragHandle = element.children[0]; if (!dragHandle) return; const rect = element.getBoundingClientRect(); xOffset = rect.left; yOffset = rect.top; dragHandle.style.cursor = 'move'; dragHandle.style.userSelect = 'none'; dragHandle.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); function dragStart(e) { e.stopPropagation(); initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; if (e.target === dragHandle || dragHandle.contains(e.target)) { isDragging = true; element.style.transition = 'none'; element.style.left = xOffset + 'px'; element.style.top = yOffset + 'px'; element.style.right = 'auto'; } } function drag(e) { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const elementWidth = element.offsetWidth; const elementHeight = element.offsetHeight; if (currentX < 0) currentX = 0; if (currentX > windowWidth - elementWidth) currentX = windowWidth - elementWidth; if (currentY < 0) currentY = 0; if (currentY > windowHeight - elementHeight) currentY = windowHeight - elementHeight; element.style.left = currentX + 'px'; element.style.top = currentY + 'px'; element.style.right = 'auto'; } } function dragEnd(e) { if (isDragging) { initialX = currentX; initialY = currentY; isDragging = false; element.style.transition = 'all 0.2s ease'; } } }; let aid = null; let tid = null; const showNewUserGuide = () => { const modalOverlay = document.createElement('div'); modalOverlay.className = 'modal-overlay'; modalOverlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10001; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s ease; `; const modal = document.createElement('div'); modal.className = 'new-user-guide-modal'; modal.style.cssText = ` background: #fff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); max-width: 500px; width: 90%; max-height: 80vh; overflow: hidden; transform: scale(0.9); transition: transform 0.3s ease; `; modal.innerHTML = `

欢迎使用慕课助手

1 使用步骤

  1. 1.等待助手加载完成
  2. 2.扫码进入小程序后,扫描绑定二维码完成账号绑定
  3. 3.进入作业、测试或考试页面
  4. 4.在小程序中查看答案
  5. 5.按ESC控制窗口的显示和隐藏

2 注意事项

  • 1.可能题库中不存在题目的情况
  • 2.程序将收集用户的答案用于完善题库,不会收集个人信息
  • 3.请合理使用本工具,主要用于学习参考
  • 4.遵守平台使用规范,不要恶意刷题
`; modalOverlay.appendChild(modal); document.body.appendChild(modalOverlay); setTimeout(() => { modalOverlay.style.opacity = '1'; modal.style.transform = 'scale(1)'; }, 10); const closeBtn = modal.querySelector('.close-btn'); const confirmBtn = modal.querySelector('.confirm-btn'); const closeModal = () => { modalOverlay.style.opacity = '0'; modal.style.transform = 'scale(0.9)'; setTimeout(() => { document.body.removeChild(modalOverlay); }, 300); }; closeBtn.addEventListener('click', closeModal); confirmBtn.addEventListener('click', () => { GM_setValue('hasCompletedGuide', true); closeModal(); }); modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) { closeModal(); } }); }; // ================ 快捷键支持 ================ let keysPressed = {}; document.addEventListener('keydown', (event) => { keysPressed[event.key] = true; // Z+1: 显示慢刷控制面板 if (keysPressed['z'] && keysPressed['1']) { keysPressed = {}; const sidePanel = document.querySelector('[data-tab="slowlearn"]'); if (sidePanel) { sidePanel.click(); } toast.info('快捷键', '已切换到慢刷控制面板', 2000); } // Z+2: 开始慢刷 else if (keysPressed['z'] && keysPressed['2']) { keysPressed = {}; slow_skip_lesson(true); toast.info('慢刷启动', '慢刷任务已开始!', 3000); } // Z+3: 停止慢刷 else if (keysPressed['z'] && keysPressed['3']) { keysPressed = {}; global_data.slow = false; if (global_data.check_handle) clearInterval(global_data.check_handle); if (global_data.interval_check) clearInterval(global_data.interval_check); const progressElement = document.getElementById('lesson_progress'); if (progressElement) { progressElement.style.color = 'red'; progressElement.textContent = '未开始'; } toast.info('慢刷已停止', '慢刷任务已停止'); } }); document.addEventListener('keyup', (event) => { keysPressed = {}; }); // 检查用户状态并根据结果决定是否加载脚本 checkUserStatus().then(isEnabled => { if (isEnabled) { if (global_data.webUser.is_new_user || !GM_getValue('hasCompletedGuide', false)) { setTimeout(() => { showNewUserGuide(); }, 1500); } createUI(); hookXHR(); GM_setValue('webUser', global_data.webUser); setTimeout(() => { toast.success('脚本已加载', '慕课助手已成功加载!', 3000); }, 1000); } else { if (global_data.webUser && global_data.webUser.is_banned) { console.log('用户已被封禁,脚本不会加载'); toast.error('账户被禁用', '您的账户已被禁用,无法使用此功能。如有疑问请联系管理员。', 0); } else if (global_data.webUser && global_data.webUser.is_bound === false) { console.log('用户未绑定,显示绑定二维码'); createBindingUI(); toast.info('需要绑定', '请扫描二维码完成账户绑定', 0); } else { console.log('用户状态异常,脚本不会加载'); toast.error('状态异常', '用户状态异常,请检查登录状态。', 0); } } }); })();