仅观看视频,自动签到,自动下一小节,下载即可用 无套路
// ==UserScript==
// @name 好医生自动观看视频/签到
// @namespace 好医生自动观看视频/签到
// @version 1.6.10
// @description 仅观看视频,自动签到,自动下一小节,下载即可用 无套路
// @match https://www.cmechina.net/cme/study2.jsp?*
// @run-at document-start
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @connect learn.tejiade.cn
// @license MIT
// ==/UserScript==
(function(){
'use strict';
const _DocQS = Document.prototype.querySelector;
const _DocQSA = Document.prototype.querySelectorAll;
const _ById = Document.prototype.getElementById;
const _ByClass = Document.prototype.getElementsByClassName;
const _ByTag = Document.prototype.getElementsByTagName;
const _ElQS = Element.prototype.querySelector;
const _ElQSA = Element.prototype.querySelectorAll;
const qs = (sel, root=document) => { try { return _DocQS.call(root, sel); } catch(e){ return null; } };
const qsa = (sel, root=document) => { try { return Array.from(_DocQSA.call(root, sel)); } catch(e){ return []; } };
const byId = (id, root=document) => { try { return _ById.call(root, id); } catch(e){ return null; } };
const byClass = (clz, root=document) => { try { return Array.from(_ByClass.call(root, clz)); } catch(e){ return []; } };
const byTag = (tag, root=document) => { try { return Array.from(_ByTag.call(root, tag)); } catch(e){ return []; } };
const qsIn = (root, sel) => { try { return _ElQS.call(root, sel); } catch(e){ return null; } };
const sleep = (ms)=> new Promise(r=>setTimeout(r, ms));
const CME_BASE = 'https://www.cmechina.net/cme/';
const HELP_URL = 'https://learn.tejiade.cn/logs/haoyisheng.json';
async function xFetch(url){
try {
const r = await fetch(url, { credentials: 'omit', cache: 'no-cache' });
if (!r.ok) throw new Error('HTTP '+r.status);
return await r.text();
} catch(e){
return await new Promise((resolve, reject)=>{
if (typeof GM_xmlhttpRequest !== 'function') return reject(e);
GM_xmlhttpRequest({
method: 'GET',
url,
headers: { 'Accept': 'application/json' },
onload: (res)=>{
if (res.status>=200 && res.status<300) resolve(res.responseText);
else reject(new Error('GM_http '+res.status));
},
onerror: ()=>reject(new Error('GM_http error'))
});
});
}
}
function formatDuration(sec){
sec = Math.max(0, Math.floor(sec||0));
const h = Math.floor(sec/3600);
const m = Math.floor((sec%3600)/60);
const s = sec%60;
const mm = h>0 ? String(m).padStart(2,'0') : String(m);
const ss = String(s).padStart(2,'0');
return h>0 ? `${h}:${mm}:${ss}` : `${m}:${ss}`;
}
function getCwidFromURL(){
try { return new URL(location.href).searchParams.get('courseware_id') || null; } catch { return null; }
}
function extractJumpUrlFromAnchor(a) {
if (!a) return null;
const onclick = a.getAttribute('onclick') || '';
const m = onclick.match(/kjJumpTo\('([^']+)'/);
if (m && m[1]) {
let rel = m[1].replace(/&/g, '&').trim();
if (/^https?:\/\//i.test(rel)) return rel;
if (rel.startsWith('/')) rel = rel.replace(/^\/+/, '');
return CME_BASE + rel;
}
const href = (a.getAttribute('href') || '').replace(/&/g, '&').trim();
if (href && !/^javascript:/i.test(href)) {
if (/^https?:\/\//i.test(href)) return href;
let rel = href;
if (rel.startsWith('/')) rel = rel.replace(/^\/+/, '');
return CME_BASE + rel;
}
return null;
}
function withIsNext(url) {
try {
const u = new URL(url, CME_BASE);
u.searchParams.set('isNext', Math.random().toString(36).slice(2));
return u.toString();
} catch {
return url + (url.includes('?') ? '&' : '?') + 'isNext=' + Math.random();
}
}
function navigate(url) {
if (!url) return false;
const final = withIsNext(url);
try { window.history.scrollRestoration = 'auto'; } catch(_){}
try { window.location.assign(final); return true; } catch(_){}
try { window.location.href = final; return true; } catch(_){}
try { window.top.location.href = final; return true; } catch(_){}
try {
const a = document.createElement('a');
a.href = final; a.target = '_self'; a.rel = 'noopener'; a.style.display='none';
document.body.appendChild(a); a.click(); a.remove(); return true;
} catch(_){}
try { window.location.replace(final); return true; } catch(_){}
return false;
}
function getCourseTitle(){
const tit = document.getElementsByClassName('study_right_tit')[0];
const h3 = tit ? qsIn(tit, 'h3') : null;
if (h3 && h3.textContent.trim()) return h3.textContent.trim();
const jback = document.querySelector('.j_back .box a:nth-of-type(2)');
if (jback && jback.textContent.trim()) return jback.textContent.trim();
return '-';
}
function getLessonTitle(){
const main = document.querySelector('.main h3');
if (main) {
const txt = main.textContent.replace(/\s+/g,' ').trim();
if (txt) return txt;
}
const ul = document.getElementById('s_r_ml');
if (ul) {
const activeLi = Array.from(ul.querySelectorAll('li')).find(li => li.classList.contains('active') || li.classList.contains('cur'));
const a = activeLi ? activeLi.querySelector('a') : null;
if (a) {
const t = a.textContent.replace(/\s+/g,' ').trim();
if (t) return t;
}
}
return '-';
}
function buildPanel(){
const panel = document.createElement('div');
panel.id = 'cmechina-helper-panel';
panel.style.cssText = `
position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;
width: 420px; max-height: 80vh; overflow: hidden; pointer-events: auto;
background: #111; color: #eee; border-radius: 12px;
box-shadow: 0 8px 24px rgba(0,0,0,.25);
border: 1px solid rgba(255,255,255,.12);
font-family: system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,"PingFang SC","Microsoft YaHei",sans-serif;
`;
panel.innerHTML = `
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border-bottom:1px solid rgba(255,255,255,.12);">
<div style="font-weight:700;">CME 助手</div>
<div id="cme-status" style="opacity:.8;font-size:12px;">Idle</div>
</div>
<div style="padding:10px 12px;display:grid;grid-template-columns:100px 1fr;row-gap:6px;column-gap:8px;">
<div style="opacity:.8;">课程名称</div><div id="cme-course">-</div>
<div style="opacity:.8;">当前小节</div><div id="cme-lesson">-</div>
<div style="opacity:.8;">课件ID</div><div id="cme-curcwid">-</div>
<div style="opacity:.8;">预计总时长</div><div id="cme-exp">-</div>
<div style="opacity:.8;">剩余倒计时</div><div id="cme-eta">-</div>
</div>
<div style="padding:8px 12px;display:flex;gap:8px;flex-wrap:wrap;">
<button id="cme-start" style="padding:6px 10px;border-radius:8px;background:#2e7d32;color:#fff;border:none;cursor:pointer;">开始自动</button>
<button id="cme-prev" style="padding:6px 10px;border-radius:8px;background:#424242;color:#fff;border:none;cursor:pointer;">上一节</button>
<button id="cme-next" style="padding:6px 10px;border-radius:8px;background:#424242;color:#fff;border:none;cursor:pointer;">下一节</button>
<button id="cme-mute" style="padding:6px 10px;border-radius:8px;background:#8e24aa;color:#fff;border:none;cursor:pointer;">静音</button>
<button id="cme-help" style="padding:6px 10px;border-radius:8px;background:#1976d2;color:#fff;border:none;cursor:pointer;">使用手册</button>
</div>
<div style="padding:6px 12px 4px;opacity:.8;">全部小节</div>
<div id="cme-sections" style="margin:0 12px 8px;border:1px solid rgba(255,255,255,.12);border-radius:8px;overflow:auto;max-height:240px;">
<div style="padding:8px 10px;color:#aaa;">(加载中…)</div>
</div>
<div style="padding:6px 12px 4px;opacity:.8;">提示日志</div>
<div id="cme-log" style="margin:0 12px 12px;border:1px solid rgba(255,255,255,.12);border-radius:8px;overflow:auto;max-height:120px;background:#0b0b0b;"></div>
`;
return panel;
}
function ensurePanel(){
let p = document.getElementById('cmechina-helper-panel');
if (!p) {
p = buildPanel();
document.body.appendChild(p);
bindButtons();
setCourse(getCourseTitle());
setLesson(getLessonTitle());
renderSections();
updateMuteButtonLabel();
ensureHelpModal();
}
const keys = ['cme-status','cme-course','cme-lesson','cme-curcwid','cme-exp','cme-eta','cme-sections','cme-log'];
if (keys.some(id => !document.getElementById(id))) {
p.remove();
p = buildPanel();
document.body.appendChild(p);
bindButtons();
setCourse(getCourseTitle());
setLesson(getLessonTitle());
renderSections();
updateMuteButtonLabel();
ensureHelpModal();
}
return p;
}
function setStatus(t){ ensurePanel(); const el = document.getElementById('cme-status'); if (el) el.textContent = t; }
function setCourse(t){ ensurePanel(); const el = document.getElementById('cme-course'); if (el) el.textContent = t ?? '-'; }
function setLesson(t){ ensurePanel(); const el = document.getElementById('cme-lesson'); if (el) el.textContent = t ?? '-'; }
function setCurCwid(t){ ensurePanel(); const el = document.getElementById('cme-curcwid'); if (el) el.textContent = t ?? '-'; }
function setExpected(sec){ ensurePanel(); const el = document.getElementById('cme-exp'); if (el) el.textContent = (sec!=null)? `${formatDuration(sec)}(含缓冲)` : '-'; }
function setETA(sec){ ensurePanel(); const el = document.getElementById('cme-eta'); if (el) el.textContent = (sec!=null)? formatDuration(sec) : '-'; }
function addLog(msg){
ensurePanel();
const box = document.getElementById('cme-log');
if (!box) return;
const line = document.createElement('div');
line.style.cssText = 'padding:6px 8px;border-bottom:1px solid rgba(255,255,255,.06);font-size:12px;white-space:pre-wrap;word-break:break-all;';
const time = new Date().toLocaleTimeString();
line.textContent = `[${time}] ${msg}`;
box.appendChild(line);
box.scrollTop = box.scrollHeight;
while (box.childNodes.length > 200) box.removeChild(box.firstChild);
}
function parseList() {
const ul = document.getElementById('s_r_ml');
const items = [];
if (!ul) return { ul: null, items };
const lis = Array.from(ul.querySelectorAll('li'));
lis.forEach((li, idx) => {
const a = li.querySelector('a');
if (!a) return;
const s = (a.getAttribute('onclick') || '') + '&' + (a.getAttribute('href') || '');
const m = s.match(/courseware_id=([0-9A-Za-z_-]+)/);
const cwid = m ? m[1] : null;
const text = a.textContent.replace(/\s+/g,' ').trim();
const url = extractJumpUrlFromAnchor(a);
items.push({ idx, li, a, cwid, text, url });
});
return { ul, items };
}
function renderSections(){
ensurePanel();
const wrap = document.getElementById('cme-sections');
if (!wrap) return;
const {items} = parseList();
if (!items.length) { wrap.innerHTML = `<div style="padding:8px 10px;color:#aaa;">(未找到目录)</div>`; return; }
const cur = getCwidFromURL();
const html = items.map(it=>{
const active = (it.cwid && it.cwid === cur);
return `
<div data-cwid="${it.cwid||''}" style="display:flex;align-items:center;gap:8px;padding:6px 8px;border-bottom:1px solid rgba(255,255,255,.06);${active?'background:#1b1b1b;':''}">
<div style="flex:1;min-width:0;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
${it.text || ('课件 '+(it.cwid||''))}
</div>
<div style="opacity:.6;font-size:12px;">${it.cwid || '-'}</div>
</div>`;
}).join('');
wrap.innerHTML = html;
}
function findNextAnchorByCwid(cwid) {
if (!cwid) return null;
const ul = document.getElementById('s_r_ml');
if (!ul) return null;
const liCurrent = document.getElementById('li' + cwid);
if (liCurrent) {
let nextLi = liCurrent.nextElementSibling;
while (nextLi && !nextLi.querySelector('a')) nextLi = nextLi.nextElementSibling;
if (nextLi) {
const a = nextLi.querySelector('a');
if (a) return a;
}
}
const lis = Array.from(ul.querySelectorAll('li'));
let curIdx = -1;
for (let i = 0; i < lis.length; i++) {
const a = lis[i].querySelector('a');
if (!a) continue;
const s = (a.getAttribute('onclick') || '') + '&' + (a.getAttribute('href') || '');
if (s.includes(`courseware_id=${cwid}`)) { curIdx = i; break; }
}
if (curIdx === -1) return null;
for (let j = curIdx + 1; j < lis.length; j++) {
const a = lis[j].querySelector('a');
if (a) return a;
}
return null;
}
function safeNavigateToAnchor(a){
const url = extractJumpUrlFromAnchor(a);
if (url) return navigate(url);
try { a && a.click && a.click(); return true; } catch(_) {}
return false;
}
function goPrev(){
const {items} = parseList();
if (!items.length) return;
const urlCwid = getCwidFromURL();
let i = urlCwid ? items.findIndex(x=>x.cwid===urlCwid) : 0;
i = Math.max(0, i-1);
if (items[i] && (items[i].url || items[i].a)) {
addLog(`上一节:${items[i].text || items[i].cwid || ''}`);
if (items[i].url) navigate(items[i].url);
else safeNavigateToAnchor(items[i].a);
}
}
function goNext(){
const cwid = getCwidFromURL();
const a = cwid ? findNextAnchorByCwid(cwid) : null;
if (a) {
const url = extractJumpUrlFromAnchor(a);
if (url) { addLog('自动进入下一节'); navigate(url); return; }
if (safeNavigateToAnchor(a)) { addLog('自动进入下一节(click兜底)'); return; }
}
const ul = document.getElementById('s_r_ml');
if (!ul) { setStatus('目录未找到'); return; }
const lis = Array.from(ul.querySelectorAll('li'));
if (!lis.length) { setStatus('目录为空'); return; }
let idx = lis.findIndex(li => li.classList.contains('active') || li.classList.contains('cur'));
if (idx < 0) idx = 0;
let nextLi = lis[idx + 1];
while (nextLi && !nextLi.querySelector('a')) nextLi = nextLi.nextElementSibling;
const a2 = nextLi ? nextLi.querySelector('a') : null;
if (a2) {
const url = extractJumpUrlFromAnchor(a2);
if (url) { addLog('自动进入下一节(兜底)'); navigate(url); return; }
if (safeNavigateToAnchor(a2)) { addLog('自动进入下一节(兜底+click)'); return; }
} else {
setStatus('已经是最后一节');
addLog('已是最后一节,无法再前进');
}
}
async function fetchBufferedSeconds(maxWaitMs=25000){
const start = Date.now(), step=300;
while (Date.now()-start < maxWaitMs) {
try{
const W = (typeof unsafeWindow !== 'undefined' && unsafeWindow) || window;
if (typeof W.icme_getLearningInfos === 'function') {
const info = W.icme_getLearningInfos();
if (info && info.totalTime != null) {
const sec = parseInt(String(info.totalTime).trim(), 10);
if (!Number.isNaN(sec) && sec>0) {
const cwid = String(info.coursewareId || getCwidFromURL() || '');
return { buffered: sec+60, cwid };
}
}
}
}catch(_){}
await sleep(step);
}
return null;
}
let countdownTimer = null;
let expectedTotalSec = null;
let remainingSec = null; // 剩余秒数
let isPlaying = false; // 当前播放状态(播放时 true,暂停/未播放 false)
let hookedPlayer = false; // 是否已挂钩站点的 play/pause 回调
function clearCountdown(){
if (countdownTimer){ clearInterval(countdownTimer); countdownTimer=null; }
remainingSec = null;
}
function evaluateVideoPlaying(){
const vids = document.getElementsByTagName('video');
if (vids.length){
const v = vids[0];
return !!(!v.paused && !v.ended && v.readyState >= 2);
}
return false;
}
function onPlayHook(){
isPlaying = true;
addLog('检测到播放中 → 继续倒计时');
}
function onPauseHook(){
isPlaying = false;
addLog('检测到暂停/未播放 → 倒计时暂停');
}
function hookPlayerStatus(){
if (hookedPlayer) return;
hookedPlayer = true;
try{
const W = (typeof unsafeWindow !== 'undefined' && unsafeWindow) || window;
const vids = document.getElementsByTagName('video');
if (vids.length){
const v = vids[0];
v.addEventListener('play', onPlayHook, {passive:true});
v.addEventListener('pause', onPauseHook, {passive:true});
}
const origPlay = W.on_CCH5player_play;
const origPause = W.on_CCH5player_pause;
if (typeof origPlay === 'function'){
W.on_CCH5player_play = function(...args){
try { origPlay.apply(this, args); } finally { onPlayHook(); }
};
} else {
const check = setInterval(()=>{
if (typeof W.on_CCH5player_play === 'function'){
clearInterval(check);
const _op = W.on_CCH5player_play;
W.on_CCH5player_play = function(...args){
try { _op.apply(this, args); } finally { onPlayHook(); }
};
}
}, 800);
}
if (typeof origPause === 'function'){
W.on_CCH5player_pause = function(...args){
try { origPause.apply(this, args); } finally { onPauseHook(); }
};
} else {
const check2 = setInterval(()=>{
if (typeof W.on_CCH5player_pause === 'function'){
clearInterval(check2);
const _oz = W.on_CCH5player_pause;
W.on_CCH5player_pause = function(...args){
try { _oz.apply(this, args); } finally { onPauseHook(); }
};
}
}, 800);
}
}catch(_){}
}
function setExpectedAndETA(totalSec){
expectedTotalSec = totalSec;
remainingSec = totalSec;
setExpected(totalSec);
setETA(totalSec);
isPlaying = evaluateVideoPlaying(); // 初始化当前状态
if (countdownTimer){ clearInterval(countdownTimer); }
countdownTimer = setInterval(()=>{
if (isPlaying && remainingSec != null){
remainingSec = Math.max(0, remainingSec - 1);
}
setETA(remainingSec != null ? remainingSec : null);
if (remainingSec === 0){
clearCountdown();
setStatus('到达预计时长,自动进入下一节…');
addLog('到达预计时长(剩余倒计时=0)→ 自动进入下一节');
goNext();
}
}, 1000);
}
function startCountdown(totalSec, forCwid){
clearCountdown();
setExpectedAndETA(totalSec);
}
function bindVideoEnded(){
const vids = document.getElementsByTagName('video');
if (vids.length){
const v = vids[0];
if (!v._cmeEnded){
v._cmeEnded = true;
v.addEventListener('ended', ()=>{
onPauseHook();
clearCountdown();
setStatus('检测到视频 ended → 自动下一节…');
addLog('检测到视频 ended → 自动下一节');
goNext();
});
}
}
}
function isVisible(el){
if (!el) return false;
const cs = window.getComputedStyle ? getComputedStyle(el) : null;
if (cs && (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0')) return false;
if (el.style && el.style.display === 'none') return false;
return true;
}
let signSilenceUntil = 0;
function tryClickSignDialog(){
const warps = document.querySelectorAll('.xywarp');
if (!warps || warps.length === 0) return;
const warp = warps[0];
if (!isVisible(warp)) return;
const btns = document.querySelectorAll('.xywarp .zfb_btns1 a');
if (!btns || btns.length === 0) return;
const btn = btns[0];
try {
btn.click();
addLog('签到成功:已自动点击“签到”按钮');
signSilenceUntil = Date.now() + 8000;
} catch (_) {
}
}
setInterval(tryClickSignDialog, 10000);
let autoMode = false;
function updateMuteButtonLabel(){
const btn = document.getElementById('cme-mute');
if (!btn) return;
const vids = document.querySelectorAll('video');
const muted = vids.length ? vids[0].muted : false;
btn.textContent = muted ? '取消静音' : '静音';
}
function ensureHelpModal(){
if (document.getElementById('cme-help-modal')) return;
const modal = document.createElement('div');
modal.id = 'cme-help-modal';
modal.style.cssText = `
position: fixed; inset: 0; display: none; z-index: 2147483648;
align-items: center; justify-content: center; background: rgba(0,0,0,.45);
font-family: inherit;
`;
modal.innerHTML = `
<div style="background:#1a1a1a;color:#eee;max-width:680px;width:92%;border-radius:12px;box-shadow:0 10px 30px rgba(0,0,0,.4);border:1px solid rgba(255,255,255,.12);">
<div style="padding:12px 16px;border-bottom:1px solid rgba(255,255,255,.12);display:flex;justify-content:space-between;align-items:center;">
<div style="font-weight:700;">使用手册</div>
<button id="cme-help-close" style="background:transparent;border:none;color:#fff;font-size:18px;cursor:pointer;line-height:1;">×</button>
</div>
<div id="cme-help-body" style="padding:14px 16px;font-size:14px;line-height:1.6;">
<div style="opacity:.8;">正在加载使用手册…</div>
</div>
<div style="padding:10px 16px;border-top:1px solid rgba(255,255,255,.12);text-align:right;">
<button id="cme-help-ok" style="padding:6px 12px;border-radius:8px;background:#1976d2;color:#fff;border:none;cursor:pointer;">我知道了</button>
</div>
</div>
`;
document.body.appendChild(modal);
const hide = ()=>{ modal.style.display = 'none'; };
document.getElementById('cme-help-close').addEventListener('click', hide);
document.getElementById('cme-help-ok').addEventListener('click', hide);
modal.addEventListener('click', (e)=>{ if (e.target === modal) hide(); });
}
function escapeHTML(s){
return String(s ?? '').replace(/[&<>"']/g, m=>({ '&':'&','<':'>','>':'>','"':'"',"'":''' }[m]));
}
let helpLoaded = false;
async function loadHelpContent(){
const body = document.getElementById('cme-help-body');
if (!body) return;
if (helpLoaded) return; // 只加载一次
try {
const txt = await xFetch(HELP_URL);
let data = JSON.parse(txt);
if (!Array.isArray(data)) throw new Error('格式错误');
const rows = data.map(item=>{
const func = item['功能'] ?? item['feature'] ?? '';
const desc = item['描述'] ?? item['description'] ?? '';
return `
<div style="padding:8px 10px;border:1px solid rgba(255,255,255,.08);border-radius:8px;margin-bottom:8px;background:#121212;">
<div style="font-weight:600;margin-bottom:4px;">${escapeHTML(func)}</div>
<div style="opacity:.9;">${escapeHTML(desc)}</div>
</div>
`;
}).join('');
body.innerHTML = rows || `<div style="opacity:.8;">暂无内容</div>`;
helpLoaded = true;
} catch(e){
body.innerHTML = `<div style="color:#ff8a80;">使用手册加载失败,请稍后重试。</div>`;
}
}
function showHelp(){
ensureHelpModal();
const modal = document.getElementById('cme-help-modal');
if (modal) modal.style.display = 'flex';
loadHelpContent();
}
function bindButtons(){
document.getElementById('cme-start')?.addEventListener('click', async ()=>{
autoMode = true;
setStatus('自动已开启,正在获取时长…');
setCourse(getCourseTitle());
setLesson(getLessonTitle());
setCurCwid(getCwidFromURL() || '-');
renderSections();
if (!countdownTimer) {
const got = await fetchBufferedSeconds();
if (got && got.buffered) {
setCurCwid(got.cwid || (getCwidFromURL()||'-'));
startCountdown(got.buffered, got.cwid);
setStatus('已获取预计时长(含缓冲)');
addLog(`预计总时长:${formatDuration(got.buffered)}(含缓冲)`);
} else {
setStatus('未获取到 totalTime,稍后重试或等待 ended…');
addLog('未获取到 totalTime,稍后自动重试');
}
}
});
document.getElementById('cme-prev')?.addEventListener('click', ()=>{
autoMode=false; clearCountdown(); goPrev();
});
document.getElementById('cme-next')?.addEventListener('click', ()=>{
autoMode=false; clearCountdown(); goNext();
});
document.getElementById('cme-mute')?.addEventListener('click', ()=>{
const vids = document.querySelectorAll('video');
if (!vids.length) return;
const newMuted = !vids[0].muted;
vids.forEach(v=> v.muted = newMuted);
updateMuteButtonLabel();
console.log(`视频已${newMuted?'静音':'取消静音'}`);
});
// 使用手册按钮(远程加载)
document.getElementById('cme-help')?.addEventListener('click', showHelp);
}
async function init(){
window.addEventListener('load', async ()=>{
ensurePanel();
setCourse(getCourseTitle());
setLesson(getLessonTitle());
setCurCwid(getCwidFromURL() || '-');
renderSections();
setStatus('页面已加载');
updateMuteButtonLabel();
ensureHelpModal();
hookPlayerStatus(); // 页面加载完尝试挂钩
isPlaying = evaluateVideoPlaying();
});
(async ()=>{
const got = await fetchBufferedSeconds();
if (got && got.buffered) {
setCurCwid(got.cwid || (getCwidFromURL()||'-'));
startCountdown(got.buffered, got.cwid);
setStatus('已获取预计时长(含缓冲)');
addLog(`预计总时长:${formatDuration(got.buffered)}(含缓冲)`);
} else {
setStatus('等待站点初始化…');
addLog('等待站点初始化以获取 totalTime…');
}
})();
let lastHref = location.href;
setInterval(async ()=>{
ensurePanel();
bindVideoEnded();
hookPlayerStatus(); // 持续确保已挂钩
isPlaying = evaluateVideoPlaying() || isPlaying;
if (location.href !== lastHref) {
lastHref = location.href;
if (Date.now() < signSilenceUntil) {
} else {
clearCountdown();
setCourse(getCourseTitle());
setLesson(getLessonTitle());
setCurCwid(getCwidFromURL() || '-');
setExpected(null);
setETA(null);
renderSections();
addLog('检测到章节切换,已刷新面板信息');
if (autoMode){
const got = await fetchBufferedSeconds();
if (got && got.buffered) {
setCurCwid(got.cwid || (getCwidFromURL()||'-'));
startCountdown(got.buffered, got.cwid);
setStatus('已获取新课件预计时长');
addLog(`新课件预计时长:${formatDuration(got.buffered)}(含缓冲)`);
}
}
}
}
if (autoMode && !countdownTimer) {
const got = await fetchBufferedSeconds(6000);
if (got && got.buffered) {
setCurCwid(got.cwid || (getCwidFromURL()||'-'));
startCountdown(got.buffered, got.cwid);
setStatus('已获取预计时长(重试)');
addLog(`重试成功,预计总时长:${formatDuration(got.buffered)}(含缓冲)`);
}
}
}, 1200);
}
setInterval(() => {
const playButtons = document.querySelectorAll("#replaybtn");
if (playButtons && playButtons.length > 0) {
const playButton = playButtons[0];
if (playButton.style.display == "none") return;
if (playButton.className == "ccH5PlayBtn") {
try {
playButton.click();
console.log("已自动播放");
} catch(e){}
}
}
}, 10000);
console.log("自动播放功能已启动!");
// 启动
init().catch(e=>{
console.error(e);
try { addLog('初始化失败:' + (e && e.message ? e.message : e)); } catch(_){}
});
})();