// ==UserScript== // @name 上海开放大学视频智能学习助手 // @namespace http://tampermonkey.net/ // @version 1.1.1 // @description 上海开放大学学习平台专用,智能自动播放、进度记忆、防掉线、学习统计,q反馈群:612441267 // @author lakay666 // @match *://*.shtvu.edu.cn/* // @match *://elearning.shtvu.edu.cn/* // @match *://study.shtvu.edu.cn/* // @match *://course.shtvu.edu.cn/* // @match *://mooc.shtvu.edu.cn/* // @match *://learning.shou.org.cn/* // @match *://*.shou.org.cn/* // @icon https://www.google.com/s2/favicons?sz=64&domain=shtvu.edu.cn // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @grant unsafeWindow // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; // ==================== 配置管理 ==================== const CONFIG = { autoPlay: GM_getValue('autoPlay', true), autoMute: GM_getValue('autoMute', true), autoNext: GM_getValue('autoNext', true), showContact: GM_getValue('showContact', true), customSpeed: parseFloat(GM_getValue('customSpeed', 1.0)), skipCompleted: GM_getValue('skipCompleted', true), disableMonitor: GM_getValue('disableMonitor', false), rememberProgress: GM_getValue('rememberProgress', true), // 记忆播放进度 keyboardShortcuts: GM_getValue('keyboardShortcuts', true), // 键盘快捷键 antiOffline: GM_getValue('antiOffline', true), // 防掉线检测 studyStats: GM_getValue('studyStats', true), // 学习统计 smartSpeed: GM_getValue('smartSpeed', false), // 智能变速 focusMode: GM_getValue('focusMode', false), // 专注模式 }; // 学习统计数据 let studyData = { todayWatchTime: parseInt(GM_getValue('todayWatchTime', 0)), totalWatchTime: parseInt(GM_getValue('totalWatchTime', 0)), videosCompleted: parseInt(GM_getValue('videosCompleted', 0)), lastStudyDate: GM_getValue('lastStudyDate', new Date().toDateString()), currentSessionTime: 0, sessionStart: Date.now() }; // 重置每日统计 if (studyData.lastStudyDate !== new Date().toDateString()) { studyData.todayWatchTime = 0; studyData.lastStudyDate = new Date().toDateString(); saveStudyData(); } function saveConfig() { try { Object.keys(CONFIG).forEach(key => { GM_setValue(key, CONFIG[key]); }); } catch (e) { console.error('[白白助手] 保存配置失败:', e); } } function saveStudyData() { try { GM_setValue('todayWatchTime', studyData.todayWatchTime); GM_setValue('totalWatchTime', studyData.totalWatchTime); GM_setValue('videosCompleted', studyData.videosCompleted); GM_setValue('lastStudyDate', studyData.lastStudyDate); } catch (e) { console.error('[白白助手] 保存学习数据失败:', e); } } // ==================== 工具函数 ==================== const $ = (selector, context = document) => context.querySelector(selector); const $$ = (selector, context = document) => Array.from(context.querySelectorAll(selector)); function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // 格式化时间 function formatTime(seconds) { const hrs = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hrs > 0) return `${hrs}小时${mins}分`; return `${mins}分${secs}秒`; } // ==================== 通知系统 ==================== function showNotification(message, type = 'info', duration = 3000) { try { if (!document.body) return; const notification = document.createElement('div'); notification.className = `baibai-notification notification-${type}`; const icons = { success: '✓', error: '✗', info: 'ℹ', warning: '⚠' }; notification.innerHTML = ` ${message} `; document.body.appendChild(notification); requestAnimationFrame(() => { notification.classList.add('show'); }); setTimeout(() => { notification.classList.add('fade-out'); setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, duration); } catch (e) { console.error('[白白助手] 显示通知失败:', e); } } // ==================== 视频进度记忆 ==================== const ProgressMemory = { getKey(video) { const courseId = this.getCourseId(); const videoSrc = video.src || video.currentSrc || 'unknown'; return `progress_${courseId}_${videoSrc}`; }, getCourseId() { const match = location.href.match(/course[_-]?id[=\/](\d+)/i) || location.href.match(/[?&]id=(\d+)/); if (match) return match[1]; const courseTitle = $('.course-title, .lesson-title, h1'); if (courseTitle) return courseTitle.textContent.trim().slice(0, 50); return location.pathname; }, save(video) { if (!CONFIG.rememberProgress || !video.duration) return; try { const key = this.getKey(video); const data = { currentTime: video.currentTime, duration: video.duration, timestamp: Date.now(), url: location.href }; GM_setValue(key, JSON.stringify(data)); } catch (e) { console.error('[白白助手] 保存进度失败:', e); } }, load(video) { if (!CONFIG.rememberProgress) return null; try { const key = this.getKey(video); const data = GM_getValue(key); if (!data) return null; const parsed = JSON.parse(data); if (Date.now() - parsed.timestamp > 7 * 24 * 60 * 60 * 1000) { GM_deleteValue(key); return null; } if (parsed.url && parsed.url.split('?')[0] !== location.href.split('?')[0]) { return null; } return parsed; } catch (e) { console.error('[白白助手] 读取进度失败:', e); return null; } }, clear(video) { try { const key = this.getKey(video); GM_deleteValue(key); } catch (e) { console.error('[白白助手] 清除进度失败:', e); } } }; // ==================== 核心视频处理 ==================== const VideoHandler = { processedVideos: new WeakSet(), init() { this.observeVideos(); this.startProgressTracking(); }, observeVideos() { const observer = new MutationObserver(() => { const videos = $$('video'); videos.forEach(video => this.processVideo(video)); }); if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); } $$('video').forEach(video => this.processVideo(video)); }, processVideo(video) { if (this.processedVideos.has(video)) return; this.processedVideos.add(video); if (video.readyState < 1) { video.addEventListener('loadedmetadata', () => this.setupVideo(video), { once: true }); } else { this.setupVideo(video); } }, setupVideo(video) { try { if (CONFIG.rememberProgress) { const progress = ProgressMemory.load(video); if (progress && progress.currentTime > 10 && progress.currentTime < progress.duration - 10) { video.currentTime = progress.currentTime; showNotification(`已恢复上次进度: ${formatTime(Math.floor(progress.currentTime))}`, 'info', 2000); } } if (CONFIG.autoMute) { video.muted = true; } this.applySpeed(video); if (CONFIG.autoPlay && video.paused) { this.attemptPlay(video); } this.attachEventListeners(video); } catch (e) { console.error('[白白助手] 设置视频失败:', e); } }, attemptPlay(video) { const playPromise = video.play(); if (playPromise !== undefined) { playPromise.catch(err => { console.log('[白白助手] 自动播放被阻止,尝试交互模拟:', err); this.simulateInteraction(video); }); } }, simulateInteraction(video) { const events = ['mousedown', 'mouseup', 'click']; events.forEach((type, index) => { setTimeout(() => { const event = new MouseEvent(type, { bubbles: true, cancelable: true, view: unsafeWindow }); video.dispatchEvent(event); }, index * 50); }); setTimeout(() => { video.play().catch(() => { showNotification('请点击页面任意位置后,视频将自动播放', 'warning'); }); }, 200); }, attachEventListeners(video) { video.addEventListener('timeupdate', throttle(() => { if (video.currentTime > 0 && video.currentTime % 5 < 0.5) { ProgressMemory.save(video); this.updateStudyTime(); } }, 1000)); video.addEventListener('ended', () => { ProgressMemory.clear(video); studyData.videosCompleted++; saveStudyData(); showNotification('🎉 视频播放完成!', 'success'); if (CONFIG.autoNext) { setTimeout(() => Navigation.next(), 2000); } }); video.addEventListener('error', () => { showNotification('视频加载出错,尝试刷新...', 'error'); setTimeout(() => location.reload(), 3000); }); if (CONFIG.disableMonitor) { video.addEventListener('pause', () => { if (CONFIG.autoPlay && video.currentTime > 0 && video.currentTime < video.duration - 1) { setTimeout(() => video.play().catch(() => {}), 1000); } }); } }, applySpeed(video) { if (!video) return; try { if (CONFIG.smartSpeed && video.duration) { if (video.duration < 300) video.playbackRate = 1.5; else if (video.duration > 1800) video.playbackRate = 1.25; else video.playbackRate = CONFIG.customSpeed; } else { video.playbackRate = CONFIG.customSpeed; } } catch (e) { console.error('[白白助手] 设置速度失败:', e); } }, updateStudyTime() { const now = Date.now(); const delta = Math.floor((now - studyData.sessionStart) / 1000); if (delta > 0) { studyData.currentSessionTime += delta; studyData.todayWatchTime += delta; studyData.totalWatchTime += delta; studyData.sessionStart = now; saveStudyData(); } }, startProgressTracking() { setInterval(() => this.updateStudyTime(), 30000); }, getAllVideos() { return $$('video'); } }; // ==================== 导航控制 ==================== const Navigation = { next() { const selectors = [ '.next-btn', '.next-button', '.btn-next', '.next-step', '.next-chapter', '.next-lesson', '.shtvu-next', '[class*="next"][class*="btn"]', '[class*="next"][class*="button"]', '.task-next', '.lesson-next', '.course-next', '.finish-button', '.complete-button', '.continue-button' ]; for (const selector of selectors) { const btn = $(selector); if (btn && this.isVisible(btn)) { btn.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(() => { btn.click(); showNotification('已自动跳转到下一节', 'success'); }, 500); return true; } } return this.findNextUncompleted(); }, findNextUncompleted() { const itemSelectors = [ '.task-item', '.lesson-item', '.chapter-item', '.course-item', '.content-item', '.video-item', '.list-item', '.menu-item', '.nav-item' ]; for (const selector of itemSelectors) { const items = $$(selector); for (const item of items) { const isCompleted = item.querySelector('.completed, .finished, .success, .check, [class*="finish"], [class*="complete"]'); const isActive = item.classList.contains('active') || item.classList.contains('current'); if (!isCompleted && !isActive && this.isVisible(item)) { item.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(() => { item.click(); showNotification('已跳转到下一个未完成任务', 'success'); }, 500); return true; } } } showNotification('未找到可跳转的下一节内容', 'info'); return false; }, isVisible(el) { return el && el.offsetParent !== null && getComputedStyle(el).display !== 'none' && getComputedStyle(el).visibility !== 'hidden'; } }; // ==================== 防掉线系统 ==================== const AntiOffline = { interval: null, init() { if (!CONFIG.antiOffline) return; this.interval = setInterval(() => { this.simulateActivity(); }, 60000 + Math.random() * 30000); if (unsafeWindow.document) { Object.defineProperty(unsafeWindow.document, 'hidden', { get: () => false, configurable: true }); Object.defineProperty(unsafeWindow.document, 'visibilityState', { get: () => 'visible', configurable: true }); } }, simulateActivity() { try { const event = new MouseEvent('mousemove', { bubbles: true, cancelable: true, clientX: Math.random() * window.innerWidth, clientY: Math.random() * window.innerHeight }); document.dispatchEvent(event); const keyEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Shift', code: 'ShiftLeft' }); document.dispatchEvent(keyEvent); VideoHandler.getAllVideos().forEach(video => { if (video.paused && CONFIG.autoPlay) { VideoHandler.attemptPlay(video); } }); console.log('[白白助手] 防掉线心跳'); } catch (e) { console.error('[白白助手] 防掉线失败:', e); } }, stop() { if (this.interval) { clearInterval(this.interval); this.interval = null; } } }; // ==================== 专注模式 ==================== const FocusMode = { toggle() { CONFIG.focusMode = !CONFIG.focusMode; saveConfig(); this.apply(); }, apply() { const style = $('#baibai-focus-mode') || document.createElement('style'); style.id = 'baibai-focus-mode'; if (CONFIG.focusMode) { style.textContent = ` .sidebar, .nav-menu, .header, .footer, .chat-box, .recommend, .related-courses, .advertisement, .course-forum, .student-list, .notification-center { display: none !important; } .main-content, .video-container, .course-content { width: 100% !important; max-width: 100% !important; } `; showNotification('专注模式已开启', 'success'); } else { style.textContent = ''; } if (!style.parentNode) document.head.appendChild(style); } }; // ==================== 键盘快捷键 ==================== const Shortcuts = { init() { if (!CONFIG.keyboardShortcuts) return; document.addEventListener('keydown', (e) => { if (e.target.matches('input, textarea, [contenteditable]')) return; const video = $('video'); if (!video) return; switch(e.key.toLowerCase()) { case ' ': e.preventDefault(); video.paused ? video.play() : video.pause(); break; case 'arrowright': e.preventDefault(); video.currentTime += 10; showNotification(`快进 10秒`, 'info', 1000); break; case 'arrowleft': e.preventDefault(); video.currentTime -= 10; showNotification(`后退 10秒`, 'info', 1000); break; case 'arrowup': e.preventDefault(); video.volume = Math.min(1, video.volume + 0.1); showNotification(`音量: ${Math.round(video.volume * 100)}%`, 'info', 1000); break; case 'arrowdown': e.preventDefault(); video.volume = Math.max(0, video.volume - 0.1); showNotification(`音量: ${Math.round(video.volume * 100)}%`, 'info', 1000); break; case 'm': e.preventDefault(); video.muted = !video.muted; showNotification(video.muted ? '已静音' : '已取消静音', 'info', 1000); break; case 'n': e.preventDefault(); Navigation.next(); break; case 's': if (e.ctrlKey || e.metaKey) return; e.preventDefault(); CONFIG.customSpeed = CONFIG.customSpeed >= 2 ? 0.5 : CONFIG.customSpeed + 0.25; VideoHandler.applySpeed(video); showNotification(`播放速度: ${CONFIG.customSpeed}x`, 'info', 1000); break; } }); } }; // ==================== 控制面板 ==================== const ControlPanel = { create() { if ($('#baibai-auto-play-panel')) return; const panel = document.createElement('div'); panel.id = 'baibai-auto-play-panel'; panel.innerHTML = this.getHTML(); document.body.appendChild(panel); this.attachEvents(panel); this.updateStats(); }, getHTML() { const createToggle = (id, label, checked) => `
💬 制作人: lakay666