// ==UserScript==
// @name 嘉职院沃创刷课助手
// @namespace https://github.com/woczx-auto-study
// @version 2.2.0
// @description 嘉职院沃创刷课助手 - 支持跨单元自动跳转
// @match *://jxvtcedu.woczx.com/*
// @match *://*.woczx.com/*
// @include *woczx.com/*
// @include *busionline.com/*
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-end
// @license MIT
// ==/UserScript==
(function(){
'use strict';
// ======================== 配置 ========================
const CONFIG={
speed:GM_getValue('woczx_speed',4),
smartSkip:GM_getValue('woczx_smartSkip',true),
skipSpeed:GM_getValue('woczx_skipSpeed',2),
skipRatio:GM_getValue('woczx_skipRatio',0.9),
autoMute:GM_getValue('woczx_autoMute',true),
autoNext:GM_getValue('woczx_autoNext',true),
nextDelay:GM_getValue('woczx_nextDelay',2),
showPanel:GM_getValue('woczx_showPanel',true),
debug:GM_getValue('woczx_debug',true),
endThreshold:GM_getValue('woczx_endThreshold',5),
speedEnforceInterval:GM_getValue('woczx_speedEnforce',500),
studyId:GM_getValue('woczx_studyId',''),
};
const STATE={
running:false,currentCourse:'',currentChapter:'',videoCount:0,popupsClosed:0,
startTime:null,playerInstance:null,videoEnded:false,nextAttemptCount:0,maxNextAttempts:15,
};
const log=(...a)=>{if(CONFIG.debug)console.log('[嘉职院刷课]',...a);};
const $=(s,r)=>(r||document).querySelector(s);
const $$=(s,r)=>[...(r||document).querySelectorAll(s)];
function isVideoPage(){return location.hash.includes('/unit/video');}
function isGradePage(){return location.hash.includes('/student/grade/detail');}
// ======================== 面板 ========================
function createPanel(){
const p=document.createElement('div'); p.id='woczx-panel';
p.innerHTML=''
+'
'
+''
+'
支持跨单元自动跳转
'
+'
防检测
'
+'
'+Math.min(CONFIG.speed,8)+'x
'
+'
'
+'
'
+'
'
+'
课程: --
章节: --
弹窗: 次
运行: 00:00
倍速: 1x
状态: 待启动
'
+'
'
+'
'
+'
'
+'
'
+'
'
+'
'
+'
4x
';
document.body.appendChild(p);
bindEvents(p); return p;
}
function bindEvents(p){
p.querySelector('.wz-toggle').addEventListener('click',function(){
const b=p.querySelector('.wz-body'); b.classList.toggle('collapsed'); this.textContent=b.classList.contains('collapsed')?'+':'−';
});
p.querySelector('#wz-speed').addEventListener('input',function(){
const v=parseFloat(this.value); p.querySelector('#wz-speed-val').textContent=v+'x';
CONFIG.speed=v; GM_setValue('woczx_speed',v); applySpeed(v);
});
p.querySelector('#wz-smartskip').addEventListener('change',function(){CONFIG.smartSkip=this.checked;GM_setValue('woczx_smartSkip',this.checked);});
p.querySelector('#wz-skip-speed').addEventListener('change',function(){CONFIG.skipSpeed=parseInt(this.value);GM_setValue('woczx_skipSpeed',parseInt(this.value));});
p.querySelector('#wz-mute').addEventListener('change',function(){CONFIG.autoMute=this.checked;GM_setValue('woczx_autoMute',this.checked);applyMute(this.checked);});
p.querySelector('#wz-autonext').addEventListener('change',function(){CONFIG.autoNext=this.checked;GM_setValue('woczx_autoNext',this.checked);});
p.querySelector('#wz-btn-start').addEventListener('click',startAutoStudy);
p.querySelector('#wz-btn-pause').addEventListener('click',pauseAutoStudy);
p.querySelector('#wz-btn-stop').addEventListener('click',stopAutoStudy);
p.querySelector('#wz-btn-next').addEventListener('click',()=>{if(isVideoPage())goToNextChapter();else if(isGradePage())startNextUnit();});
p.querySelector('#wz-btn-grade').addEventListener('click',goToGradePage);
p.querySelector('#wz-btn-debug').addEventListener('click',debugPage);
makeDraggable(p);
}
function makeDraggable(p){
const h=p.querySelector('.wz-header'); let ox,oy,d=false;
h.addEventListener('mousedown',e=>{if(e.target.classList.contains('wz-toggle'))return;d=true;ox=e.clientX-p.offsetLeft;oy=e.clientY-p.offsetTop;p.style.transition='none';});
document.addEventListener('mousemove',e=>{if(!d)return;p.style.left=(e.clientX-ox)+'px';p.style.top=(e.clientY-oy)+'px';p.style.right='auto';});
document.addEventListener('mouseup',()=>{d=false;p.style.transition='';});
}
function updateStatus(u){for(const[k,v]of Object.entries(u)){const e=document.getElementById(k);if(e)e.textContent=v;}}
// ======================== 播放器 ========================
function findVideo(){
const vs=$$('video');for(const v of vs){if(v.readyState>=1||v.src||v.querySelector('source'))return v;}
if(vs.length>0)return vs[0];
for(const f of $$('iframe')){try{const d=f.contentDocument||f.contentWindow?.document;if(d){const v=d.querySelector('video');if(v)return v;}}catch(e){}}
for(const c of $$('[class*="player"],[id*="player"],[class*="video-wrap"],[class*="videoBox"]')){const v=c.querySelector('video');if(v)return v;}
return null;
}
function getVE(p){if(!p)return null;if(p.tagName==='VIDEO')return p;if(p.querySelector){const v=p.querySelector('video');if(v)return v;}return p._video||p.video||p;}
function applySpeed(s){
const ve=getVE(STATE.playerInstance)||findVideo();if(!ve)return;
const sp=s||(CONFIG.smartSkip&&STATE._skipDone?CONFIG.skipSpeed:CONFIG.speed);
try{if(ve.playbackRate!==undefined)ve.playbackRate=sp;if(ve.setRate)ve.setRate(sp);}catch(e){}
const fl=document.getElementById('wz-float-speed');
if(fl){fl.textContent=sp+'x';fl.style.display='block';clearTimeout(fl._t);fl._t=setTimeout(()=>{fl.style.display='none';},1500);}
updateStatus({'wz-current-speed':sp+'x'});
}
function applyMute(m){const ve=getVE(STATE.playerInstance)||findVideo();if(ve){try{ve.muted=m;}catch(e){}}}
function hookVideo(player){
const ve=getVE(player);if(!ve||ve._wzHooked)return;ve._wzHooked=true;STATE.playerInstance=ve;STATE._skipDone=false;log('挂钩视频');
ve.addEventListener('play',()=>{
STATE.videoEnded=false;updateStatus({'wz-run-status':'播放中'});applySpeed();applyMute(CONFIG.autoMute);
if(CONFIG.smartSkip&&!STATE._skipDone&&ve.duration&&isFinite(ve.duration)&&ve.duration>10){
const to=ve.duration*CONFIG.skipRatio;log('跳尾: '+to.toFixed(1)+'s/'+ve.duration.toFixed(1)+'s');
ve.currentTime=to;STATE._skipDone=true;setTimeout(()=>applySpeed(CONFIG.skipSpeed),200);
}
});
ve.addEventListener('pause',()=>{if(!STATE.videoEnded&&STATE.running&&ve.paused)setTimeout(()=>{if(STATE.running&&ve.paused&&!STATE.videoEnded){ve.muted=true;ve.play().catch(()=>{});}},300);});
ve.addEventListener('ended',()=>{
if(STATE._navigating)return;log('ended');STATE.videoEnded=true;STATE.videoCount++;STATE._skipDone=false;
updateStatus({'wz-run-status':'已完成'});if(CONFIG.autoNext)setTimeout(()=>{if(STATE.running)goToNextChapter();},CONFIG.nextDelay*1000);
});
ve.addEventListener('loadeddata',()=>{if(STATE.running){STATE.videoEnded=false;STATE.nextAttemptCount=0;STATE._skipDone=false;applyMute(CONFIG.autoMute);}});
ve.addEventListener('ratechange',()=>{if(STATE.running&&!STATE.videoEnded){const t=(CONFIG.smartSkip&&STATE._skipDone)?CONFIG.skipSpeed:CONFIG.speed;if(ve.playbackRate!==t)requestAnimationFrame(()=>applySpeed(t));}});
}
// ======================== 弹窗 ========================
function closePopups(){
if(STATE._navigating)return 0;let c=0;
for(const m of $$('.ant-modal-wrap,.ant-modal-root')){
if(!m.offsetParent&&m.style.display==='none')continue;const t=m.textContent||'';
if(t.includes('继续')||t.includes('确定')||t.includes('确认')||t.includes('认真度')||t.includes('还在吗')){
const b=m.querySelector('.ant-btn-primary')||m.querySelector('.ant-modal-confirm-btns .ant-btn:last-child')||m.querySelector('button:last-of-type');
if(b){b.click();c++;continue;}
}
const x=m.querySelector('.ant-modal-close');if(x){x.click();c++;}
}
for(const s of ['.ant-modal','.ant-popover','.ant-popconfirm','.ant-drawer','[class*=dialog]','[class*=modal]','[class*=popup]','[class*=toast]','[role=dialog]','[class*=alert]','[class*=mask]']){
for(const d of $$(s)){
if(!d.offsetParent&&d.style.display==='none')continue;const t=(d.textContent||'').trim();if(!t||t.length<2)continue;
if(['继续学习','确定','知道了','认真度检测','学习确认','确认继续','还在吗','是否继续'].some(p=>t.includes(p))){
const btns=[...d.querySelectorAll('button,.ant-btn')].filter(b=>b.offsetParent);if(btns.length>=1){btns[btns.length-1].click();c++;}
}
}
}
if(c>0){STATE.popupsClosed+=c;updateStatus({'wz-popup-count':String(STATE.popupsClosed)});}return c;
}
// ======================== 下一节 ========================
function goToNextChapter(){
if(STATE._navigating){log('跳转中');return false;}
STATE.nextAttemptCount++;log('===== 跳转('+STATE.nextAttemptCount+') =====');
// 超过最大尝试次数 → DOM找不到下一节,走兜底:回学习详情页重新定位
if(STATE.nextAttemptCount>STATE.maxNextAttempts){
log('已达最大尝试次数 → 回学习详情页重新定位');
STATE._navigating=false;STATE.nextAttemptCount=0;
updateStatus({'wz-run-status':'回详情页定位'});
return goToGradePage();
}
STATE._navigating=true;
// 先尝试所有策略找下一节
if(tryFindNextChapter()){
STATE.nextAttemptCount=0;
return true;
}
// 没找到 → 解锁并等待重试,不立刻放弃
log('暂未找到下一节入口,'+CONFIG.nextDelay+'秒后重试('+STATE.nextAttemptCount+'/'+STATE.maxNextAttempts+')');
STATE._navigating=false;
setTimeout(()=>{if(STATE.running&&!STATE._navigating)goToNextChapter();},CONFIG.nextDelay*1000);
return false;
}
function tryFindNextChapter(){
// 策略0: 章节列表
for(let i=0;i<$$('[class*=list] [class*=item]').length;i++){
const t=($$('[class*=list] [class*=item]')[i].textContent||'').trim();
if(t.includes('已学')&&!t.includes('已完成')&&i+1<$$('[class*=list] [class*=item]').length){
log('策略0: 章节'+i+'→'+(i+1));clickEl($$('[class*=list] [class*=item]')[i+1]);resetChapter();return true;
}
}
// 策略0b: Endmodal
const emb=$('[class*=Endmodal] [class*=next_button],[class*=endmodal] [class*=next_button],[class*=Endmodal] .ant-btn-primary');
if(emb&&emb.offsetParent){log('Endmodal');clickEl(emb);resetChapter();return true;}
// 策略1: 文字
for(const t of['下一单元','下一节','下一章','下一课','下一个','下节']){
for(const e of $$('button,a,span,div,li,.ant-btn,[class*=btn]')){
if((e.textContent||'').trim()===t&&e.offsetParent&&!e.closest('#woczx-panel')){log('文字:',t);clickEl(e);resetChapter();return true;}
}
}
// 侧边栏
for(const s of['.ant-menu-item','[class*=chapter-item]','[class*=lesson-item]','[class*=list-item]','[class*=catalog] li']){
let found=false;
for(let i=0;i<$$(s).length;i++){
const cl=$$(s)[i].className||'';const act=cl.includes('active')||cl.includes('selected')||cl.includes('current')||cl.includes('playing');
if(found&&$$(s)[i].offsetParent){log('侧边栏下一个');clickEl($$(s)[i].querySelector('a')||$$(s)[i]);resetChapter();return true;}
if(act)found=true;
}
}
return false;
}
function resetChapter(){
STATE.videoEnded=false;STATE.playerInstance=null;STATE.nextAttemptCount=0;STATE._skipDone=false;log('跳转成功');
// 保持 _navigating 锁定 5 秒,防止 ended/checkNearEnd 的二次触发
setTimeout(()=>{STATE._navigating=false;},5000);
}
function clickEl(el){
el.click();el.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true}));
el.dispatchEvent(new MouseEvent('mousedown',{bubbles:true}));el.dispatchEvent(new MouseEvent('mouseup',{bubbles:true}));
}
// ======================== ★ 跨单元跳转 ★ ========================
function goToGradePage(){
STATE._navigating=true;log('===== 跳转学习详情页 =====');
const m=location.hash.match(/study_id=(\d+)/);
const sid=m?m[1]:CONFIG.studyId;
if(sid){CONFIG.studyId=sid;GM_setValue('woczx_studyId',sid);}
if(sid){
updateStatus({'wz-run-status':'跳转学习详情...'});
location.hash='#/app/student/grade/detail/'+sid;
// 由 watchRoute() 统一触发 startNextUnit(),避免与 goToGradePage 的定时器冲突造成循环跳转
setTimeout(()=>{STATE._navigating=false;},5000);
}else{log('无study_id');STATE._navigating=false;}
return true;
}
function startNextUnit(){
if(!isGradePage())return goToGradePage();
log('===== 查找未学习单元 =====');
const units=$$('[class*="detail_list_video--"]');
// 策略1(优先): 找"继续学习"——先做完未完成的单元
for(const u of units){
const t=(u.textContent||'').trim();
if(t.includes('继续学习')&&u.offsetParent){
log('进行中单元:',t.substring(0,60));
const btn=[...u.querySelectorAll('*')].find(el=>(el.textContent||'').trim()==='继续学习'&&el.children.length===0);
if(btn)clickEl(btn);else clickEl(u);
updateStatus({'wz-run-status':'继续学习'});
startVideoModeForce();
return true;
}
}
// 策略2: 找"未开始" + "开始学习"
for(const u of units){
const t=(u.textContent||'').trim();
if(t.includes('未开始')&&t.includes('开始学习')&&u.offsetParent){
log('未学习单元:',t.substring(0,60));
const btn=[...u.querySelectorAll('*')].find(el=>(el.textContent||'').trim()==='开始学习'&&el.children.length===0);
if(btn){clickEl(btn);}else{clickEl(u);}
updateStatus({'wz-run-status':'进入新单元'});
startVideoModeForce();
return true;
}
}
// 策略3: 当前页无可用单元 → 尝试翻下一页
if(tryNextPage()){
updateStatus({'wz-run-status':'翻页中...'});
return false;
}
log('全部完成');updateStatus({'wz-run-status':'✅ 全部完成!'});stopAutoStudy();return false;
}
// ======================== ★ 翻页 ★ ========================
function tryNextPage(){
// 查找 Ant Design 分页器
const pagers=['.ant-pagination','[class*=pagination]','.ant-list-pagination','[class*=Pagination]'];
for(const s of pagers){
const pager=$(s);
if(!pager||!pager.offsetParent)continue;
// 找"下一页"按钮(未禁用)
const nextBtn=pager.querySelector('.ant-pagination-next:not(.ant-pagination-disabled)')
||pager.querySelector('[class*=next]:not([class*=disabled]):not([disabled])')
||pager.querySelector('[title="下一页"]:not([disabled])')
||[...pager.querySelectorAll('li')].find(li=>(li.textContent||'').includes('下一页')&&!li.className.includes('disabled'))
||[...pager.querySelectorAll('button,a,li')].find(el=>{
const t=(el.textContent||'').trim();
return (t==='下一页'||t==='>'||t==='›'||t==='»')&&el.offsetParent&&!el.disabled&&!el.className.includes('disabled');
});
if(nextBtn){
log('翻页 → 下一页');
clickEl(nextBtn);
setTimeout(()=>{if(STATE.running)startNextUnit();},2000);
return true;
}
// 检查是否还有未激活的页码按钮
const pageItems=[...pager.querySelectorAll('.ant-pagination-item:not(.ant-pagination-item-active):not(.ant-pagination-disabled)')];
if(pageItems.length>0){
log('翻页 → 第'+pageItems[0].textContent+'页');
clickEl(pageItems[0]);
setTimeout(()=>{if(STATE.running)startNextUnit();},2000);
return true;
}
// 通用:找任何未禁用、非当前页的页码元素
for(const el of [...pager.querySelectorAll('li,a,button')]){
const cls=el.className||'';
const txt=(el.textContent||'').trim();
if(/^\d+$/.test(txt)&&!cls.includes('active')&&!cls.includes('current')&&!cls.includes('disabled')&&el.offsetParent&&!el.disabled){
log('翻页 → 页码'+txt);
clickEl(el);
setTimeout(()=>{if(STATE.running)startNextUnit();},2000);
return true;
}
}
}
// 策略B: 页面底部的分页区域(非 Ant Design 组件)
for(const el of $$('[class*=page] a,[class*=page] button,[class*=page] li,.ant-list-pagination a')) {
const t=(el.textContent||'').trim();
if((t==='下一页'||t==='>'||t==='›'||t==='»'||/^\d+$/.test(t))&&el.offsetParent&&!el.disabled&&!el.className.includes('disabled')&&!el.className.includes('active')){
log('翻页(通用) → ',t);
clickEl(el);
setTimeout(()=>{if(STATE.running)startNextUnit();},2000);
return true;
}
}
return false;
}
// ★ 强制启动视频模式(不管当前状态)
function startVideoModeForce(){
// 清理旧状态,但不碰 _navigating(防抖锁由 goToNextChapter/resetChapter 管理)
STATE.running = true;
STATE.videoEnded = false;
STATE.playerInstance = null;
STATE.nextAttemptCount = 0;
STATE._skipDone = false;
// 不重置 _navigating!否则会破坏跳转防抖
// 重启所有定时器
clearInterval(mainLoop); clearInterval(popupLoop); clearInterval(speedLoop);
log('强制启动视频模式');
updateStatus({'wz-run-status':'运行中'});
updateInfo();
// 主循环
mainLoop=setInterval(()=>{
if(!STATE.running)return;
const p=findVideo();
if(p&&(!STATE.playerInstance||STATE.playerInstance!==getVE(p))){
const v=getVE(p);if(v&&!v._wzHooked)hookVideo(p);
}
if(STATE.playerInstance){
const v=getVE(STATE.playerInstance);
if(v&&v.paused&&!STATE.videoEnded){
// 必须先静音再播放,否则浏览器拦截自动播放
v.muted=true;
v.play().then(()=>{applySpeed();applyMute(CONFIG.autoMute);}).catch(()=>{
// 浏览器拦截,尝试模拟点击视频
v.muted=true; v.play().catch(()=>{});
});
}
if(v&&!STATE.videoEnded)checkNearEnd();
}
updateInfo();
if(STATE.startTime){
const e=Math.floor((Date.now()-STATE.startTime)/1000);
updateStatus({'wz-runtime':String(Math.floor(e/60)).padStart(2,'0')+':'+String(e%60).padStart(2,'0')});
}
},2000);
// 弹窗检测
popupLoop=setInterval(()=>{if(STATE.running)closePopups();},CONFIG.popupCheckInterval);
// 速度强制
startSpeedLoop();
// 不断尝试挂钩视频,直到成功
let retries=0;
function tryHook(){
if(!STATE.running||!isVideoPage()){if(retries<20&&STATE.running)setTimeout(tryHook,1500);return;}
const p=findVideo();
if(p){
const v=getVE(p);if(v)v._wzHooked=false;
hookVideo(p);applySpeed();applyMute(CONFIG.autoMute);
log('视频挂钩成功 (重试'+retries+'次)');
} else if(retries<20){
retries++;
setTimeout(tryHook,1500);
} else {
log('视频挂钩超时');
}
}
setTimeout(tryHook,2000);
// 重启 DOM 监听
watchNew();
// 更新面板按钮
const startBtn=document.getElementById('wz-btn-start');
const pauseBtn=document.getElementById('wz-btn-pause');
const stopBtn=document.getElementById('wz-btn-stop');
if(startBtn)startBtn.style.display='none';
if(pauseBtn)pauseBtn.style.display='block';
if(stopBtn)stopBtn.style.display='block';
}
// ======================== 辅助 ========================
function updateInfo(){
for(const s of['[class*=course-title]','[class*=course-name]','h1','h2','.ant-page-header-heading-title','.ant-breadcrumb li:last-child']){
const e=$(s);if(e&&e.textContent&&e.textContent.trim().length>1&&e.textContent.trim().length<100){STATE.currentCourse=e.textContent.trim();break;}
}
for(const s of['.ant-menu-item-selected','[class*=active][class*=chapter]','[class*=current]','[class*=playing]','.is-active']){
const e=$(s);if(e&&e.textContent&&e.textContent.trim().length>1){STATE.currentChapter=e.textContent.trim();break;}
}
updateStatus({'wz-course-name':STATE.currentCourse||'未识别','wz-chapter-name':STATE.currentChapter||'未识别'});
}
function checkNearEnd(){
if(!STATE.running||STATE.videoEnded||STATE._navigating)return;
const ve=getVE(STATE.playerInstance)||findVideo();if(!ve||!ve.duration||!isFinite(ve.duration))return;
const r=ve.duration-ve.currentTime;
// 视频剩余5秒内,且已播放超过10秒(排除智能跳尾后对短片的误触发)
if(r<=5&&r>0&&ve.currentTime>10){
if(!STATE._nearEndTime)STATE._nearEndTime=Date.now();
// 持续在结尾区域超过12秒仍无ended事件 → 兜底跳转
if(Date.now()-STATE._nearEndTime>12000){
log('ended未触发,兜底跳转(r='+r.toFixed(1)+'s)');
STATE.videoEnded=true;STATE.videoCount++;STATE._nearEndTime=null;
updateStatus({'wz-run-status':'兜底跳转'});if(CONFIG.autoNext)goToNextChapter();
}
}else if(r>5){
STATE._nearEndTime=null;
}
}
// ======================== 调试 ========================
function debugPage(){
console.group('嘉职院刷课 - 页面结构');
console.log('URL:',location.href,'| 视频页:',isVideoPage(),'| 详情页:',isGradePage());
const vs=$$('video');console.log('Video:',vs.length);vs.forEach((v,i)=>console.log(' ['+i+']',v.src?.substring(0,80),v.duration,v.paused));
const bs=[...$$('button,.ant-btn,a[class*=btn]')].filter(b=>b.offsetParent&&!b.closest('#woczx-panel'));
console.log('按钮:',bs.length);bs.slice(0,30).forEach((b,i)=>console.log(' ['+i+']',(b.textContent||'').trim().substring(0,25),b.className?.substring(0,50)));
if(isGradePage()){
console.log('--- 学习详情单元 ---');
$$('[class*="detail_list_video--"]').forEach((u,i)=>{
const t=(u.textContent||'').trim();
console.log(' ['+i+']',(t.includes('未开始')?'⬜':t.includes('100%')?'✅':t.includes('%')?'📖':' '),t.substring(0,80));
});
}
const si=$$('.ant-menu-item,[class*=chapter],[class*=lesson],[class*=section]');
if(si.length){si.slice(0,20).forEach((it,i)=>{const cl=it.className||'';console.log(' '+(cl.includes('active')||cl.includes('selected')?'✅':' ')+'['+i+']',it.textContent?.trim()?.substring(0,50));});}
console.groupEnd();alert('分析完成,按F12查看');
}
// ======================== 主循环 ========================
let mainLoop,popupLoop,speedLoop;
function startAutoStudy(){
if(isGradePage()){startNextUnit();return;}
if(!isVideoPage()){log('非视频页,请进入课程');return;}
startVideoModeForce();
}
function startSpeedLoop(){if(speedLoop)clearInterval(speedLoop);speedLoop=setInterval(()=>{if(!STATE.running||STATE.videoEnded)return;applySpeed();},CONFIG.speedEnforceInterval);}
function pauseAutoStudy(){
STATE.running=false;updateStatus({'wz-run-status':'已暂停'});
document.getElementById('wz-btn-start').style.display='block';document.getElementById('wz-btn-start').textContent='继续刷课';document.getElementById('wz-btn-pause').style.display='none';
clearInterval(mainLoop);clearInterval(popupLoop);clearInterval(speedLoop);
}
function stopAutoStudy(){
STATE.running=false;updateStatus({'wz-run-status':'已停止'});
document.getElementById('wz-btn-start').style.display='block';document.getElementById('wz-btn-start').textContent='开始刷课';document.getElementById('wz-btn-pause').style.display='none';document.getElementById('wz-btn-stop').style.display='none';
clearInterval(mainLoop);clearInterval(popupLoop);clearInterval(speedLoop);
if(STATE.playerInstance)try{getVE(STATE.playerInstance).playbackRate=1;}catch(e){}
}
function watchNew(){if(STATE._obs)STATE._obs.disconnect();const o=new MutationObserver(()=>{if(!STATE.running)return;const p=findVideo();if(p){const v=getVE(p);if(v&&!v._wzHooked){hookVideo(p);applySpeed();applyMute(CONFIG.autoMute);}}});o.observe(document.body,{childList:true,subtree:true});STATE._obs=o;}
// ======================== 登录 ========================
function showLoginTip(){
if(!location.hash.includes('login'))return;const t=document.createElement('div');
t.style.cssText='position:fixed;top:16px;left:50%;transform:translateX(-50%);background:#fff3cd;border:1px solid #ffc107;border-radius:8px;padding:10px 20px;z-index:99999;font-size:13px;text-align:center;max-width:400px;';
t.innerHTML='嘉职院沃创刷课助手 v2.2
请手动登录后进入课程。
Ctrl+Shift+S 开始/停止 | Ctrl+Shift+G 返回详情页';
document.body.appendChild(t);setTimeout(()=>t.remove(),8000);
}
// ======================== 路由 ========================
function watchRoute(){
let last=location.hash;
setInterval(()=>{
if(location.hash!==last){
const prev=last;last=location.hash;log('路由:',prev,'→',location.hash);
STATE.playerInstance=null;STATE.videoEnded=false;STATE.nextAttemptCount=0;
if(STATE.running){
updateInfo();
if(isVideoPage()){startVideoModeForce();}
else if(isGradePage()){setTimeout(()=>startNextUnit(),1500);}
}
if(location.hash.includes('login'))showLoginTip();
}
},500);
}
// ======================== GM & 快捷键 ========================
if(typeof GM_registerMenuCommand==='function'){GM_registerMenuCommand('重置配置',()=>{GM_setValue('woczx_speed',4);GM_setValue('woczx_smartSkip',true);GM_setValue('woczx_skipSpeed',2);GM_setValue('woczx_autoMute',true);GM_setValue('woczx_popupInterval',1000);GM_setValue('woczx_autoNext',true);GM_setValue('woczx_nextDelay',2);GM_setValue('woczx_showPanel',true);GM_setValue('woczx_debug',true);alert('已重置,请刷新');});}
document.addEventListener('keydown',e=>{
if(e.ctrlKey&&e.shiftKey&&e.key==='S'){e.preventDefault();STATE.running?stopAutoStudy():startAutoStudy();}
if(e.ctrlKey&&e.shiftKey&&e.key==='D'){e.preventDefault();debugPage();}
if(e.ctrlKey&&e.shiftKey&&e.key==='N'){e.preventDefault();goToNextChapter();}
if(e.ctrlKey&&e.shiftKey&&e.key==='G'){e.preventDefault();goToGradePage();}
});
// ======================== 初始化 ========================
function init(){
console.log('[嘉职院刷课] v2.2 已加载 -',location.href);
if(document.body&&document.body.children.length>0)setTimeout(initCore,1500);
else document.addEventListener('DOMContentLoaded',()=>setTimeout(initCore,1500));
}
function initCore(){
const toast=document.createElement('div');toast.style.cssText='position:fixed;top:10px;left:50%;transform:translateX(-50%);background:#52c41a;color:#fff;padding:8px 20px;border-radius:6px;z-index:999999;font-size:14px;font-weight:bold;box-shadow:0 2px 8px rgba(0,0,0,.2);';toast.textContent='✅ 嘉职院沃创刷课助手 v2.2 已就绪';document.body.appendChild(toast);setTimeout(()=>toast.remove(),2500);
if(CONFIG.showPanel)createPanel();watchRoute();if(location.hash.includes('login'))showLoginTip();
// ★ 自动开始:视频页直接开始,学习详情页自动选单元
if(isVideoPage()){log('自动开始刷课');startVideoModeForce();}
else if(isGradePage()){log('自动选择单元');startNextUnit();}
log('初始化完成');
}
const w=unsafeWindow||window;w.__woczx__={start:startAutoStudy,stop:stopAutoStudy,debug:debugPage,goNext:goToNextChapter,goGrade:goToGradePage,nextUnit:startNextUnit,config:CONFIG};
init();
})();