// ==UserScript== // @name 宝武学习系统助手(网络自学+集中培训+自动考试) // @namespace https://tampermonkey.net/ // @version 1.0.1 // @description 右上角悬浮按钮,点击选择模式。网络自学、集中培训、自动考试三合一。 // @author 令狐不太冲 // @match *://learn.baowugroup.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_notification // @grant GM_xmlhttpRequest // @grant window.close // @run-at document-end // @icon https://img.mp.sohu.com/q_70,c_zoom,w_640/upload/20170615/da385146ecfa4b26a0d61eb995f5477b_th.jpg // @license MIT // ==/UserScript== (function() { 'use strict'; const MODE_STORAGE_KEY = 'baowu_unified_mode'; const EXAM_CONFIG_KEY = 'baowu_exam_config'; let currentMode = null; let modeSelector = null; let modeTriggerEl = null; // 右上角浮动按钮 let cleanupFunctions = []; // ======================== 右上角浮动按钮 ======================== function createModeTrigger() { if (modeTriggerEl) modeTriggerEl.remove(); modeTriggerEl = document.createElement('div'); modeTriggerEl.id = 'baowu-mode-trigger'; modeTriggerEl.innerHTML = '🎓 助手'; Object.assign(modeTriggerEl.style, { position: 'fixed', top: '10px', right: '10px', zIndex: '999998', padding: '6px 12px', background: 'linear-gradient(135deg, #1a1a2e, #16213e)', color: '#ffcc00', borderRadius: '8px', cursor: 'pointer', fontSize: '14px', fontWeight: 'bold', boxShadow: '0 2px 10px rgba(0,0,0,0.5)', fontFamily: 'Microsoft YaHei, sans-serif', transition: 'all 0.3s' }); modeTriggerEl.onmouseenter = () => modeTriggerEl.style.transform = 'scale(1.05)'; modeTriggerEl.onmouseleave = () => modeTriggerEl.style.transform = 'scale(1)'; modeTriggerEl.onclick = () => showModeSelector(); document.body.appendChild(modeTriggerEl); } function showModeSelector() { if (!modeSelector) { createModeSelectorPanel(); } if (modeSelector) { modeSelector.style.display = 'block'; } } function hideModeSelector() { if (modeSelector) { modeSelector.style.display = 'none'; } } function removeModeSelectorAndTrigger() { if (modeSelector) { modeSelector.remove(); modeSelector = null; } if (modeTriggerEl) { modeTriggerEl.remove(); modeTriggerEl = null; } } // ======================== 模式选择面板 ======================== function createModeSelectorPanel() { if (modeSelector) modeSelector.remove(); modeSelector = document.createElement('div'); modeSelector.id = 'baowu-mode-selector'; modeSelector.innerHTML = `

🎓 中国宝武学习助手

请选择运行模式(选择后将自动记住)

⚠️ 网络自学:到“个人中心”-“公开课”随便播放一个视频后,在视频界面开启。【确保'公开课'里面有课程】
⚠️ 集中培训:请先到专题班首页(页面底部能看到4个小卡片课程在一行的页面)
⚠️ 自动考试:需要配置 AI 接口,配置后在考试页面才出现“自动答题按钮”在屏幕右下角,点击右上角红色按钮“退出考试”-切换模式。
`; document.body.appendChild(modeSelector); // 关闭按钮 document.getElementById('mode-panel-close').addEventListener('click', hideModeSelector); // 三个模式按钮 document.getElementById('btn-network').addEventListener('click', () => { if (!checkNetworkMode()) { showModeHint('⚠️ 请先在“个人中心”页面(能看到“完成情况”按钮)再启动网络自学'); return; } removeModeSelectorAndTrigger(); tryStartMode('network'); }); document.getElementById('btn-zsh').addEventListener('click', () => { if (!checkZshMode()) { showModeHint('⚠️ 请先到达专题班首页或课程学习页再启动集中培训'); return; } removeModeSelectorAndTrigger(); tryStartMode('zsh'); }); document.getElementById('btn-exam').addEventListener('click', () => { const savedConfig = GM_getValue(EXAM_CONFIG_KEY, null); if (savedConfig) { if (!confirm('检测到已保存的 AI 配置,是否直接使用?\n\n点击“确定”直接开始考试监控\n点击“取消”重新配置 API')) { GM_setValue(EXAM_CONFIG_KEY, null); } } removeModeSelectorAndTrigger(); tryStartMode('exam'); }); } function showModeHint(msg) { const hint = document.getElementById('mode-hint'); if (hint) hint.textContent = msg; } // ======================== 环境检测 ======================== function checkNetworkMode() { const all = document.querySelectorAll('span, button, div, a'); for (const el of all) if (el.textContent.trim().replace(/\s+/g, '') === '完成情况' && el.offsetParent) return true; return false; } function checkZshMode() { return document.querySelectorAll('.content-item').length > 0 || window.location.hash.includes('courseStudy') || window.location.hash.includes('zoneMore'); } function isZshHomePage() { const hash = window.location.hash; return /^#\/zoneMore\?centerCode=[A-Za-z0-9]+&guid=/.test(hash); } // ======================== 模式启动/停止 ======================== function tryStartMode(mode) { if (currentMode === mode) return; if (currentMode) { showModeHint('请先停止当前模式'); return; } localStorage.setItem(MODE_STORAGE_KEY, mode); if (mode === 'network') startNetworkMode(); else if (mode === 'zsh') startZshMode(); else if (mode === 'exam') startExamMode(); currentMode = mode; } function stopCurrentMode() { if (!currentMode) return; cleanupFunctions.forEach(fn => { try { fn(); } catch(e) {} }); cleanupFunctions = []; currentMode = null; localStorage.removeItem(MODE_STORAGE_KEY); createModeTrigger(); // 显示右上角按钮 } function registerCleanup(fn) { cleanupFunctions.push(fn); } // ======================== 网络自学模式(完整) ======================== function startNetworkMode() { const DEFAULT_CONFIG = { checkInterval: 1, playRate: 1.0 }; let CONFIG = (() => { const s = localStorage.getItem('baowu_config'); return s ? JSON.parse(s) : { ...DEFAULT_CONFIG }; })(); const saveConfig = () => localStorage.setItem('baowu_config', JSON.stringify(CONFIG)); const PLAY_BUTTON_SELECTOR = [ 'div[style*="play"], .play-button, [aria-label="播放"], button[title="播放"], [class*="play-icon"], .icon-play', 'div[class*="circle-play"], .play-circle, [class*="icon-circle-play"], .fa-play-circle, .glyphicon-play-circle', 'svg path[d*="M"], svg[aria-label="播放"], svg[title="播放"]', 'div[style*="border-radius:50%"][style*="play"], button[style*="border-radius:50%"][class*="play"]', '[data-icon="play-circle"], [class*="video-play-btn"][style*="circle"]', '.vjs-big-play-button', '.video-control-left .play-btn, div[style*="left:"][style*="bottom:"][class*="play"], [class*="player-control"][class*="play"]:first-child', 'button[aria-label="播放"][style*="left"], span[class*="play-icon"][style*="bottom"]', '.video-player .left-control .play-button' ].join(', '); const TEXT = { confirm: '确定', nextSection: '播放下一节', later: '稍后回来', refresh: '刷新', finishStatus: '完成情况', learningStatus: '学习中', startLearn: '立即学习', replay: '重新播放' }; let intervalId, mouseSimulator, videoMonitor, btnMonitor; const forceClick = (el) => { if (!el) return false; try { el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, button: 0 })); el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, button: 0 })); el.dispatchEvent(new MouseEvent('click', { bubbles: true })); if (el.tagName === 'SPAN') el.parentElement?.click(); return true; } catch (e) { return false; } }; const clickFinishStatus = () => { const all = document.querySelectorAll('span, div, a, button'); for (const el of all) if (el.textContent.trim().replace(/\s+/g, '') === TEXT.finishStatus && el.offsetParent) return forceClick(el); return false; }; const getPassScoreFromFinishDialog = () => { const dialog = Array.from(document.querySelectorAll('.el-dialog, .modal, .popup')).find(d => d.textContent.includes('完成情况')); if (!dialog) return 80; const m = dialog.textContent.match(/合格分数线\s*[::]\s*(\d+(?:\.\d+)?)/); return m ? parseFloat(m[1]) : 80; }; const getScoreInfo = () => { const m = document.body.textContent.match(/课程成绩\s*[::]\s*(\d+(?:\.\d+)?)分/); return { score: m ? parseFloat(m[1]) : 0 }; }; const closeFinishPopup = () => { const btn = document.querySelector('button.el-dialog__headerbtn'); if (btn) { forceClick(btn); return true; } const dialog = Array.from(document.querySelectorAll('.el-dialog, .modal, .popup')).find(d => d.textContent.includes('完成情况')); if (dialog) { const close = dialog.querySelector('button.el-dialog__headerbtn, .close, [aria-label="关闭"]'); if (close) { forceClick(close); return true; } } document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27, bubbles: true })); return false; }; const clickReplay = () => { const btn = Array.from(document.querySelectorAll('button, span, div')).find(el => el.textContent.trim() === TEXT.replay && el.offsetParent); if (btn) return forceClick(btn); playVideo(); return false; }; const clickTargetStartLearn = () => { const blueBtn = Array.from(document.querySelectorAll('button, div, a')).find(el => { const bg = getComputedStyle(el).backgroundColor; return el.textContent.trim() === TEXT.startLearn && (bg.includes('rgb(0, 102, 204)') || bg.includes('#0066cc') || el.className.includes('el-button--primary')); }); if (blueBtn && forceClick(blueBtn)) return true; const anyBtn = Array.from(document.querySelectorAll('button, a, div')).find(el => el.textContent.trim() === TEXT.startLearn && el.offsetParent); if (anyBtn) { forceClick(anyBtn); return true; } return false; }; const switchLearningCourse = () => { const center = [...document.querySelectorAll('*')].find(e => e.textContent.trim() === '个人中心'); if (center) forceClick(center); setTimeout(() => { const status = [...document.querySelectorAll('*')].find(e => e.textContent.trim() === '全部状态'); if (status) forceClick(status); setTimeout(() => { const learning = [...document.querySelectorAll('*')].find(e => e.textContent.trim() === TEXT.learningStatus); if (learning) forceClick(learning); setTimeout(clickTargetStartLearn, 8000); }, 4000); }, 4500); }; const playVideo = () => { document.querySelectorAll(PLAY_BUTTON_SELECTOR).forEach(b => { if (b.offsetParent) { forceClick(b); return; } }); document.querySelectorAll('video').forEach(v => { v.play().catch(() => {}); v.playbackRate = CONFIG.playRate; }); }; const startCheck = () => { clearInterval(intervalId); const run = async () => { if (!clickFinishStatus()) return; await new Promise(r => setTimeout(r, 4000)); const refreshBtn = [...document.querySelectorAll('*')].find(e => e.textContent.trim() === TEXT.refresh); if (refreshBtn) forceClick(refreshBtn); await new Promise(r => setTimeout(r, 2000)); const passScore = getPassScoreFromFinishDialog(); const { score } = getScoreInfo(); closeFinishPopup(); updatePanelScore(score, passScore); updateMiniScore(score, passScore); if (score >= passScore) { await new Promise(r => setTimeout(r, 2000)); switchLearningCourse(); } else clickReplay(); }; setTimeout(run, 6000); intervalId = setInterval(run, CONFIG.checkInterval * 60 * 1000); }; const startMonitor = () => { videoMonitor = setInterval(() => document.querySelectorAll('video').forEach(v => { v.play().catch(() => {}); v.playbackRate = CONFIG.playRate; }), 2000); btnMonitor = setInterval(() => [TEXT.confirm, TEXT.nextSection, TEXT.later].forEach(t => { const btn = [...document.querySelectorAll('*')].find(e => e.textContent.trim() === t); if (btn) forceClick(btn); }), 1000); }; document.addEventListener('visibilitychange', () => { if (!document.hidden) document.querySelectorAll('video').forEach(v => { v.play().catch(() => {}); v.playbackRate = CONFIG.playRate; }); }); let miniPassEl, miniCurrentEl; const createMiniPanel = () => { const mini = document.createElement('div'); mini.id = 'baowu-mini-panel'; mini.style.cssText = 'position:fixed;top:100px;left:10px;width:90px;background:#111;color:#ffcc00;padding:4px 6px;border-radius:4px;font-size:11px;z-index:999999;text-align:center;'; mini.innerHTML = '
及格线:80
目前分:0
'; document.body.appendChild(mini); miniPassEl = mini.querySelector('#miniPass'); miniCurrentEl = mini.querySelector('#miniCurrent'); }; const updateMiniScore = (score, pass) => { if (miniPassEl) miniPassEl.textContent = pass; if (miniCurrentEl) miniCurrentEl.textContent = score; }; let panel, passScoreEl, currentScoreEl; const createPanel = () => { panel = document.createElement('div'); panel.id = 'baowu-network-panel'; panel.style.cssText = 'position:fixed;top:0;left:-320px;width:320px;height:100vh;background:#121212;z-index:999999;padding:20px;transition:left 0.3s;font-family:Microsoft Yahei;'; const trigger = document.createElement('div'); trigger.id = 'baowu-network-trigger'; trigger.style.cssText = 'position:fixed;top:50%;left:0;width:20px;height:60px;background:#ff3333;border-radius:0 10px 10px 0;cursor:pointer;z-index:999998;transform:translateY(-50%);'; trigger.innerHTML = '
面板
'; panel.innerHTML = `

网络自学助手

⚠️ 请确保已将所有课程添加至个人中心
合格分数线:80
当前分数:0
`; document.body.append(panel, trigger); trigger.onclick = () => { panel.style.left = panel.style.left === '0px' ? '-320px' : '0px'; trigger.innerHTML = panel.style.left === '0px' ? '
收起
' : '
面板
'; }; panel.querySelector('#closePanel').onclick = () => { panel.style.left = '-320px'; trigger.innerHTML = '
面板
'; }; panel.querySelector('#save').onclick = () => { CONFIG.checkInterval = parseInt(panel.querySelector('#interval').value); CONFIG.playRate = parseFloat(panel.querySelector('#rate').value); saveConfig(); startCheck(); alert('配置已保存!'); panel.style.left = '-320px'; }; panel.querySelector('#reset').onclick = () => { if (!confirm('确定恢复默认配置?')) return; CONFIG = { checkInterval: 1, playRate: 1.0 }; saveConfig(); startCheck(); panel.querySelector('#interval').value = 1; panel.querySelector('#rate').value = 1.0; alert('已重置'); panel.style.left = '-320px'; }; panel.querySelector('#stopNetwork').onclick = stopCurrentMode; passScoreEl = panel.querySelector('#passScore'); currentScoreEl = panel.querySelector('#currentScore'); }; const updatePanelScore = (score, pass) => { if (passScoreEl) passScoreEl.textContent = pass; if (currentScoreEl) currentScoreEl.textContent = score; }; const cleanupNetwork = () => { clearInterval(intervalId); clearInterval(videoMonitor); clearInterval(btnMonitor); clearInterval(mouseSimulator); ['baowu-mini-panel', 'baowu-network-panel', 'baowu-network-trigger'].forEach(id => { const el = document.getElementById(id); if (el) el.remove(); }); }; registerCleanup(cleanupNetwork); mouseSimulator = setInterval(() => { document.dispatchEvent(new Event('mousemove')); window.scrollBy(0, 1); window.scrollBy(0, -1); }, 60000); createMiniPanel(); createPanel(); startMonitor(); startCheck(); console.log('✅ 网络自学模式已启动'); } // ======================== 集中培训模式(完整) ======================== function startZshMode() { const CONFIG = { PLAYBACK_RATE: 1.25, MUTED: true, COMPLETION_CHECK_INTERVAL: 30000, DEBUG: true, STORAGE_KEY: 'auto_learning_state_v9', COURSE_LOG_KEY: 'auto_learning_course_log_v9', PAGE_LOAD_WAIT: 3000, PASS_SCORE: 60, LOCK_REFRESH_INTERVAL: 20000, CARD_RETRY_INTERVAL: 1000, NAVIGATION_COOLDOWN: 8000, LEARNING_TIMEOUT: 30 * 60 * 1000, SCAN_TIMEOUT: 5 * 60 * 1000, PENGUIN_MOVE_INTERVAL: 3000, BUBBLE_INTERVAL_MIN: 300, BUBBLE_INTERVAL_MAX: 700, MAX_BUBBLES_ON_SCREEN: 30, COURSES_PER_PAGE: 4, HEARTBEAT_INTERVAL: 10000 }; const BUBBLE_COLORS = ['#ff6b6b','#feca57','#48dbfb','#ff9ff3','#54a0ff','#5f27cd','#01a3a4','#f368e0','#ff6348','#7bed9f','#70a1ff','#ffa502','#2ed573','#ff4757','#1e90ff','#ff6b81','#a29bfe','#fd79a8','#00b894','#e056fd']; const channel = new BroadcastChannel('baowu_zsh_v2'); let heartbeatTimer = null; function log(...args) { if (CONFIG.DEBUG) console.log('[集中培训]', ...args); if (panel && panel.addLog) panel.addLog(args.join(' ')); } function notifyStateChange() { channel.postMessage({ type: 'STATE_CHANGED' }); localStorage.setItem('auto_learning_notify', Date.now().toString()); } function getHomeHeartbeat() { const v = localStorage.getItem('baowu_home_heartbeat'); return v ? parseInt(v) : 0; } function setHomeHeartbeat() { localStorage.setItem('baowu_home_heartbeat', Date.now().toString()); } const CourseLogger = { getAllCourses: () => JSON.parse(GM_getValue(CONFIG.COURSE_LOG_KEY, '[]')), saveAllCourses: (courses) => { GM_setValue(CONFIG.COURSE_LOG_KEY, JSON.stringify(courses)); notifyStateChange(); }, updateCourse(courseData) { const courses = this.getAllCourses(); const idx = courses.findIndex(c => c.courseId === courseData.courseId); const entry = { courseId: courseData.courseId || '', courseName: courseData.courseName || '未知', status: courseData.status || '未学习', score: courseData.score ?? null, passScore: courseData.passScore || CONFIG.PASS_SCORE, isPassed: courseData.isPassed || false, lastCheckTime: courseData.lastCheckTime || new Date().toISOString(), pageNumber: courseData.pageNumber || 1 }; if (idx >= 0) courses[idx] = { ...courses[idx], ...entry }; else courses.push(entry); this.saveAllCourses(courses); }, forceResetLearning() { const courses = this.getAllCourses(); let cnt = 0; courses.forEach(c => { if (c.status === '学习中' && !c.isPassed) { c.status = '未学习'; c.lastCheckTime = new Date().toISOString(); cnt++; } }); if (cnt) { this.saveAllCourses(courses); log(`🔄 重置 ${cnt} 门`); } return cnt; }, recalcPageNumbers() { const courses = this.getAllCourses(); courses.forEach((c,i) => c.pageNumber = Math.floor(i/CONFIG.COURSES_PER_PAGE)+1); this.saveAllCourses(courses); }, resetStaleLearning() { const courses = this.getAllCourses(); const now = Date.now(); let chg = false; courses.forEach(c => { if (c.status==='学习中' && !c.isPassed && now - new Date(c.lastCheckTime||0).getTime() > CONFIG.LEARNING_TIMEOUT) { c.status='未学习'; c.lastCheckTime=new Date().toISOString(); chg=true; log(`⏰ 超时:${c.courseName}`); } }); if (chg) this.saveAllCourses(courses); return chg; }, getNextToLearn() { const all = this.getAllCourses(); return [...all.filter(c => c.status==='学习中' && !c.isPassed), ...all.filter(c => c.status==='未学习')].sort((a,b) => (a.pageNumber||1)-(b.pageNumber||1)); }, getStats() { const all = this.getAllCourses(); return { total: all.length, passed: all.filter(c => c.isPassed).length, learning: all.filter(c => c.status==='学习中' && !c.isPassed).length, notLearned: all.filter(c => c.status==='未学习').length }; }, isAllDone() { const all = this.getAllCourses(); return all.length>0 && all.every(c => c.isPassed); }, hasLearningCourse() { return this.getAllCourses().some(c => c.status==='学习中' && !c.isPassed); } }; const SharedState = (() => { const data = { currentCourse: '', currentStatus: '空闲', totalCourses: 0, completedCourses: 0, currentScore: 0, passScore: CONFIG.PASS_SCORE, isPassed: false, isRunning: false, homeUrl: '', currentCourseId: null, lastUpdate: Date.now(), isScanning: false, scanStartTime: 0, mode: 'idle', currentPage: 1, totalPages: 1 }; const load = () => { try { const s = localStorage.getItem(CONFIG.STORAGE_KEY); if (s) Object.assign(data, JSON.parse(s)); } catch(e) {} }; const save = () => { data.lastUpdate = Date.now(); localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify(data)); }; return { load, save, set: (k,v) => { data[k] = v; save(); }, get: (k) => data[k] }; })(); SharedState.load(); if (isZshHomePage()) { setHomeHeartbeat(); heartbeatTimer = setInterval(setHomeHeartbeat, CONFIG.HEARTBEAT_INTERVAL); } const Utils = { sleep: ms => new Promise(r => setTimeout(r, ms)), forceClick: el => { if (!el) return false; try { el.click(); return true; } catch(e) { return false; } }, isCourseListPage: () => window.location.hash.includes('zoneMore'), isCourseLearningPage: () => window.location.hash.includes('courseStudy'), applyPlaySettings: () => document.querySelectorAll('video').forEach(v => { v.playbackRate = CONFIG.PLAYBACK_RATE; if (CONFIG.MUTED) v.muted = true; }), findElementByText: (text, selector='button, span, div, a') => Array.from(document.querySelectorAll(selector)).find(el => el.textContent?.trim() === text), waitForCourseCard: async function(name, max=12) { for (let i=0; i { const a = document.querySelector('.el-pager li.is-active.number'); return a ? parseInt(a.textContent) || 1 : 1; })(); log(`📋 扫描开始,共${total}页,当前第${cur}页`); this.scanCurrentPage(cur); for (let p=1; p<=total; p++) { if (p === cur) continue; log(`🔍 翻到第${p}页`); if (!await PageManager.goToPage(p)) { log(`⚠️ 翻页失败`); continue; } await Utils.sleep(1500); this.scanCurrentPage(p); if (panel) panel.updateUI(); } await PageManager.goToPage(cur>1?cur:1); const stats = CourseLogger.getStats(); SharedState.set('totalCourses', stats.total); SharedState.set('completedCourses', stats.passed); log(`✅ 扫描完成:共${stats.total}门,已完成${stats.passed}门`); } finally { SharedState.set('isScanning', false); SharedState.set('mode', 'idle'); if (panel) panel.updateUI(); } } static scanCurrentPage(pageNum) { const items = document.querySelectorAll('.content-item'); let cnt = 0; items.forEach(item => { const nameEl = item.querySelector('.content-name'), statusEl = item.querySelector('.content-status'); if (nameEl && statusEl) { const name = nameEl.textContent.trim(), status = statusEl.textContent.trim(); CourseLogger.updateCourse({ courseId: name, courseName: name, status: status==='已完成'?'已完成':status, isPassed: status==='已完成', pageNumber: pageNum }); cnt++; if (CONFIG.DEBUG) log(`📄 第${pageNum}页 - ${name} (${status==='已完成'?'已完成':status})`); } }); log(`第${pageNum}页扫描完成,发现${cnt}门课程`); } } class HomePageNavigator { constructor() { this.isRunning = false; this.lastNavigate = 0; } async start() { if (this.isRunning) return; this.isRunning = true; SharedState.set('isRunning', true); SharedState.set('mode', 'learning'); if (panel) panel.updateUI(); log('🚀 导航器启动'); await this.loop(); } stop() { this.isRunning = false; SharedState.set('isRunning', false); SharedState.set('mode', 'idle'); if (panel) panel.updateUI(); log('⏹ 导航器停止'); } async loop() { while (this.isRunning) { if (!Utils.isCourseListPage()) { await Utils.sleep(3000); continue; } if (CourseLogger.isAllDone()) { log('🎉 全部完成'); this.stop(); try { GM_notification({ title: '🎉 完成', text: '所有课程已学习完毕', timeout: 5000 }); } catch(e) {} break; } if (Date.now() - this.lastNavigate < CONFIG.NAVIGATION_COOLDOWN) { await Utils.sleep(2000); continue; } CourseLogger.resetStaleLearning(); if (CourseLogger.hasLearningCourse()) { await Utils.sleep(5000); continue; } const toLearn = CourseLogger.getNextToLearn().filter(c => c.status==='未学习'); if (!toLearn.length) { await Utils.sleep(5000); continue; } const course = toLearn[0]; log(`📚 学习: ${course.courseName} (第${course.pageNumber}页)`); SharedState.set('currentCourse', course.courseName); SharedState.set('currentCourseId', course.courseId); if (course.pageNumber && course.pageNumber !== (() => { const a = document.querySelector('.el-pager li.is-active.number'); return a ? parseInt(a.textContent)||1 : 1; })()) { await PageManager.goToPage(course.pageNumber); await Utils.sleep(2000); } const target = await Utils.waitForCourseCard(course.courseName); if (target) { Utils.forceClick(target.querySelector('.content-name') || target); CourseLogger.updateCourse({ courseId: course.courseId, status: '学习中', lastCheckTime: new Date().toISOString() }); this.lastNavigate = Date.now(); } else { CourseLogger.updateCourse({ courseId: course.courseId, status: '已完成', isPassed: true, score: CONFIG.PASS_SCORE }); } if (panel) panel.updateUI(); await Utils.sleep(3000); } } } class LearningPageMonitor { constructor() { this.checkTimer = null; this.keepAlive = null; this.nextTimer = null; this.nextPlaying = false; this.courseId = SharedState.get('currentCourseId'); } start() { if (!Utils.isCourseLearningPage()) return; log('📱 学习监控启动'); if (this.courseId) CourseLogger.updateCourse({ courseId: this.courseId, status: '学习中', lastCheckTime: new Date().toISOString() }); Utils.tryAutoPlay(); setTimeout(() => this.performCheck(), 8000); this.checkTimer = setInterval(() => this.performCheck(), CONFIG.COMPLETION_CHECK_INTERVAL); this.keepAlive = setInterval(() => { Utils.applyPlaySettings(); const v = document.querySelector('video'); if (v && v.paused && v.currentTime>0) v.play().catch(()=>{}); }, CONFIG.LOCK_REFRESH_INTERVAL); this.nextTimer = setInterval(() => this.clickNextSection(), 5000); } stop() { clearInterval(this.checkTimer); clearInterval(this.keepAlive); clearInterval(this.nextTimer); } async performCheck() { if (this.courseId) CourseLogger.updateCourse({ courseId: this.courseId, status: '学习中', lastCheckTime: new Date().toISOString() }); const btn = Utils.findElementByText('完成情况', 'button') || Array.from(document.querySelectorAll('button')).find(b => b.textContent?.includes('完成情况')); if (!btn) return; Utils.forceClick(btn); await Utils.sleep(2000); const dialog = document.querySelector('.el-dialog, .el-message-box'); if (!dialog) return; const text = dialog.textContent || ''; let pass = CONFIG.PASS_SCORE, score = null; const pm = text.match(/合格分数线\s*[::]\s*(\d+)/); if (pm) pass = parseInt(pm[1]); const sm = text.match(/课程成绩\s*[::]\s*(\d+)/); if (sm) score = parseInt(sm[1]); if (score !== null) { const passed = score >= pass; log(`📊 ${score}/${pass} ${passed?'✅达标':'❌未达标'}`); SharedState.set('currentScore', score); SharedState.set('passScore', pass); SharedState.set('isPassed', passed); CourseLogger.updateCourse({ courseId: this.courseId, status: passed?'已完成':'学习中', score, passScore: pass, isPassed: passed }); if (passed) { SharedState.set('completedCourses', SharedState.get('completedCourses')+1); this.stop(); setTimeout(() => Utils.goHomeOrOpenNew(), 2000); } } const closeBtn = document.querySelector('.el-dialog__close'); if (closeBtn) Utils.forceClick(closeBtn); } clickNextSection() { const span = document.querySelector('button.el-button--primary.is-round span'); if (span && span.textContent.trim()==='播放下一节' && !this.nextPlaying) { const btn = span.closest('button'); if (btn && !btn.disabled) { this.nextPlaying = true; Utils.forceClick(btn); setTimeout(() => { Utils.tryAutoPlay(6).finally(() => this.nextPlaying = false); }, 2000); } } } } let panel; class ControlPanel { constructor() { this.panel = null; this.trigger = null; this.isOpen = false; this.logMsgs = []; this.navigator = new HomePageNavigator(); this.moveTimer = null; this.bubbleTimer = null; this.hovering = false; this.bubbles = new Set(); this.lastMove = Date.now(); this.init(); } addLog(msg) { this.logMsgs.push({ time: new Date().toLocaleTimeString(), text: msg }); if (this.logMsgs.length > 100) this.logMsgs.shift(); const el = document.getElementById('panel-log-list'); if (el) el.innerHTML = this.logMsgs.slice(-25).reverse().map(m => `
${m.time} ${m.text}
`).join(''); } init() { document.getElementById('auto-learning-panel')?.remove(); document.getElementById('auto-learning-trigger')?.remove(); this.trigger = document.createElement('div'); this.trigger.id = 'auto-learning-trigger'; this.trigger.innerHTML = `
--
--
`; document.body.appendChild(this.trigger); this.panel = document.createElement('div'); this.panel.id = 'auto-learning-panel'; this.panel.innerHTML = this.getPanelHTML(); document.body.appendChild(this.panel); this.bindEvents(); this.addStyles(); setInterval(() => { const box = document.querySelector('.el-message-box, .el-dialog'); if (box && box.style.display!=='none') { const btn = box.querySelector('.el-button--primary'); if (btn) Utils.forceClick(btn); } }, 2000); this.autoSetHomeUrl(); document.addEventListener('mousemove', () => this.lastMove = Date.now()); this.startPenguinMove(); channel.onmessage = (e) => { if (e.data?.type === 'STATE_CHANGED') { log('🔔 收到同步'); this.updateUI(); } else if (e.data?.type === 'NAVIGATE' && e.data.url) { log('📢 收到首页导航指令'); window.location.href = e.data.url; } }; if (Utils.isCourseListPage()) { if (SharedState.get('isRunning')) this.navigator.start(); } else if (Utils.isCourseLearningPage()) { new LearningPageMonitor().start(); } setTimeout(() => this.updateUI(), 2000); setInterval(() => this.updateUI(), 3000); let hoverTimeout; this.trigger.addEventListener('mouseenter', () => { if (Date.now()-this.lastMove < 500) { clearTimeout(hoverTimeout); this.hovering = true; this.stopPenguinMove(); this.openPanel(); } }); this.trigger.addEventListener('click', () => { clearTimeout(hoverTimeout); this.hovering = true; this.stopPenguinMove(); this.openPanel(); }); this.panel.addEventListener('mouseenter', () => { clearTimeout(hoverTimeout); this.hovering = true; }); this.panel.addEventListener('mouseleave', () => { clearTimeout(hoverTimeout); this.hovering = false; hoverTimeout = setTimeout(() => { this.closePanel(); this.startPenguinMove(); }, 500); }); this.trigger.addEventListener('mouseleave', () => { if (!this.hovering) { clearTimeout(hoverTimeout); hoverTimeout = setTimeout(() => { this.closePanel(); this.startPenguinMove(); }, 300); } }); } autoSetHomeUrl() { if (isZshHomePage()) { SharedState.set('homeUrl', window.location.href); log('🏠 自动识别首页'); } else log('⚠️ 当前非课程首页格式'); } createBubbleCluster() { if (this.hovering || this.bubbles.size >= CONFIG.MAX_BUBBLES_ON_SCREEN) return; const rect = this.trigger.getBoundingClientRect(); const baseX = rect.left + rect.width/2, baseY = rect.top + 5; const count = 2 + Math.floor(Math.random() * 3); for (let i=0; i= CONFIG.MAX_BUBBLES_ON_SCREEN) break; const bubble = document.createElement('div'); bubble.className = 'penguin-bubble'; const size = 8 + Math.random() * 12; bubble.style.width = bubble.style.height = size + 'px'; bubble.style.background = BUBBLE_COLORS[Math.floor(Math.random() * BUBBLE_COLORS.length)]; bubble.style.boxShadow = `0 0 ${size/2}px ${bubble.style.background}`; bubble.style.left = (baseX + (Math.random()-0.5)*30) + 'px'; bubble.style.top = (baseY + (Math.random()-0.5)*10) + 'px'; document.body.appendChild(bubble); this.bubbles.add(bubble); bubble.addEventListener('animationend', () => { bubble.remove(); this.bubbles.delete(bubble); }); setTimeout(() => { if (bubble.parentNode) { bubble.remove(); this.bubbles.delete(bubble); } }, 2500); } } startBubbles() { this.stopBubbles(); const schedule = () => { if (this.hovering) return; const delay = CONFIG.BUBBLE_INTERVAL_MIN + Math.random() * (CONFIG.BUBBLE_INTERVAL_MAX - CONFIG.BUBBLE_INTERVAL_MIN); this.bubbleTimer = setTimeout(() => { this.createBubbleCluster(); schedule(); }, delay); }; schedule(); } stopBubbles() { if (this.bubbleTimer) { clearTimeout(this.bubbleTimer); this.bubbleTimer = null; } this.bubbles.forEach(b => b.remove()); this.bubbles.clear(); } startPenguinMove() { this.stopPenguinMove(); this.startBubbles(); const move = () => { if (this.hovering) return; this.trigger.style.transition = 'left 1.5s ease-in-out, top 1.5s ease-in-out'; this.trigger.style.left = Math.random() * (window.innerWidth - 80) + 10 + 'px'; this.trigger.style.top = Math.random() * (window.innerHeight - 80) + 10 + 'px'; }; move(); this.moveTimer = setInterval(move, CONFIG.PENGUIN_MOVE_INTERVAL); } stopPenguinMove() { if (this.moveTimer) { clearInterval(this.moveTimer); this.moveTimer = null; } this.stopBubbles(); this.trigger.style.transition = 'none'; this.trigger.style.left = 'auto'; this.trigger.style.top = '10px'; this.trigger.style.right = '10px'; } getPanelHTML() { const stats = CourseLogger.getStats(); const data = SharedState; const isScan = data.get('isScanning'), isRun = data.get('isRunning'); const homeUrl = data.get('homeUrl') || ''; const homeStatus = isZshHomePage() ? '' : '⚠️ 当前不是课程首页'; return `
🎓 集中培训助手
${homeStatus}
课程: ${data.get('currentCourse')||'无'}
成绩: ${data.get('currentScore')!==null?data.get('currentScore')+'/'+data.get('passScore')+(data.get('isPassed')?' ✅':' ❌'):'--'}
${stats.passed}
📖${stats.learning}
📋${stats.notLearned}
📦${stats.total}
${this.getCourseListHTML()}
就绪
`; } getCourseListHTML() { const courses = CourseLogger.getAllCourses(); if (!courses.length) return '
请先扫描
'; return courses.slice(0,30).map(c => { const icon = c.isPassed ? '✅' : c.status==='学习中' ? '📖' : '📋'; const color = c.isPassed ? '#00b894' : c.status==='学习中' ? '#fdcb6e' : '#aaa'; return `
${icon}${c.courseName}${c.isPassed?(c.score||'')+'分':c.status}
`; }).join(''); } addStyles() { if (document.getElementById('auto-learning-styles')) return; const s = document.createElement('style'); s.id = 'auto-learning-styles'; s.textContent = `#auto-learning-trigger{position:fixed;top:10px;right:10px;width:52px;height:48px;padding:3px;background:linear-gradient(135deg,#1a1a2e,#16213e);color:#e0e0e0;border-radius:10px;font-size:12px;cursor:pointer;z-index:999998;border:1px solid rgba(255,255,255,0.15);transition:all 0.3s;box-shadow:0 4px 15px rgba(0,0,0,0.3);line-height:1;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;justify-content:center;}#auto-learning-trigger:hover{border-color:rgba(74,158,255,0.5);}#auto-learning-trigger:hover svg{animation:penguinWiggle 0.5s ease-in-out infinite alternate;}@keyframes penguinWiggle{from{transform:rotate(-5deg);}to{transform:rotate(5deg);}}#auto-learning-panel{position:fixed;top:0;right:-400px;width:380px;height:100vh;background:linear-gradient(135deg,#1a1a2e,#16213e);color:#e0e0e0;z-index:999999;transition:right 0.35s;overflow-y:auto;padding:10px;font-size:12px;}#auto-learning-panel.open{right:0;}.panel-header{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.1);margin-bottom:10px;}.panel-section{margin-bottom:10px;background:rgba(255,255,255,0.03);border-radius:6px;padding:8px;}.section-label{color:#aaa;font-size:10px;margin-bottom:6px;}.panel-input{background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.12);border-radius:4px;padding:4px 8px;color:#e0e0e0;font-size:11px;flex:1;}.panel-btn{padding:4px 12px;border:none;border-radius:4px;cursor:pointer;font-size:11px;color:#fff;}.panel-btn:disabled{opacity:0.5;}.panel-btn-success{background:#00b894;}.panel-btn-danger{background:#ff6b6b;}.panel-btn-info{background:#0984e3;}.panel-btn-primary{background:#6c5ce7;}.panel-btn-sm{padding:2px 8px;font-size:10px;}.progress-stats{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:4px;font-size:10px;text-align:center;}.progress-bar-track{height:3px;background:rgba(255,255,255,0.1);border-radius:2px;margin-top:4px;}.progress-bar-fill{height:100%;background:#00b894;border-radius:2px;}.history-list{max-height:150px;overflow-y:auto;font-size:10px;}.history-item{display:flex;gap:4px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.03);}.history-empty{color:#666;text-align:center;padding:15px;}.log-list{max-height:120px;overflow-y:auto;font-size:10px;color:#aaa;}.log-item{padding:2px 0;border-bottom:1px solid rgba(255,255,255,0.02);}.log-time{color:#666;margin-right:6px;font-size:9px;}.penguin-bubble{position:fixed;border-radius:50%;pointer-events:none;z-index:999997;animation:bubbleFloat 2s ease-out forwards;}@keyframes bubbleFloat{0%{opacity:0.9;transform:translateY(0) scale(0.6);}50%{opacity:0.7;transform:translateY(-40px) scale(1.2);}100%{opacity:0;transform:translateY(-100px) scale(0.3);}}`; document.head.appendChild(s); } bindEvents() { this.panel.querySelector('#panel-save-url').addEventListener('click', () => { SharedState.set('homeUrl', this.panel.querySelector('#panel-home-url').value.trim()); this.updateUI(); }); this.panel.querySelector('#panel-scan').addEventListener('click', () => { if (!SharedState.get('isScanning')) ScanManager.scanAllPages(); }); this.panel.querySelector('#panel-start').addEventListener('click', () => { CourseLogger.forceResetLearning(); CourseLogger.recalcPageNumbers(); this.navigator.start(); this.updateUI(); }); this.panel.querySelector('#panel-stop').addEventListener('click', () => { this.navigator.stop(); CourseLogger.saveAllCourses([]); log('🗑️ 课程列表已清空'); if (isZshHomePage()) { const currentUrl = window.location.href; SharedState.set('homeUrl', currentUrl); log('🏠 已自动保存当前首页地址: ' + currentUrl); } else { log('⚠️ 当前不是课程首页,未保存地址'); } this.updateUI(); }); this.panel.querySelector('#panel-reset-learning').addEventListener('click', () => { CourseLogger.forceResetLearning(); this.updateUI(); }); this.panel.querySelector('#panel-export').addEventListener('click', () => { const blob = new Blob([JSON.stringify(CourseLogger.getAllCourses(), null, 2)], {type:'application/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `courses_${new Date().toISOString().slice(0,10)}.json`; a.click(); }); this.panel.querySelector('.panel-close-btn').addEventListener('click', () => this.closePanel()); this.panel.querySelector('#stopZsh').addEventListener('click', stopCurrentMode); } openPanel() { this.isOpen = true; this.panel.classList.add('open'); } closePanel() { this.isOpen = false; this.panel.classList.remove('open'); } updateUI() { SharedState.load(); const data = SharedState, stats = CourseLogger.getStats(); document.getElementById('panel-course').textContent = data.get('currentCourse') || '无'; document.getElementById('panel-score').textContent = data.get('currentScore')!==null ? data.get('currentScore')+'/'+data.get('passScore')+(data.get('isPassed')?' ✅':' ❌') : '--'; document.getElementById('panel-passed').textContent = stats.passed; document.getElementById('panel-learning').textContent = stats.learning; document.getElementById('panel-pending').textContent = stats.notLearned; document.getElementById('panel-total').textContent = stats.total; document.getElementById('panel-progress').style.width = (stats.total>0?Math.round(stats.passed/stats.total*100):0)+'%'; document.getElementById('panel-course-list').innerHTML = this.getCourseListHTML(); document.getElementById('panel-scan').disabled = data.get('isScanning'); document.getElementById('panel-start').disabled = data.get('isRunning'); const homeStatusEl = document.getElementById('panel-home-status'); if (homeStatusEl) homeStatusEl.innerHTML = isZshHomePage() ? '' : '⚠️ 当前不是课程首页'; const scoreEl = document.getElementById('trigger-score'); if (scoreEl) { let txt = '--', clr = '#ccc'; if (data.get('currentScore')!==null && data.get('passScore')!==null) { txt = `${data.get('currentScore')}/${data.get('passScore')}`; clr = data.get('isPassed')?'#00b894':'#fdcb6e'; } scoreEl.textContent = txt; scoreEl.style.color = clr; } const statsEl = document.getElementById('trigger-stats'); if (statsEl) statsEl.innerHTML = `${stats.passed}/${stats.learning}/${stats.notLearned}`; } } function cleanupZsh() { if (panel) { panel.navigator.stop(); panel.stopPenguinMove(); document.getElementById('auto-learning-panel')?.remove(); document.getElementById('auto-learning-trigger')?.remove(); document.getElementById('auto-learning-styles')?.remove(); } if (heartbeatTimer) clearInterval(heartbeatTimer); channel.close(); } registerCleanup(cleanupZsh); panel = new ControlPanel(); log('✅ 集中培训模式已启动'); } // ======================== 自动考试模式 ======================== function startExamMode() { const savedConfig = GM_getValue(EXAM_CONFIG_KEY, null); if (savedConfig) { activateExamMonitor(savedConfig); console.log('✅ 自动考试模式已启动(使用已保存的配置)'); } else { showExamConfigDialog((config) => { GM_setValue(EXAM_CONFIG_KEY, config); activateExamMonitor(config); console.log('✅ 自动考试模式已启动(新配置已保存)'); }); } } function showExamConfigDialog(onSave) { const old = document.getElementById('exam-config-dialog'); if (old) old.remove(); const dialog = document.createElement('div'); dialog.id = 'exam-config-dialog'; dialog.innerHTML = `

🤖 自动考试 - AI 配置

`; document.body.appendChild(dialog); const providerSel = dialog.querySelector('#exam-provider'); const apiUrlInput = dialog.querySelector('#exam-api-url'); const apiKeyInput = dialog.querySelector('#exam-api-key'); const modelInput = dialog.querySelector('#exam-model'); const testBtn = dialog.querySelector('#exam-test-btn'); const goBtn = dialog.querySelector('#exam-go-btn'); const resultDiv = dialog.querySelector('#exam-test-result'); const closeBtn = dialog.querySelector('#exam-config-close'); closeBtn.addEventListener('click', () => { dialog.remove(); stopCurrentMode(); }); providerSel.addEventListener('change', () => { const map = { openai: { url: 'https://api.deepseek.com/v1/chat/completions', model: 'deepseek-chat' }, anthropic: { url: 'https://api.anthropic.com/v1/messages', model: 'claude-3-opus-20240229' }, gemini: { url: 'https://generativelanguage.googleapis.com/v1beta/models/', model: 'gemini-pro' }, dashscope: { url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', model: 'qwen-plus' } }; const val = providerSel.value; if (map[val]) { apiUrlInput.value = map[val].url; modelInput.value = map[val].model; } }); testBtn.addEventListener('click', () => { resultDiv.style.color = '#aaa'; resultDiv.textContent = '测试中...'; const provider = providerSel.value; const apiUrl = apiUrlInput.value.trim(); const apiKey = apiKeyInput.value.trim(); const model = modelInput.value.trim(); if (!apiUrl || !apiKey) { resultDiv.style.color = '#e74c3c'; resultDiv.textContent = '请填写 API 地址和 Key'; return; } let headers, payload; if (provider === 'openai' || provider === 'dashscope') { headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }; payload = { model, messages: [{ role: 'user', content: 'ping' }], max_tokens: 5 }; } else if (provider === 'anthropic') { headers = { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' }; payload = { model, messages: [{ role: 'user', content: 'ping' }], max_tokens: 5 }; } else if (provider === 'gemini') { const url = apiUrl + '?key=' + apiKey; GM_xmlhttpRequest({ method: 'POST', url, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ contents: [{ parts: [{ text: 'ping' }] }] }), onload: (res) => { if (res.status === 200) { resultDiv.style.color = '#2ecc71'; resultDiv.textContent = '✅ 测试正常,连接成功!'; } else { resultDiv.style.color = '#e74c3c'; resultDiv.textContent = `连接失败 (HTTP ${res.status})`; } }, onerror: () => { resultDiv.style.color = '#e74c3c'; resultDiv.textContent = '网络错误'; } }); return; } GM_xmlhttpRequest({ method: 'POST', url: apiUrl, headers, data: JSON.stringify(payload), onload: (res) => { if (res.status >= 200 && res.status < 300) { resultDiv.style.color = '#2ecc71'; resultDiv.textContent = '✅ 测试正常,连接成功!'; } else { resultDiv.style.color = '#e74c3c'; let errMsg = `连接失败 (HTTP ${res.status})`; try { const data = JSON.parse(res.responseText); if (data.error?.message) errMsg += ': ' + data.error.message; } catch(e) {} resultDiv.textContent = errMsg; } }, onerror: () => { resultDiv.style.color = '#e74c3c'; resultDiv.textContent = '网络错误'; } }); }); goBtn.addEventListener('click', () => { const config = { provider: providerSel.value, apiUrl: apiUrlInput.value.trim(), apiKey: apiKeyInput.value.trim(), model: modelInput.value.trim() }; if (!config.apiUrl || !config.apiKey) { resultDiv.style.color = '#e74c3c'; resultDiv.textContent = '请完整填写 API 信息'; return; } dialog.remove(); GM_setValue(EXAM_CONFIG_KEY, config); onSave(config); }); } function activateExamMonitor(config) { cleanupExam(); registerCleanup(cleanupExam); const examState = { config, floatingBtn: null, isSolving: false, interval: null, exitBtn: null }; function addExitButton() { if (document.getElementById('exam-exit-btn')) return; const btn = document.createElement('button'); btn.id = 'exam-exit-btn'; btn.innerText = '🛑 退出考试'; Object.assign(btn.style, { position: 'fixed', top: '10px', right: '10px', zIndex: '999999', padding: '8px 12px', backgroundColor: '#ff6b6b', color: '#fff', border: 'none', borderRadius: '6px', fontSize: '12px', fontWeight: 'bold', cursor: 'pointer', boxShadow: '0 2px 8px rgba(0,0,0,0.3)', fontFamily: 'system-ui, sans-serif' }); btn.onclick = () => { if (confirm('确定退出考试,返回模式选择?')) stopCurrentMode(); }; document.body.appendChild(btn); examState.exitBtn = btn; } function waitForExamPage() { addExitButton(); const check = () => { const questions = document.querySelectorAll('.examination-item-child'); if (questions.length > 0 && !examState.floatingBtn) addFloatingButton(); else if (questions.length === 0 && examState.floatingBtn) { examState.floatingBtn.remove(); examState.floatingBtn = null; } }; examState.interval = setInterval(check, 2000); } function addFloatingButton() { if (document.getElementById('ai-exam-helper-btn')) return; const btn = document.createElement('button'); btn.id = 'ai-exam-helper-btn'; btn.innerText = '一键AI答题'; Object.assign(btn.style, { position: 'fixed', bottom: '20px', right: '20px', zIndex: '999999', padding: '12px 20px', backgroundColor: '#1890ff', color: '#fff', border: 'none', borderRadius: '8px', fontSize: '16px', fontWeight: 'bold', cursor: 'pointer', boxShadow: '0 2px 8px rgba(0,0,0,0.2)', transition: 'all 0.3s', fontFamily: 'system-ui, sans-serif' }); btn.onclick = () => { if (examState.isSolving) return; examState.isSolving = true; btn.innerText = '答题中...'; btn.style.backgroundColor = '#52c41a'; btn.disabled = true; startAutoSolve(config).finally(() => { examState.isSolving = false; btn.innerText = '一键AI答题'; btn.style.backgroundColor = '#1890ff'; btn.disabled = false; }); }; document.body.appendChild(btn); examState.floatingBtn = btn; } async function startAutoSolve(config) { try { const questions = collectQuestions(); if (!questions.length) throw new Error('未找到题目'); const answers = await fetchAnswersFromAI(questions, config); await autoAnswer(questions, answers); notify('成功完成所有题目!请手动检查并提交。'); } catch (e) { notify('答题失败: ' + e.message, true); } } function collectQuestions() { const items = document.querySelectorAll('.examination-item-child'); const questions = []; items.forEach((qEl, idx) => { const titleEl = qEl.querySelector('.examination-item-child-title .editor-content'); let title = titleEl ? titleEl.innerText.trim() : qEl.querySelector('.examination-item-child-title')?.innerText.replace(/\s*(\d+分)\s*$/, '').trim() || ''; const optsContainer = qEl.querySelector('.examination-item-options'); if (!optsContainer) return; const isMulti = optsContainer.querySelector('input[type="checkbox"]') !== null; const optionItems = optsContainer.querySelectorAll('.option-item'); const options = []; const letterMap = {}; optionItems.forEach((item, i) => { const letter = String.fromCharCode(65 + i); const content = item.querySelector('.option-content')?.innerText.trim() || item.innerText.trim(); const input = item.querySelector('input'); if (input) { options.push({ letter, text: content }); letterMap[letter] = { text: content, inputElement: input }; } }); if (options.length === 0) return; questions.push({ order: idx + 1, element: qEl, title, type: isMulti ? 'multiple' : 'single', options, letterMap }); }); return questions; } function fetchAnswersFromAI(questions, config) { return new Promise((resolve, reject) => { const examPaper = questions.map(q => { const optText = q.options.map(o => `${o.letter}、${o.text}`).join('\n'); return `题目${q.order}: ${q.type==='single'?'【单选】':'【多选】'}\n${q.title}\n选项:\n${optText}`; }).join('\n\n'); const systemPrompt = `你是法学专家,请根据《习近平法治思想学习纲要》回答题目。只返回JSON对象,格式如{"1":"A","2":["B","C"]}`; const userMsg = `共${questions.length}题:\n\n${examPaper}`; const payload = { model: config.model, messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userMsg }], temperature: 0.1, max_tokens: 2000 }; let headers = { 'Content-Type': 'application/json' }; if (config.provider === 'openai' || config.provider === 'dashscope') headers['Authorization'] = `Bearer ${config.apiKey}`; else if (config.provider === 'anthropic') { headers['x-api-key'] = config.apiKey; headers['anthropic-version'] = '2023-06-01'; } GM_xmlhttpRequest({ method: 'POST', url: config.apiUrl, headers, data: JSON.stringify(payload), onload: function(res) { try { const data = JSON.parse(res.responseText); let content = ''; if (config.provider === 'anthropic') content = data.content[0].text; else if (config.provider === 'gemini') content = data.candidates[0].content.parts[0].text; else content = data.choices[0].message.content; const jsonMatch = content.match(/\{[\s\S]*\}/); if (jsonMatch) resolve(JSON.parse(jsonMatch[0])); else reject(new Error('AI返回格式非JSON')); } catch(e) { reject(new Error('解析AI返回失败: ' + e.message)); } }, onerror: () => reject(new Error('网络错误')) }); }); } async function autoAnswer(questions, answersMap) { for (let q of questions) { const key = q.order.toString(); let answer = answersMap[key]; if (!answer) continue; if (q.type === 'multiple' && !Array.isArray(answer)) answer = [answer]; if (q.type === 'single' && Array.isArray(answer)) answer = answer[0]; const letters = Array.isArray(answer) ? answer : [answer]; const allInputs = q.element.querySelectorAll('input[type="radio"], input[type="checkbox"]'); allInputs.forEach(inp => { if (inp.checked) { inp.checked = false; inp.dispatchEvent(new Event('change', { bubbles: true })); } }); letters.forEach(letter => { const target = q.letterMap[letter]; if (target && target.inputElement) { target.inputElement.checked = true; target.inputElement.dispatchEvent(new Event('change', { bubbles: true })); if (target.inputElement.type === 'radio') target.inputElement.dispatchEvent(new MouseEvent('click', { bubbles: true })); } }); q.element.scrollIntoView({ behavior: 'smooth', block: 'center' }); await new Promise(r => setTimeout(r, 400)); } } function notify(msg, isError = false) { if (typeof GM_notification !== 'undefined') GM_notification({ title: isError ? '考试助手出错' : '考试助手', text: msg, timeout: 3000 }); else alert(msg); } waitForExamPage(); } function cleanupExam() { const ids = ['ai-exam-helper-btn', 'exam-exit-btn']; ids.forEach(id => { const el = document.getElementById(id); if (el) el.remove(); }); if (window._examInterval) clearInterval(window._examInterval); } // ======================== 初始化 ======================== function init() { const savedMode = localStorage.getItem(MODE_STORAGE_KEY); if (savedMode) { tryStartMode(savedMode); return; } createModeTrigger(); // 显示右上角按钮 } if (document.readyState === 'complete') init(); else window.addEventListener('load', init); })();