// ==UserScript== // @name 陕西继续教育-视频辅助 // @version 5.2 // @description 隐身倍速、防暂停、自动静音、自动下一节、自动下一章、悬浮窗可拖拽、防挂机鼠标模拟 // @author FengJB // @match *://mooc1.xidian.edu.cn/* // @noframes // @license MIT // @grant none // ==/UserScript== (function() { 'use strict'; // ================= 全局配置 ================= let currentRate = parseFloat(localStorage.getItem('xd_rate')) || 1.25; let isMuted = localStorage.getItem('xd_muted') === 'true'; let autoNext = localStorage.getItem('xd_auto_next') === 'true'; let targetVideo = null; // 自动连播重试机制变量 let retryCount = 0; const MAX_RETRY = 3; let retryTimer = null; let isAutoNextRunning = false; // ========================================== // 拖拽功能函数 function makeDraggable(elmnt, handle) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; handle.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // 计算新位置 let newTop = elmnt.offsetTop - pos2; let newLeft = elmnt.offsetLeft - pos1; // 防止拖出屏幕外 if (newTop < 0) newTop = 0; if (newLeft < 0) newLeft = 0; elmnt.style.top = newTop + "px"; elmnt.style.left = newLeft + "px"; elmnt.style.right = 'auto'; // 确保 right 属性不干扰 } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; // 保存位置到 localStorage localStorage.setItem('xd_panel_left', elmnt.offsetLeft); localStorage.setItem('xd_panel_top', elmnt.offsetTop); } } // 1. 创建悬浮控制面板 UI function createUI() { if (document.getElementById('xd-helper-panel')) return; const panel = document.createElement('div'); panel.id = 'xd-helper-panel'; panel.innerHTML = `
📺 网课助手 未连接视频
* 拖拽标题可移动
`; Object.assign(panel.style, { position: 'fixed', top: '80px', right: '20px', width: '220px', backgroundColor: 'rgba(255, 255, 255, 0.95)', border: '1px solid #1890ff', borderRadius: '8px', padding: '12px', zIndex: '999999', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', fontFamily: 'sans-serif', fontSize: '14px', color: '#333' }); // 恢复上次拖拽的位置 const savedLeft = localStorage.getItem('xd_panel_left'); const savedTop = localStorage.getItem('xd_panel_top'); if (savedLeft !== null && savedTop !== null) { panel.style.left = savedLeft + 'px'; panel.style.top = savedTop + 'px'; panel.style.right = 'auto'; } document.body.appendChild(panel); // 初始化拖拽功能,绑定在头部 const header = document.getElementById('xd-panel-header'); header.style.cursor = 'move'; makeDraggable(panel, header); const rateSelect = document.getElementById('xd-rate-select'); const muteCheckbox = document.getElementById('xd-mute-checkbox'); const autoNextCheckbox = document.getElementById('xd-autonext-checkbox'); const nextBtn = document.getElementById('xd-next-btn'); rateSelect.value = currentRate; muteCheckbox.checked = isMuted; autoNextCheckbox.checked = autoNext; rateSelect.addEventListener('change', (e) => { currentRate = parseFloat(e.target.value); localStorage.setItem('xd_rate', currentRate); applySettings(); }); muteCheckbox.addEventListener('change', (e) => { isMuted = e.target.checked; localStorage.setItem('xd_muted', isMuted); applySettings(); }); autoNextCheckbox.addEventListener('change', (e) => { autoNext = e.target.checked; localStorage.setItem('xd_auto_next', autoNext); }); nextBtn.addEventListener('click', () => { clickNextButton(true); }); } // 更新面板提示文字 function updateLog(text, color = '#666') { const logEl = document.getElementById('xd-log'); if (logEl) { logEl.innerText = text; logEl.style.color = color; } } // 2. 寻找并点击“下一节”按钮 function clickNextButton(isManual = false) { let nextBtn = document.querySelector('.nextChapter') || document.querySelector('#prevNextFocusNext') || document.querySelector('.prev_next .next') || document.querySelector('a[onclick*="next"]'); if (!nextBtn) { const links = document.querySelectorAll('a, div, button'); for (let el of links) { if (el.innerText && el.innerText.trim() === '下一节') { nextBtn = el; break; } } } if (nextBtn) { console.log('【网课助手】找到下一节按钮,正在点击...', nextBtn); nextBtn.click(); if (isManual) updateLog('已手动点击下一节', 'blue'); } else { console.warn('【网课助手】未找到下一节按钮'); updateLog('未找到下一节按钮', 'red'); } } // 3. 自动连播重试逻辑 function startAutoNextWithRetry() { if (isAutoNextRunning) return; isAutoNextRunning = true; retryCount = 0; updateLog('3秒后自动下一节...', 'blue'); // 第一步:3秒后点击下一节 setTimeout(() => { clickNextButton(); updateLog('已点击下一节,等待加载...', 'blue'); // 第二步:等待10秒后检查 setTimeout(() => { checkAndRetry(); }, 10000); }, 3000); } function checkAndRetry() { // 如果视频已经在播放(currentTime > 0 且未暂停),说明跳转成功 if (targetVideo && targetVideo.currentTime > 0 && !targetVideo.paused) { updateLog('✅ 下一节播放正常', 'green'); isAutoNextRunning = false; retryCount = 0; return; } // 如果没播放,判断是否还能重试 if (retryCount < MAX_RETRY) { retryCount++; updateLog(`⚠️ 未自动播放,第 ${retryCount} 次重试...`, 'orange'); console.log(`【网课助手】视频未播放,开始第 ${retryCount} 次重试`); // 尝试手动触发播放 if (targetVideo) { targetVideo.play().catch(() => {}); } // 再次尝试点击下一节(有些平台加载慢,重新点一下按钮) clickNextButton(); // 开启下一轮10秒等待 setTimeout(() => { checkAndRetry(); }, 10000); } else { updateLog('❌ 重试3次失败,请手动处理', 'red'); isAutoNextRunning = false; retryCount = 0; } } // 4. 穿透 iframe 找视频 function findVideo() { if (targetVideo && !targetVideo.paused) return targetVideo; let video = document.querySelector('video'); if (video) return video; const iframes = document.querySelectorAll('iframe'); for (let iframe of iframes) { try { const doc = iframe.contentDocument || iframe.contentWindow.document; video = doc.querySelector('video'); if (video) return video; const innerIframes = doc.querySelectorAll('iframe'); for (let inner of innerIframes) { const innerDoc = inner.contentDocument || inner.contentWindow.document; video = innerDoc.querySelector('video'); if (video) return video; } } catch (e) {} } return null; } // 5. 倍速隐身劫持 function hijackVideo(video) { if (!video || video.dataset.xdHijacked) return; const win = video.ownerDocument.defaultView; const nativeDescriptor = Object.getOwnPropertyDescriptor(win.HTMLMediaElement.prototype, 'playbackRate'); if (!nativeDescriptor) return; let internalRate = currentRate; try { Object.defineProperty(video, 'playbackRate', { configurable: true, get: function() { return 1.0; }, // 永远告诉平台是1.0 set: function(val) { if (val === 1.0) { nativeDescriptor.set.call(video, internalRate); } else { internalRate = val; nativeDescriptor.set.call(video, val); } } }); video.addEventListener('ratechange', (e) => e.stopImmediatePropagation(), true); video.dataset.xdHijacked = 'true'; nativeDescriptor.set.call(video, currentRate); } catch (e) {} } function applySettings() { if (!targetVideo) return; targetVideo.muted = isMuted; targetVideo.volume = isMuted ? 0 : 1; try { targetVideo.playbackRate = currentRate; } catch(e) {} } // 6. 绑定视频事件 function bindVideoEvents(video) { if (video.dataset.xdBound) return; video.dataset.xdBound = 'true'; const statusEl = document.getElementById('xd-status'); if (statusEl) { statusEl.innerText = '已连接(隐身) ✅'; statusEl.style.color = 'green'; } hijackVideo(video); applySettings(); // 视频结束事件 video.addEventListener('ended', () => { console.log('【网课助手】当前视频播放结束!'); if (autoNext && !isAutoNextRunning) { startAutoNextWithRetry(); } else { updateLog('播放结束,等待手动操作', 'orange'); } }); // 防切屏暂停机制 video.addEventListener('pause', () => { if (!video.ended) { setTimeout(() => { video.play().catch(e => {}); }, 1000); } }); video.play().catch(() => { console.log('【网课助手】自动播放被阻止,请手动点击播放'); }); } // ========================================== // 7. 新增:主动防挂机模块 // ========================================== function setupAntiAFK() { // 1. 尝试拦截平台的鼠标移出/失焦事件 const blockEvent = (e) => e.stopImmediatePropagation(); ['blur', 'visibilitychange', 'mouseout', 'mouseleave'].forEach(eventType => { window.addEventListener(eventType, blockEvent, true); }); // 2. 定时模拟鼠标微动 setInterval(() => { // 只有在视频播放时才模拟(避免后台无限发事件) if (targetVideo && !targetVideo.paused && !targetVideo.ended) { const moveEvent = new MouseEvent('mousemove', { view: window, bubbles: true, cancelable: true, clientX: window.innerWidth / 2 + (Math.random() * 20 - 10), clientY: window.innerHeight / 2 + (Math.random() * 20 - 10) }); document.dispatchEvent(moveEvent); } }, 30000); // 每30秒模拟一次 } // 8. 主循环检测 function loop() { createUI(); const video = findVideo(); if (video && video !== targetVideo) { targetVideo = video; bindVideoEvents(targetVideo); // 如果切到了新视频,重置自动连播状态 if (isAutoNextRunning) { isAutoNextRunning = false; retryCount = 0; } } else if (targetVideo) { if (targetVideo.muted !== isMuted) targetVideo.muted = isMuted; } setTimeout(loop, 2000); } window.addEventListener('load', () => { loop(); setupAntiAFK(); // 启动防挂机 }); })();