// ==UserScript== // @name 中国宝武学习系统-全能版本学习助手【多账户、多模式】 // @namespace https://learn.baowugroup.com/ // @version 8.9.61 // @description 宝武学习平台全自动挂机(AI答题、自动考试、自动调查、自动收藏点赞评论、多账号托管) // @author 令狐不太冲 // @icon https://learn.baowugroup.com/favicon.png // @license MIT // @match https://learn.baowugroup.com/* // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @require https://scriptcat.org/lib/8095/1.0.0/bw_learn_tool_utl_lib.js?sha384-8DUdixfmCvNg3ejsXviRkFfJm0drMFgmWTCOuEYiRa3Hgxr8W+Ct3J6BoKYtnKGp // @require https://scriptcat.org/lib/8094/1.0.0/bw_api_lib.js?sha384-hcDDeNwBTdkm5rbC0Z4vcJJbgwAPqfJrUrYSFWt9J1gS0h7fkedEMabxFST8im8s // @require https://cdn.jsdelivr.net/npm/tesseract.js@5.0.5/dist/tesseract.min.js // @run-at document-idle // ==/UserScript== (function () { 'use strict'; var BWAPI = unsafeWindow.BWAPI; var BWUTL = unsafeWindow.BWUTL; const STORE = { get: (k,d)=>GM_getValue(k,d), set: (k,v)=>GM_setValue(k,v) }; const S = { running: STORE.get('bw_running', false), queue: STORE.get('bw_queue', []), queueIdx: STORE.get('bw_queueIdx', 0), accounts: STORE.get('bw_accounts', []), currentAccIdx: STORE.get('bw_currentAccIdx', 0), zones: {}, tab: 'zone', speed: STORE.get('bw_speed', 1.0), scoreInterval: STORE.get('bw_scoreInterval', 30), autoExam: STORE.get('bw_autoExam', true), autoSurvey: STORE.get('bw_autoSurvey', true), autoFav: STORE.get('bw_autoFav', true), autoLike: STORE.get('bw_autoLike', true), autoComment: STORE.get('bw_autoComment', true), aiApiKey: STORE.get('bw_aiApiKey', ''), logEl: null, studyTimer: null, chapterTimer: null, lastScoreCheck: 0, retryCount: 0, doneBefore: STORE.get('bw_doneBefore', 0), doneToday: 0, ocr: null, lastScore: null, lastStudyMinutes: -1, apiStaleCount: 0, surveyHandled: false, examHandled: false, }; const $ = (s,r=document)=>r.querySelector(s); const $$ = (s,r=document)=>Array.from(r.querySelectorAll(s)); const sleep = ms=>new Promise(r=>setTimeout(r,ms)); const now = ()=>{const d=new Date();return `[${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}]`;}; function log(msg, type='info') { const c = {info:'#4fc3f7',ok:'#81c784',warn:'#ffb74d',err:'#e57373',run:'#ce93d8'}; const line = document.createElement('div'); line.style.cssText = `color:${c[type]||'#ccc'};font-size:12px;line-height:1.6;`; line.textContent = `${now()} ${msg}`; if (S.logEl) { S.logEl.appendChild(line); S.logEl.scrollTop = S.logEl.scrollHeight; while (S.logEl.children.length>300) S.logEl.removeChild(S.logEl.firstChild); } console.log(`[BW] ${msg}`); } function route() { const h = location.hash; if (h.includes('/auth/login')) return 'login'; if (h.includes('/zoneMore')) return 'zone'; if (h.includes('/courseStudy')) return 'study'; return 'home'; } function getLoginName() { try { return JSON.parse(localStorage.getItem('userInfo')).loginName || null; } catch(e) { return null; } } async function fetchZones(classType) { if (classType === 'OCE') return await BWAPI.fetchPublicCourses('', 1, 100); else return await BWAPI.fetchZoneList('', 1, 100, classType); } async function fetchZoneCourses(zone) { return await BWAPI.fetchZoneCoursesDetail(zone.centerCode||'C001', zone.olClassNo||zone.guid, zone.olClassType||'ZE0'); } function createPanel() { if ($('#bw-helper')) return; const p = document.createElement('div'); p.id = 'bw-helper'; p.innerHTML = `
宝武学习助手 v8.9.61 待机
当前学习账号: 未登录
公开课
学习专区
网络专题班
培训班
倍速: x 检验合格:
视频进度
合格成绩 -- / --
`; document.body.appendChild(p); S.logEl = $('#bw-log'); $('#bw-mn').onclick = (e)=>{e.stopPropagation(); p.classList.add('off'); p.style.left='auto'; p.style.top='auto'; p.style.right='16px'; p.style.bottom='16px';}; $('#bw-hdr').onclick = ()=>{ if(p.classList.contains('off')){ p.classList.remove('off'); } }; $('#bw-hdr').addEventListener('mousedown', e => { if (p.classList.contains('off')) return; let dx=e.clientX-p.offsetLeft, dy=e.clientY-p.offsetTop, dr=true; const mv=ev=>{if(dr){p.style.left=(ev.clientX-dx)+'px';p.style.top=(ev.clientY-dy)+'px';p.style.right='auto';p.style.bottom='auto';}}; const up=()=>{dr=false;document.removeEventListener('mousemove',mv);document.removeEventListener('mouseup',up);}; document.addEventListener('mousemove',mv);document.addEventListener('mouseup',up); }); $$('.tab').forEach(t=>t.onclick=()=>{$$('.tab').forEach(x=>x.classList.remove('on'));t.classList.add('on');S.tab=t.dataset.t;renderList();}); $('#bw-refresh').onclick = ()=>{ loadAllZones(true); }; $('#bw-all').onclick = ()=>$$('#bw-cl input[type=checkbox]').forEach(c=>{if(!c.closest('.ci').querySelector('.cs').classList.contains('done'))c.checked=true;}); $('#bw-go').onclick = startLearning; $('#bw-stop').onclick = stopLearning; $('#bw-speed').onchange = ()=>{S.speed=parseFloat($('#bw-speed').value)||1.0;STORE.set('bw_speed',S.speed);log('倍速'+S.speed+'x');}; $('#bw-score-interval').onchange = ()=>{S.scoreInterval=parseInt($('#bw-score-interval').value)||30;STORE.set('bw_scoreInterval',S.scoreInterval);log('检验间隔'+S.scoreInterval+'秒');}; $('#bw-auto-exam').onchange = (e)=>{S.autoExam=e.target.checked;STORE.set('bw_autoExam',S.autoExam);log('自动考试'+(S.autoExam?'开启':'关闭'));}; $('#bw-auto-survey').onchange = (e)=>{S.autoSurvey=e.target.checked;STORE.set('bw_autoSurvey',S.autoSurvey);log('自动调查'+(S.autoSurvey?'开启':'关闭'));}; $('#bw-auto-fav').onchange = (e)=>{S.autoFav=e.target.checked;STORE.set('bw_autoFav',S.autoFav);log('自动收藏'+(S.autoFav?'开启':'关闭'));}; $('#bw-auto-like').onchange = (e)=>{S.autoLike=e.target.checked;STORE.set('bw_autoLike',S.autoLike);log('自动点赞'+(S.autoLike?'开启':'关闭'));}; $('#bw-auto-comment').onchange = (e)=>{S.autoComment=e.target.checked;STORE.set('bw_autoComment',S.autoComment);log('自动评论'+(S.autoComment?'开启':'关闭'));}; $('#bw-add-acc').onclick = addAccount; $('#bw-host').onclick = startHosting; $('#bw-ai-apikey').onchange = async function() { const input = this; const newValue = input.value.trim(); input.disabled = true; setTimeout(() => { input.disabled = false; }, 10000); S.aiApiKey = newValue; STORE.set('bw_aiApiKey', S.aiApiKey); log('AI API Key已保存,正在测试连接...', 'run'); if (!newValue) { log('AI API Key已清空,关闭AI答题', 'warn'); input.disabled = false; return; } try { const testResult = await callAI('测试题目:1+1=?', ['1', '2', '3'], 'single'); if (testResult && testResult.length > 0) log('AI连接测试成功!答题可用', 'ok'); else log('AI连接测试失败,将使用默认答案', 'err'); } catch (e) { log('AI连接测试失败: ' + e.message + ',将使用默认答案', 'err'); } input.disabled = false; }; $('#bw-ai-header').onclick = () => { $('#bw-ai-section').classList.toggle('collapsed'); }; $('#bw-acc-header').onclick = () => { $('#bw-acc-section').classList.toggle('collapsed'); }; renderAccList(); } function setStatus(t,c){const e=$('#bw-st');if(e){e.textContent=t;e.style.color=c||'#f9e2af';}} function setWorking(on){setStatus(on?'工作中':'待机',on?'#a6e3a1':'#f9e2af');} function setProg(p){const b=$('#bw-pb');if(b)b.style.width=Math.min(100,Math.max(0,p))+'%';} function isPassed(score) { if (!score || score.score == null) return false; return score.score >= (score.passLine || 60); } function setScoreProg(score,passLine){ const b=$('#bw-pb-score'); const t=$('#bw-score-text'); const mt=$('#bw-min-score'); if(!b||!t)return; if(score!=null&&passLine!=null){ const pct=Math.min(100,Math.max(0,score/passLine*100)); b.style.width=pct+'%'; t.textContent=`${score} / ${passLine} (${Math.round(pct)}%)`; b.style.background=pct>=100?'#81c784':'#f38ba8'; if(mt) mt.textContent = `(${score}/${passLine}(${Math.round(pct)}%))`; } else { b.style.width='0%'; t.textContent='-- / --'; if(mt) mt.textContent = ''; } } function setProgressText(){ const e=$('#bw-prog-text'); if(e){ const currentAcc = getLoginName() || '未登录'; e.textContent=`当前学习账号: ${currentAcc}`; } } function addAccount() { const acc = $('#bw-acc').value.trim(); const pwd = $('#bw-pwd').value; if (!acc || !pwd) { log('请输入工号和密码','warn'); return; } const zones = $$('#bw-zones-check input:checked').map(c=>c.value); let found = false; for (let i = 0; i < S.accounts.length; i++) { if (S.accounts[i].acc === acc) { S.accounts[i] = {acc, pwd, zones}; found = true; break; } } if (!found) S.accounts.push({acc, pwd, zones}); STORE.set('bw_accounts', S.accounts); $('#bw-acc').value=''; $('#bw-pwd').value=''; renderAccList(); log(`账号${acc}已${found?'更新':'添加'} (${zones.join(',')})`,'ok'); } function renderAccList() { const el = $('#bw-acc-list'); if (!el) return; el.innerHTML = ''; S.accounts.forEach((a, i) => { const d = document.createElement('div'); d.className = 'acc-item'; d.innerHTML = `${i+1}. ${a.acc}${a.zones.join('/')}编辑删除`; d.querySelector('.del').onclick = ()=>{ S.accounts.splice(i,1); STORE.set('bw_accounts',S.accounts); renderAccList(); }; d.querySelector('.edit').onclick = ()=>{ $('#bw-acc').value = a.acc; $('#bw-pwd').value = a.pwd; $$('#bw-zones-check input').forEach(cb => { cb.checked = a.zones.includes(cb.value); }); log('编辑账号: ' + a.acc); }; el.appendChild(d); }); const header = document.querySelector('#bw-acc-header span'); if (header) header.innerHTML = `账号托管 (${S.accounts.length}个账号)`; } const TAB_CLASS = {open:'OCE', zone:'ZE0', online:'ZE1', train:'TCE'}; const TAB_NAME = {open:'公开课', zone:'学习专区', online:'网络专题班', train:'培训班'}; async function loadAllZones(force) { log('后台API读取各专区...','run'); setStatus('读取中','#89b4fa'); for (const [key, ct] of Object.entries(TAB_CLASS)) { if (!force && S.zones[key]?.length) continue; try { const list = await fetchZones(ct); S.zones[key] = list; log(`${TAB_NAME[key]}: ${list.length}个`); } catch(e) { log(`${TAB_NAME[key]}读取失败: ${e.message}`,'err'); } } renderList(); if (!S.running) setWorking(false); log('读取完成','ok'); } function renderList() { const list = S.zones[S.tab] || []; const cl = $('#bw-cl'); cl.innerHTML = ''; if (!list.length) { cl.innerHTML = '
暂无数据
'; return; } list.forEach((item, i) => { let st='', cls='todo'; let name = item.courseName || item.olClassName || item.name || item.guid; if (item.isOpenCourse || item.courseGuid) { st = item.learnStatus==='2'?'已完成':'未学习'; cls = item.learnStatus==='2'?'done':'todo'; } else { const total=parseInt(item.courseNum)||0, done=parseInt(item.learnNum)||0; if(total>0&&done>=total){st='已完成';cls='done';} else if(done>0){st=`学习中 ${done}/${total}`;cls='doing';} else{st='未学习';cls='todo';} } const d=document.createElement('div'); d.className='ci'; d.innerHTML=`${name}${st}`; cl.appendChild(d); }); } async function startHosting() { if (!S.accounts.length) { log('请先添加账号','warn'); return; } S.running = true; STORE.set('bw_running', true); S.currentAccIdx = 0; setWorking(true); log(`托管开始,共${S.accounts.length}个账号`,'run'); await hostNextAccount(); } async function hostNextAccount() { if (!S.running) return; if (S.currentAccIdx >= S.accounts.length) { setWorking(false); log('所有账号托管完成!','ok'); STORE.set('bw_running', false); return; } const acc = S.accounts[S.currentAccIdx]; log(`=== 账号 ${S.currentAccIdx+1}/${S.accounts.length}: ${acc.acc} ===`, 'run'); const curRoute = route(); if (curRoute === 'login') { log('当前已在登录页,直接登录'); await doLoginWithAccount(acc); } else { const currentAcc = getLoginName(); log(`当前登录账号: ${currentAcc||'未知'},目标: ${acc.acc}`); if (currentAcc && String(currentAcc) === String(acc.acc)) { log('账号一致,跳过退出登录','ok'); await sleep(3000); } else { log(`账号不一致,需要切换`,'warn'); await logoutAndLogin(acc); await sleep(3000); } } S.zones = {}; await loadAllZones(true); let totalAll = 0, doneAll = 0; const todoCourses = []; for (const zk of acc.zones) { const list = S.zones[zk] || []; for (const item of list) { if (item.isOpenCourse || item.courseGuid) { totalAll++; if (item.learnStatus === '2') doneAll++; else todoCourses.push(item); } else { const zoneName = item.courseName || item.olClassName || item.name || item.guid; log(`扫描: ${zoneName}...`); try { const courses = await fetchZoneCourses(item); courses.forEach(c => { totalAll++; if (c.learnStatus === '2') doneAll++; else todoCourses.push({...c, zoneName}); }); log(` ${zoneName}: 共${courses.length}门,已完成${courses.filter(c=>c.learnStatus==='2').length}门`); } catch(e) { log(`扫描${zoneName}失败`,'err'); } } } } S.totalCourses = totalAll; S.doneBefore = doneAll; S.doneToday = 0; STORE.set('bw_totalCourses', totalAll); STORE.set('bw_doneBefore', doneAll); S.queue = todoCourses; S.queueIdx = 0; STORE.set('bw_queue', S.queue); STORE.set('bw_queueIdx', 0); setProgressText(); log(`统计: 共${totalAll}门,已完成${doneAll}门,待学${todoCourses.length}门`,'ok'); if (todoCourses.length === 0) { log('无待学课程,切换下一账号','warn'); S.currentAccIdx++; STORE.set('bw_currentAccIdx', S.currentAccIdx); await sleep(2000); await hostNextAccount(); } else await learnNext(); } async function logoutAndLogin(acc) { log('退出当前账号...','run'); const avatarBtn = document.querySelector('button.outline-none'); if (avatarBtn) { avatarBtn.click(); await sleep(2000); } const items = document.querySelectorAll('.el-dropdown-menu__item, [role="menuitem"], .el-dropdown-menu li'); for (const item of items) { if (item.textContent.includes('退出登录') && item.offsetParent !== null) { item.click(); log('点了退出登录'); break; } } await sleep(2000); const confirmBtns = document.querySelectorAll('.el-message-box button, .el-dialog__footer button, .el-button--primary'); for (const btn of confirmBtns) { if (btn.offsetParent !== null && btn.textContent.trim().match(/确定|确认|退出/)) { btn.click(); log('确认退出弹窗: 点确定'); break; } } await sleep(4000); location.hash = '#/auth/login'; await sleep(5000); await doLoginWithAccount(acc); } function solveMath(str) { let expr = str.split(/[==??]/)[0]; expr = expr.replace(/[¥¥]/g, '+'); expr = expr.replace(/[^\d+\-×xX*÷/]/g, ''); const m = expr.match(/(\d+)\s*([+\-×xX*÷/])\s*(\d+)/); if (!m) return null; const a=parseInt(m[1]),op=m[2],b=parseInt(m[3]); if(op==='+')return a+b; if(op==='-')return a-b; if(op==='×'||op==='x'||op==='X'||op==='*')return a*b; if(op==='÷'||op==='/')return b?Math.round(a/b):null; return null; } async function ocrCaptcha() { const img = document.querySelector('img.captcha-image'); if (!img || !img.src) return null; const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth * 5; canvas.height = img.naturalHeight * 5; const ctx = canvas.getContext('2d'); ctx.fillStyle = 'white'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); const d = ctx.getImageData(0,0,canvas.width,canvas.height); for (let i=0; i{}}); await S.ocr.setParameters({tessedit_pageseg_mode:'7'}); const {data} = await S.ocr.recognize(canvas.toDataURL()); return (data.text||'').trim(); } async function doLoginWithAccount(acc) { log('登录: ' + acc.acc, 'run'); let inputs = []; for (let i=0; i<10; i++) { inputs = $$('input.el-input__inner'); if (inputs.length >= 3) break; await sleep(1000); } if (inputs.length < 3) { log('登录页输入框未加载','err'); return; } inputs[0].focus(); inputs[0].value = acc.acc; inputs[0].dispatchEvent(new Event('input',{bubbles:true})); inputs[0].dispatchEvent(new Event('change',{bubbles:true})); await sleep(300); inputs[1].focus(); inputs[1].value = acc.pwd; inputs[1].dispatchEvent(new Event('input',{bubbles:true})); inputs[1].dispatchEvent(new Event('change',{bubbles:true})); await sleep(300); for (let attempt = 1; attempt <= 15; attempt++) { await sleep(1000); let text = ''; try { text = await ocrCaptcha(); } catch(e) { log('OCR异常: '+e.message,'err'); } log(`验证码OCR(第${attempt}次): "${text}"`); const ans = solveMath(text); if (ans !== null) { const capInput = $$('input.el-input__inner')[2]; if (capInput) { capInput.focus(); capInput.value = String(ans); capInput.dispatchEvent(new Event('input',{bubbles:true})); capInput.dispatchEvent(new Event('change',{bubbles:true})); log('填入验证码: ' + ans); } await sleep(500); const btns = Array.from(document.querySelectorAll('button')); for (const b of btns) { if (b.textContent.trim() === '登录' && b.offsetParent !== null) { b.click(); break; } } log('点了登录,等待响应...'); await sleep(4000); if (!location.hash.includes('/auth/login')) { log('登录成功!','ok'); return; } log('验证码错误,自动重试...','warn'); await sleep(1000); } else { log('验证码识别失败,点击刷新','warn'); const img = document.querySelector('img.captcha-image'); if (img) img.click(); await sleep(1500); } } log('验证码重试15次仍未成功','err'); } async function startLearning() { const checked = $$('#bw-cl input[type=checkbox]:checked'); if (!checked.length) { log('请先勾选','warn'); return; } setStatus('加载中','#89b4fa'); let totalAll = 0, doneAll = 0; const todoCourses = []; for (const cb of checked) { const i = parseInt(cb.dataset.i); const item = (S.zones[S.tab]||[])[i]; if (!item) continue; if (item.isOpenCourse || item.courseGuid) { totalAll++; if (item.learnStatus === '2') doneAll++; else todoCourses.push(item); } else { const zoneName = item.courseName || item.olClassName || item.name || item.guid; log(`扫描: ${zoneName}...`); try { const courses = await fetchZoneCourses(item); courses.forEach(c => { totalAll++; if (c.learnStatus === '2') doneAll++; else todoCourses.push({...c, zoneName}); }); log(` 共${courses.length}门,已完成${courses.filter(c=>c.learnStatus==='2').length}门`); } catch(e) { log(`扫描失败: ${e.message}`,'err'); } } } if (!todoCourses.length) { log('无未完成课程','warn'); setWorking(false); return; } todoCourses.sort((a,b)=> (a.learnStatus==='1'?0:1)-(b.learnStatus==='1'?0:1)); S.queue = todoCourses; S.queueIdx = 0; S.running = true; S.totalCourses = totalAll; S.doneBefore = doneAll; S.doneToday = 0; STORE.set('bw_running',true); STORE.set('bw_queue',S.queue); STORE.set('bw_queueIdx',0); STORE.set('bw_totalCourses',totalAll); STORE.set('bw_doneBefore',doneAll); setProgressText(); setWorking(true); log(`开始学习,共${totalAll}门(已完成${doneAll}),待学${todoCourses.length}门`,'run'); learnNext(); } function stopLearning() { S.running=false; STORE.set('bw_running',false); if(S.studyTimer)clearInterval(S.studyTimer); if(S.chapterTimer)clearInterval(S.chapterTimer); setWorking(false); log('已停止','warn'); } async function learnNext() { if (!S.running) return; if (S.queueIdx >= S.queue.length) { const done = Math.min(S.doneBefore + S.doneToday, S.totalCourses); log(`本批次完成: ${done}/${S.totalCourses}门`,'ok'); if (S.accounts.length > 1 && S.currentAccIdx < S.accounts.length) { S.currentAccIdx++; STORE.set('bw_currentAccIdx',S.currentAccIdx); await sleep(2000); await hostNextAccount(); } else { setWorking(false); log('全部学习完毕!','ok'); STORE.set('bw_running',false); } return; } const c = S.queue[S.queueIdx]; S.retryCount = 0; S.lastStudyMinutes = -1; S.apiStaleCount = 0; S.surveyHandled = false; S.examHandled = false; setScoreProg(null,null); setProgressText(); log(`=== ${S.queueIdx+1}/${S.queue.length}: ${c.courseName} ===`, 'run'); location.assign(`https://learn.baowugroup.com/#/courseStudy?centerCode=${c.centerCode}&guid=${c.guid}`); } function playCurrentVideo(video) { if (!video || video.ended || !video.paused) return false; const strategies = [ { sel: '.xgplayer-start', name: '中央播放按钮' }, { sel: '.xgplayer-play .xgplayer-icon', name: '控制栏播放图标' }, { sel: '#xg-video, .xgplayer', name: '视频画面区域' } ]; for (const { sel, name } of strategies) { const el = document.querySelector(sel); if (el && el.offsetParent) { el.click(); log(`点击${name}`); return true; } } video.play().catch(()=>{}); log('兜底video.play()'); return true; } function cleanupVideoOverlays() { const start = document.querySelector('.xgplayer-start'); if (start) start.classList.add('hide'); const overlay = document.querySelector('.video-overlay'); if (overlay) overlay.style.display = 'none'; } async function callAI(question, options, type) { if (!S.aiApiKey) return null; try { const prompt = `你是一个考试答题专家。请根据以下题目和选项,给出正确答案。题目类型:${type === 'single' ? '单选题' : (type === 'multiple' ? '多选题' : '判断题')} 题目:${question} 选项: ${options.map((opt, i) => `${i + 1}. ${opt}`).join('\n')} 请只返回正确选项的编号,用逗号分隔,例如:1,2`; const response = await fetch('https://api.deepseek.com/v1/chat/completions', { method: 'POST', headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + S.aiApiKey}, body: JSON.stringify({ model: 'deepseek-chat', messages: [ { role: 'system', content: '你是一个专业的考试答题助手。请只返回正确选项的编号,用逗号分隔。' }, { role: 'user', content: prompt } ], temperature: 0.1, max_tokens: 50 }) }); if (!response.ok) throw new Error('HTTP ' + response.status); const data = await response.json(); const answer = data.choices[0].message.content.trim(); const nums = answer.match(/\d+/g) || []; const indices = nums.map(n => parseInt(n) - 1).filter(i => i >= 0 && i < options.length); return indices.length > 0 ? indices : null; } catch (e) { log('AI答题失败: ' + e.message + ',使用默认答案', 'warn'); return null; } } function getDefaultAnswer(options, type) { if (type === 'single' || type === 'judge') return [0]; if (type === 'multiple') return options.map((_, i) => i); return [0]; } async function doExamQuestions() { for (let retry = 0; retry < 10; retry++) { const questionContainers = $$('.option-group'); if (questionContainers.length === 0) { log('没找到题目选项,第' + (retry+1) + '次重试...','warn'); await sleep(5000); continue; } log(`共${questionContainers.length}道题,开始答题...`); let answered = 0; for (const group of questionContainers) { let questionText = ''; const parent = group.parentElement; if (parent) { const fullText = parent.innerText; const lines = fullText.split('\n').filter(l => l.trim().length > 0); if (lines.length > 0) questionText = lines[0].trim(); } if (!questionText) questionText = '考试题目'; const radios = group.querySelectorAll('input[type="radio"]'); if (radios.length > 0) { const options = Array.from(radios).map(r => r.parentElement.textContent.trim()); const type = radios.length === 2 && (radios[0].parentElement.textContent.includes('对') || radios[0].parentElement.textContent.includes('正确')) ? 'judge' : 'single'; let correctIndices = await callAI(questionText, options, type); if (!correctIndices) { correctIndices = getDefaultAnswer(options, type); log(`默认答: ${questionText.slice(0, 30)}... -> 选项${correctIndices[0] + 1}`); } else log(`AI答: ${questionText.slice(0, 30)}... -> 选项${correctIndices[0] + 1}`); radios[correctIndices[0]].parentElement.click(); answered++; } const checkboxes = group.querySelectorAll('input[type="checkbox"]'); if (checkboxes.length > 0) { const options = Array.from(checkboxes).map(cb => cb.parentElement.textContent.trim()); let correctIndices = await callAI(questionText, options, 'multiple'); if (!correctIndices) { correctIndices = getDefaultAnswer(options, 'multiple'); log(`默认多: ${questionText.slice(0, 30)}... -> 全选`); } else log(`AI多: ${questionText.slice(0, 30)}... -> 选项${correctIndices.map(i => i + 1).join(',')}`); correctIndices.forEach(i => { if (!checkboxes[i].checked) checkboxes[i].parentElement.click(); }); answered++; } } log(`已答${answered}道题`); await sleep(2000); const submitBtn = Array.from($$('button')).find(b => b.textContent.includes('我要交卷') && b.offsetParent); if (!submitBtn) { log('没找到交卷按钮','warn'); break; } submitBtn.click(); await sleep(2000); const confirmSubmitBtn = Array.from($$('button')).find(b => b.textContent.includes('提交试卷') && b.offsetParent); if (confirmSubmitBtn) { confirmSubmitBtn.click(); log('已提交试卷'); await sleep(8000); } const pageText = document.body.innerText; const scoreMatch = pageText.match(/得分[::]\s*(\d+\.?\d*)\s*分/); const isPassMatch = pageText.match(/考试及格|考试合格|恭喜您.*及格|成绩.*合格/); const isFailMatch = pageText.match(/考试不及格|未及格|成绩.*不合格|很遗憾/); if (isPassMatch || (scoreMatch && parseFloat(scoreMatch[1]) >= 60)) { log(`考试及格!${scoreMatch ? scoreMatch[1] : ''}分`,'ok'); const exitBtn = Array.from($$('button')).find(b => b.textContent.includes('退出考试') && b.offsetParent); if (exitBtn) exitBtn.click(); await sleep(3000); return true; } else if (isFailMatch || (scoreMatch && parseFloat(scoreMatch[1]) < 60)) { log(`考试不及格,得分${scoreMatch ? scoreMatch[1] : '?'}分,重新考`,'warn'); retry++; const againBtn = Array.from($$('button')).find(b => b.textContent.includes('重新考试') && b.offsetParent); if (againBtn) { againBtn.click(); await sleep(5000); } else { log('没找到重新考试按钮','warn'); break; } } else { log('没识别到成绩,可能还在加载','warn'); await sleep(3000); retry++; } } return true; } async function handleExam() { if (!S.autoExam) return true; if (location.hash.includes('/examination') || location.hash.includes('/exam')) { log('已经在考试页面,直接答题...'); await doExamQuestions(); return true; } await sleep(3000); log('查找页面上的未完成考试...','run'); const allElements = $$('div, .el-card, .card, .item'); let hasUnfinishedExam = false; for (const el of allElements) { if (!el.offsetParent) continue; const text = el.innerText || ''; if (text.includes('参与状态:未参加') || text.includes('参与状态: 未参加')) { if (text.includes('考试') || text.includes('开始时间')) { hasUnfinishedExam = true; const enterBtn = Array.from(el.querySelectorAll('button')).find(b => b.textContent.includes('进入考试') || b.textContent.includes('开始考试')); if (enterBtn && enterBtn.offsetParent) { log('发现未完成的考试,点击进入...'); enterBtn.click(); await sleep(3000); const confirmStartBtn = Array.from($$('button')).find(b => b.textContent.trim() === '确定' && b.offsetParent && b.closest('.el-dialog, .el-message-box')); if (confirmStartBtn) { log('点击考试弹窗的确定按钮'); confirmStartBtn.click(); await sleep(5000); } await doExamQuestions(); break; } } } } if (!hasUnfinishedExam) log('没有发现未完成的考试,跳过','ok'); log('考试完成检查完毕','info'); return true; } async function doSurveyQuestions() { // 等待radio加载,最多重试6次(每次2秒) let allRadios = []; for (let waitRound = 0; waitRound < 6; waitRound++) { await sleep(2000); allRadios = $$('.el-radio'); if (allRadios.length > 0) break; log(`调查题加载中,第${waitRound+1}次等待...`,'warn'); } if (allRadios.length === 0) { log('调查页未找到选项,跳过','warn'); return true; } // 自动检测每组选项数:通过第一个radio所在行的兄弟radio数量推断 let groupSize = 4; const firstRow = allRadios[0].closest('.el-radio-group, .el-form-item__content, .survey-item, [class*="question"], [class*="item"]'); if (firstRow) { const rowRadios = firstRow.querySelectorAll('.el-radio'); if (rowRadios.length >= 2) groupSize = rowRadios.length; } // 兜底:如果总radio数不能被4整除但能被3整除,按3处理 if (allRadios.length % 4 !== 0 && allRadios.length % 3 === 0) groupSize = 3; const totalQuestions = Math.floor(allRadios.length / groupSize); log(`共${totalQuestions}道调查题(每组${groupSize}个选项),开始填写...`); // 选每组第0个选项(最左边=好/较好/合理) for (let i = 0; i < allRadios.length; i += groupSize) { if (allRadios[i] && allRadios[i].offsetParent) allRadios[i].click(); } await sleep(2000); let submitBtn = null; const footer = document.querySelector('.footer-button'); if (footer) { const btns = footer.querySelectorAll('button'); for (const btn of btns) { if (btn.textContent.includes('提交') && btn.offsetParent) { submitBtn = btn; break; } } } if (!submitBtn) { const btns = $$('.el-button'); for (const btn of btns) { if (btn.textContent.includes('提交') && btn.offsetParent) { submitBtn = btn; break; } } } if (submitBtn) { log('点击提交按钮'); submitBtn.click(); await sleep(2000); const confirmBtn = Array.from($$('button')).find(b => b.textContent.includes('确定') && b.offsetParent && b.closest('.el-message-box, .el-dialog')); if (confirmBtn) { log('点击确认提交'); confirmBtn.click(); } await sleep(5000); log('调查已提交!','ok'); } else log('没找到调查提交按钮','warn'); return true; } async function handleSurvey() { if (!S.autoSurvey) return true; if (S.surveyHandled) { log('本课程调查已处理过,跳过','info'); return true; } if (location.hash.includes('/questionnaire') || location.hash.includes('/survey')) { log('已经在调查页面,直接填写...'); await doSurveyQuestions(); S.surveyHandled = true; return true; } await sleep(2000); log('查找页面上的未完成调查...','run'); const allElements = $$('div, .el-card, .card, .item'); let hasUnfinishedSurvey = false; for (const el of allElements) { if (!el.offsetParent) continue; const text = el.innerText || ''; if (text.includes('参与状态:未参与') || text.includes('参与状态: 未参与')) { if (text.includes('调查') || text.includes('问卷') || text.includes('开始时间')) { hasUnfinishedSurvey = true; const enterBtn = Array.from(el.querySelectorAll('button')).find(b => b.textContent.includes('进入调查') || b.textContent.includes('开始调查')); if (enterBtn && enterBtn.offsetParent) { log('发现未完成的调查,点击进入...'); enterBtn.click(); await sleep(5000); await doSurveyQuestions(); S.surveyHandled = true; break; } } } } if (!hasUnfinishedSurvey) log('没有发现未完成的调查,跳过','ok'); log('调查完成检查完毕','info'); return true; } const COMMENT_TEXTS = ['讲解清晰易懂,重点突出,收获很大。','课程内容扎实,逻辑严谨,学起来很顺畅。','老师讲得生动有趣,枯燥知识也变得好懂。','视频画质清晰,声音洪亮,学习体验很好。','节奏把控得当,难点讲解透彻,非常实用。','案例丰富贴近实际,帮助理解抽象概念。','知识点梳理系统,条理分明,便于记忆。','语言简洁明了,没有废话,效率很高。','互动设计巧妙,引人思考,不只是灌输。','每节课都有小结,重点复习很方便。','实操演示到位,步骤清楚,跟着做就会。','理论结合实践,讲解深入浅出,受益匪浅。','老师态度认真,答疑及时,很有责任心。','课程结构合理,由浅入深,循序渐进。','字幕准确同步,方便回看和做笔记。','内容更新及时,紧跟行业发展趋势。','讲解耐心细致,零基础也能听懂。','举例生动形象,复杂问题简单化。','视频时长适中,碎片时间也能学习。','重点难点反复强调,记忆更深刻。','课件制作精美,图文并茂,辅助效果好。','老师声音好听,语速适中,听着不累。','课程配套资料齐全,复习巩固很方便。','每章都有练习,检验学习效果很实用。','讲解风格幽默,学习过程轻松愉快。','逻辑链条完整,前后呼应,没有断层。','针对考点讲解精准,备考效率高。','实际操作环节演示清晰,上手很快。','老师经验丰富,分享很多干货技巧。','课程设计科学,符合认知学习规律。']; async function autoLikeFavComment() { if (!S.autoFav && !S.autoLike && !S.autoComment) return; log('开始自动收藏、点赞、评论...','run'); if (S.autoFav) { const favBtn = Array.from(document.querySelectorAll('button.el-button')).find(b => b.textContent.includes('收藏') && b.offsetParent); if (favBtn) { favBtn.click(); log('已点击课程页收藏按钮'); await sleep(500); } } if (S.autoLike) { const likeBtn = document.querySelector('button.like-button'); if (likeBtn) { likeBtn.click(); log('已点击课程页点赞按钮'); await sleep(500); } } if (S.autoComment) { const commentTab = Array.from($$('.tab-item button')).find(b => b.textContent.trim().includes('交流')); if (commentTab) commentTab.click(); await sleep(1000); const textarea = document.querySelector('.talk-input-wrapper textarea'); if (textarea) { const randomComment = COMMENT_TEXTS[Math.floor(Math.random() * COMMENT_TEXTS.length)]; textarea.focus(); textarea.value = randomComment; textarea.dispatchEvent(new Event('input', {bubbles: true})); await sleep(500); const submitBtn = Array.from(document.querySelectorAll('.talk-input-wrapper button')).find(b => b.textContent.includes('发表评论')); if (submitBtn) { submitBtn.click(); log('已发表评论'); await sleep(1000); } } } log('收藏点赞评论完成','ok'); } async function studyCourse() { setProgressText(); await sleep(5000); await handleExam(); await handleSurvey(); await autoLikeFavComment(); const allTabs = $$('.el-tabs__item, .tab-item, [role="tab"]').filter(t => t.offsetParent); const videoTab = allTabs.find(t => t.textContent.trim().includes('视频') || t.textContent.trim().includes('播放')) || allTabs[0]; if (videoTab) videoTab.click(); await sleep(3000); let v = null; for (let i=0;i<30;i++){ v=$('#xg-video video')||$('video'); if(v&&v.readyState>=1&&v.duration>0)break; await sleep(1000); } if (!v||!v.duration) { log('视频加载失败,重试','err'); setTimeout(studyCourse, 5000); return; } v.muted = true; v.playbackRate = S.speed; v.play().catch(()=>{}); await sleep(1500); playCurrentVideo(v); await sleep(2000); const score = await checkScore(); if (score) { S.lastScore = score; setScoreProg(score.score, score.passLine); log(`初始: 成绩${score.score??'--'}/${score.passLine||60}分 (${isPassed(score)?'已及格':'进行中'})`); if (isPassed(score)) { log(`已及格:${score.score}分`,'ok'); S.queueIdx++; S.doneToday++; setProgressText(); STORE.set('bw_queueIdx',S.queueIdx); await sleep(2000); await learnNext(); return; } } await playChapter(0); S.studyTimer = setInterval(async ()=>{ if(!S.running){clearInterval(S.studyTimer);return;} handlePopups(); watchdog(); const vv=$('#xg-video video')||$('video'); if(vv&&vv.duration) setProg(vv.currentTime/vv.duration*100); const now=Date.now(); if(now-S.lastScoreCheck>S.scoreInterval*1000){ S.lastScoreCheck=now; const s=await checkScore(); if(s){ S.lastScore=s; setScoreProg(s.score, s.passLine); if(isPassed(s)){ log(`及格!${s.score}分/${s.passLine}分`,'ok'); clearInterval(S.studyTimer); S.queueIdx++; S.doneToday++; setProgressText(); STORE.set('bw_queueIdx',S.queueIdx); await sleep(2000); await learnNext(); } else log(`进度: 成绩${s.score??'--'}/${s.passLine||60}分`); } } }, 3000); } async function playChapter(idx) { if (!S.running) return; const leaves = $$('.course-chapter').filter(el=>!el.classList.contains('chapter-parent')); if (!leaves.length) { const v=$('#xg-video video')||$('video'); if(v){v.muted=true;v.playbackRate=S.speed;v.play().catch(()=>{});} if(S.chapterTimer)clearInterval(S.chapterTimer); S.chapterTimer=setInterval(()=>{ const vv=$('#xg-video video')||$('video'); if(!vv)return; if(vv.muted!==true)vv.muted=true; if(Math.abs(vv.playbackRate-S.speed)>0.1)vv.playbackRate=S.speed; if(vv.paused&&!vv.ended){playCurrentVideo(vv);} if(vv.ended||(vv.duration>0&&vv.currentTime>=vv.duration-0.5)){clearInterval(S.chapterTimer);onVideoEnded();} },2000); return; } if (idx >= leaves.length) { onVideoEnded(); return; } const ch = leaves[idx]; ch.scrollIntoView({block:'center'}); ch.click(); log(`小节${idx+1}/${leaves.length}: ${ch.textContent.trim().slice(0,40)}`); await sleep(3000); const v=$('#xg-video video')||$('video'); if(v){v.muted=true;v.playbackRate=S.speed;playCurrentVideo(v);} if(S.chapterTimer)clearInterval(S.chapterTimer); S.chapterTimer=setInterval(()=>{ const vv=$('#xg-video video')||$('video'); if(!vv)return; if(vv.muted!==true)vv.muted=true; if(Math.abs(vv.playbackRate-S.speed)>0.1)vv.playbackRate=S.speed; if(vv.paused&&!vv.ended){playCurrentVideo(vv);} if(vv.ended||(vv.duration>0&&vv.currentTime>=vv.duration-0.5)){ clearInterval(S.chapterTimer); log(`小节${idx+1}完毕`); setTimeout(()=>playChapter(idx+1),2000); } },2000); } async function onVideoEnded() { log('全部小节播完,检查成绩...','run'); await sleep(2000); const s = await checkScore(); if (s) { S.lastScore=s; setScoreProg(s.score,s.passLine); } if (isPassed(s)) { log(`及格!${s.score}分`,'ok'); S.queueIdx++; S.doneToday++; setProgressText(); STORE.set('bw_queueIdx',S.queueIdx); await sleep(2000); await learnNext(); } else { S.retryCount++; log(`未及格(${s?s.score:'?'}分/${s?s.passLine:60}), 第${S.retryCount}次重播`,'warn'); if (S.retryCount >= 5) { log('重播5次仍未及格,跳下一门','err'); S.queueIdx++; STORE.set('bw_queueIdx',S.queueIdx); await sleep(2000); await learnNext(); } else setTimeout(()=>playChapter(0), 3000); } } function watchdog(){ const v=$('#xg-video video')||$('video'); if(!v)return; if(v.muted!==true)v.muted=true; if(Math.abs(v.playbackRate-S.speed)>0.1)v.playbackRate=S.speed; if(v.paused&&!v.ended){playCurrentVideo(v);} if(!v.paused) cleanupVideoOverlays(); } async function checkScore() { return await checkScoreFromDOM(); } async function checkScoreFromDOM() { const btn = Array.from($$('button')).find(b=>b.textContent.trim()==='完成情况' && b.offsetParent); if (!btn) return null; btn.click(); await sleep(5000); const dlg = $('.el-dialog'); if (!dlg) return null; const t = dlg.innerText; const pm1 = t.match(/合格分数线[::]\s*(\d+)/); const pm2 = t.match(/及格分数[::]\s*(\d+\.?\d*)/); const passLine = pm1 ? parseFloat(pm1[1]) : (pm2 ? parseFloat(pm2[1]) : 60); const sm1 = t.match(/课程成绩[::]\s*([\d.]+)\s*分/); const sm2 = t.match(/总分[::]\s*([\d.]+)\s*分/); const sm3 = t.match(/最终成绩[::]\s*([\d.]+)\s*分/); const score = sm1 ? parseFloat(sm1[1]) : (sm2 ? parseFloat(sm2[1]) : (sm3 ? parseFloat(sm3[1]) : null)); const close = dlg.querySelector('.el-dialog__close, [aria-label="Close"]'); if (close) close.click(); else document.body.click(); await sleep(500); return {score, passLine}; } function handlePopups(){ $$('.el-message-box, .el-dialog, [class*="popup"], [class*="modal"]').forEach(el=>{ const r=el.getBoundingClientRect(); if(r.width>100&&el.offsetParent!==null){ const txt=el.textContent; if(txt.includes('休息')||txt.includes('专注学习')){ log('休息弹窗,点继续','warn'); const btns=el.querySelectorAll('button'); for(const b of btns){ const bt=b.textContent.trim(); if(bt==='继续'||bt==='确定'||bt==='知道了'||bt==='确认'){b.click();break;} } } } }); const nextBtn = document.querySelector('.play-chapter button.el-button--primary'); if (nextBtn && nextBtn.offsetParent && !nextBtn.disabled && nextBtn.textContent.includes('播放下一节')) { log('自动点了: 播放下一节'); nextBtn.click(); return; } } async function boot(){ createPanel(); setProgressText(); setWorking(S.running); log('宝武学习助手 v8.9.61 启动','ok'); const r=route(); if (location.hash.includes('/questionnaire') || location.hash.includes('/survey')) { log('检测到调查页面,自动填写...'); await doSurveyQuestions(); return; } if (location.hash.includes('/examination') || location.hash.includes('/exam')) { log('检测到考试页面,自动答题...'); await doExamQuestions(); return; } if(r==='login'){setStatus('登录中','#f38ba8');} if(!S.zones.open || S.zones.open.length===0) loadAllZones(false); if(r==='study' && S.running) await studyCourse(); setInterval(()=>{handlePopups();if(S.running&&route()==='study')watchdog();},2000); window.addEventListener('hashchange',()=>{ setTimeout(async()=>{ if(S.running&&route()==='study')await studyCourse(); if(S.running&&route()==='home'&&S.queue[S.queueIdx]){ const c=S.queue[S.queueIdx]; location.assign(`https://learn.baowugroup.com/#/courseStudy?centerCode=${c.centerCode}&guid=${c.guid}`); } },3000); }); } if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot); else boot(); })();