// ==UserScript== // @name 免费中国大学Mooc答题助手(HappyMooc) // @namespace http://tampermonkey.net/ // @version 1.3 // @description 免费答题助手(不需要购买题库) // @author Your Name // @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' } }; let qrcode = document.createElement('script'); qrcode.src = "https://cdn.bootcdn.net/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"; 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(); const createUI = () => { const sidePanel = document.createElement('div'); Object.assign(sidePanel.style, { position: 'fixed', top: '20%', right: '20px', width: '220px', 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' }); 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: '#007bff', backgroundColor: 'white', borderBottom: '2px solid #007bff', 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: '#666', backgroundColor: '#f8f9fa', borderBottom: '2px solid transparent', 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' }); 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://gxx.cool/adnexa/suncode.png'; 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 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); setTimeout(() => { generateQRCode(qrElement, encodedUserId); }, 3000); 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); }; 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' }); 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://gxx.cool/adnexa/suncode.png'; 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', marginBottom: '16px', padding: '12px', 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); bindingQRContainer.appendChild(qrElement); const debugInfo = document.createElement('details'); Object.assign(debugInfo.style, { marginTop: '12px', fontSize: '11px', color: '#999' }); debugInfo.innerHTML = ` 显示二维码内容
${encodedUserId}
`; bindingContent.appendChild(bindingTitle); bindingContent.appendChild(bindingDescription); bindingContent.appendChild(bindingQRContainer); bindingContent.appendChild(debugInfo); 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); }; const generateQRCode = async (container, text) => { 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); } catch (error) { console.error('二维码生成失败:', error); console.log('切换到备用方案'); generateFallbackQRCode(container, text); } }; 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; 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; checkUserStatus().then(isEnabled => { if (isEnabled) { 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); } } }); })();