// ==UserScript== // @name 上开刷课 // @namespace http://tampermonkey.net/ // @version 2.0 // @description 支持进度跳转和自动跳过失效视频 // @author juejue // @match *://*.shou.org.cn/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addValueChangeListener // @grant GM_registerMenuCommand // @grant GM_setClipboard // @grant GM_notification // @require https://code.jquery.com/jquery-3.6.0.min.js // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/531136/%E4%B8%8A%E5%BC%80%E5%88%B7%E8%AF%BE.user.js // @updateURL https://update.greasyfork.icu/scripts/531136/%E4%B8%8A%E5%BC%80%E5%88%B7%E8%AF%BE.meta.js // ==/UserScript== const STATE = { KEY: 'VIDEO_CONTROL_STATE_v2', config: { currentIndex: GM_getValue('currentIndex', 0), totalLinks: 0, isAuto: false, playDuration: 10, // 默认播放时长(秒) failedVideos: GM_getValue('failedVideos', []) // 记录失效视频 } }; class StateManager { static update(config) { GM_setValue(STATE.KEY, config); STATE.config = config; } static sync() { const saved = GM_getValue(STATE.KEY, null); if (saved) STATE.config = saved; return STATE.config; } } function createControlPanel() { const panelHTML = `

视频控制中心

×
进度: ${STATE.config.currentIndex + 1}/${STATE.config.totalLinks} 失败: ${STATE.config.failedVideos.length}
播放时长:
等待操作...
`; document.querySelector('#vc-panel')?.remove(); document.body.insertAdjacentHTML('beforeend', panelHTML); // 事件绑定 document.getElementById('vc-play').addEventListener('click', playCurrentVideo); document.getElementById('vc-next').addEventListener('click', goToNextVideo); document.getElementById('vc-auto').addEventListener('click', toggleAutoMode); document.getElementById('vc-jump-btn').addEventListener('click', jumpToVideo); document.getElementById('vc-copy').addEventListener('click', copyProgress); document.getElementById('vc-close').addEventListener('click', () => { document.getElementById('vc-panel').style.display = 'none'; }); document.getElementById('vc-duration').addEventListener('change', (e) => { STATE.config.playDuration = parseInt(e.target.value); StateManager.update(STATE.config); }); } function refreshVideoLinks() { const links = [...document.querySelectorAll('li.cell_info1[data-type="RESOURCE"] > a[href]')]; STATE.config.totalLinks = links.length; StateManager.update(STATE.config); return links; } function playCurrentVideo() { const playBtn = document.querySelector('.dplayer-icon.dplayer-play-icon'); if (playBtn) { playBtn.click(); updateStatus(`正在播放 #${STATE.config.currentIndex + 1}`); return true; } else { handleFailedVideo(); return false; } } function handleFailedVideo() { const currentUrl = window.location.href; if (!STATE.config.failedVideos.includes(currentUrl)) { STATE.config.failedVideos.push(currentUrl); StateManager.update(STATE.config); document.getElementById('vc-failed').textContent = STATE.config.failedVideos.length; } updateStatus(`视频 #${STATE.config.currentIndex + 1} 无法播放,已记录`, true); } function goToNextVideo() { const links = refreshVideoLinks(); if (STATE.config.currentIndex >= links.length - 1) { updateStatus('已完成所有视频!'); GM_notification({ title: '视频跳转完成', text: `已完成 ${links.length} 个视频,其中 ${STATE.config.failedVideos.length} 个失败`, timeout: 5000 }); return; } STATE.config.currentIndex++; StateManager.update(STATE.config); updateProgress(); const nextLink = links[STATE.config.currentIndex]; updateStatus(`跳转到: ${nextLink.textContent.trim()}`); setTimeout(() => { window.location.href = nextLink.href; }, 500); } function jumpToVideo() { const input = document.getElementById('vc-jump-input'); const targetIndex = parseInt(input.value) - 1; const links = refreshVideoLinks(); if (isNaN(targetIndex) || targetIndex < 0 || targetIndex >= links.length) { updateStatus(`无效的序号: ${input.value}`, true); return; } STATE.config.currentIndex = targetIndex; StateManager.update(STATE.config); updateProgress(); const targetLink = links[targetIndex]; updateStatus(`跳转到: ${targetLink.textContent.trim()}`); setTimeout(() => { window.location.href = targetLink.href; }, 500); } function toggleAutoMode() { STATE.config.isAuto = !STATE.config.isAuto; StateManager.update(STATE.config); createControlPanel(); if (STATE.config.isAuto) { startAutoPlay(); } else { updateStatus('已停止自动模式'); } } function startAutoPlay() { if (!STATE.config.isAuto) return; // 尝试播放当前视频 const isPlaying = playCurrentVideo(); // 无论是否播放成功都继续流程 setTimeout(() => { if (STATE.config.isAuto) { goToNextVideo(); } }, (isPlaying ? STATE.config.playDuration : 3) * 1000); } function copyProgress() { const progress = `${STATE.config.currentIndex + 1}/${STATE.config.totalLinks}`; GM_setClipboard(progress); updateStatus(`已复制进度: ${progress}`); } function updateProgress() { document.getElementById('vc-progress').textContent = `${STATE.config.currentIndex + 1}/${STATE.config.totalLinks}`; document.getElementById('vc-jump-input').value = STATE.config.currentIndex + 1; document.querySelector('#vc-panel progress').value = STATE.config.currentIndex; } function updateStatus(message, isError = false) { const statusEl = document.getElementById('vc-status'); if (statusEl) { statusEl.textContent = message; statusEl.style.color = isError ? 'red' : '#666'; } } function init() { StateManager.sync(); refreshVideoLinks(); createControlPanel(); // 自动模式恢复 if (STATE.config.isAuto) { setTimeout(startAutoPlay, 1500); } // 右键菜单 GM_registerMenuCommand("重置进度", () => { STATE.config.currentIndex = 0; StateManager.update(STATE.config); updateProgress(); updateStatus('进度已重置'); }); GM_registerMenuCommand("清空失败记录", () => { STATE.config.failedVideos = []; StateManager.update(STATE.config); document.getElementById('vc-failed').textContent = '0'; updateStatus('已清空失败记录'); }); } // 启动脚本 if (document.readyState === 'complete') { init(); } else { window.addEventListener('load', init); } // 跨页面状态同步 GM_addValueChangeListener(STATE.KEY, (name, oldVal, newVal) => { if (newVal) { STATE.config = newVal; updateProgress(); } });