// ==UserScript==
// @name AHNYJY安徽公需课-安徽农业大学科目三继续教育自动刷课(免费版)
// @namespace ahnyjy/auto
// @version 6.0.0
// @author 道道龙
// @description 【免费版】安徽公需课-安徽农业大学科目三继续教育自动刷课,列表页读取全部课程,播放页自动倍速,播完关闭,列表页自动继续下一课
// @match https://www.ahnyjy.cn/*
// @match https://ilearn.cfyedu.com/student/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_deleteValue
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
var IS_AH_LIST = /ahnyjy\.cn\/course\/center\/detail/.test(location.href);
var IS_AH_PREVIEW = /ahnyjy\.cn\/course\/preview/.test(location.href);
var IS_AH_OTHER = /ahnyjy\.cn/.test(location.href);
var IS_IL = /ilearn\.cfyedu\.com/.test(location.href);
var K_COURSES = 'ahnyjy5_courses';
var K_CURSOR = 'ahnyjy5_cursor';
var K_SPEED = 'ahnyjy5_speed';
var K_RUNNING = 'ahnyjy5_running';
var K_PHASE = 'ahnyjy5_phase'; // 'idle' | 'opening' | 'playing' | 'done'
function log() {
console.log('[AHNYJY-v5] ' + Array.prototype.slice.call(arguments).join(' '));
}
function sleep(ms) { return new Promise(function(r) { setTimeout(r, ms); }); }
function $1(s) { return document.querySelector(s); }
function $A(s) { return Array.prototype.slice.call(document.querySelectorAll(s)); }
function cfg(k, f) {
try { var v = GM_getValue(k); return v !== undefined ? v : f; }
catch(e) { return f; }
}
function setCfg(k, v) { try { GM_setValue(k, v); } catch(e) {} }
function fmt(s) {
if (!s || isNaN(s)) return '--:--';
return String(Math.floor(s/60)).padStart(2,'0') + ':' + String(Math.floor(s%60)).padStart(2,'0');
}
// ================================================================
// 列表页 UI
// ================================================================
function createListUI() {
var old = $1('#ah5-panel');
if (old) { try { old.parentNode.removeChild(old); } catch(e) {} }
if ($1('#ah5-panel')) return;
var html = '
' +
'
' +
'🎓' +
'安徽继续教育刷课助手' +
'FREE' +
'v6.0
' +
'
准备中...
' +
'
' +
'
' +
'
' +
'' +
'' +
'' +
'' +
'
' +
'
' +
'倍速' +
'' +
'
' +
'
' +
'
' +
'ℹ️ 读取全部课程 → 开始刷课 → 播放页自动播放倍速 → 播完关闭 → 自动继续下一课
' +
'⚠️ 免费版全部强制播放;代刷8元 👉 微信 wkds995
';
document.body.insertAdjacentHTML('beforeend', html);
$1('#ah5-speed').value = String(cfg(K_SPEED, 2));
$1('#ah5-speed').onchange = function() { setCfg(K_SPEED, Number(this.value)); };
$1('#ah5-btn-read').onclick = function() { readAllCourses(); };
$1('#ah5-btn-start').onclick = function() { startAuto(); };
$1('#ah5-btn-stop').onclick = function() { stopAuto(); };
$1('#ah5-btn-next').onclick = function() { openNextCourse(); };
}
function setStatus(text, sub) {
var el = $1('#ah5-status');
if (el) el.textContent = text;
var sub_el = $1('#ah5-sub');
if (sub_el) sub_el.textContent = sub || '';
}
function setProgress(cur, total, label) {
var wrap = $1('#ah5-pbar');
if (!wrap) return;
var pct = total > 0 ? Math.round(cur / total * 100) : 0;
wrap.style.display = 'block';
var fill = $1('#ah5-pfill');
var lbl = $1('#ah5-plabel');
var pct_el = $1('#ah5-ppct');
if (fill) fill.style.width = pct + '%';
if (lbl) lbl.textContent = label || (cur + ' / ' + total);
if (pct_el) pct_el.textContent = pct + '%';
}
function syncButtons() {
var running = cfg(K_RUNNING, false);
var courses = cfg(K_COURSES, []);
var startBtn = $1('#ah5-btn-start');
var stopBtn = $1('#ah5-btn-stop');
var readBtn = $1('#ah5-btn-read');
var nextBtn = $1('#ah5-btn-next');
if (startBtn) {
startBtn.disabled = running || courses.length === 0;
startBtn.style.opacity = (running || courses.length === 0) ? '0.5' : '1';
}
if (stopBtn) {
stopBtn.disabled = !running;
stopBtn.style.background = running ? '#ef4444' : '#475569';
stopBtn.style.cursor = running ? 'pointer' : 'not-allowed';
}
if (readBtn) {
readBtn.disabled = running;
readBtn.style.opacity = running ? '0.5' : '1';
}
if (nextBtn) {
nextBtn.disabled = running;
nextBtn.style.opacity = running ? '0.5' : '1';
}
}
function renderList(courses, cursor) {
var list = $1('#ah5-list');
if (!list || !courses || courses.length === 0) {
if (list) list.style.display = 'none';
return;
}
list.style.display = 'block';
list.innerHTML = '' +
'课程列表(' + courses.length + '门)· 已完成 ' + cursor + ' 门
';
courses.forEach(function(c, i) {
var done = i < cursor;
var cur = i === cursor;
var div = document.createElement('div');
div.style.cssText = 'font-size:11px;padding:3px 4px;color:' +
(done ? '#10b981' : cur ? '#fbbf24' : '#94a3b8') + ';' +
'cursor:' + (cur ? 'pointer' : 'default') + ';' +
'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
var icon = done ? '✅ ' : cur ? '👉 ' : ' ';
div.textContent = icon + (i+1) + '. ' + c.courseName.substring(0, 28) +
' [' + (c.classHour || '?') + 'h] ' + (c.learnRate || 0) + '%';
if (cur) {
div.style.background = 'rgba(251,191,36,.1)';
div.style.borderRadius = '4px';
div.onclick = function() { openNextCourse(); };
div.title = '点击手动打开';
}
list.appendChild(div);
});
}
// ================================================================
// 列表页:读取课程(支持分页)
// ================================================================
async function readAllCourses() {
setStatus('正在读取课程...', '');
var allCourses = [];
var totalInput = $1('.el-pagination .el-input__inner[type="number"]');
var totalPages = totalInput ? parseInt(totalInput.getAttribute('max') || '1') : 1;
var currentPager = $1('.el-pagination .el-pager');
var currentActive = currentPager ? currentPager.querySelector('.number.active') : null;
var currentPage = currentActive ? parseInt(currentActive.textContent.trim()) : 1;
log('total pages:', totalPages, 'current:', currentPage);
if (currentPage !== 1) {
var firstBtn = $1('.el-pagination .el-pager .number');
if (firstBtn) {
var evt = new MouseEvent('click', { bubbles: true, cancelable: true, view: window });
firstBtn.dispatchEvent(evt);
await sleep(1500);
}
}
while (true) {
currentPager = $1('.el-pagination .el-pager');
currentActive = currentPager ? currentPager.querySelector('.number.active') : null;
currentPage = currentActive ? parseInt(currentActive.textContent.trim()) : 1;
var pageCourses = readPageCourses();
log('page', currentPage, ':', pageCourses.length, 'courses');
var existingIds = new Set(allCourses.map(function(c) { return c.courseId + '_' + c.clazzId; }));
pageCourses.forEach(function(c) {
var key = c.courseId + '_' + c.clazzId;
if (!existingIds.has(key)) {
allCourses.push(c);
existingIds.add(key);
}
});
setStatus('第' + currentPage + '页 / 共' + totalPages + '页', allCourses.length + '门已读取');
setProgress(currentPage, totalPages, allCourses.length + '门已读取');
if (currentPage >= totalPages) break;
var nextBtn = $1('.el-pagination .btn-next');
if (!nextBtn || nextBtn.disabled) break;
nextBtn.click();
await sleep(1500);
}
if (allCourses.length === 0) {
setStatus('未找到课程数据', '');
return;
}
setCfg(K_COURSES, allCourses);
setCfg(K_CURSOR, 0);
setCfg(K_RUNNING, false);
setCfg(K_PHASE, 'idle');
setStatus('已读取 ' + allCourses.length + ' 门课程', '免费版:全部强制播放');
setProgress(0, allCourses.length, '待播放');
renderList(allCourses, 0);
syncButtons();
log('total:', allCourses.length, 'courses');
allCourses.forEach(function(c, i) {
log(' ' + (i+1) + '. ' + c.courseName + ' [clazzId:' + c.clazzId + ' courseId:' + c.courseId + ']');
});
}
function readPageCourses() {
var vueApps = [];
document.querySelectorAll('*').forEach(function(el) {
if (el.__vue__) vueApps.push(el.__vue__);
});
for (var i = 0; i < vueApps.length; i++) {
var vue = vueApps[i];
var data = vue.$data || vue._data;
if (!data) continue;
var courseList = null;
if (data.clazz && data.clazz.clazzCourses) courseList = data.clazz.clazzCourses;
else if (data.clazzCourses) courseList = data.clazzCourses;
if (courseList && courseList.length > 0) {
return courseList.map(function(c) {
var activityNodes = [];
try { activityNodes = JSON.parse(c.activityNodes || '[]'); } catch(e) {}
return {
clazzId: c.clazzId,
courseId: c.courseId,
onlineCourseId: c.onlineCourseId,
courseStudentId: c.courseStudentId,
courseName: c.courseName || '',
classHour: c.classHour || 0,
learnRate: c.learnRate || 0,
status: c.status || '',
activityNodes: activityNodes
};
});
}
}
return [];
}
// ================================================================
// 列表页:开始 / 停止 / 下一课
// ================================================================
function openNextCourse() {
var courses = cfg(K_COURSES, []);
var cursor = cfg(K_CURSOR, 0);
if (courses.length === 0) { setStatus('请先读取课程', ''); return; }
if (cursor >= courses.length) {
setStatus('🎉 全部 ' + courses.length + ' 门刷完!', '');
setCfg(K_RUNNING, false);
syncButtons();
return;
}
var c = courses[cursor];
var url = 'https://www.ahnyjy.cn/course/preview/' + c.courseId + '?clazzId=' + c.clazzId;
log('open:', c.courseName, '->', url);
setCfg(K_RUNNING, true);
setCfg(K_PHASE, 'opening');
setStatus('(' + (cursor+1) + '/' + courses.length + ') ' + c.courseName.substring(0, 25), '正在打开...');
window.open(url, '_blank');
setProgress(cursor + 1, courses.length, '播放中');
syncButtons();
}
function startAuto() {
var courses = cfg(K_COURSES, []);
if (courses.length === 0) { setStatus('请先读取课程', ''); return; }
setCfg(K_RUNNING, true);
setStatus('自动刷课已开始', '免费版:全部强制播放');
syncButtons();
openNextCourse();
}
function stopAuto() {
setCfg(K_RUNNING, false);
setCfg(K_PHASE, 'idle');
setStatus('已停止', '');
syncButtons();
}
// 列表页:持续轮询,等待播放页更新cursor
function setupListPolling() {
var prevCursor = cfg(K_CURSOR, 0);
setInterval(function() {
var cursor = cfg(K_CURSOR, 0);
var running = cfg(K_RUNNING, false);
var courses = cfg(K_COURSES, []);
var phase = cfg(K_PHASE, 'idle');
// 检测cursor变化(播放页完成了一个课程)
if (cursor > prevCursor) {
log('cursor changed:', prevCursor, '->', cursor);
prevCursor = cursor;
setProgress(cursor, courses.length, '已完成');
renderList(courses, cursor);
// 播完了
if (cursor >= courses.length) {
setStatus('🎉 全部 ' + courses.length + ' 门刷完!', '');
setCfg(K_RUNNING, false);
setCfg(K_PHASE, 'done');
syncButtons();
return;
}
// 自动继续下一课(cursor变化说明播放页刚关闭,强制设running=true)
setCfg(K_RUNNING, true);
setStatus('(' + (cursor+1) + '/' + courses.length + ') 自动打开下一课...', '');
setTimeout(function() {
if (cfg(K_RUNNING, false)) {
openNextCourse();
}
}, 2500);
}
}, 2000);
}
// ================================================================
// 播放页 UI(只监控,不控制播放)
// ================================================================
function createPlayUI() {
var old = $1('#ah5-panel');
if (old) { try { old.parentNode.removeChild(old); } catch(e) {} }
if ($1('#ah5-panel')) return;
var html = '' +
'
' +
'🎬' +
'播放页监控' +
'FREE' +
'v5.0
' +
'
等待视频...
' +
'
' +
'
' +
'
' +
'' +
'
' +
'
' +
'倍速' +
'' +
'' +
'' +
'
' +
'
' +
'
' +
'🎬 自动监控:检测到视频自动播放,代刷8元 👉 微信 wkds995
';
document.body.insertAdjacentHTML('beforeend', html);
var speed = cfg(K_SPEED, 2);
var speedEl = $1('#ah5-speed');
speedEl.value = String(speed);
speedEl.onchange = function() {
var s = Number(this.value);
setCfg(K_SPEED, s);
applySpeedToVideo(s);
};
$1('#ah5-btn-close').onclick = function() { doClose(); };
$1('#ah5-btn-speed-up').onclick = function() { changeSpeed(1); };
$1('#ah5-btn-speed-down').onclick = function() { changeSpeed(-1); };
}
function setPlayStatus(text, sub) {
var el = $1('#ah5-status');
if (el) el.textContent = text;
var sub_el = $1('#ah5-sub');
if (sub_el) sub_el.textContent = sub || '';
}
function setPlayProgress(pct) {
var fill = $1('#ah5-pfill');
var pct_el = $1('#ah5-ppct');
if (fill) fill.style.width = pct + '%';
if (pct_el) pct_el.textContent = pct + '%';
}
function addLog(msg) {
var logEl = $1('#ah5-log');
if (!logEl) return;
var ts = new Date().toLocaleTimeString();
var line = document.createElement('div');
line.textContent = '[' + ts.substring(3, 8) + '] ' + msg;
logEl.appendChild(line);
while (logEl.children.length > 5) logEl.removeChild(logEl.firstChild);
}
// ================================================================
// 播放页:视频播放 + 倍速 + 关闭逻辑
// 策略:
// 1. 有多个视频 → 都监控,暂停就点播放
// 2. 进度100%不代表完成 → 等"已经是最后一个任务了"出现才算完成
// ================================================================
var currentSpeed = 2;
var currentVideo = null;
var monitorTimer = null;
function changeSpeed(dir) {
var speeds = [1, 1.5, 2, 3, 5];
var idx = speeds.indexOf(currentSpeed);
var newIdx = Math.max(0, Math.min(speeds.length - 1, idx + dir));
currentSpeed = speeds[newIdx];
setCfg(K_SPEED, currentSpeed);
var el = $1('#ah5-speed');
if (el) el.value = String(currentSpeed);
// 同步到所有Video.js播放器
if (typeof videojs !== 'undefined') {
videojs.getAllPlayers().forEach(function(p) {
try { p.playbackRate(currentSpeed); } catch(e) {}
});
}
addLog('倍速: ' + currentSpeed + 'x');
}
// 点击大播放按钮(触发用户互动,解除浏览器拦截)
function clickBigPlayButton() {
var btn = $1('.vjs-big-play-button');
if (!btn) { addLog('clickBigPlayButton: 按钮不存在'); return false; }
try {
btn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
addLog('已点击 .vjs-big-play-button');
return true;
} catch(e) {
addLog('点击btn失败: ' + e.message);
return false;
}
}
// 让所有Video.js播放器播放
function playAllPlayers() {
if (typeof videojs === 'undefined') return;
videojs.getAllPlayers().forEach(function(p) {
try {
p.play().catch(function() {});
} catch(e) {}
});
}
// 关闭播放页:更新cursor并关闭窗口
async function doClose() {
if (monitorTimer) { clearInterval(monitorTimer); monitorTimer = null; }
addLog('doClose: 更新cursor');
var cursor = cfg(K_CURSOR, 0);
cursor++;
setCfg(K_CURSOR, cursor);
setCfg(K_RUNNING, false);
setCfg(K_PHASE, 'done');
addLog('cursor -> ' + cursor + ',准备关闭');
await sleep(500);
window.close();
}
// 检测页面是否出现"已经是最后一个任务了"提示
function checkLastTaskHint() {
var all = document.querySelectorAll('*');
for (var i = 0; i < all.length; i++) {
var el = all[i];
if (el.children.length > 0) continue;
var txt = (el.textContent || '').replace(/\s+/g, '');
if (txt.indexOf('已经是最后一个任务了') !== -1) {
return true;
}
}
return false;
}
// 获取页面所有视频元素
function getAllVideos() {
var videos = [];
document.querySelectorAll('video').forEach(function(v) {
if (document.contains(v)) videos.push(v);
});
return videos;
}
// 主监控逻辑(每2秒执行一次)
function monitorTick() {
var videos = getAllVideos();
if (videos.length === 0) {
addLog('本页无video元素');
setPlayStatus('等待视频加载...', '');
return;
}
// 汇总进度
var totalDur = 0, totalCur = 0, allEnded = true, allPaused = true;
videos.forEach(function(v) {
totalDur += v.duration || 0;
totalCur += v.currentTime || 0;
if (v.duration > 5) {
if (!v.ended && v.currentTime < v.duration - 0.5) allEnded = false;
if (!v.paused) allPaused = false;
}
});
var pct = totalDur > 0 ? Math.round(totalCur / totalDur * 100) : 0;
var hint = allEnded ? ' [全部播完]' : '';
setPlayStatus('🎬 ' + currentSpeed + 'x' + hint, '已看 ' + pct + '% (' + videos.length + '个视频)');
setPlayProgress(pct);
// 信号1:出现"最后一个任务"提示 = 全部完成
if (checkLastTaskHint()) {
addLog('检测到"最后一个任务",全部完成!');
setPlayStatus('✅ 全部完成!5秒后关闭...', '');
setTimeout(function() { doClose(); }, 5000);
return;
}
// 信号2:有视频暂停了 → 点击播放
if (allPaused) {
addLog('视频暂停,点击播放');
clickBigPlayButton();
playAllPlayers();
}
// 信号3:所有视频都ended了但还没出提示 → 点击播放按钮 + 立刻再次检查提示
if (allEnded && videos[0].duration > 5) {
addLog('所有视频播完,点击播放触发完成检测');
setPlayStatus('⏳ 等最后一个任务提示...', '');
clickBigPlayButton();
playAllPlayers();
// 点击后立刻再检查一次提示(避免等下一个2秒tick)
if (checkLastTaskHint()) {
addLog('点击后立即检测到提示,关闭!');
setPlayStatus('✅ 全部完成!5秒后关闭...', '');
setTimeout(function() { doClose(); }, 5000);
return;
}
}
}
// 开始监控
function startMonitor(video) {
if (monitorTimer) clearInterval(monitorTimer);
addLog('startMonitor()');
if (video) { currentVideo = video; addLog('监控指定video'); }
// 立即播放
clickBigPlayButton();
playAllPlayers();
// 设倍速到所有player
if (typeof videojs !== 'undefined') {
videojs.getAllPlayers().forEach(function(p) {
try { p.playbackRate(currentSpeed); } catch(e) {}
});
}
monitorTimer = setInterval(monitorTick, 2000);
}
// ================================================================
// 初始化
// ================================================================
// ================================================================
// 路由检测:注入页面,捕获所有SPA导航
// ================================================================
(function() {
if (window._ahRouteInjected) return;
window._ahRouteInjected = true;
var last = location.pathname;
var lastHash = location.hash;
function onRoute() {
var p = location.pathname;
var h = location.hash;
if (p === last && h === lastHash) return;
last = p;
lastHash = h;
// postMessage通知主脚本
window.postMessage({ type: 'AH_ROUTE_CHANGE', path: p + h }, '*');
}
// 拦截所有导航API
var _push = history.pushState;
history.pushState = function() { _push.apply(history, arguments); onRoute(); };
var _rep = history.replaceState;
history.replaceState = function() { _rep.apply(history, arguments); onRoute(); };
window.addEventListener('popstate', onRoute);
// 兜底轮询(SPA可能不触发任何事件)
setInterval(onRoute, 800);
// hash路由也监控
setInterval(function() {
if (location.hash !== lastHash) onRoute();
}, 800);
})();
function init() {
if (IS_AH_LIST) {
createListUI();
var courses = cfg(K_COURSES, []);
var cursor = cfg(K_CURSOR, 0);
if (courses.length > 0) {
setStatus('已读取 ' + courses.length + ' 门课程', '免费版:全部强制播放');
setProgress(cursor, courses.length, '待播放');
renderList(courses, cursor);
} else {
setStatus('请点击"读取全部课程"', '');
}
syncButtons();
setupListPolling();
} else if (IS_AH_PREVIEW) {
setStatus('等待跳转到播放页...', '');
var t0 = Date.now();
var iv = setInterval(function() {
if (/ilearn/.test(location.href)) {
clearInterval(iv);
setTimeout(function() { location.reload(); }, 1500);
} else if (Date.now() - t0 > 90000) {
clearInterval(iv);
setStatus('跳转超时,请手动刷新', '');
}
}, 1000);
} else if (IS_IL) {
createPlayUI();
currentSpeed = cfg(K_SPEED, 2);
findAndActivate();
} else if (IS_AH_OTHER) {
createWaitUI();
setupRouteWatch();
}
}
// ================================================================
// postMessage 接收路由变化通知
// ================================================================
window.addEventListener('message', function(e) {
if (!e.data || e.data.type !== 'AH_ROUTE_CHANGE') return;
var path = e.data.path || '';
var isList = /ahnyjy\.cn\/course\/center\/detail/.test(location.href);
var isOtherAH = /ahnyjy\.cn/.test(location.href) && !isList;
if (isList) {
// 进入列表页 → 重新加载
var panel = $1('#ah5-panel');
if (panel) { try { panel.parentNode.removeChild(panel); } catch(err) {} }
setTimeout(function() { location.reload(); }, 200);
} else if (isOtherAH) {
// 离开列表页 → 显示等待UI
var panel = $1('#ah5-panel');
if (panel) { try { panel.parentNode.removeChild(panel); } catch(err) {} }
setTimeout(function() {
createWaitUI();
setupRouteWatch();
}, 200);
}
});
// ================================================================
// 首页提示UI + 路由监控
// ================================================================
function createWaitUI() {
var old = $1('#ah5-panel');
if (old) { try { old.parentNode.removeChild(old); } catch(e) {} }
if ($1('#ah5-panel')) return;
var html = '' +
'
' +
'🎓' +
'安徽继续教育刷课助手' +
'FREE' +
'v6.0
' +
'
' +
'
⚠️ 请进入课程列表页
' +
'
点击菜单"个人中心"后"点击需学习课程",' +
'进入课程列表页后,本助手将自动激活。
' +
'
' +
'目标页面示例:' + location.host + '/course/center/detail/{clazzId}
' +
'
等待进入课程列表页...
' +
'
' +
'ℹ️ 读取全部课程 → 开始刷课 → 播放页自动播放倍速 → 播完关闭 → 自动继续下一课
' +
'⚠️ 免费版全部强制播放;代刷8元 👉 微信 wkds995
';
document.body.insertAdjacentHTML('beforeend', html);
}
function setWaitStatus(text) {
var el = $1('#ah5-status');
if (el) el.textContent = text;
}
// 路由监控:监听 SPA 路由变化 + 定期检查 URL
var routeWatchTimer = null;
var lastKnownPath = location.pathname;
function setupRouteWatch() {
// 先清理旧的
if (routeWatchTimer) { clearInterval(routeWatchTimer); routeWatchTimer = null; }
window.removeEventListener('popstate', onRouteChange);
// popstate:浏览器前进后退
window.addEventListener('popstate', onRouteChange);
// pushState/replaceState:Vue路由跳转(SPA核心)
var _pushState = history.pushState;
var _replaceState = history.replaceState;
history.pushState = function() {
_pushState.apply(history, arguments);
onRouteChange();
};
history.replaceState = function() {
_replaceState.apply(history, arguments);
onRouteChange();
};
// 兜底轮询
routeWatchTimer = setInterval(onRouteChange, 1000);
}
function onRouteChange() {
var currentPath = location.pathname + location.hash;
if (currentPath === lastKnownPath) return;
lastKnownPath = currentPath;
var nowIsList = /ahnyjy\.cn\/course\/center\/detail/.test(location.href);
var nowIsAH = /ahnyjy\.cn/.test(location.href);
// 进入或离开课程列表页 → 强制重新加载脚本
// 这是最可靠的SPA导航处理方式
if (nowIsList) {
clearInterval(routeWatchTimer);
window.removeEventListener('popstate', onRouteChange);
location.reload();
} else if (nowIsAH) {
// 其他ahnyjy页面 → 移除旧面板,重新显示等待UI
clearInterval(routeWatchTimer);
window.removeEventListener('popstate', onRouteChange);
var panel = $1('#ah5-panel');
if (panel) { try { panel.parentNode.removeChild(panel); } catch(e) {} }
setTimeout(function() {
createWaitUI();
setupRouteWatch();
}, 100);
}
}
// ================================================================
// 播放页:查找播放器并激活
// ================================================================
function findAndActivate() {
addLog('findAndActivate() 启动, speed=' + currentSpeed + 'x');
function phase2() {
if (tryFindAndPlay()) return;
setTimeout(function() {
if (tryFindAndPlay()) return;
setTimeout(function() {
if (tryFindAndPlay()) return;
addLog('未找到播放器,请手动播放');
setPlayStatus('未找到播放器', '请手动播放');
}, 1000);
}, 500);
}
function phase1() {
var c = $1('.video-js');
if (c) {
addLog('.video-js 容器已就绪');
setTimeout(phase2, 2000);
} else {
setTimeout(phase1, 300);
}
}
phase1();
}
// ================================================================
// 播放页:Video.js播放器查找 + 播放
// ================================================================
function tryFindAndPlay() {
addLog('tryFindAndPlay()');
// 方式1:videojs.getAllPlayers()
if (typeof videojs !== 'undefined') {
var players = videojs.getAllPlayers();
addLog('getAllPlayers: ' + players.length);
if (players.length > 0) {
playViaVideoJS(players[0]);
return true;
}
} else {
addLog('videojs 全局不存在');
}
// 方式2:.video-js容器下的video标签
var c = $1('.video-js');
if (c) {
var vs = c.querySelectorAll('video');
addLog('.video-js下video: ' + vs.length);
if (vs.length > 0) {
playNativeVideo(vs[0]);
return true;
}
}
// 方式3:页面任意video
var v = $1('video');
addLog('任意video: ' + (v ? '有' : '无'));
if (v) {
playNativeVideo(v);
return true;
}
return false;
}
function playViaVideoJS(player) {
addLog('playViaVideoJS()');
if (typeof player.ready === 'function') {
player.ready(function() { doPlay(player); });
} else {
doPlay(player);
}
}
function doPlay(player) {
// 获取video标签
var v = null;
try { v = player.tech_ && player.tech_.el_; } catch(e) {}
if (!v || v.tagName !== 'VIDEO') {
try { var all = player.el_.querySelectorAll('video'); if (all.length > 0) v = all[0]; } catch(e) {}
}
if (!v) { addLog('doPlay: 无法获取video'); startMonitor(null); return; }
addLog('video: tag=' + v.tagName + ' readyState=' + v.readyState);
currentVideo = v;
// 设倍速
try { player.playbackRate(currentSpeed); addLog('player.rate -> ' + currentSpeed + 'x'); } catch(e) {}
try { v.playbackRate = currentSpeed; addLog('v.rate -> ' + currentSpeed + 'x'); } catch(e) {}
// 锁定playbackRate
lockRate(v, currentSpeed);
// 点击大按钮建立用户互动
clickBigPlayButton();
// 播放
try {
player.play().then(function() {
addLog('player.play() OK');
startMonitor(v);
}).catch(function(e) {
addLog('player.play() 阻止: ' + e.message.substring(0, 50));
startMonitor(v);
});
} catch(e) {
addLog('player.play() 异常: ' + e.message);
startMonitor(v);
}
}
function playNativeVideo(v) {
addLog('playNativeVideo() readyState=' + v.readyState);
currentVideo = v;
lockRate(v, currentSpeed);
clickBigPlayButton();
v.play().then(function() {
addLog('v.play() OK');
startMonitor(v);
}).catch(function(e) {
addLog('v.play() 阻止: ' + e.message.substring(0, 50));
startMonitor(v);
});
}
function lockRate(video, speed) {
if (!video) return;
try {
Object.defineProperty(video, 'playbackRate', {
get: function() { return speed; },
set: function(v2) { /* 忽略外部设置,保持倍速 */ }
});
video.playbackRate = speed;
addLog('lockRate OK');
} catch(e) {
addLog('lockRate失败,用轮询同步');
var si = setInterval(function() {
if (!document.contains(video)) { clearInterval(si); return; }
if (Math.abs(video.playbackRate - speed) > 0.05) {
video.playbackRate = speed;
}
}, 3000);
video._rateSync = si;
}
}
function clickBigPlayButton() {
var btn = $1('.vjs-big-play-button');
if (!btn) { addLog('clickBtn: 不存在'); return; }
try {
btn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
addLog('已点击大播放按钮');
} catch(e) { addLog('点击失败: ' + e.message); try { btn.click(); addLog('btn.click() 备用成功'); } catch(e2) { addLog('btn.click() 也失败'); } }
}
// ================================================================
// 播放页:视频播放 + 倍速 + 关闭逻辑
// 策略:
// 1. 有多个视频 → 都监控,暂停就点播放
// 2. 进度100%不代表完成 → 等"已经是最后一个任务了"出现才算完成
// ================================================================
var currentSpeed = 2;
var currentVideo = null;
var monitorTimer = null;
function changeSpeed(dir) {
var speeds = [1, 1.5, 2, 3, 5];
var idx = speeds.indexOf(currentSpeed);
var newIdx = Math.max(0, Math.min(speeds.length - 1, idx + dir));
currentSpeed = speeds[newIdx];
setCfg(K_SPEED, currentSpeed);
var el = $1('#ah5-speed');
if (el) el.value = String(currentSpeed);
if (typeof videojs !== 'undefined') {
videojs.getAllPlayers().forEach(function(p) {
try { p.playbackRate(currentSpeed); } catch(e) {}
});
}
addLog('倍速: ' + currentSpeed + 'x');
}
// 关闭播放页
async function doClose() {
if (monitorTimer) { clearInterval(monitorTimer); monitorTimer = null; }
var cursor = cfg(K_CURSOR, 0);
cursor++;
setCfg(K_CURSOR, cursor);
setCfg(K_RUNNING, false);
setCfg(K_PHASE, 'done');
addLog('cursor -> ' + cursor + ',准备关闭');
await sleep(500);
window.close();
}
// 检测"最后一个任务"提示
function checkLastTaskHint() {
var all = document.querySelectorAll('*');
for (var i = 0; i < all.length; i++) {
var el = all[i];
if (el.children.length > 0) continue;
var txt = (el.textContent || '').replace(/\s+/g, '');
if (txt.indexOf('已经是最后一个任务了') !== -1) return true;
}
return false;
}
// 获取所有视频
function getAllVideos() {
var vs = [];
document.querySelectorAll('video').forEach(function(v) {
if (document.contains(v)) vs.push(v);
});
return vs;
}
// 监控循环(每2秒)
function monitorTick() {
var videos = getAllVideos();
if (videos.length === 0) {
addLog('无video元素');
setPlayStatus('等待视频加载...', '');
return;
}
var totalDur = 0, totalCur = 0, allEnded = true, allPaused = true;
videos.forEach(function(v) {
totalDur += v.duration || 0;
totalCur += v.currentTime || 0;
if (v.duration > 5) {
if (!v.ended && v.currentTime < v.duration - 0.5) allEnded = false;
if (!v.paused) allPaused = false;
}
});
var pct = totalDur > 0 ? Math.round(totalCur / totalDur * 100) : 0;
var hint = allEnded ? ' [全部播完]' : '';
setPlayStatus('🎬 ' + currentSpeed + 'x' + hint, '已看 ' + pct + '% (' + videos.length + '个)');
setPlayProgress(pct);
// 信号1:最后一个任务提示
if (checkLastTaskHint()) {
addLog('检测到"最后一个任务",全部完成!');
setPlayStatus('✅ 全部完成!5秒后关闭...', '');
setTimeout(function() { doClose(); }, 5000);
return;
}
// 信号2:视频暂停 → 点击播放
if (allPaused && !allEnded) {
addLog('视频暂停,点击播放');
clickBigPlayButton();
if (typeof videojs !== 'undefined') {
videojs.getAllPlayers().forEach(function(p) { try { p.play(); } catch(e) {} });
}
}
// 信号3:所有视频播完但没提示 → 等提示
if (allEnded && videos[0].duration > 5) {
addLog('视频全部播完,等提示出现...');
setPlayStatus('⏳ 等最后一个任务提示...', '');
if (typeof videojs !== 'undefined') {
videojs.getAllPlayers().forEach(function(p) { try { p.play(); } catch(e) {} });
}
}
}
function startMonitor(video) {
if (monitorTimer) clearInterval(monitorTimer);
if (video) {
currentVideo = video;
} else if (currentVideo && document.contains(currentVideo)) {
video = currentVideo;
} else {
addLog('monitor: 无video,等待...');
setTimeout(function() { startMonitor(null); }, 1000);
return;
}
if (video._rateSync) { clearInterval(video._rateSync); video._rateSync = null; }
// 重新锁定
try {
Object.defineProperty(video, 'playbackRate', {
get: function() { return currentSpeed; },
set: function() {}
});
} catch(e) {}
addLog('startMonitor OK, videos=' + getAllVideos().length);
monitorTimer = setInterval(monitorTick, 2000);
}
if (document.readyState === 'complete') setTimeout(init, 800);
else window.addEventListener('load', function() { setTimeout(init, 800); });
})();