// ==UserScript==
// @name 法治云培训-课程助手
// @namespace http://tampermonkey.net/zzzzzzys_法治云培训-课程助手
// @version 1.0.0
// @copyright zzzzzzys.All Rights Reserved.
// @description 法治云培训-课程助手(https://www.fzypx.com/),脚本免费功能有限,可自动静音播放视频,防暂停。更全自动方法,请查看文档介绍!客户端支持最高2倍速,可多课程同时学习,等效于可自动控制高倍速学习!实际视频学习速度还可2倍速学习!因此最快单个视频时长决定了总学习时长!
// @author zzzzzzys
// @match https://www.fzypx.com/*
// @require https://fastly.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js
// @resource https://cdn.staticfile.org/limonte-sweetalert2/11.7.1/sweetalert2.min.css
// @require https://fastly.jsdelivr.net/npm/sweetalert2@11.12.2/dist/sweetalert2.all.min.js
// @require https://scriptcat.org/lib/637/1.4.5/ajaxHooker.js#sha256=EGhGTDeet8zLCPnx8+72H15QYRfpTX4MbhyJ4lJZmyg=
// @connect fc-mp-8ba0e2a3-d9c9-45a0-a902-d3bde09f5afd.next.bspapp.com
// @connect mp-8ba0e2a3-d9c9-45a0-a902-d3bde09f5afd.cdn.bspapp.com
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// @grant GM_info
// @grant GM_addStyle
// @run-at document-start
// @antifeature ads 有弹窗广告,介绍完全自动化软件
// @antifeature payment 脚本基础功能使用免费,但需要使用完全自动化软件时,可能收费
// ==/UserScript==
(function() {
'use strict';
// ==================== 工具函数 ====================
const delay = ms => new Promise(r => setTimeout(r, ms));
// ==================== 配置常量 ====================
const CFG = {
vipFlag: 'hnedu123_VIP',
jsCache: 'hnedu123_jsCode',
vipToggle: 'hnedu123_vipSign',
siteId: '6a16492799c624935cd81eef',
notifyTxt: "请在视频播放页面使用脚本,脚本检测到视频会自动开始,脚本功能有限,也可能页面有所更新,导致脚本不能正常使用,建议下载软件使用!全自动学习所有未完成视频",
baseNotify: '建议下载软件使用!全自动学习所有未完成视频',
vipNotify: '全自动建议下载客户端使用!法治云培训-客户端',
advBtn: "前往软件使用课程助手,全自动学习所有未完成视频!",
scriptCaps: [
"辅助当前页面视频播放",
"防暂停",
"自动静音播放",
"仅限单页面使用",
],
clientCaps: [
"输入账号密码即可全自动",
"全自动完成所有未完成课程",
"单账号内最多200课程同时学习",
"2倍速",
],
aliDown: "https://www.alipan.com/s/wViqbLvgSF8",
directDown: 'http://112.124.58.51/static/课程助手.exe',
buyLinks: [
"https://68n.cn/IJ8QB",
"https://68n.cn/RM9ob",
],
mirrors: [
{ name: "备用地址0", url: "https://www.zzzzzzys.com/" },
{ name: "备用地址1", url: "https://zzzzzzys.lovestoblog.com/" },
{ name: "备用地址2", url: "https://zzzzzzys.us.kg/" },
{ name: "备用地址3", url: "https://zzysdocs.dpdns.org/" },
{ name: "备用地址4", url: "https://zzzzzzys.dpdns.org/" },
{ name: "备用地址5", url: "https://zzzzzzys.kesug.com/" },
{ name: "备用地址6", url: "https://zzysdocs.great-site.net/" },
]
};
CFG.docUrl = CFG.mirrors[0].url + '?webId=' + CFG.siteId;
// ==================== 辅助工具类 ====================
class Toolkit {
static isVip() {
return false;
}
static async authCheck(payload) {
try {
Toolkit.upgradeModal();
return;
const resp = await new Promise((ok, fail) => {
GM_xmlhttpRequest({
url: "https://fc-mp-8ba0e2a3-d9c9-45a0-a902-d3bde09f5afd.next.bspapp.com/utils/validCodeCas?" + new URLSearchParams(payload),
method: 'GET',
onload: function(res) {
if (res.status === 200) {
const result = JSON.parse(res.response);
console.log(result);
ok(result);
}
fail('请求失败:' + res.response);
},
onerror: function(err) {
console.error(err);
fail('请求错误!' + err.toString());
}
});
});
if (resp.code !== 200) {
GM_deleteValue(CFG.vipFlag);
GM_deleteValue(CFG.jsCache);
throw new Error('验证失败:' + resp.data);
}
Swal.fire({
title: "高级功能已启用!",
text: "校验成功!",
icon: 'success',
confirmButtonText: '确定',
});
GM_setValue(CFG.vipFlag, true);
return resp.data;
} catch (e) {
console.error(e);
Swal.fire({
title: "验证失败!",
text: e.toString(),
icon: 'error',
confirmButtonText: '确定',
});
}
}
static async fetchRemoteJs(url) {
try {
let cached = GM_getValue(CFG.jsCache);
if (!cached) {
const jsCode = await new Promise((ok, fail) => {
GM_xmlhttpRequest({
url: url,
method: 'GET',
onload: function(res) {
console.log(res);
if (res.status === 200) {
ok(res.responseText);
} else {
fail('服务器拒绝:' + res.response);
}
},
onerror: function(err) {
console.error(err);
fail('请求错误!' + err.toString());
}
});
});
cached = jsCode
.replace(/\\/g, '\\\\')
.replace(/'/g, '\'')
.replace(/"/g, '\"');
GM_setValue(CFG.jsCache, cached);
}
return cached;
} catch (error) {
console.error('远程加载失败:', error);
throw new Error("远程加载失败");
}
}
static buyCodeModal() {
const links = CFG.buyLinks;
Swal.fire({
title: ' 高级功能解锁',
html: `
`,
icon: 'info',
confirmButtonText: '前往激活',
showCloseButton: true,
timer: 30000,
customClass: {
popup: 'vip-alert-popup',
confirmButton: 'vip-confirm-btn'
},
willClose: () => {}
});
}
static async waitNode(selector, mode = 'node', root, timeout = 10000) {
return new Promise((resolve, reject) => {
if (!['node', 'nodeList'].includes(mode)) {
console.error('Invalid type parameter. Expected "node" or "nodeList"');
reject('Invalid type parameter. Expected "node" or "nodeList"');
}
const cleanup = (tid, iid) => {
clearTimeout(tid);
clearInterval(iid);
};
const success = (res, tid, iid) => {
console.log(`${selector} ready!`);
cleanup(tid, iid);
resolve(res);
};
const fail = (tid, iid) => {
cleanup(tid, iid);
resolve(null);
};
const probe = () => {
try {
let nodes;
if (mode === 'node') {
nodes = root ? root.querySelector(selector) : document.querySelector(selector);
return nodes;
}
nodes = root ? root.querySelectorAll(selector) : document.querySelectorAll(selector);
return nodes.length > 0 ? nodes : null;
} catch (error) {
console.error('节点检查错误:', error);
reject('节点检查错误:', error);
}
};
const iid = setInterval(() => {
const result = probe();
if (result) {
success(result, tid, iid);
} else {
console.log(`等待节点: ${selector}...`);
}
}, 1000);
const tid = setTimeout(() => {
console.error(`节点获取超时: ${selector}`);
fail(tid, iid);
}, timeout);
});
}
static parseTime(str, opts = {}) {
const m = str.match(/(?:(\d+)小时)?(?:(\d+)分)?(?:(\d+)秒)?/) || [];
const h = parseInt(m[1] || 0, 10);
const min = parseInt(m[2] || 0, 10);
const s = parseInt(m[3] || 0, 10);
const total = h * 3600 + min * 60 + s;
return opts.returnObject ? { hours: h, minutes: min, seconds: s } : total;
}
static decodeToken(token) {
try {
const [h64, p64] = token.split('.');
const b64url = str => atob(str.replace(/-/g, '+').replace(/_/g, '/').padEnd(str.length + (4 - str.length % 4) % 4, '='));
const header = JSON.parse(b64url(h64));
const payload = JSON.parse(
decodeURIComponent(
b64url(p64)
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
)
);
return { header, payload };
} catch (error) {
console.error('解码失败:', error);
return null;
}
}
static upgradeModal() {
return Swal.fire({
title: '全自动学习功能说明',
html: `
⚠️ 网页脚本有局限性
浏览器脚本运行在沙盒中,<无法跨页面、无法自动登录、无法批量管理账号。
若需要 输入账号密码后全自动完成所有视频,推荐使用本地客户端。
📄 当前脚本
${CFG.scriptCaps.map(f => `- ${f}
`).join('')}
推荐
🖥️ 客户端工具
${CFG.clientCaps.map((f, i) => `- ${i === 0 ? `${f}` : f}
`).join('')}
▸ 官网无法访问?点击展开备用网址
`,
showCancelButton: true,
confirmButtonText: '前往官网',
cancelButtonText: '关闭',
confirmButtonColor: '#38a169',
cancelButtonColor: '#a0aec0',
width: '620px',
padding: '1.8em',
background: '#ffffff',
backdrop: 'rgba(0,0,0,0.25)',
customClass: { popup: 'shadow-lg' }
}).then((result) => {
if (result.isConfirmed) {
window.open(CFG.docUrl, '_blank');
}
});
}
}
// ==================== UI面板类 ====================
class Dashboard {
constructor({ VIPBtnText = "高级功能,极速刷课", VIPInfo = "您正在使用基础版本,功能可能存在限制" }) {
this.storeKey = 'AuthData';
this.injectStyles();
this.buildDOM();
this.restoreData();
this.display();
this.setVIPText(CFG.vipNotify);
this.setHint(VIPInfo);
this.makeToggle();
}
injectStyles() {
GM_addStyle(`
.auth-window { position: fixed; bottom: 10px; right: 10px; z-index: 999999999999; background: white; padding: 24px; border-radius: 12px; box-shadow: 0 6px 30px rgba(0,0,0,0.15); border: 1px solid #e4e7ed; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; min-width: 320px; transform: translateY(20px); opacity: 0; transition: all 0.3s ease; } .auth-window.visible { transform: translateY(0); opacity: 1; } .auth-title { margin: 0 0 16px; font-size: 20px; color: #2c3e50; font-weight: 600; display: flex; align-items: center; gap: 8px; } .auth-version { font-size: 12px; color: #95a5a6; font-weight: normal; } .auth-tip { margin: 0 0 20px; color: #ffbb00; font-size: 14px; font-weight: weight; line-height: 1.5; } .input-group { margin-bottom: 18px; } .input-label { display: block; margin-bottom: 6px; color: #34495e; font-size: 14px; font-weight: 500; } .input-field { width: 80%; padding: 10px 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px; transition: border-color 0.2s; } .input-field:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 3px rgba(52,152,219,0.1); } .auth-button { width: 100%; padding: 12px; background: #3498db; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; gap: 8px; } .auth-button:hover { background: #2980b9; transform: translateY(-1px); } .auth-button:active { transform: translateY(0); } .error-message { color: #e74c3c; font-size: 13px; margin-top: 8px; padding: 8px; background: #fdeded; border-radius: 6px; display: none; animation: shake 0.4s; } @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } .control-panel { opacity: 1; transform: translateY(10px); transition: all 0.3s ease; } .control-panel.visible { opacity: 1; transform: translateY(0); } .auth-button[disabled] { background: #bdc3c7 !important; cursor: not-allowed; } .auth-window { position: fixed; right: 30px; bottom: 80px; transition: transform 0.3s ease; } .window-toggle:hover .toggle-icon { animation: bounce 0.6s; } .toggle-icon { width: 20px; height: 20px; transition: transform 0.3s ease; } @keyframes bounce { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(4px); } } .vip-btn { width: 100%; position: relative; padding: 12px 24px; border: none; border-radius: 8px; background: linear-gradient(135deg, #ffd700 0%, #ffd900 30%, #ffae00 70%, #ff8c00 100%); color: #2c1a00; font-weight: 600; font-family: 'Segoe UI', sans-serif; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; box-shadow: 0 4px 15px rgba(255, 174, 0, 0.3); } .glow-effect::after { content: ''; position: absolute; inset: 0; background: radial-gradient(circle at 50% 0%, rgba(255, 255, 255, 0.4) 0%, transparent 70%); opacity: 0; transition: opacity 0.3s; } .vip-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255, 174, 0, 0.5); } .vip-btn:hover::after { opacity: 1; } .vip-btn:active { transform: translateY(1px); box-shadow: 0 2px 8px rgba(255, 174, 0, 0.3); } .crown-icon { width: 20px; height: 20px; margin-right: 8px; vertical-align: middle; transition: transform 0.3s; } .vip-btn:hover .crown-icon { transform: rotate(10deg) scale(1.1); } .vip-text { background: linear-gradient(45deg, #2c1a00, #5a3a00); -webkit-background-clip: text; background-clip: text; color: transparent; display: inline-block; } * 弹窗容器 */ .vip-alert-popup { border: 2px solid #ffd700; border-radius: 12px; background: linear-gradient(145deg, #1a1a1a, #2d2d2d); } .alert-header { border-bottom: 1px solid #404040; padding-bottom: 12px; margin-bottom: 15px; } .swal-vip-icon { color: #ffd700; font-size: 2.2em; margin-right: 8px; } .requirements-box { background: rgba(255,215,0,0.1); border-radius: 8px; padding: 15px; margin: 15px 0; } .requirement-item { display: flex; align-items: center; margin: 10px 0; } .number-badge { background: #ffd700; color: #000; width: 24px; height: 24px; border-radius: 50%; text-align: center; margin-right: 12px; font-weight: bold; } .status-tag { padding: 4px 8px; border-radius: 4px; font-size: 0.9em; } .free-status { background: #ff4444; color: white; } .action-guide { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px; } .step-list li { margin: 8px 0; padding-left: 8px; } .pricing-link { color: #00ff9d !important; text-decoration: underline dotted; transition: all 0.3s; } .pricing-link:hover { color: #00cc7a !important; text-decoration: underline; } .vip-confirm-btn { background: linear-gradient(135deg, #ffd700 0%, #ff9900 100%) !important; border: none !important; font-weight: bold !important; transition: transform 0.2s !important; } .vip-confirm-btn:hover { transform: scale(1.05); }
`);
GM_addStyle(` div.swal2-container { all: initial !important; position: fixed !important; z-index: 999999 !important; inset: 0 !important; display: flex !important; align-items: center !important; justify-content: center !important; background: rgba(0,0,0,0.4) !important; } .swal2-popup { all: initial !important; max-width: 600px !important; width: 90vw !important; min-width: 300px !important; position: relative !important; box-sizing: border-box !important; padding: 20px !important; background: white !important; border-radius: 8px !important; font-family: Arial !important; animation: none !important; } @keyframes swal2-show { 0% { transform: scale(0.9); opacity: 0 } 100% { transform: scale(1); opacity: 1 } } `);
GM_addStyle(` .beta-container { margin: 18px 0; border-radius: 10px; background: linear-gradient(145deg, #2d2d2d, #1a1a1a); border: 1px solid rgba(255, 215, 0, 0.2); box-shadow: 0 4px 20px rgba(0,0,0,0.2); } .beta-card { padding: 16px; } .beta-header { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; } .beta-icon { width: 28px; height: 28px; fill: #ffd700; filter: drop-shadow(0 0 4px rgba(255,215,0,0.3)); } .beta-title { margin: 0; color: #ffd700; font-size: 16px; font-weight: 600; text-shadow: 0 2px 4px rgba(0,0,0,0.2); } .beta-toggle { display: flex; align-items: center; gap: 12px; cursor: pointer; padding: 10px; border-radius: 8px; transition: background 0.3s; } .beta-toggle:hover { background: rgba(255,215,0,0.05); } .beta-checkbox { display: none; } .beta-track { position: relative; width: 50px; height: 28px; border-radius: 14px; background: rgba(255,215,0,0.1); border: 1px solid rgba(255,215,0,0.3); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .beta-thumb { position: absolute; left: 2px; top: 2px; width: 24px; height: 24px; background: linear-gradient(145deg, #ffd700, #ffae00); border-radius: 50%; transform: translateX(0); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 2px 4px rgba(0,0,0,0.2); } .beta-checkbox:checked + .beta-track { background: rgba(255,215,0,0.2); border-color: #ffd700; } .beta-checkbox:checked + .beta-track .beta-thumb { transform: translateX(22px); } .beta-sparkles { position: absolute; width: 100%; height: 100%; background: radial-gradient(circle at 50% 50%, rgba(255,255,255,0.8) 10%, transparent 60%); opacity: 0; transition: opacity 0.3s; } .beta-checkbox:checked + .beta-track .beta-sparkles { opacity: 0.3; } .beta-label { color: #fff; font-size: 14px; font-weight: 500; letter-spacing: 0.5px; background: linear-gradient(90deg, #ffd700, #ffae00); -webkit-background-clip: text; background-clip: text; color: transparent; } .beta-tip { margin: 12px 0 0; color: rgba(255,215,0,0.6); font-size: 12px; line-height: 1.4; padding-left: 8px; border-left: 3px solid rgba(255,215,0,0.3); } .progress-overlay { position: fixed; bottom: 0; left: 30%; transform: translate(0 -50%); background: rgba(0,0,0,0.8); padding: 24px; border-radius: 12px; color: white; z-index: 9999999999; display: none; min-width: 300px; height:100px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); backdrop-filter: blur(8px); } .progress-header { margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; } .progress-title { margin: 0; font-size: 16px; color: #fff; } .progress-bar { display:block; width: 100%; height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; overflow: hidden; } .progress-fill { height: 100%; background: linear-gradient(90deg, #00ff88, #00ccff); transition: width 0.3s ease; } .progress-info { margin-top: 15px; text-align: center; gap: 20px; font-size: 12px; color: rgba(255,255,255,0.8); }`);
}
buildDOM() {
this.wrapper = document.createElement('div');
this.wrapper.className = 'auth-window';
const heading = document.createElement('h3');
heading.className = 'auth-title';
heading.innerHTML = `
脚本控制台v${GM_info.script.version}
`;
const hint = document.createElement('p');
hint.className = 'auth-tip';
hint.textContent = '您正在使用基础版本,功能可能存在限制';
this.hintEl = hint;
this.pwdInput = this.mkInput(' 授权密钥', 'password', '#auth');
const lnk1 = this.mkLink('authLink1', CFG.buyLinks[0], '获取授权链接1');
const lnk2 = this.mkLink('authLink2', CFG.buyLinks[1], '获取授权链接2');
this.checkBtn = document.createElement('button');
this.checkBtn.className = 'auth-button';
this.checkBtn.innerHTML = `
验证授权码
`;
this.checkBtn.onclick = () => this.doVerify();
this.ctrlPanel = document.createElement('div');
this.ctrlPanel.className = 'control-panel';
this.ctrlPanel.style.cssText = `margin-top: 20px; border-top: 1px solid #eee; padding-top: 16px;`;
this.vipBtn = document.createElement('button');
this.vipBtn.className = 'vip-btn glow-effect';
this.vipBtn.innerHTML = `
高级功能-全自动挂机
`;
this.vipBtn.addEventListener('click', () => this.onVipClick());
this.timerEl = document.createElement('div');
this.timerEl.className = 'timer';
this.timerEl.textContent = '运行时间: 00:00:00';
this.timerEl.style.cssText = `color: #2ecc71; font-size: 13px; margin-bottom: 12px;`;
this.runBtn = document.createElement('button');
this.runBtn.className = 'auth-button';
this.runBtn.style.backgroundColor = '#2ecc71';
this.runBtn.innerHTML = `
开始运行-自动化挂机
`;
this.runBtn.onclick = () => this.launch();
this.errBox = document.createElement('div');
this.errBox.className = 'error-message';
this.vipGroup = document.createElement('div');
this.vipGroup.className = 'beta-container';
this.vipGroup.innerHTML = `
`;
this.speedToggle = this.vipGroup.querySelector('#beta-speed');
this.speedToggle.checked = GM_getValue(CFG.vipToggle, false);
this.speedToggle.onchange = (e) => {
GM_setValue(CFG.vipToggle, e.target.checked);
};
this.ctrlPanel.append(this.vipBtn, this.timerEl, this.runBtn);
this.wrapper.append(heading, hint, this.pwdInput.box, lnk1, lnk2, this.checkBtn, this.ctrlPanel, this.errBox);
document.body.appendChild(this.wrapper);
}
makeToggle() {
this.toggleBtn = document.createElement('button');
this.toggleBtn.className = 'window-toggle';
this.toggleBtn.innerHTML = `
展开面板
`;
this.toggleBtn.style.cssText = `
position: fixed; right: 30px; bottom: 30px; padding: 12px 20px; background: #fff;
border: none; border-radius: 30px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
cursor: pointer; display: flex; align-items: center; gap: 8px;
transition: all 0.3s ease; z-index: 9999999;
`;
this.toggleBtn.addEventListener('mouseenter', () => {
this.toggleBtn.style.transform = 'translateY(-2px)';
this.toggleBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)';
});
this.toggleBtn.addEventListener('mouseleave', () => {
this.toggleBtn.style.transform = 'none';
this.toggleBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
});
this.toggleBtn.onclick = () => {
const visible = this.wrapper.style.display !== 'none';
this.wrapper.style.display = visible ? 'none' : 'block';
this.toggleBtn.querySelector('.toggle-icon').style.transform = visible ? 'rotate(180deg)' : 'none';
this.toggleBtn.querySelector('.toggle-text').textContent = visible ? '展开面板' : '收起面板';
if (!visible) {
this.wrapper.animate([
{ opacity: 0, transform: 'translateY(20px)' },
{ opacity: 1, transform: 'none' }
], { duration: 300, easing: 'ease-out' });
}
};
document.body.appendChild(this.toggleBtn);
}
launch(callback) {
if (!this._working) {
this._startAt = Date.now();
this._working = true;
this.runBtn.innerHTML = `
运行中...
`;
this.runBtn.style.backgroundColor = '#e67e22';
this.runBtn.disabled = true;
this._ticker = setInterval(() => {
const elapsed = Date.now() - this._startAt;
const hh = Math.floor(elapsed / 3600000);
const mm = Math.floor((elapsed % 3600000) / 60000);
const ss = Math.floor((elapsed % 60000) / 1000);
this.timerEl.textContent = `运行时间: ${String(hh).padStart(2,'0')}:${String(mm).padStart(2,'0')}:${String(ss).padStart(2,'0')}`;
}, 1000);
if (this._onBegin && typeof this._onBegin === 'function') {
this._onBegin();
}
if (typeof callback === 'function') {
callback();
}
}
}
mkInput(label, type, id) {
const box = document.createElement('div');
box.className = 'input-group';
const lbl = document.createElement('label');
lbl.className = 'input-label';
lbl.textContent = label;
lbl.htmlFor = id;
const inp = document.createElement('input');
inp.className = 'input-field';
inp.type = type;
inp.id = id;
inp.maxLength = 16;
box.appendChild(lbl);
box.appendChild(inp);
return { box, inp };
}
mkLink(id, href, text) {
const a = document.createElement('a');
a.id = id;
a.className = 'auth-link';
a.href = href;
a.target = '_blank';
a.textContent = text;
a.style.cssText = `display: block; margin: 12px 0; color: #3498db; text-decoration: none; font-size: 13px; transition: opacity 0.2s;`;
a.addEventListener('mouseenter', () => {
a.style.opacity = '0.8';
a.style.textDecoration = 'underline';
});
a.addEventListener('mouseleave', () => {
a.style.opacity = '1';
a.style.textDecoration = 'none';
});
return a;
}
display() {
setTimeout(() => this.wrapper.classList.add('visible'), 100);
}
showErr(msg) {
this.errBox.textContent = msg;
this.errBox.style.display = 'block';
setTimeout(() => { this.errBox.style.display = 'none'; }, 5000);
}
async doVerify() {
const payload = { key: this.pwdInput.inp.value };
console.log(payload);
if (!payload.key || !(/^[A-Z0-9]{16}$/).test(payload.key)) {
Swal.fire({
title: "授权码不正确,应为16位",
text: "请正确输入!",
icon: 'info',
confirmButtonText: '确定',
});
return;
}
if (this._onVerify) {
if (await this._onVerify(payload)) {
GM_setValue(this.storeKey, JSON.stringify(payload));
}
}
}
onVipClick() {
if (this._vipCb) {
this._vipCb();
} else {
Swal.fire({
title: "提示",
text: "请在视频播放页面使用!",
icon: 'info',
confirmButtonText: '确定',
willClose: () => {
console.log(' 用户确认错误,脚本已停止');
}
});
}
}
restoreData() {
let saved = GM_getValue(this.storeKey);
if (saved) {
saved = JSON.parse(saved);
this.pwdInput.inp.value = saved.key || '';
}
}
hide() {
this.wrapper.style.display = 'none';
}
get key() {
return this.pwdInput.inp.value;
}
setHint(txt) {
this.hintEl.innerText = txt;
}
setVIPText(txt) {
this.vipBtn.innerHTML = `
${txt}
`;
}
onVerify(fn) { this._onVerify = fn; }
onBegin(fn) { this._onBegin = fn; }
onVIP(fn) { this._vipCb = fn; }
}
// ==================== 课程核心类 ====================
class LessonMgr {
constructor(pipe = "channel-hunau") {
this.ui = new Dashboard({
VIPBtnText: "高级功能-已弃用,请前往软件学习"
});
this.channel = new BroadcastChannel(pipe);
this.isVip = false;
this.active = false;
this._antiPause = null;
this._pauseTimer = null;
this.setup();
}
setup() {
this.ui.onVerify(async (data) => {
this.remoteUrl = await Toolkit.authCheck(data);
if (this.remoteUrl) {
this.ui.setHint(CFG.vipNotify);
this.isVip = true;
return true;
}
});
this.ui.onBegin(() => {
if (!this.active) {
this.active = true;
console.log("运行时:", this.isVip);
this.mainloop().then(() => {
this.active = false;
});
}
});
this.ui.onVIP(async () => {
if (!this.remoteUrl) {
await this.ui.doVerify();
}
await this.runAdvanced();
});
this.loadVipState();
try {
Swal.fire({
title: "提示",
text: CFG.notifyTxt,
icon: 'info',
timer: 5000,
confirmButtonText: '确定',
timerProgressBar: true,
willClose: () => {
this.ui.launch();
}
});
} catch (e) {
console.error(e);
this.ui.launch();
}
}
captchaWatcher() {
const dialogSel = ".layui-layer1";
const self = this;
const watcher = setInterval(async () => {
const dom = document.querySelector(dialogSel);
if (dom) {
console.log("检查到验证码窗口");
clearInterval(watcher);
const inputSel = "#captchaInput";
const btnSel = ".layui-layer-btn0";
const max = 20;
for (let i = 0; i < max; i++) {
try {
const input = dom.querySelector(inputSel);
const button = dom.querySelector(btnSel);
input.value = i;
await delay(100);
button.click();
await delay(100);
} catch (err) {
console.error(err);
break;
}
}
self.captchaWatcher();
}
}, 5000);
}
loadVipState() {
if (Toolkit.isVip()) {
this.ui.setHint(CFG.vipNotify);
this.isVip = true;
} else {
this.ui.setHint(CFG.baseNotify);
this.isVip = false;
}
console.log("VIP:", this.isVip);
}
async runAdvanced() {
try {
Toolkit.upgradeModal();
} catch (error) {
console.error(error);
Swal.fire({
title: "高级功能执行失败!",
text: "若一直失败,请联系进行售后处理!",
icon: 'error',
confirmButtonText: '确定',
allowOutsideClick: false,
willClose: () => {
console.log(' 用户确认错误,脚本已停止');
}
});
}
}
async autoPlay() {
const vid = document.querySelector('video');
if (!vid) return;
vid.volume = 0;
vid.muted = true;
if (this._antiPause) {
vid.removeEventListener('pause', this._antiPause);
}
this._antiPause = async () => {
if (vid.ended) return;
if (this._pauseTimer) clearTimeout(this._pauseTimer);
this._pauseTimer = setTimeout(async () => {
console.log("检测到视频暂停,自动恢复播放...");
vid.volume = 0;
vid.muted = true;
try { await vid.play(); } catch (e) { console.error("恢复播放失败:", e); }
}, 500);
};
vid.addEventListener('pause', this._antiPause);
vid.addEventListener('ended', () => {
this.jumpNext();
}, { once: true });
await vid.play();
}
jumpNext() {
const items = Array.from(document.querySelectorAll('ul.lis-content li.lis-inside-content'));
if (!items.length) {
console.log("未找到目录列表");
return;
}
const curIdx = items.findIndex(li => li.querySelector('#top_play'));
console.log("当前目录索引:", curIdx);
const start = curIdx + 1;
for (let i = start; i < items.length; i++) {
const li = items[i];
const btn = li.querySelector('button');
const h2 = li.querySelector('h2');
if (!h2) continue;
const btnTxt = btn ? btn.textContent.trim() : '';
console.log(`目录[${i}] 状态: ${btnTxt}`);
const onclick = h2.getAttribute('onclick') || '';
const match = onclick.match(/window\.location\.href='([^']+)'/);
if (match) {
const nextUrl = match[1];
const nextTitle = h2.textContent.trim().replace(/\s+/g, ' ');
console.log("跳转下一节:", nextUrl);
Swal.fire({
title: "视频已播放完毕",
html: `即将跳转到下一节:
${nextTitle}`,
icon: 'success',
timer: 5000,
timerProgressBar: true,
confirmButtonText: '立即跳转',
showCancelButton: true,
cancelButtonText: '取消',
}).then((result) => {
if (result.isConfirmed || result.dismiss === Swal.DismissReason.timer) {
window.location.href = nextUrl;
}
});
return;
}
}
console.log("已是最后一节,无下一个目录");
this.complete();
}
async mainloop() {
try {
await this.autoPlay();
} catch (e) {
console.error(e);
Swal.fire({
title: "失败!",
text: `视频基础播放失败!`,
icon: 'error',
confirmButtonColor: "#FF4DAFFF",
confirmButtonText: "确定",
timer: 5000,
timerProgressBar: true,
willClose: () => {}
});
}
}
notify(msg) {
const pipe = new BroadcastChannel(this.channel.name);
pipe.postMessage(msg);
}
complete() {
if (!this.isVip) {
Swal.fire({
title: "请升级高级版!",
text: `脚本已停止!基础版只能连播几个视频!`,
icon: 'info',
confirmButtonColor: "#FF4DAFFF",
confirmButtonText: "确定",
timer: 0,
willClose: () => {}
});
return;
}
this.notify('finish');
if (Swal) {
this.notify('finish');
Swal.fire({
title: "学习完成!",
text: `学习完成,5s后页面自动关闭!`,
icon: 'success',
confirmButtonColor: "#FF4DAFFF",
confirmButtonText: "确定",
timer: 5000,
willClose: () => {
history.back();
setTimeout(() => location.reload(), 1000);
}
});
}
}
async watchVideo(v, dom) {
return new Promise(resolve => {
const iv = setInterval(async () => {
try {
const current = document.querySelector('video');
if (!current) {
clearInterval(iv);
resolve();
}
v.volume = 0;
v.muted = true;
if (v && v.paused) {
console.log("视频暂停了,重新开始播放...");
v.volume = 0;
v.muted = true;
await v.play();
}
if (!v.src) {
console.error("视频源未设置,即将重新加载");
setTimeout(() => location.reload(), 5000);
}
try {
const quizDlg = document.querySelector('div.el-dialog[aria-label="随机练习"]');
if (quizDlg) {
const ans = window.QuestionInfo.correctAnswer;
console.log("答题弹窗:答案:", ans);
const opts = Array.from(quizDlg.querySelectorAll('input'));
const correctOpt = opts.find(opt => opt.value.includes(ans));
correctOpt.click();
await delay(100);
quizDlg.querySelector('.submit').click();
await delay(500);
document.querySelector('.el-message-box button').click();
}
} catch (e) {}
try {
const tipDlg = document.querySelector('div.el-dialog[aria-label="温馨提示"]');
if (tipDlg) {
tipDlg.querySelector('button').click();
}
} catch (e) {}
} catch (e) {
console.error("checkInterval error:", e);
clearInterval(iv);
setTimeout(() => {}, 2000);
}
}, 5000);
v.addEventListener('ended', () => {
clearInterval(iv);
resolve();
}, { once: true });
});
}
async waitElement(sel, kind = 'node', root, timeout = 10000) {
return new Promise((resolve, reject) => {
if (!['node', 'nodeList'].includes(kind)) {
console.error('Invalid type parameter. Expected "node" or "nodeList"');
reject('Invalid type parameter. Expected "node" or "nodeList"');
}
const cleanup = (tid, iid) => {
clearTimeout(tid);
clearInterval(iid);
};
const done = (res, tid, iid) => {
console.log(`${sel} ready!`);
cleanup(tid, iid);
resolve(res);
};
const timeoutFn = (tid, iid) => {
cleanup(tid, iid);
resolve(null);
};
const probe = () => {
try {
let nodes;
if (kind === 'node') {
nodes = root ? root.querySelector(sel) : document.querySelector(sel);
return nodes;
}
nodes = root ? root.querySelectorAll(sel) : document.querySelectorAll(sel);
return nodes.length > 0 ? nodes : null;
} catch (error) {
console.error('节点检查错误:', error);
reject('节点检查错误:', error);
}
};
const iid = setInterval(() => {
const result = probe();
if (result) {
done(result, tid, iid);
} else {
console.log(`等待节点: ${sel}...`);
}
}, 1000);
const tid = setTimeout(() => {
console.error(`节点获取超时: ${sel}`);
timeoutFn(tid, iid);
}, timeout);
});
}
isDone(dom) {
return dom.querySelector('.el-progress__text').innerText.includes("100");
}
isDonePost(dom) {
return dom.querySelector('.xxzt_icon3');
}
detectType(dom) {
if (dom.querySelector('.font-syllabus-online-video')) return 0;
if (dom.querySelector('.font-syllabus-page')) return 1;
if (dom.querySelector('.font-syllabus-material')) return 2;
}
}
// ==================== 请求拦截 ====================
class Interceptor {
constructor() {
this.init();
}
init() {
const self = this;
ajaxHooker.hook(request => {
if (request.url.includes('vedioValidQuestions/getQuestions')) {
request.response = res => {
window.QuestionInfo = JSON.parse(res.responseText).data;
console.log("QuestionInfo:", window.QuestionInfo);
};
} else if (request.url.includes('p/play/config')) {
request.response = res => {
const json = JSON.parse(res.responseText);
console.log("play/config':");
console.log(json);
window.playConfig = json.data;
};
} else if (request.url.includes('learning/learnVerify/checkCode')) {
request.abort = true;
request.response = res => {
res.responseText = '{"code":0,"msg":null,"data":{"data":"请勿频繁请求","status":9999}}';
};
} else if (request.url.includes('learning/learnVerify')) {
request.abort = true;
}
});
console.log("hooker:", ajaxHooker);
}
}
// ==================== 启动器 ====================
class Bootstrap {
constructor() {
this.engine = null;
this.sniffer = new Interceptor();
this.waitReady();
}
waitReady() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.boot());
} else {
this.boot();
}
}
boot() {
this.engine = new LessonMgr("channel-hunau");
}
}
new Bootstrap();
})();