// ==UserScript== // @name 学习通阅读助手 // @version 2.0.0 // @description 超星学习通阅读助手,专注处理学习通阅读任务,支持逐段 / 整页 ,键盘控制(K 开始 / Z 暂停 / S 设置) // @author yyy. // @match *://*.chaoxing.com/* // @match *://mooc1-*.chaoxing.com/* // @grant GM_getValue // @grant GM_setValue // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 全局状态管理(跨页面保持) window.readingAssistantGlobalState = window.readingAssistantGlobalState || { isRunning: false, isPaused: false, scrollPosition: 0, manuallyPaused: false // 新增:标记是否为手动暂停 }; // 配置 const CONFIG = { scrollSpeed: parseFloat(GM_getValue('scrollSpeed', 1.0)), scrollMode: GM_getValue('scrollMode', 'pixel'), // paragraph, page, pixel scrollPixel: parseInt(GM_getValue('scrollPixel', 300)), autoStart: GM_getValue('autoStart', true), showTips: GM_getValue('showTips', true), highlightMode: GM_getValue('highlightMode', false), loopMode: GM_getValue('loopMode', true) // 循环阅读模式 }; // 状态 const STATE = { get isRunning() { return window.readingAssistantGlobalState.isRunning; }, set isRunning(value) { window.readingAssistantGlobalState.isRunning = value; }, get isPaused() { return window.readingAssistantGlobalState.isPaused; }, set isPaused(value) { window.readingAssistantGlobalState.isPaused = value; }, get currentScrollTop() { return window.readingAssistantGlobalState.scrollPosition; }, set currentScrollTop(value) { window.readingAssistantGlobalState.scrollPosition = value; }, get manuallyPaused() { return window.readingAssistantGlobalState.manuallyPaused; }, set manuallyPaused(value) { window.readingAssistantGlobalState.manuallyPaused = value; }, contentElements: [], currentIndex: 0, scrollTimer: null }; // 日志 function log(msg) { console.log(`[学习通阅读助手] ${msg}`); } // 页面检测 function isReadingTaskPage() { return location.href.includes('/mooc-ans/course/') && location.href.includes('.html'); } function isReadingPage() { return location.href.includes('/ztnodedetailcontroller/visitnodedetail'); } // 自动跳转到阅读页面 function autoJumpToReading() { if (!isReadingTaskPage()) return; log('检测到阅读任务目录页面,准备跳转'); setTimeout(() => { // 查找第一个阅读章节链接 const readingLink = document.querySelector('a[href*="/ztnodedetailcontroller/visitnodedetail"]'); if (readingLink) { log('找到阅读章节,正在跳转'); readingLink.click(); } else { log('未找到阅读章节链接'); } }, 1000); } // 收集内容元素 function collectContent() { const selectors = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'video']; STATE.contentElements = []; selectors.forEach(selector => { document.querySelectorAll(selector).forEach(el => { if (el.offsetHeight > 20 && el.offsetWidth > 20) { STATE.contentElements.push(el); } }); }); // 按位置排序 STATE.contentElements.sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top ); log(`找到 ${STATE.contentElements.length} 个内容元素`); } // 清除高亮 function clearHighlight() { document.querySelectorAll('.reading-highlight').forEach(el => { el.classList.remove('reading-highlight'); el.style.outline = ''; }); } // 段落阅读 function scrollToNext() { if (STATE.isPaused || STATE.currentIndex >= STATE.contentElements.length) { completeReading(); return; } const element = STATE.contentElements[STATE.currentIndex]; // 高亮当前阅读内容 if (CONFIG.highlightMode) { clearHighlight(); element.classList.add('reading-highlight'); element.style.outline = '4px solid #00FF00'; element.style.transition = 'outline 0.3s ease'; } element.scrollIntoView({ behavior: 'smooth', block: 'center' }); STATE.currentIndex++; STATE.scrollTimer = setTimeout(scrollToNext, CONFIG.scrollSpeed * 1000); } // 整页阅读 function pageScroll() { const totalHeight = document.documentElement.scrollHeight - window.innerHeight; const scrollStep = totalHeight / (CONFIG.scrollSpeed * 10); // 如果已经在运行整页滚动,继续使用当前位置 if (!STATE.currentScrollTop) { STATE.currentScrollTop = 0; } const scroll = () => { if (STATE.isPaused) return; STATE.currentScrollTop += scrollStep; if (STATE.currentScrollTop >= totalHeight) { completeReading(); } else { window.scrollTo({ top: STATE.currentScrollTop, behavior: 'smooth' }); STATE.scrollTimer = setTimeout(scroll, 100); } }; scroll(); } // 像素滚动 function pixelScroll() { const totalHeight = document.documentElement.scrollHeight - window.innerHeight; // 如果已经在运行像素滚动,继续使用当前位置 if (!STATE.currentScrollTop) { STATE.currentScrollTop = window.pageYOffset || document.documentElement.scrollTop; } const scroll = () => { if (STATE.isPaused) return; STATE.currentScrollTop += CONFIG.scrollPixel; if (STATE.currentScrollTop >= totalHeight) { completeReading(); } else { window.scrollTo({ top: STATE.currentScrollTop, behavior: 'smooth' }); STATE.scrollTimer = setTimeout(scroll, CONFIG.scrollSpeed * 1000); } }; scroll(); } // 完成阅读 function completeReading() { STATE.isRunning = false; STATE.isPaused = false; clearHighlight(); clearTimeout(STATE.scrollTimer); log('阅读完成'); // 查找下一章按钮 setTimeout(() => { const nextBtn = document.querySelector('.nodeItem.r i') || document.querySelector('a[title="下一章"]') || document.querySelector('.next_btn') || document.querySelector('.nextBtn') || Array.from(document.querySelectorAll('*')).find(el => el.textContent && (el.textContent.includes('下一章') || el.textContent.includes('下一节')) ); if (nextBtn) { log('找到下一章按钮,正在跳转'); nextBtn.click(); } else if (CONFIG.loopMode) { // 循环模式:没有下一章时跳转到第一章 log('未找到下一章按钮,循环模式开启,准备跳转到第一章'); setTimeout(() => { jumpToFirstChapter(); }, 1000); } else { log('未找到下一章按钮,阅读结束'); } }, 2000); } // 跳转到第一章 function jumpToFirstChapter() { log('开始寻找第一章...'); // 方法1: 尝试在当前页面查找第一章链接 const firstChapterSelectors = [ '.posCatalog_select:first-child a', '.posCatalog_name:first-child a', '.catalog_points_yi:first-child a', '.catalog_title:first-child a', '.nodeItem:first-child a', '.catalogDetail:first-child a', '.catalog_sectionLevel1:first-child a', 'a[href*="/ztnodedetailcontroller/visitnodedetail"]' ]; let firstChapterLink = null; for (const selector of firstChapterSelectors) { const links = document.querySelectorAll(selector); if (links.length > 0) { firstChapterLink = links[0]; log(`通过选择器 ${selector} 找到第一章链接`); break; } } if (firstChapterLink) { log('找到第一章链接,正在跳转...'); firstChapterLink.click(); return; } // 方法2: 尝试通过URL模式跳转 log('未在当前页面找到第一章,尝试URL跳转...'); const currentUrl = location.href; // 提取课程ID和其他参数 const courseMatch = currentUrl.match(/courseId=(\d+)/); const knowledgeMatch = currentUrl.match(/knowledgeId=(\d+)/); const clazzidMatch = currentUrl.match(/clazzid=(\d+)/); if (courseMatch) { const courseId = courseMatch[1]; log(`提取到课程ID: ${courseId}`); // 构造课程主页URL const baseUrl = currentUrl.split('/ztnodedetailcontroller')[0]; const courseMainUrl = `${baseUrl}/course?courseId=${courseId}`; log(`跳转到课程主页: ${courseMainUrl}`); location.href = courseMainUrl; return; } // 方法3: 返回上级页面 log('尝试返回上级页面...'); if (window.history.length > 1) { window.history.back(); } else { // 最后的尝试:解析URL返回课程根目录 const urlParts = currentUrl.split('/'); if (urlParts.length > 3) { const rootUrl = urlParts.slice(0, 4).join('/'); log(`跳转到根目录: ${rootUrl}`); location.href = rootUrl; } } } // 开始阅读 function startReading() { if (STATE.isRunning) return; STATE.isRunning = true; STATE.isPaused = false; STATE.currentIndex = 0; log(`开始${CONFIG.scrollMode}阅读`); switch(CONFIG.scrollMode) { case 'paragraph': collectContent(); if (STATE.contentElements.length > 0) { scrollToNext(); } else { log('未找到段落内容,切换整页模式'); pageScroll(); } break; case 'page': pageScroll(); break; case 'pixel': pixelScroll(); break; } } // 暂停阅读 function pauseReading() { if (STATE.isRunning) { STATE.isPaused = true; STATE.manuallyPaused = true; // 标记为手动暂停 GM_setValue('manuallyPaused', true); // 持久化保存 clearTimeout(STATE.scrollTimer); log('阅读已暂停'); } } // 继续阅读 function resumeReading() { if (!STATE.isRunning) { STATE.manuallyPaused = false; // 清除手动暂停标记 GM_setValue('manuallyPaused', false); // 持久化保存 startReading(); } else { STATE.isPaused = false; STATE.manuallyPaused = false; // 清除手动暂停标记 GM_setValue('manuallyPaused', false); // 持久化保存 log('继续阅读'); switch(CONFIG.scrollMode) { case 'paragraph': scrollToNext(); break; case 'page': pageScroll(); break; case 'pixel': pixelScroll(); break; } } } // 停止阅读 function stopReading() { STATE.isRunning = false; STATE.isPaused = false; STATE.manuallyPaused = false; // 清除手动暂停标记 clearTimeout(STATE.scrollTimer); clearHighlight(); log('阅读已停止'); } // 显示设置 function showSettings() { // 移除已存在的设置面板 const existingModal = document.getElementById('reading-settings-modal'); if (existingModal) { existingModal.remove(); return; } const modal = document.createElement('div'); modal.id = 'reading-settings-modal'; modal.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 9999; font-family: Arial, sans-serif; min-width: 320px; border: 1px solid #ddd; `; modal.innerHTML = `

阅读器设置

生活不易,猪猪叹气 —— 赏口饲料,让我少气!🐷✨

⚠️ 请保持阅读页面打开,否则无法正确计时
`; document.body.appendChild(modal); // 模式切换事件 document.getElementById('mode').onchange = function() { const pixelSetting = document.getElementById('pixelSetting'); pixelSetting.style.display = this.value === 'pixel' ? 'block' : 'none'; }; document.getElementById('save').onclick = () => { CONFIG.scrollSpeed = parseFloat(document.getElementById('speed').value); CONFIG.scrollMode = document.getElementById('mode').value; CONFIG.scrollPixel = parseInt(document.getElementById('pixel').value); CONFIG.autoStart = document.getElementById('autoStart').checked; CONFIG.showTips = document.getElementById('showTips').checked; CONFIG.highlightMode = document.getElementById('highlightMode').checked; CONFIG.loopMode = document.getElementById('loopMode').checked; GM_setValue('scrollSpeed', CONFIG.scrollSpeed); GM_setValue('scrollMode', CONFIG.scrollMode); GM_setValue('scrollPixel', CONFIG.scrollPixel); GM_setValue('autoStart', CONFIG.autoStart); GM_setValue('showTips', CONFIG.showTips); GM_setValue('highlightMode', CONFIG.highlightMode); GM_setValue('loopMode', CONFIG.loopMode); modal.remove(); log('设置已保存'); // 更新提示显示 if (isReadingPage()) { const oldTips = document.getElementById('reading-tips'); if (oldTips) oldTips.remove(); showTips(); } }; document.getElementById('close').onclick = () => { modal.remove(); }; } // 显示快捷键提示 function showTips() { if (!CONFIG.showTips || !isReadingPage()) return; // 移除已存在的提示 const existingTips = document.getElementById('reading-tips'); if (existingTips) existingTips.remove(); const tips = document.createElement('div'); tips.id = 'reading-tips'; tips.style.cssText = ` position: fixed; bottom: 10px; right: 10px; background: rgba(0,0,0,0.7); color: white; padding: 10px; border-radius: 6px; font-size: 12px; z-index: 9998; font-family: Arial, sans-serif; `; tips.innerHTML = `K: 开始 | Z: 暂停 | S: 设置`; document.body.appendChild(tips); } // 键盘事件 function bindKeys() { // 避免重复绑定 if (window.readingAssistantKeysbound) return; document.addEventListener('keydown', (e) => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; switch(e.key.toLowerCase()) { case 'k': e.preventDefault(); resumeReading(); break; case 'z': e.preventDefault(); pauseReading(); break; case 's': e.preventDefault(); showSettings(); break; } }); window.readingAssistantKeysbound = true; } // 初始化 function init() { // 键盘事件只绑定一次 bindKeys(); if (isReadingTaskPage()) { autoJumpToReading(); } else if (isReadingPage()) { // 对于像素滚动模式,检查是否需要重新初始化 if (CONFIG.scrollMode === 'pixel' && window.readingAssistantInitialized) { // 像素滚动模式下,只显示提示,不重新初始化其他内容 showTips(); return; } showTips(); // 检查是否手动暂停过 const wasManuallyPaused = GM_getValue('manuallyPaused', false); if (CONFIG.autoStart && !wasManuallyPaused) { setTimeout(startReading, 2000); } else if (wasManuallyPaused) { log('检测到手动暂停状态,不自动开始阅读'); } // 标记已初始化 window.readingAssistantInitialized = true; } } // 启动 init(); log('学习通阅读助手已加载'); })();