// ==UserScript== // @name 湖南大学国家级专业技术人员继续教育基地-课程助手 // @namespace http://userscripts.net/hnu-jx-helper // @version 1.0.0 // @copyright edu-helper.All Rights Reserved. // @description 湖南大学国家级专业技术人员继续教育基地-课程助手(http://jxjyjd.hnu.edu.cn/),提供基础播放辅助,支持静音防中断。完整自动化请使用桌面客户端。 // @author edu-helper // @match http://jxjyjd.hnu.edu.cn/* // @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 // @connect api.edu-helper.net // @connect cdn.edu-helper.net // @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== class CoreEngine { constructor() { this.engine = null this.setupNetworkInterceptor() this.ensurePageReady() } setupNetworkInterceptor() { const _self=this ajaxHooker.hook(_request => { if (_request.url.includes('vedioValidQuestions/getQuestions')) { _request.response = _response => { window.quizData=JSON.parse(_response.responseText).data console.log("QuizData:", window.quizData) }; } else if (_request.url.includes('p/play/config')) { _request.response = _response => { const _json = JSON.parse(_response.responseText) console.log("MediaCfg:"); console.log(_json); window.mediaConfig = _json.data }; } else if (_request.url.includes('learning/learnVerify/checkCode')) { _request.abort=true _request.response=_response =>{ _response.responseText='{"code":0,"msg":null,"data":{"data":"请勿频繁请求","status":9999}}' } }else if(_request.url.includes('learning/learnVerify')) { _request.abort=true } }); console.log("Interceptor:", ajaxHooker) } ensurePageReady() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.run()); } else { this.run(); } } run() { const url = location.href; this.engine = new LessonController("hnu-edu-channel") } } class LessonController { constructor(channel = "hnu-edu-comm") { this.ui = new ControlPanel({ _vipBtnLbl: "增强模式-已停用,请切换至客户端" }) this.commChannel = new BroadcastChannel(channel) this.premium = false this.active = false this.init() } init() { this.ui.onLicenseVerify(async (_payload) => { this.endpoint = await ToolKit.validateCode(_payload) if (this.endpoint) { this.ui.updateStatus(ToolKit.premiumVersionNotice) this.premium = true return true } }) this.ui.onStartRequest(() => { if (!this.active) { this.active = true console.log("Runtime:", this.premium) this.run().then(r => { this.active = false }) } }) this.ui.onPremiumRequest(async () => { if (!this.endpoint) { await this.ui.processLicense() } await this.executeAdvancedMode() }) this.loadPremiumState() try { Swal.fire({ title: "通知", text: ToolKit.welcomeMessage, icon: 'info', timer: 5000, confirmButtonText: '确认', timerProgressBar: true, willClose: () => { this.ui.beginOperation() } }); } catch (_ex) { console.error(_ex) this.ui.beginOperation() } } handleCaptcha(){ const dialogSelector=".verify-layer" const _self=this const _checker=setInterval(async () => { const _dom = document.querySelector(dialogSelector) if (_dom) { console.log("发现验证弹窗") clearInterval(_checker) const _inputSelector = "#verifyInput" const _btnSelector = ".verify-submit" const _max = 20 for (let _idx = 0; _idx < _max; _idx++) { try{ const _input = _dom.querySelector(_inputSelector) const _button = _dom.querySelector(_btnSelector) _input.value=_idx await delay(100) _button.click() await delay(100) }catch(_err){ console.error(_err) break } } _self.handleCaptcha() } },5000) } loadPremiumState() { if (ToolKit.loadStatus()) { this.ui.updateStatus(ToolKit.premiumVersionNotice) this.premium = true } else { this.ui.updateStatus(ToolKit.freeVersionNotice) this.premium = false } console.log("Premium:", this.premium) } async executeAdvancedMode() { try { ToolKit.showUpgradeAlert() } catch (error) { console.error(error) Swal.fire({ title: "增强模式执行异常!", text: "如持续异常,请联系技术支持处理!", icon: 'error', confirmButtonText: '确认', allowOutsideClick: false, willClose: () => { console.log(' 用户确认后任务终止'); } }); } } async startMediaPlayback(){ const _video = document.querySelector('video') if (_video){ _video.volume = 0 _video.muted = true if (this._antiPauseListener) { _video.removeEventListener('pause', this._antiPauseListener) } let _pauseTimer = null this._antiPauseListener = async () => { if (_video.ended) return if (_pauseTimer) clearTimeout(_pauseTimer) _pauseTimer = setTimeout(async () => { console.log("发现播放中断,正在恢复...") _video.volume = 0 _video.muted = true try { await _video.play() } catch (_ex) { console.error("恢复失败:", _ex) } }, 500) } _video.addEventListener('pause', this._antiPauseListener) _video.addEventListener('ended', () => { this.navigateToNextChapter() }, { once: true }) await _video.play() } } navigateToNextChapter() { const _items = Array.from(document.querySelectorAll('ul.lis-content li.lis-inside-content')) if (!_items.length) { console.log("目录列表未找到") return } const _currIdx = _items.findIndex(li => li.querySelector('#top_play')) console.log("CurrentIndex:", _currIdx) if (_currIdx === -1) { console.log("未定位当前章节,将从首个未完成项开始") } const _startIdx = _currIdx + 1 for (let _idx = _startIdx; _idx < _items.length; _idx++) { const _item = _items[_idx] const _button = _item.querySelector('button') const _heading = _item.querySelector('h2') if (!_heading) continue const _btnText = _button ? _button.textContent.trim() : '' console.log(`章节[${_idx}] 进度: ${_btnText}`) const _onclick = _heading.getAttribute('onclick') || '' const _match = _onclick.match(/window\.location\.href='([^']+)'/) if (_match) { const _nextUrl = _match[1] const _nextTitle = _heading.textContent.trim().replace(/\s+/g, ' ') console.log("NextChapter:", _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.completeSession() } async run() { try { await this.startMediaPlayback() } catch (_ex) { console.error(_ex) Swal.fire({ title: "出错了!", text: `媒体播放初始化失败!`, icon: 'error', confirmButtonColor: "#ec4899", confirmButtonText: "确认", timer: 5000, timerProgressBar: true, willClose: () => { } }) } } broadcastEvent(_message) { const _channel = new BroadcastChannel(this.commChannel); _channel.postMessage(_message); } completeSession() { if (!this.premium) { Swal.fire({ title: "请升级至增强版!", text: `任务已终止!标准版仅支持有限连续播放!`, icon: 'info', confirmButtonColor: "#ec4899", confirmButtonText: "确认", timer: 0, willClose: () => { } }) return } this.broadcastEvent('completed') if (Swal) { this.broadcastEvent('completed') Swal.fire({ title: "全部完成!", text: `学习完毕,5秒后自动关闭页面!`, icon: 'success', confirmButtonColor: "#ec4899", confirmButtonText: "确认", timer: 5000, willClose: () => { history.back() setTimeout(()=>{ location.reload() },1000) } }) } } async monitorPlaybackProgress(_video,_dom) { return new Promise(resolve => { const checkInterval = setInterval(async () => { try { const vid=document.querySelector('video') if(!vid){ clearInterval(checkInterval) resolve() } _video.volume = 0 _video.muted = true if (_video && _video.paused) { console.log("播放已暂停,正在重新启动..."); _video.volume = 0 _video.muted = true await _video.play(); } if (!_video.src) { console.error("媒体源缺失,准备刷新页面"); setTimeout(() => { location.reload() }, 5000) } try { const _dialog=document.querySelector('div.el-dialog[aria-label="随机练习"]') if(_dialog){ const _answer=window.quizData.correctAnswer console.log("测验窗口:正确选项:",_answer) const _options = Array.from(_dialog.querySelectorAll('input')); const _correctOption = _options.find(opt => { return opt.value.includes(_answer); }); _correctOption.click() await delay(100) _dialog.querySelector('.submit').click() await delay(500) document.querySelector('.el-message-box button').click() } }catch (_ex) {} try{ const _dialog=document.querySelector('div.el-dialog[aria-label="温馨提示"]') if(_dialog) { _dialog.querySelector('button').click() } }catch (_ex) {} } catch (_ex) { console.error("checkInterval error:", _ex); clearInterval(checkInterval); setTimeout(() => { }, 2000); } }, 5000); _video.addEventListener('ended', () => { clearInterval(checkInterval); resolve() }, {once: true}); }); } findElement(_query, _kind = 'node', _document, _timeout = 10000) { return new Promise((resolve, reject) => { if (!['node', 'nodeList'].includes(_kind)) { console.error('Invalid _kind parameter. Expected "node" or "nodeList"'); reject('Invalid _kind parameter. Expected "node" or "nodeList"'); } const cleanup = (_timeoutId, _intervalId) => { clearTimeout(_timeoutId); clearInterval(_intervalId); }; const handleSuccess = (_result, _timeoutId, _intervalId) => { console.log(`${_query} ready!`); cleanup(_timeoutId, _intervalId); resolve(_result); }; const handleFailure = (_timeoutId, _intervalId) => { cleanup(_timeoutId, _intervalId); resolve(null); }; const checkNode = () => { try { let _nodes; if (_kind === 'node') { _nodes = _document ? _document.querySelector(_query) : document.querySelector(_query); return _nodes } _nodes = _document ? _document.querySelectorAll(_query) : document.querySelectorAll(_query); return _nodes.length > 0 ? _nodes : null; } catch (error) { console.error('元素查询异常:', error); reject('元素查询异常:', error) } }; const _intervalId = setInterval(() => { const _result = checkNode(); if (_result) { handleSuccess(_result, _timeoutId, _intervalId); } else { console.log(`等待元素: ${_query}...`); } }, 1000); const _timeoutId = setTimeout(() => { console.error(`元素查找超时: ${_query}`); handleFailure(_timeoutId, _intervalId); }, _timeout); }); } verifyCompletion(_dom) { return _dom.querySelector('.el-progress__text').innerText.includes("100") } checkPostStatus(_dom) { return _dom.querySelector('.xxzt_icon3') } identifyContentType(_dom) { if (_dom.querySelector('.font-syllabus-online-video')) { return 0 } else if (_dom.querySelector('.font-syllabus-page')) { return 1 } else if (_dom.querySelector('.font-syllabus-material')) { return 2 } } } class ToolKit { constructor() { } static storageKey = 'hnu_edu_premium' static scriptCacheKey = 'hnu_edu_script_cache' static fastModeFlag = 'hnu_edu_fast_mode' static siteIdentifier = '3ed13579a43472299a123aea' static welcomeMessage = "请在课程播放界面启动本工具,系统将自动识别媒体资源并开始播放。受限于浏览器环境,部分功能可能因平台更新而失效,建议下载独立客户端以获得完整自动化体验。" static freeVersionNotice = '推荐使用客户端程序!一键完成全部未学内容' static premiumVersionNotice = '完整自动化请使用桌面应用!湖南大学继教平台-辅助工具' static upgradeButtonLabel = "打开客户端实现全自动课程学习" static currentCapabilities = [ "支持当前页面媒体播放", "防止播放中断", "自动静音连续播放", "仅支持当前页面", ] static appCapabilities = [ "登录凭证后全自动运行", "一键自动学习未学课程", "支持多账户批量并行处理", ] static cloudStorageUrl = "https://www.alipan.com/s/wViqbLvgSF8" static mirrorDownloadUrl = 'https://eduhelper.lanzouu.com/b00zxor42h' static purchaseUrls = [ "https://68n.cn/IJ8QB", "https://68n.cn/RM9ob", ] static mirrorSites = [ { name: "备用地址0", url: "https://www.eduhelper.net/" }, { name: "备用地址1", url: "https://eduhelper.lovestoblog.com/" }, { name: "备用地址2", url: "https://eduhelper.us.kg/" }, { name: "备用地址3", url: "https://edu-docs.dpdns.org/" }, { name: "备用地址4", url: "https://eduhelper.dpdns.org/" }, { name: "备用地址5", url: "https://eduhelper.kesug.com/" }, { name: "备用地址6", url: "https://edu-docs.great-site.net/" }, ] static documentationUrl = `${ToolKit.mirrorSites[0].url}?webId=` + ToolKit.siteIdentifier static loadStatus() { return false } static async validateCode(_payload) { try { ToolKit.showUpgradeAlert() return const _res = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ 'url': "https://api.edu-helper.net/api/v1/external/quota/external/proxy/script-activate?" + new URLSearchParams(_payload), method: 'GET', onload: function (_response) { if (_response.status === 200) { const _result = JSON.parse(_response.response) console.log(_result) resolve(_result) } reject('服务端返回错误:' + _response.response) }, onerror: function (_error) { console.error(_error) reject('网络请求异常!' + _error.toString()) } }) }) if (_res.code !== 200) { GM_deleteValue(ToolKit.storageKey) GM_deleteValue(ToolKit.scriptCacheKey) throw new Error('校验失败:' + _res.data) } Swal.fire({ title: "增强模式已激活!", text: "验证通过!", icon: 'success', confirmButtonText: '确认', }); GM_setValue(ToolKit.storageKey, true) return _res.data } catch (_ex) { console.error(_ex) Swal.fire({ title: "校验未通过!", text: _ex.toString(), icon: 'error', confirmButtonText: '确认', }); } } static async getJsCode(url) { try { let _code = GM_getValue(ToolKit.scriptCacheKey) if (!_code) { const jsUrl = url const _jsCode = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ 'url': jsUrl, method: 'GET', onload: function (_response) { console.log(_response) if (_response.status === 200) { const _result = (_response.responseText) resolve(_result) } else { reject('服务端拒绝:' + _response.response) } }, onerror: function (_error) { console.error(_error) reject('网络请求异常!' + _error.toString()) } }) }) _code = _jsCode .replace(/\\/g, '\\\\') .replace(/'/g, '\'') .replace(/"/g, '\"') GM_setValue(ToolKit.scriptCacheKey, _code) } return _code } catch (error) { console.error('远端资源加载失败:', error); throw new Error("远端资源加载失败") } } static showLinkSwal() { const _link = [ "https://68n.cn/IJ8QB", "https://68n.cn/RM9ob", ] Swal.fire({ title: ' 增强模式激活', html: `

需完成授权验证后方可启用

增强版

1

需要有效的许可证密钥来解锁增强模块

2

当前账户状态:标准版

许可证获取流程:

  1. 点击下方链接获取许可证密钥
  2. 许可证获取入口一
  3. 许可证获取入口二
`, icon: 'info', confirmButtonText: '立即激活', showCloseButton: true, timer: 25000, customClass: { popup: 'premium-popup', confirmButton: 'premium-confirm' }, willClose: () => { } }); } static getStudyNode(_query, _kind = 'node', _timeout = 10000) { return new Promise((resolve, reject) => { if (!['node', 'nodeList'].includes(_kind)) { console.error('Invalid _kind parameter. Expected "node" or "nodeList"'); reject('Invalid _kind parameter. Expected "node" or "nodeList"'); } const cleanup = (_timeoutId, _intervalId) => { clearTimeout(_timeoutId); clearInterval(_intervalId); }; const handleSuccess = (_result, _timeoutId, _intervalId) => { console.log(`${_query} ready!`); cleanup(_timeoutId, _intervalId); resolve(_result); }; const handleFailure = (_timeoutId, _intervalId) => { cleanup(_timeoutId, _intervalId); resolve(null); }; const checkNode = () => { try { let _nodes; if (_kind === 'node') { _nodes = document.querySelector(_query); return _nodes } _nodes = document.querySelectorAll(_query); return _nodes.length > 0 ? _nodes : null; } catch (error) { console.error('元素查询异常:', error); reject('元素查询异常:', error) } }; const _intervalId = setInterval(() => { const _result = checkNode(); if (_result) { handleSuccess(_result, _timeoutId, _intervalId); } else { console.log(`等待元素: ${_query}...`); } }, 1000); const _timeoutId = setTimeout(() => { console.error(`元素查找超时: ${_query}`); handleFailure(_timeoutId, _intervalId); }, _timeout); }); } static parseChineseTime(_timeStr, _opts = {}) { const _pat = /(?:(\d+)小时)?(?:(\d+)分)?(?:(\d+)秒)?/; const _matches = _timeStr.match(_pat) || []; const _hrs = parseInt(_matches[1] || 0, 10); const _mins = parseInt(_matches[2] || 0, 10); const _secs = parseInt(_matches[3] || 0, 10); const _totalSec = _hrs * 3600 + _mins * 60 + _secs; return _opts.returnObject ? {_hrs, _mins, _secs} : _totalSec; } static decodeJWT(_jwt){ try { const [_headerB64, _payloadB64] = _jwt.split('.'); const _decodeB64 = (str) => { return atob(str.replace(/-/g, '+').replace(/_/g, '/').padEnd(str.length + (4 - str.length % 4) % 4, '=')); }; const _hdr = JSON.parse(_decodeB64(_headerB64)); const _pld = JSON.parse( decodeURIComponent( _decodeB64(_payloadB64) .split('') .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join('') ) ); return { _hdr, _pld }; } catch (error) { console.error('解码失败:', error); return null; } } static showUpgradeAlert() { return Swal.fire({ title: '智能学习功能指南', html: `

⚠️ 浏览器扩展存在限制

由于浏览器安全沙箱机制,不能跨标签页操作、不支持自动认证、无法批量处理多账户
如需 登录后全自动完成全部课程,建议使用本地应用程序。

📄 本扩展

    ${ToolKit.currentCapabilities.map(_feat => `
  • ${_feat}
  • `).join('')}
优选

🖥️ 桌面应用

    ${ToolKit.appCapabilities.map((_feat, _index) => `
  • ${_index === 0 ? `${_feat}` : _feat}
  • `).join('')}

🔗 访问官网 / 下载

官方文档 · 查阅指南并下载
📦 云盘备用通道 ⚡ 直接下载,密码:zzzzys
▸ 主站无法打开?点此查看镜像地址
${ToolKit.mirrorSites.map(_entry => ` ${_entry.name} `).join('')}
`, showCancelButton: true, confirmButtonText: '前往官网', cancelButtonText: '关闭', confirmButtonColor: '#22c55e', cancelButtonColor: '#adb5bd', 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(ToolKit.documentationUrl, '_blank'); } }); } } class ControlPanel { constructor({_vipBtnLbl = "增强模式,极速刷课", _vipInfo = "当前运行标准版本,功能可能存在限制",}) { this.persistKey = 'AuthData'; this.applyStylesheet(); this.buildInterface(); this.restoreSettings(); this.reveal(); this.updatePremiumLabel(ToolKit.premiumVersionNotice); this.updateStatus(_vipInfo) } applyStylesheet() { GM_addStyle(` .ctrl-frame { 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; } .ctrl-frame.shown { transform: translateY(0); opacity: 1; } .ctrl-header { margin: 0 0 16px; font-size: 20px; color: #1a252f; font-weight: 600; display: flex; align-items: center; gap: 8px; } .ctrl-ver { font-size: 12px; color: #7f8c8d; font-weight: normal; } .ctrl-status { margin: 0 0 20px; color: #f39c12; font-size: 14px; font-weight: weight; line-height: 1.5; } .field-wrap { margin-bottom: 18px; } .field-label { display: block; margin-bottom: 6px; color: #2c3e50; font-size: 14px; font-weight: 500; } .field-input { width: 80%; padding: 10px 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px; transition: border-color 0.2s; } .field-input:focus { outline: none; border-color: #2b7de1; box-shadow: 0 0 0 3px rgba(43,125,225,0.1); } .ctrl-btn { width: 100%; padding: 12px; background: #2b7de1; 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; } .ctrl-btn:hover { background: #2068a8; transform: translateY(-1px); } .ctrl-btn:active { transform: translateY(0); } .alert-msg { color: #c0392b; font-size: 13px; margin-top: 8px; padding: 8px; background: #ffeaea; border-radius: 6px; display: none; animation: wobble 0.4s; } @keyframes wobble { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } .action-panel { opacity: 1; transform: translateY(10px); transition: all 0.3s ease; } .action-panel.shown { opacity: 1; transform: translateY(0); } .ctrl-btn[disabled] { background: #bdc3c7 !important; cursor: not-allowed; } .ctrl-frame { position: fixed; right: 30px; bottom: 80px; transition: transform 0.3s ease; } .panel-toggle:hover .toggle-svg { animation: pulse 0.6s; } .toggle-svg { width: 20px; height: 20px; transition: transform 0.3s ease; } @keyframes pulse { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(4px); } } .premium-btn { width: 100%; position: relative; padding: 12px 24px; border: none; border-radius: 8px; background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 30%, #d97706 70%, #ea580c 100%); color: #451a03; 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(217, 119, 6, 0.3); } .shine-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; } .premium-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(217, 119, 6, 0.5); } .premium-btn:hover::after { opacity: 1; } .premium-btn:active { transform: translateY(1px); box-shadow: 0 2px 8px rgba(217, 119, 6, 0.3); } .star-icon { width: 20px; height: 20px; margin-right: 8px; vertical-align: middle; transition: transform 0.3s; } .premium-btn:hover .star-icon { transform: rotate(10deg) scale(1.1); } .premium-txt { background: linear-gradient(45deg, #451a03, #78350f); -webkit-background-clip: text; background-clip: text; color: transparent; display: inline-block; } * 弹窗容器 */ .premium-popup { border: 2px solid #fbbf24; border-radius: 12px; background: linear-gradient(145deg, #111827, #1f2937); } .guide-box { border-bottom: 1px solid #374151; padding-bottom: 12px; margin-bottom: 15px; } .premium-icon { color: #fbbf24; font-size: 2.2em; margin-right: 8px; } .req-box { background: rgba(251,191,36,0.1); border-radius: 8px; padding: 15px; margin: 15px 0; } .req-item { display: flex; align-items: center; margin: 10px 0; } .num-badge { background: #fbbf24; color: #000; width: 24px; height: 24px; border-radius: 50%; text-align: center; margin-right: 12px; font-weight: bold; } .state-tag { padding: 4px 8px; border-radius: 4px; font-size: 0.9em; } .std-status { background: #ef4444; color: white; } .guide-box { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px; } .step-items li { margin: 8px 0; padding-left: 8px; } .buy-link { color: #10b981 !important; text-decoration: underline dotted; transition: all 0.3s; } .buy-link:hover { color: #059669 !important; text-decoration: underline; } .premium-confirm { background: linear-gradient(135deg, #fbbf24 0%, #d97706 100%) !important; border: none !important; font-weight: bold !important; transition: transform 0.2s !important; } .premium-confirm: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(` .exp-box { margin: 18px 0; border-radius: 10px; background: linear-gradient(145deg, #1f2937, #111827); border: 1px solid rgba(251, 191, 36, 0.2); box-shadow: 0 4px 20px rgba(0,0,0,0.2); } .exp-card { padding: 16px; } .exp-header { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; } .exp-icon { width: 28px; height: 28px; fill: #fbbf24; filter: drop-shadow(0 0 4px rgba(251,191,36,0.3)); } .exp-title { margin: 0; color: #fbbf24; font-size: 16px; font-weight: 600; text-shadow: 0 2px 4px rgba(0,0,0,0.2); } .exp-toggle { display: flex; align-items: center; gap: 12px; cursor: pointer; padding: 10px; border-radius: 8px; transition: background 0.3s; } .exp-toggle:hover { background: rgba(251,191,36,0.05); } .exp-check { display: none; } .exp-track { position: relative; width: 50px; height: 28px; border-radius: 14px; background: rgba(251,191,36,0.1); border: 1px solid rgba(251,191,36,0.3); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .exp-thumb { position: absolute; left: 2px; top: 2px; width: 24px; height: 24px; background: linear-gradient(145deg, #fbbf24, #d97706); 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); } .exp-check:checked + .exp-track { background: rgba(251,191,36,0.2); border-color: #fbbf24; } .exp-check:checked + .exp-track .exp-thumb { transform: translateX(22px); } .exp-shine { 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; } .exp-check:checked + .exp-track .exp-shine { opacity: 0.3; } .exp-label { color: #fff; font-size: 14px; font-weight: 500; letter-spacing: 0.5px; background: linear-gradient(90deg, #fbbf24, #d97706); -webkit-background-clip: text; background-clip: text; color: transparent; } .exp-hint { margin: 12px 0 0; color: rgba(251,191,36,0.6); font-size: 12px; line-height: 1.4; padding-left: 8px; border-left: 3px solid rgba(251,191,36,0.3); } .prog-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); } .prog-header { margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; } .prog-title { margin: 0; font-size: 16px; color: #fff; } .prog-track { display:block; width: 100%; height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; overflow: hidden; } .prog-bar { height: 100%; background: linear-gradient(90deg, #34d399, #38bdf8); transition: width 0.3s ease; } .prog-info { margin-top: 15px; text-align: center; gap: 20px; font-size: 12px; color: rgba(255,255,255,0.8); }`); } buildInterface() { this.mainFrame = document.createElement('div'); this.mainFrame.className = 'ctrl-frame'; const _title = document.createElement('h3'); _title.className = 'ctrl-header'; _title.innerHTML = ` 控制面板v${GM_info.script.version} `; const _tip = document.createElement('p'); _tip.className = 'ctrl-status'; _tip.textContent = '当前运行标准版本,功能可能存在限制'; this.statusBar = _tip this.licenseField = this.buildInputGroup(' 许可证密钥', 'password', '#license'); const _link =ToolKit.purchaseUrls const _authLink1 = this.buildHyperlink('authLink1', _link[0], '许可证入口一'); const _authLink2 = this.buildHyperlink('authLink2', _link[1], '许可证入口二'); this.activateBtn = document.createElement('button'); this.activateBtn.className = 'ctrl-btn'; this.activateBtn.innerHTML = ` 校验许可证 `; this.activateBtn.onclick = () => this.processLicense(); this.actionArea = document.createElement('div'); this.actionArea.className = 'action-panel'; this.actionArea.style.cssText = ` margin-top: 20px; border-top: 1px solid #eee; padding-top: 16px; `; this.advancedBtn = document.createElement('button'); this.advancedBtn.className = 'premium-btn shine-effect'; this.advancedBtn.innerHTML = ` 增强模式-无人值守 `; this.advancedBtn.addEventListener('click', () => { this.triggerPremium() }) this.elapsedTime = document.createElement('div'); this.elapsedTime.className = 'chrono'; this.elapsedTime.textContent = '已运行: 00:00:00'; this.elapsedTime.style.cssText = ` color: #27c26a; font-size: 13px; margin-bottom: 12px; `; this.launchBtn = document.createElement('button'); this.launchBtn.className = 'ctrl-btn'; this.launchBtn.style.backgroundColor = '#27c26a'; this.launchBtn.innerHTML = ` 启动任务-自动运行 `; this.launchBtn.onclick = () => this.beginOperation(); this.alertBox = document.createElement('div'); this.alertBox.className = 'alert-msg'; this.premiumSection = document.createElement('div'); this.premiumSection.className = 'exp-box'; this.premiumSection.innerHTML = `

增强模式选项

* 启用后请刷新页面。每个视频播放约三分钟后自动完成!

`; this.speedToggle = this.premiumSection.querySelector('#beta-speed'); this.speedToggle.checked = GM_getValue(ToolKit.fastModeFlag, false); this.speedToggle.onchange = (e) => { GM_setValue(ToolKit.fastModeFlag, e.target.checked); }; this.actionArea.append( this.advancedBtn, this.elapsedTime, this.launchBtn ); this.mainFrame.append( _title, _tip, this.licenseField.container, _authLink1, _authLink2, this.activateBtn, this.actionArea, this.alertBox ); document.body.appendChild(this.mainFrame); this.setupToggleButton() } setupToggleButton() { this.collapseBtn = document.createElement('button'); this.collapseBtn.className = 'panel-toggle'; this.collapseBtn.innerHTML = ` 显示面板 `; this.collapseBtn.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.collapseBtn.addEventListener('mouseenter', () => { this.collapseBtn.style.transform = 'translateY(-2px)'; this.collapseBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)'; }); this.collapseBtn.addEventListener('mouseleave', () => { this.collapseBtn.style.transform = 'none'; this.collapseBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; }); this.collapseBtn.onclick = () => { const _visible = this.mainFrame.style.display !== 'none'; this.mainFrame.style.display = _visible ? 'none' : 'block'; this.collapseBtn.querySelector('.toggle-svg').style.transform = _visible ? 'rotate(180deg)' : 'none'; this.collapseBtn.querySelector('.toggle-lbl').textContent = _visible ? '显示面板' : '隐藏面板'; if (!_visible) { this.mainFrame.animate([ {opacity: 0, transform: 'translateY(20px)'}, {opacity: 1, transform: 'none'} ], {duration: 300, easing: 'ease-out'}); } }; document.body.appendChild(this.collapseBtn); } beginOperation(_cb) { if (!this.inProgress) { this.beginTimestamp = Date.now(); this.inProgress = true; this.launchBtn.innerHTML = ` 执行中... `; this.launchBtn.style.backgroundColor = '#d35400'; this.launchBtn.disabled = true; this.clockInterval = setInterval(() => { const _elapsed = Date.now() - this.beginTimestamp; const _hrs = Math.floor(_elapsed / 3600000); const _mins = Math.floor((_elapsed % 3600000) / 60000); const _secs = Math.floor((_elapsed % 60000) / 1000); this.elapsedTime.textContent = `已运行: ${_hrs.toString().padStart(2, '0')}:` + `${_mins.toString().padStart(2, '0')}:` + `${_secs.toString().padStart(2, '0')}`; }, 1000); if (this.startCallback && typeof this.startCallback === 'function') { this.startCallback() } if (typeof _cb === 'function') { _cb() } } } buildInputGroup(_label, _type, _id) { const _container = document.createElement('div'); _container.className = 'field-wrap'; const label = document.createElement('label'); label.className = 'field-label'; label.textContent = _label; label.htmlFor = _id; const _input = document.createElement('input'); _input.className = 'field-input'; _input.type = _type; _input.id = _id; _input.maxLength = 16 _container.appendChild(label); _container.appendChild(_input); return {container: _container, input: _input}; } buildHyperlink(_id, _link, _name) { const authLink = document.createElement('a'); authLink.id = _id; authLink.className = 'auth-link'; authLink.href = _link; authLink.target = '_blank'; authLink.textContent = _name; authLink.style.cssText = ` display: block; margin: 12px 0; color: #2b7de1; text-decoration: none; font-size: 13px; transition: opacity 0.2s; `; authLink.addEventListener('mouseenter', () => { authLink.style.opacity = '0.8'; authLink.style.textDecoration = 'underline'; }); authLink.addEventListener('mouseleave', () => { authLink.style.opacity = '1'; authLink.style.textDecoration = 'none'; }); return authLink } reveal() { setTimeout(() => { this.mainFrame.classList.add('shown'); }, 100); } displayAlert(_msg) { this.alertBox.textContent = _msg; this.alertBox.style.display = 'block'; setTimeout(() => { this.alertBox.style.display = 'none'; }, 5000); } async processLicense() { const _payload = { key: this.licenseField.input.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.verifyCallback) { if (await this.verifyCallback(_payload)) { GM_setValue(this.persistKey, JSON.stringify(_payload)) } else { } } } triggerPremium() { if (this.premiumCallback) { this.premiumCallback() } else { Swal.fire({ title: "通知", text: "请在课程播放页面使用!", icon: 'info', confirmButtonText: '确认', willClose: () => { console.log(' 用户确认后任务终止'); } }); } } restoreSettings() { let _saved = GM_getValue(this.persistKey); if (_saved) { _saved = JSON.parse(_saved) this.licenseField.input.value = _saved.key || ''; } } dismiss() { this.mainFrame.style.display = 'none'; } get key() { return this.licenseField.input.value; } set key(_val) { } updateStatus(_txt) { this.statusBar.innerText = _txt } onLicenseVerify(_cb) { this.verifyCallback = _cb; } onStartRequest(_cb) { this.startCallback = _cb; } onPremiumRequest(_cb) { this.premiumCallback = _cb; } updatePremiumLabel(_txt) { this.advancedBtn.innerHTML = ` ${_txt} `; } } const delay = function (time) { return new Promise(resolve => setTimeout(resolve, time)); } new CoreEngine()