// ==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: `

需要验证授权码才能使用

高级版

1

需有效授权码激活高级功能模块

2

当前账户权限:基础版

获取授权码步骤:

  1. 点击前往以下链接,获取授权码
  2. 获取授权码链接1
  3. 获取授权码链接2
`, 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('')}

🔗 前往查看 / 下载

官网文档 · 查看使用说明 & 下载
📦 阿里云盘备用下载 ⚡ 直链下载
▸ 官网无法访问?点击展开备用网址
${CFG.mirrors.map(item => ` ${item.name} `).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(); })();