// ==UserScript==
// @name 中国宝武学习系统-全能版本学习助手【多账户、多模式】
// @namespace https://learn.baowugroup.com/
// @version 7.8.0
// @description 宝武学习平台全自动挂机:多账号托管、API扫描四专区、自动播放静音倍速、算术验证码重试刷新、未及格重播、自动点播放按钮
// @author 令狐不太冲
// @icon https://learn.baowugroup.com/favicon.png
// @license MIT
// @match https://learn.baowugroup.com/*
// @grant GM_setValue
// @grant GM_getValue
// @require https://cdn.jsdelivr.net/npm/tesseract.js@5.0.5/dist/tesseract.min.js
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
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),
logEl: null, studyTimer: null, chapterTimer: null, lastScoreCheck: 0,
retryCount: 0, doneToday: 0, totalToday: 0, ocr: null,
};
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 getToken() {
try { const k = Object.keys(localStorage).find(x=>x.includes('core-access'));
if (k) return JSON.parse(localStorage.getItem(k)).accessToken; } catch(e) {}
return null;
}
async function api(url, body) {
const token = getToken(); if (!token) throw new Error('未登录');
const r = await fetch('/learn-gateway'+url, {method:'POST',
headers:{'Content-Type':'application/json','Authorization':'Bearer '+token,'token':token},
body: JSON.stringify(body||{})});
return await r.json();
}
async function fetchZones(classType) {
if (classType === 'OCE') {
const all = [];
for (let p=1; p<=10; p++) {
const j = await api('/service/tms/ols/student/queryPageOpenClass', {
current: p, size: 50,
data: {learnStatus:"", searchInfo:"", searchType:"1", sortClass:"2", sortType:"desc"}
});
if (!j.isSuccess || !j.data?.records?.length) break;
j.data.records.forEach(r => all.push({
name: r.courseName, guid: r.courseGuid, courseNo: r.courseNo,
centerCode: r.centerCode, olClassType: 'OCE',
learnStatus: (r.courseSum && r.courseSum > 0) ? '2' : '0', isOpenCourse: true
}));
if (all.length >= j.data.total) break;
}
return all;
} else {
const all = [];
for (let p=1; p<=10; p++) {
const j = await api('/service/tms/ols/student/myClassPage', {
current: p, size: 50,
data: {classType, isLearnNum:"1", keyWord:"", lastLearnTime:"1", learnStatus:"", sortClass:"2", sortType:"desc", status:""}
});
if (!j.isSuccess || !j.data?.records?.length) break;
j.data.records.forEach(r => all.push({
name: r.olClassName, guid: r.olClassNo, olClassNo: r.olClassNo,
centerCode: r.centerCode, olClassType: r.olClassType || classType,
courseNum: r.courseNum, learnNum: r.learnNum, isOpenCourse: false
}));
if (all.length >= j.data.total) break;
}
return all;
}
}
async function fetchZoneCourses(zone) {
const all = [];
const olClassNo = zone.olClassNo || zone.guid;
const olClassType = zone.olClassType || 'ZE0';
const centerCode = zone.centerCode || 'C001';
for (let p=1; p<=20; p++) {
const j = await api('/service/tms/ols/onlineClassCourse/getOnlineClassCourseSortPage', {
current: p, size: 100,
data: {centerCode, courseName:'', courseTypeCode:'', isMine:'1', isRecursiveCourse:'1', olClassNo, olClassType}
});
if (!j.isSuccess || !j.data?.records?.length) break;
j.data.records.forEach(r => all.push({
guid: r.guid, courseNo: r.courseNo, courseName: r.courseName,
learnStatus: r.learnStatus, isMustTeach: r.isMustTeach,
centerCode: r.centerCode, olClassNo: r.olClassNo, olClassType: r.olClassType
}));
if (all.length >= j.data.total) break;
}
return all;
}
function createPanel() {
if ($('#bw-helper')) return;
const p = document.createElement('div');
p.id = 'bw-helper';
p.innerHTML = `
宝武学习助手 v7.8
待机−
`;
document.body.appendChild(p);
S.logEl = $('#bw-log');
$('#bw-mn').onclick = () => p.classList.toggle('off');
$('#bw-hdr').addEventListener('mousedown', e => {
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-add-acc').onclick = addAccount;
$('#bw-host').onclick = startHosting;
renderAccList();
}
function setStatus(t,c){const e=$('#bw-st');if(e){e.textContent=t;e.style.color=c||'#f9e2af';}}
function setProg(p){const b=$('#bw-pb');if(b)b.style.width=Math.min(100,Math.max(0,p))+'%';}
function setProgressText(done,total){const e=$('#bw-prog-text');if(e)e.textContent=`今日进度: ${done} / ${total}`;}
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);
S.accounts.push({acc, pwd, zones});
STORE.set('bw_accounts', S.accounts);
$('#bw-acc').value=''; $('#bw-pwd').value='';
renderAccList();
log(`账号${acc}已添加 (${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(); };
el.appendChild(d);
});
}
const TAB_CLASS = {open:'OCE', zone:'ZE0', online:'ZE1', train:'TCE'};
const TAB_NAME = {open:'公开课', zone:'学习专区', online:'网络专题班', train:'培训班'};
async function loadAllZones(force) {
if (route() !== 'home') { location.hash='#/userCenter'; await sleep(5000); }
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();
setStatus('待机','#f9e2af');
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';
if (item.isOpenCourse) {
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=`${item.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;
setStatus('托管中','#a6e3a1');
log(`托管开始,共${S.accounts.length}个账号`,'run');
await hostNextAccount();
}
async function getCurrentLoginAcc() {
try {
if (!getToken()) return null;
const allText = document.body.innerText || '';
const m = allText.match(/(\d{6,10})/);
return m ? m[1] : null;
} catch(e) { return null; }
}
async function hostNextAccount() {
if (!S.running) return;
if (S.currentAccIdx >= S.accounts.length) {
setStatus('托管完成','#a6e3a1'); 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 = await getCurrentLoginAcc();
if (currentAcc && String(currentAcc) === String(acc.acc)) {
log(`当前已是账号${acc.acc},跳过退出登录`,'ok');
await sleep(3000);
} else {
log(`当前账号${currentAcc||'未知'} ≠ ${acc.acc},需要切换`,'warn');
await logoutAndLogin(acc);
await sleep(3000);
}
}
S.zones = {};
await loadAllZones(true);
const allCourses = [];
for (const zk of acc.zones) {
const list = S.zones[zk] || [];
for (const item of list) {
if (item.isOpenCourse) {
if (item.learnStatus !== '2') allCourses.push(item);
} else {
log(`扫描: ${item.name}...`);
try {
const courses = await fetchZoneCourses(item);
courses.forEach(c=>{if(c.learnStatus!=='2')allCourses.push({...c,zoneName:item.name});});
} catch(e) { log(`扫描${item.name}失败`,'err'); }
}
}
}
S.queue = allCourses;
S.queueIdx = 0;
S.doneToday = 0; S.totalToday = allCourses.length;
STORE.set('bw_queue', S.queue); STORE.set('bw_queueIdx', 0);
setProgressText(0, allCourses.length);
log(`共${allCourses.length}门待学课程`,'ok');
if (allCourses.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) {
// 只取=前面的表达式,=后面是?(OCR会误识别成7)
let expr = str.split(/[==??]/)[0];
// OCR把+识别成¥,替换回来
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');
const allCourses = [];
for (const cb of checked) {
const i = parseInt(cb.dataset.i);
const item = (S.zones[S.tab]||[])[i];
if (!item) continue;
if (item.isOpenCourse) { if(item.learnStatus!=='2')allCourses.push(item); }
else {
log(`扫描: ${item.name}...`);
try {
const courses = await fetchZoneCourses(item);
courses.forEach(c=>{if(c.learnStatus!=='2')allCourses.push({...c,zoneName:item.name});});
} catch(e) { log(`扫描失败: ${e.message}`,'err'); }
}
}
if (!allCourses.length) { log('无未完成课程','warn'); setStatus('待机'); return; }
allCourses.sort((a,b)=> (a.learnStatus==='1'?0:1)-(b.learnStatus==='1'?0:1));
S.queue = allCourses; S.queueIdx = 0; S.running = true;
STORE.set('bw_running',true); STORE.set('bw_queue',S.queue); STORE.set('bw_queueIdx',0);
S.doneToday = 0; S.totalToday = allCourses.length;
setProgressText(0, allCourses.length);
setStatus('学习中','#a6e3a1');
log(`开始学习${allCourses.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);
setStatus('已停止','#f9e2af'); log('已停止','warn');
}
async function learnNext() {
if (!S.running) return;
if (S.queueIdx >= S.queue.length) {
log(`本账号完成: ${S.doneToday}门`,'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 {
setStatus('全部完成','#a6e3a1'); log('全部学习完毕!','ok');
STORE.set('bw_running',false);
}
return;
}
const c = S.queue[S.queueIdx];
S.retryCount = 0;
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 studyCourse() {
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 && score.score>=score.passLine) {
log(`已及格:${score.score}分`,'ok');
S.queueIdx++; S.doneToday++; setProgressText(S.doneToday,S.totalToday);
STORE.set('bw_queueIdx',S.queueIdx);
await sleep(2000); await learnNext(); return;
}
log(`成绩:${score?score.score:'?'}分 及格线${score?score.passLine:60}`);
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.score>=s.passLine){
log(`及格!${s.score}分`,'ok'); clearInterval(S.studyTimer);
S.queueIdx++; S.doneToday++; setProgressText(S.doneToday,S.totalToday);
STORE.set('bw_queueIdx',S.queueIdx);
await sleep(2000); await learnNext();
} else if(s) { log(`当前${s.score}分/${s.passLine}分`,'info'); }
}
}, 3000);
}
async function playChapter(idx) {
if (!S.running) return;
const leaves = $$('.course-chapter').filter(el=>!el.classList.contains('chapter-parent'));
if (!leaves.length) {
log('无章节,直接播放');
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.score >= s.passLine) {
log(`及格!${s.score}分`,'ok');
S.queueIdx++; S.doneToday++; setProgressText(S.doneToday,S.totalToday);
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(){
const btn=Array.from($$('button')).find(b=>b.textContent.trim()==='完成情况');
if(!btn)return null; btn.click(); await sleep(5000);
const dlg=$('.el-dialog');
if(!dlg)return null;
const t=dlg.textContent;
const sm=t.match(/课程成绩[::]\s*([\d.]+)\s*分/);
const pm=t.match(/合格分数线[::]\s*([\d.]+)/);
const r={score:sm?parseFloat(sm[1]):null, passLine:pm?parseFloat(pm[1]):60};
const close=dlg.querySelector('.el-dialog__close, [aria-label="Close"]');
if(close)close.click();else document.body.click();
await sleep(500); return r;
}
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();
log('宝武学习助手 v7.8 启动','ok');
const r=route();
if(r==='login'){setStatus('登录中','#f38ba8');}
else if(r==='home'){setStatus('待机','#f9e2af');loadAllZones(false);}
else if(r==='zone'){setStatus('专区页','#89b4fa');}
else if(r==='study'){setStatus('学习页','#a6e3a1');if(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();
})();