// ==UserScript== // @name 广东教师继续教育(免费全自动挂机,1倍速) // @namespace http://tampermonkey.net/zzzzzzys_广东教师继续教育 // @version 1.0.7-free // @copyright zzzzzzys.All Rights Reserved. // @description 广东教师继续教育公需课全自动挂机,1倍速,防挂机检测,无限制看完所有课程。 // @author zzzzzzys (已优化免费) // @match https://jsxx.gdedu.gov.cn/*study/*course/* // @match https://jsxx.gdedu.gov.cn/*study/course/* // @match https://jsxx.gds.edu.cn/* // @match https://jsxx.gds.edu.cn/*study/*course* // @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 // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_info // @grant GM_addStyle // @run-at document-end // @license // ==/UserScript== class Runner { constructor() { this.runner = null this.run() } run() { const url = location.href; if (url.includes("study/course")) { this.runner = new Course("channel-gdedu") } } } class Course { constructor(channel = "channel-my") { this.panel = new AuthWindow({ VIPBtnText: "全自动挂机(免费版)", VIPInfo: "所有课程自动连续播放(1倍速)" }) this.channel = channel this.VIP = true this.running = false this.antiIdleTimer = null this.init() } init() { // 隐藏付费相关元素 this.hideAuthElements(); this.panel.setTip("全功能已启用(免费版),自动连续播放所有课程(1倍速)"); this.panel.setOnBegin(() => { if (!this.running) { this.running = true console.log("免费全自动运行中...") // 启动防挂机心跳:每15分钟轻微滚动一次 this.startAntiIdle(); this.run().then(r => { this.running = false this.stopAntiIdle(); }) } }) // 三秒后自动开始 Swal.fire({ title: "提示", text: "脚本三秒后自动开始!将自动完成所有课程", icon: 'info', timer: 3000, confirmButtonText: '确定', timerProgressBar: true, willClose: () => { this.panel.startAutomation() } }).catch(e => { this.panel.startAutomation() }); } hideAuthElements() { if (this.panel.authInput) this.panel.authInput.container.style.display = 'none'; if (this.panel.verifyBtn) this.panel.verifyBtn.style.display = 'none'; if (this.panel.vipBtn) this.panel.vipBtn.style.display = 'none'; const links = this.panel.container.querySelectorAll('.auth-link'); links.forEach(link => link.style.display = 'none'); } startAntiIdle() { this.antiIdleTimer = setInterval(() => { window.scrollBy(0, 1); console.log('防挂机心跳'); }, 15 * 60 * 1000); } stopAntiIdle() { if (this.antiIdleTimer) { clearInterval(this.antiIdleTimer); this.antiIdleTimer = null; } } goNextVideo() { try { if (typeof unsafeWindow.goNext === 'function') { unsafeWindow.goNext(); } else { throw new Error('goNext not found'); } } catch (e) { const cur = document.querySelector('.section.z-crt'); if (cur) { const all = Array.from(document.querySelectorAll('.section')); const idx = all.indexOf(cur); if (idx !== -1 && idx < all.length - 1) { all[idx + 1].click(); } } } } async run() { try { // 外层循环,每次重新获取当前课程列表 while (true) { const sections = await Utils.getStudyNode('.section', 'nodeList', 5000); if (!sections || sections.length === 0) { console.log('未找到课程节点,可能已完成'); break; } // 寻找当前激活的课程 let currentSection = null; for (let i = 0; i < sections.length; i++) { if (sections[i].classList.contains('z-crt')) { currentSection = sections[i]; break; } } if (!currentSection) { console.log('未找到激活课程,尝试选择第一个未完成项'); for (let i = 0; i < sections.length; i++) { if (!sections[i].classList.contains('completed')) { currentSection = sections[i]; break; } } if (!currentSection) break; // 所有课程都已完成 } console.log('当前课程:', currentSection.querySelector('span')?.innerText); // 检查是否已经完成 const completed = await this.checkStatus(); if (completed) { console.log('已完成,跳转下一个'); this.goNextVideo(); await sleep(3000); continue; } // 获取视频元素 const video = await Utils.getStudyNode('video', 'node', 10000); if (!video) { console.error('视频元素未找到,刷新页面'); location.reload(); return; } video.muted = true; video.volume = 0; video.currentTime = 0; await video.play(); video.currentTime = 0; // 等待视频播放完成 await this.waitForVideoEnd(video); // 弹出跳转提示 await Swal.fire({ title: "完成当前课程", text: '5秒后自动跳转下一个', icon: 'success', confirmButtonColor: "#FF4DAFFF", confirmButtonText: "确定", timer: 5000, timerProgressBar: true, allowOutsideClick: false, allowEscapeKey: false, }); this.goNextVideo(); await sleep(3000); } this.finish(); } catch (e) { console.error(e); Swal.fire({ title: "出错", text: e.toString(), icon: 'error', confirmButtonColor: "#FF4DAFFF", confirmButtonText: "确定", }); } } sendMsg(msg) { const channel = new BroadcastChannel(this.channel); channel.postMessage(msg); } finish() { if (Swal) { this.sendMsg('finish'); Swal.fire({ title: "学习完成!", text: `全部课程已学完!`, icon: 'success', confirmButtonColor: "#FF4DAFFF", confirmButtonText: "确定", timerProgressBar: true, timer: 0, willClose: () => { } }); setTimeout(() => { window.close(); }, 10000); } } async waitForVideoEnd(video) { return new Promise(resolve => { const checkInterval = setInterval(async () => { try { if (video && video.paused) { await video.play(); } if (!video.src) { console.error("视频源丢失,重新加载页面"); clearInterval(checkInterval); location.reload(); return; } // 自动关闭答题弹窗 try { if ($('#questionDiv').length) { $('#questionDiv').stopTime('C'); $('.mylayer-closeico').trigger('click'); } } catch (e) { } const done = await this.checkStatus(); if (done) { clearInterval(checkInterval); resolve(); } } catch (e) { console.error("监控循环出错", e); clearInterval(checkInterval); setTimeout(() => location.reload(), 3000); } }, 3000); video.addEventListener('ended', () => { clearInterval(checkInterval); resolve(); }, { once: true }); }); } async checkStatus() { try { const dom = await Utils.getStudyNode('.g-study-dt', 'node', 3000); const time = document.querySelector('#viewTimeTxt'); if (!dom || !time) return false; const requireText = dom.querySelector('span')?.innerText; if (!requireText) return false; const require = parseInt(requireText); const current = parseInt(time.innerText); if (isNaN(require) || isNaN(current)) return false; return current >= require; } catch (e) { console.error("检查状态出错", e); return false; } } } class Utils { static async getStudyNode(selector, type = 'node', timeout = 10000) { return new Promise((resolve, reject) => { if (!['node', 'nodeList'].includes(type)) { reject('Invalid type parameter. Expected "node" or "nodeList"'); return; } const cleanup = (timeoutId, intervalId) => { clearTimeout(timeoutId); clearInterval(intervalId); }; const handleSuccess = (result, timeoutId, intervalId) => { console.log(`${selector} ready!`); cleanup(timeoutId, intervalId); resolve(result); }; const handleFailure = (timeoutId, intervalId) => { cleanup(timeoutId, intervalId); resolve(null); }; const checkNode = () => { try { if (type === 'node') { return document.querySelector(selector); } const nodes = document.querySelectorAll(selector); return nodes.length > 0 ? nodes : null; } catch (error) { reject('节点检查错误:', error) } }; const intervalId = setInterval(() => { const result = checkNode(); if (result) { handleSuccess(result, timeoutId, intervalId); } else { console.log(`等待节点: ${selector}...`); } }, 1000); const timeoutId = setTimeout(() => { console.error(`节点获取超时: ${selector}`); handleFailure(timeoutId, intervalId); }, timeout); }); } } class AuthWindow { constructor({ VIPBtnText = "全自动挂机", VIPInfo = "免费全功能版" }) { this.storageKey = 'AuthData'; this.injectGlobalStyles(); this.initDOM(); this.show(); this.setVIPBtnText(VIPBtnText); this.setTip(VIPInfo); } injectGlobalStyles() { GM_addStyle(` .auth-window { position: fixed; bottom: 10px; right: 10px; z-index: 9999; 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: #2ecc71; font-size: 14px; 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; } .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; } .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; cursor: pointer; transition: all 0.3s; overflow: hidden; box-shadow: 0 4px 15px rgba(255, 174, 0, 0.3); } .vip-btn:hover { transform: translateY(-2px); } .vip-text { background: linear-gradient(45deg, #2c1a00, #5a3a00); -webkit-background-clip: text; background-clip: text; color: transparent; display: inline-block; } `); 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; }`); } initDOM() { this.container = document.createElement('div'); this.container.className = 'auth-window'; const title = document.createElement('h3'); title.className = 'auth-title'; title.innerHTML = ` 脚本控制台v${GM_info.script.version} `; const tip = document.createElement('p'); tip.className = 'auth-tip'; tip.textContent = '初始化...'; this.tip = tip; this.authInput = this.createInput(' 授权密钥', 'password', '#auth'); const authLink1 = this.createLink('authLink1', '#', '获取授权链接1'); const authLink2 = this.createLink('authLink2', '#', '获取授权链接2'); this.verifyBtn = document.createElement('button'); this.verifyBtn.className = 'auth-button'; this.verifyBtn.innerHTML = `验证授权码`; this.verifyBtn.onclick = () => this.handleVerify(); this.controlPanel = document.createElement('div'); this.controlPanel.className = 'control-panel'; this.controlPanel.style.marginTop = '20px'; this.vipBtn = document.createElement('button'); this.vipBtn.className = 'vip-btn'; this.vipBtn.innerHTML = `高级功能-全自动挂机`; this.vipBtn.onclick = () => this.handleVIPClick(); this.timerDisplay = document.createElement('div'); this.timerDisplay.textContent = '运行时间: 00:00:00'; this.timerDisplay.style.color = '#2ecc71'; this.startBtn = document.createElement('button'); this.startBtn.className = 'auth-button'; this.startBtn.style.backgroundColor = '#2ecc71'; this.startBtn.innerHTML = `▶ 开始运行`; this.startBtn.onclick = () => this.startAutomation(); this.errorBox = document.createElement('div'); this.errorBox.className = 'error-message'; this.controlPanel.append(this.vipBtn, this.timerDisplay, this.startBtn); this.container.append(title, tip, this.authInput.container, authLink1, authLink2, this.verifyBtn, this.controlPanel, this.errorBox); document.body.appendChild(this.container); this.initControlBtn(); } initControlBtn() { this.toggleBtn = document.createElement('button'); this.toggleBtn.className = 'window-toggle'; this.toggleBtn.innerHTML = '☰'; this.toggleBtn.style.cssText = 'position:fixed;right:30px;bottom:30px;z-index:9999999;'; this.toggleBtn.onclick = () => { const hidden = this.container.style.display === 'none'; this.container.style.display = hidden ? 'block' : 'none'; }; document.body.appendChild(this.toggleBtn); } startAutomation() { if (!this.isRunning) { this.startTime = Date.now(); this.isRunning = true; this.startBtn.textContent = '运行中...'; this.startBtn.disabled = true; this.timer = setInterval(() => { const elapsed = Date.now() - this.startTime; const h = Math.floor(elapsed / 3600000).toString().padStart(2,'0'); const m = Math.floor((elapsed % 3600000) / 60000).toString().padStart(2,'0'); const s = Math.floor((elapsed % 60000) / 1000).toString().padStart(2,'0'); this.timerDisplay.textContent = `运行时间: ${h}:${m}:${s}`; }, 1000); if (this.begin) this.begin(); } } createInput(label, type, id) { const c = document.createElement('div'); c.className = 'input-group'; const l = document.createElement('label'); l.textContent = label; const i = document.createElement('input'); i.className = 'input-field'; i.type = type; i.id = id; c.appendChild(l); c.appendChild(i); return { container: c, input: i }; } createLink(id, href, text) { const a = document.createElement('a'); a.className = 'auth-link'; a.href = href; a.textContent = text; return a; } show() { setTimeout(() => this.container.classList.add('visible'), 100); } handleVerify() { Swal.fire({title:'提示',text:'免费版无需授权',icon:'info'}); } handleVIPClick() { Swal.fire({title:'提示',text:'所有功能已免费开放',icon:'info'}); } setTip(text) { this.tip.innerText = text; } setVIPBtnText(text) { this.vipBtn.innerHTML = text; } setOnVerifyCallback(cb) { this.onVerify = cb; } setOnBegin(cb) { this.begin = cb; } setOnVIP(cb) { this.vipCallback = cb; } } const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); new Runner();