// ==UserScript==
// @name 超星学习通自动阅读
// @namespace http://tampermonkey.net/
// @version 1.1.0
// @description 支持后台运行、最小化、无限时长、记忆配置(反馈群:612441267)
// @author lakay666
// @match https://readsvr-fanya.sslibrary.com/n/readsvr/book/mooc/*
// @run-at document-end
// @grant none
// ==/UserScript==
(function() {
'use strict';
// ==================== 加载保存的设置 ====================
const STORAGE_KEY = 'mooc_scroll_config';
function loadConfig() {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
try { return JSON.parse(saved); } catch(e) {}
}
return {
speed: 100, // 滚动速度 px/s
minutes: 10, // 时长(分钟),0=无限
pauseBottom: 3, // 到底暂停秒
pauseTop: 2 // 到顶暂停秒
};
}
function saveConfig(config) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
}
let config = loadConfig();
// ==========================================================
console.log('[自动滚动] 脚本已加载,配置:', config);
function createPanel(container) {
if (document.getElementById('scroll-panel')) return;
const div = document.createElement('div');
div.id = 'scroll-panel';
div.innerHTML = `
📜 循环滚动
0次
就绪
后台运行 ✅ | 记忆配置 ✅ | Alt+Q/W/E
`;
document.body.appendChild(div);
// 面板事件
const settingsArea = document.getElementById('settings-area');
document.getElementById('btn-settings-toggle').addEventListener('click', () => {
settingsArea.style.display = settingsArea.style.display === 'none' ? 'block' : 'none';
});
document.getElementById('input-speed').addEventListener('input', function() {
config.speed = parseInt(this.value);
document.getElementById('speed-label').textContent = config.speed;
saveConfig(config);
});
document.getElementById('input-time').addEventListener('change', function() {
config.minutes = Math.max(0, parseInt(this.value) || 0);
this.value = config.minutes;
saveConfig(config);
});
document.getElementById('input-bottom').addEventListener('input', function() {
config.pauseBottom = parseFloat(this.value);
document.getElementById('bottom-label').textContent = config.pauseBottom;
saveConfig(config);
});
document.getElementById('input-top').addEventListener('input', function() {
config.pauseTop = parseFloat(this.value);
document.getElementById('top-label').textContent = config.pauseTop;
saveConfig(config);
});
return {};
}
function waitAndStart() {
const checkInterval = setInterval(() => {
let container = document.querySelector('#Readweb');
if (!container) {
const all = [...document.querySelectorAll('*')];
let best = null, maxH = 0;
all.forEach(el => {
const style = getComputedStyle(el);
if (style.overflow === 'auto' || style.overflow === 'scroll' ||
style.overflowY === 'auto' || style.overflowY === 'scroll') {
const h = el.scrollHeight - el.clientHeight;
if (h > maxH && h > 100) {
maxH = h;
best = el;
}
}
});
container = best;
}
if (container) {
clearInterval(checkInterval);
console.log('[自动滚动] 容器:', container.id || container.className || container.tagName);
console.log('[自动滚动] scrollHeight:', container.scrollHeight, 'clientHeight:', container.clientHeight);
createPanel(container);
startScrolling(container);
}
}, 500);
}
function startScrolling(container) {
const statusEl = document.getElementById('scroll-status');
const loopCountEl = document.getElementById('loop-count');
let isRunning = false;
let isPaused = false;
let elapsedSeconds = 0;
let direction = 'down';
let scrollAccumulator = 0;
let intervalId = null;
let pauseTimer = null;
let loopCount = 0;
const TICK_MS = 50; // 每50ms更新一次(后台也能跑)
function updateStatus(text) {
if (statusEl) statusEl.textContent = text;
}
function updateLoopCount() {
if (loopCountEl) loopCountEl.textContent = loopCount + '次';
}
function getMaxScroll() {
return container.scrollHeight - container.clientHeight;
}
function tick() {
if (!isRunning || isPaused) return;
elapsedSeconds += TICK_MS / 1000;
const totalSeconds = config.minutes > 0 ? config.minutes * 60 : Infinity;
if (elapsedSeconds >= totalSeconds) {
stop();
updateStatus(`✅ 已完成 | ${loopCount}次循环`);
return;
}
const deltaPx = config.speed * (TICK_MS / 1000);
scrollAccumulator += deltaPx;
if (scrollAccumulator >= 10) {
const amount = scrollAccumulator;
scrollAccumulator = 0;
const current = container.scrollTop;
const maxScroll = getMaxScroll();
if (direction === 'down') {
const newTop = current + amount;
if (newTop >= maxScroll - 1) {
container.scrollTop = maxScroll;
loopCount++;
updateLoopCount();
updateStatus(`📄 到底 | 🔄${loopCount}次 | 暂停中...`);
if (pauseTimer) clearTimeout(pauseTimer);
pauseTimer = setTimeout(() => {
direction = 'up';
scrollAccumulator = 0;
}, config.pauseBottom * 1000);
return;
}
container.scrollTop = newTop;
} else {
const newTop = current - amount;
if (newTop <= 1) {
container.scrollTop = 0;
updateStatus(`🔝 到顶 | 🔄${loopCount}次 | 暂停中...`);
if (pauseTimer) clearTimeout(pauseTimer);
pauseTimer = setTimeout(() => {
direction = 'down';
scrollAccumulator = 0;
}, config.pauseTop * 1000);
return;
}
container.scrollTop = newTop;
}
const remaining = totalSeconds === Infinity ? '∞' : Math.round(totalSeconds - elapsedSeconds);
const pct = totalSeconds === Infinity ? '∞' : ((elapsedSeconds / totalSeconds) * 100).toFixed(1);
const max = Math.round(getMaxScroll());
const cur = Math.round(container.scrollTop);
updateStatus(`${pct}% | ⏱${remaining}s | ${direction==='down'?'⬇':'⬆'} ${cur}/${max}`);
}
}
function start() {
if (isRunning && !isPaused) return;
if (isPaused) {
isPaused = false;
updateStatus('▶ 继续滚动');
return;
}
isRunning = true;
isPaused = false;
elapsedSeconds = 0;
direction = 'down';
scrollAccumulator = 0;
loopCount = 0;
updateLoopCount();
if (intervalId) clearInterval(intervalId);
intervalId = setInterval(tick, TICK_MS);
updateStatus('🚀 滚动中...');
console.log('[自动滚动] 速度:', config.speed, 'px/s | 时长:', config.minutes > 0 ? config.minutes+'min' : '无限');
}
function pause() {
isPaused = true;
updateStatus('⏸ 已暂停');
}
function stop() {
isRunning = false;
isPaused = false;
if (intervalId) clearInterval(intervalId);
if (pauseTimer) clearTimeout(pauseTimer);
updateStatus('⏹ 已停止');
console.log('[自动滚动] 停止 | 运行:', Math.round(elapsedSeconds), '秒 | 循环:', loopCount, '次');
}
document.getElementById('btn-start').addEventListener('click', start);
document.getElementById('btn-pause').addEventListener('click', pause);
document.getElementById('btn-stop').addEventListener('click', stop);
document.addEventListener('keydown', (e) => {
if (e.altKey && e.key === 'q') { e.preventDefault(); start(); }
if (e.altKey && e.key === 'w') { e.preventDefault(); pause(); }
if (e.altKey && e.key === 'e') { e.preventDefault(); stop(); }
});
setTimeout(start, 2000);
}
waitAndStart();
})();