// ==UserScript==
// @name 2026 广东专业技术人员继续教育公需课自动刷课助手
// @namespace https://github.com/cxcs
// @version 1.1.2
// @description 自用免费,自动挂机自动播放、最小音量、答题、跳转下一节,支持页面刷新后自动恢复,浏览器后台播放
// @author CxCS
// @match https://ggfw.hrss.gd.gov.cn/zxpx/auc/play/player*
// @tag 刷课
// ==/UserScript==
(function () {
'use strict';
if (window.__GD_AUTO_SCRIPT_ACTIVE__) return;
window.__GD_AUTO_SCRIPT_ACTIVE__ = true;
let isRunning = true;
let currentOptionIndex = 0;
let hasAttemptedJump = false;
let coolUntil = 0;
let holdUntil = 0;
let fullMute = false;
// 随机延迟工具
function rd(min, max) {
min = min || 2000;
max = max || 5000;
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* 控制面板
*/
const panel = document.createElement('div');
panel.id = 'gd-auto-panel';
panel.style.cssText = 'position:fixed;top:10px;left:10px;z-index:999999;';
panel.innerHTML = `
广东公需课自动助手 v2.5
作 者:CxCS
自动播放·答题·自动跳转下一章
`;
document.body.appendChild(panel);
const fullPanel = panel.querySelector('#gd-full');
const collapseBtn = panel.querySelector('#gd-collapse');
const toggleBtn = panel.querySelector('#gd-toggle');
const startBtn = panel.querySelector('#gd-start');
const stopBtn = panel.querySelector('#gd-stop');
function updateBtns() {
startBtn.style.display = isRunning ? 'none' : 'block';
stopBtn.style.display = isRunning ? 'block' : 'none';
}
toggleBtn.onclick = () => {
fullPanel.style.display = 'none';
collapseBtn.style.display = 'block';
};
collapseBtn.onclick = () => {
collapseBtn.style.display = 'none';
fullPanel.style.display = 'block';
};
startBtn.onclick = () => {
isRunning = true;
hasAttemptedJump = false;
updateBtns();
console.log('[GD助手] 已启动');
};
stopBtn.onclick = () => {
isRunning = false;
updateBtns();
console.log('[GD助手] 已暂停');
};
/*const muteBtn = panel.querySelector('#gd-mute');
muteBtn.onclick = () => {
fullMute = !fullMute;
muteBtn.textContent = fullMute ? '关闭完全静音' : '打开完全静音';
const v = document.querySelector('video');
if (v) keepAudible(v);
console.log('[GD助手] 完全静音:' + (fullMute ? '开' : '关'));
};*/
updateBtns();
// 默认近静音0.05
function keepAudible(v) {
try {
if (fullMute) {
v.muted = true;
v.volume = 0;
return;
}
if (!('userActivation' in navigator) || navigator.userActivation.hasBeenActive) {
v.muted = false;
v.volume = 0.05;
return;
}
} catch (e) {}
v.muted = true;
v.volume = 0;
}
/**
* 静音
*/
function muteVideo() {
const vids = document.querySelectorAll('video');
let ok = false;
vids.forEach(v => {
if (v.src && (v.src.startsWith('blob:') || v.src.includes('m3u8'))) {
try { keepAudible(v); ok = true; } catch (e) {}
}
});
const vv = document.querySelector('.volume-value');
const vc = document.querySelector('.volume-cursor');
if (vv && vc) {
vv.style.height = '0%';
vc.style.bottom = '0%';
const vt = document.querySelector('.prism-info-display');
if (vt) vt.style.display = 'none';
console.log('[GD助手] 强制静音');
return;
}
if (ok) {
console.log('[GD助手] 已静音');
return;
}
console.warn('[GD助手] 静音失败');
}
/**
* 跳转下一章
*/
let jumpLock = false;
function checkVideoCompleted() {
if (!isRunning || hasAttemptedJump || jumpLock) return;
const p = document.querySelector('.learnpercent span span');
if (!p) return;
if (/已完成/.test(p.textContent.trim())) {
console.log('[GD助手] 已完成,稍后跳转...');
hasAttemptedJump = true;
jumpLock = true;
setTimeout(() => {
doJump();
jumpLock = false;
}, rd(3000, 7000));
}
}
function doJump() {
const items = Array.from(document.querySelectorAll('#content li.level1'));
const cur = items.findIndex(li => li.querySelector('.content-learning, .content-finished'));
if (cur === -1) {
console.log('[GD助手] 无法定位当前项');
return;
}
for (let i = cur + 1; i < items.length; i++) {
const unstart = items[i].querySelector('.content-unstart');
const link = items[i].querySelector('a[href*="regexLogin"]');
if (unstart && link) {
const oc = link.getAttribute('onclick');
if (oc && oc.includes('myClick')) {
const m = oc.match(/myClick\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/);
if (m && m[1]) {
try {
window.myClick(m[1]);
console.log('[GD助手] 已跳转下一节');
return;
} catch (e) { console.error('[GD助手] myClick失败:', e); }
}
}
link.click();
return;
}
}
console.log('[GD助手] 全部完成');
}
/**
* 点击播放
*/
function clickPlay() {
const btn = document.querySelector('.prism-big-play-btn:not([data-clicked])');
if (btn) {
btn.click();
btn.setAttribute('data-clicked', '1');
console.log('[GD助手] 已点击播放');
setTimeout(() => {
enablePictureInPicture();
}, 2000);
}
}
// 识别风控提示,触发冷却;返回是否命中
function markRiskCooldown(text) {
if (/频繁|异常学习|正常节奏/.test(text)) {
coolUntil = Date.now() + rd(120000, 300000);
console.log('[GD助手] 检测到风控提示,冷却后再恢复播放:' + Date.now());
return true;
}
return false;
}
/**
* 关闭弹窗
*/
function closeCommonDialogs() {
const b = document.querySelector('.messager-button .l-btn');
if (b && b.offsetParent) {
const mb = document.querySelector('.messager-body');
if (mb) markRiskCooldown(mb.innerText);
console.log('[GD助手] 关闭通用弹窗');
setTimeout(() => b.click(), rd(3000, 6000));
return true;
}
const f = document.querySelector('.layui-layer-btn a, .layui-layer-btn button, .modal-footer button');
if (f && f.offsetParent && /确定|关闭|知道了/i.test(f.innerText)) {
console.log('[GD助手] 关闭结束弹窗');
setTimeout(() => f.click(), rd(3000, 6000));
return true;
}
return false;
}
function closeAnswerResultDialog() {
const mb = document.querySelector('.messager-button .l-btn');
if (mb && mb.offsetParent) {
const body = document.querySelector('.messager-body');
if (body && body.offsetParent) {
// 风控弹窗:优先取消/关闭,只有单个"确定"时也点确定
if (markRiskCooldown(body.innerText)) {
const btns = Array.from(document.querySelectorAll('.messager-button .l-btn'));
const safe = btns.find(x => /取消|关闭|否/.test(x.innerText)) || btns[btns.length - 1] || mb;
setTimeout(() => safe.click(), rd(3000, 6000));
return true;
}
console.log('[GD助手] 关闭答题结果弹窗');
setTimeout(() => mb.click(), rd(3000, 6000));
return true;
}
}
return false;
}
/**
* 自动答题
*/
let ansLock = false;
function autoAnswer() {
if (ansLock) return;
const d = document.getElementById('dd');
if (!d) return;
const wp = d.closest('.panel.window');
if (!wp || getComputedStyle(wp).display === 'none') return;
const form = d.querySelector('#from_ejectque');
if (!form) return;
const opts = Array.from(form.querySelectorAll('input[type="radio"], input[type="checkbox"]'));
if (!opts.length) return;
if (opts.some(o => o.checked)) return;
ansLock = true;
const isCb = opts[0].type === 'checkbox';
setTimeout(() => {
if (isCb) {
opts.forEach(o => o.checked = true);
} else {
opts[currentOptionIndex % opts.length].checked = true;
currentOptionIndex++;
}
setTimeout(() => {
const sb = Array.from(d.querySelectorAll('button, a, input[type="button"], input[type="submit"]'))
.find(x => /提交|确定|完成|确认/i.test(x.innerText || x.value));
if (sb) sb.click();
ansLock = false;
}, rd(1000, 3000));
}, rd());
}
/**
* 画中画
*/
function enablePictureInPicture() {
// const v = document.querySelector('video');
// if (!v || !('requestPictureInPicture' in v)) return;
// if (document.pictureInPictureElement) return;
// v.requestPictureInPicture().catch(() => {});
}
/**
* 强制播放(节流)
*/
let lastPlay = 0;
let playPending = false;
function forcePlay() {
const v = document.querySelector('video');
if (!v) return;
if (!v.paused && !v.ended) return;
if (document.hidden && !document.pictureInPictureElement) return;
if (Date.now() < coolUntil) return;
const dd = document.getElementById('dd');
const wp = dd && dd.closest('.panel.window');
if (wp && getComputedStyle(wp).display !== 'none') return;
if (playPending) return;
const now = Date.now();
if (now - lastPlay < 10000) return;
playPending = true;
lastPlay = now;
setTimeout(() => {
playPending = false;
if (!isRunning) return;
if (document.hidden && !document.pictureInPictureElement) return;
const vv = document.querySelector('video');
if (!vv || (!vv.paused && !vv.ended)) return;
vv.muted = true;
vv.volume = 0;
try {
const p = vv.play();
if (p && p.catch) p.catch(e => {
if (e.name !== 'NotAllowedError' && e.name !== 'AbortError')
console.warn('[GD助手] 播放失败:', e);
});
} catch (e) {}
}, rd(3000, 9000));
}
/**
* 卡死检测
*/
let lastT = -1;
let stuck = 0;
function checkStuck() {
if (!isRunning) return;
const v = document.querySelector('video');
if (!v || v.paused || v.ended) return;
const t = v.currentTime;
const dur = v.duration;
if (isNaN(dur) || dur <= 0) return;
if (lastT === t) {
stuck++;
if (stuck >= 5) {
const n = parseInt(sessionStorage.getItem('gd_reload') || '0');
if (n < 2) {
sessionStorage.setItem('gd_reload', n + 1);
console.log(`[GD助手] 卡死,第${n + 1}次刷新...`);
setTimeout(() => location.reload(), rd(2000, 4000));
} else {
console.warn('[GD助手] 刷新次数用完');
sessionStorage.removeItem('gd_reload');
}
return;
}
} else {
stuck = 0;
}
lastT = t;
}
// 初始化
setInterval(checkStuck, 20000);
console.log('[GD助手] 已加载');
setTimeout(() => {
if (!isRunning) return;
clickPlay();
setTimeout(muteVideo, 5000);
}, rd(3000, 6000));
document.addEventListener('visibilitychange', () => {
if (!document.hidden) holdUntil = Date.now() + rd(8000, 20000);
});
let dialogDirty = true;
let lastDialogCheck = 0;
new MutationObserver(() => { dialogDirty = true; }).observe(document.body, { childList: true, subtree: true });
/**
* 主循环
*/
setInterval(() => {
if (!isRunning) return;
if (Date.now() < holdUntil) return;
clickPlay();
forcePlay();
const pv = document.querySelector('video');
if (pv && !pv.paused && !pv.ended) keepAudible(pv);
checkVideoCompleted();
if (dialogDirty || Date.now() - lastDialogCheck > 10000) {
dialogDirty = false;
lastDialogCheck = Date.now();
if (closeAnswerResultDialog()) return;
if (closeCommonDialogs()) return;
autoAnswer();
}
}, 2000);
})();