// ==UserScript== // @name 学习通阅读助手 // @version 2.0.3 // @description 超星学习通阅读助手,专注处理学习通阅读任务,支持逐段 / 整页 ,键盘控制(K 开始 / Z 暂停 / S 设置 / R 重置总时长),跨页面时长不重置 // @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, startTime: 0, chapterElapsed: 0, timerInterval: null }; // 防重复跳转锁 let hasAutoJump = 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}`); } // 格式化时间(秒 → 00:00:00) function formatTime(seconds) { const h = String(Math.floor(seconds / 3600)).padStart(2, '0'); const m = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0'); const s = String(seconds % 60).padStart(2, '0'); return `${h}:${m}:${s}`; } // 获取总阅读时长(持久化) function getTotalElapsed() { return GM_getValue('totalElapsedTime', 0); } // 保存总时长 function saveTotalElapsed(seconds) { GM_setValue('totalElapsedTime', seconds); } // 章节结束,累加总时长 function addChapterToTotal() { const current = window.readingAssistantGlobalState.chapterElapsed; const total = getTotalElapsed(); saveTotalElapsed(total + current); } // 启动计时(当前章) function startTimer() { if (window.readingAssistantGlobalState.timerInterval) return; window.readingAssistantGlobalState.startTime = Date.now() - window.readingAssistantGlobalState.chapterElapsed * 1000; window.readingAssistantGlobalState.timerInterval = setInterval(() => { window.readingAssistantGlobalState.chapterElapsed = Math.floor((Date.now() - window.readingAssistantGlobalState.startTime) / 1000); updateTips(); }, 1000); } // 停止计时 function stopTimer() { if (window.readingAssistantGlobalState.timerInterval) { clearInterval(window.readingAssistantGlobalState.timerInterval); window.readingAssistantGlobalState.timerInterval = null; } } // 重置当前章时长 function resetChapterTimer() { stopTimer(); window.readingAssistantGlobalState.chapterElapsed = 0; window.readingAssistantGlobalState.startTime = 0; } // 新增:一键重置总时长 function resetTotalTimer() { saveTotalElapsed(0); updateTips(); log('总阅读时长已重置'); // 按要求修改的提示文字 alert('总阅读时长已重置!yyy.提示您:不破不立,人也要在重置后重建。'); // 控制台输出作者主页链接(alert不支持超链接,用此方式补充) console.log('作者 yyy. 主页:https://scriptcat.org/zh-CN/users/162063'); } // 更新提示栏(总时长+当前章) function updateTips() { const tips = document.getElementById('reading-tips'); if (!tips) return; const total = getTotalElapsed(); const chapter = window.readingAssistantGlobalState.chapterElapsed; tips.innerHTML = `K: 开始 | Z: 暂停 | S: 设置 | R: 重置总时长:${formatTime(total)} | 本章:${formatTime(chapter)}`; } // 页面检测(兼容新旧目录页) function isReadingTaskPage() { return (location.href.includes('/mooc-ans/course/') && location.href.includes('.html')) //感谢用户:ᦸᐝSᴗhi꧂的反馈 || location.href.includes('/mooc-ans/zt/portal/'); } function isReadingPage() { return location.href.includes('/ztnodedetailcontroller/visitnodedetail'); } // 自动跳转到阅读页面 function autoJumpToReading() { if (hasAutoJump) return; if (!isReadingTaskPage()) return; log('检测到阅读任务目录页面,准备跳转'); const observer = new MutationObserver(() => { const readingLink = document.querySelector( 'a[href*="/ztnodedetailcontroller/visitnodedetail"],.catalog_detail a,.chapter_item a,.nodeItem a,.posCatalog_name a' ); //感谢用户:ᦸᐝSᴗhi꧂的反馈 if (readingLink) { log('找到阅读章节,正在跳转'); readingLink.click(); hasAutoJump = true; observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); } // 收集内容元素 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++; const randomSpeed = CONFIG.scrollSpeed * (0.9 + Math.random() * 0.3); STATE.scrollTimer = setTimeout(scrollToNext, randomSpeed * 1000); } // 整页阅读 function pageScroll() { const totalHeight = document.documentElement.scrollHeight - window.innerHeight - 50; 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 - 50; 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); addChapterToTotal(); resetChapterTimer(); 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('开始寻找第一章...'); 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; } log('尝试返回上级页面...'); if (window.history.length > 1) { window.history.back(); } else { const currentUrl = location.href; const urlParts = currentUrl.split('/'); if (urlParts.length > 3) { const rootUrl = urlParts.slice(0, 4).join('/'); log(`跳转到根目录: ${rootUrl}`); location.href = rootUrl; } } } // 开始阅读 function startReading() { if (STATE.scrollTimer) clearTimeout(STATE.scrollTimer); if (STATE.isRunning) return; startTimer(); 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) { stopTimer(); 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); startTimer(); startReading(); } else { STATE.isPaused = false; STATE.manuallyPaused = false; GM_setValue('manuallyPaused', false); startTimer(); 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(); resetChapterTimer(); 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 = `
小猫 or 小狗