// ==UserScript== // @name DGUT求是读书-全自动阅读助手 // @namespace http://tampermonkey.net/ // @version 3.2.1 // @license MIT // @description DGUT莞工求是读书计划自动阅读助手 — 获取优学院真实阅读时长、自动翻页/章节 // @author vanilla、DeepSeek // @match https://ua.dgut.edu.cn/learnCourse/learnCourse.html?* // @match https://*.ulearning.cn/* // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const PAGE_WIN = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; const HOST_RE = /^https:\/\/ua\.dgut\.edu\.cn\/learnCourse\/learnCourse\.html\?.*/i; const KEY = 'dgut_single_file_helper_config'; const RECORDS_KEY = 'dgut_reading_records'; const MSG = 'DGUT_SINGLE_FILE_READER_SYNC'; const SAVE_INTERVAL = 30; const D = { posX:20, posY:120, readerSec:30, readerAutoStart:true }; const MIN_BOOK_SEC = 4 * 3600 + 10; const NAV_SEC = 3; const cachedCourseId = new URL(location.href).searchParams.get('courseId') || ''; if (HOST_RE.test(location.href)) { if (window.__DGUT_SINGLE_FILE_INITED__) return; window.__DGUT_SINGLE_FILE_INITED__ = true; initHost(); return; } bootstrapReader(); // --- 书目标识 & 持久化 --- function getActiveSectionName() { const activePage = document.querySelector('.page-name.active'); if (!activePage) return ''; const sectionItem = activePage.closest('.section-item'); if (!sectionItem) return ''; const nameEl = sectionItem.querySelector('.section-name .text'); return nameEl ? trimName(nameEl.textContent) : ''; } function getBookKey() { const name = getActiveSectionName(); return name ? (cachedCourseId ? `${cachedCourseId}|${name}` : name) : (cachedCourseId || location.href); } function loadRecords() { return GM_getValue(RECORDS_KEY, {}); } function saveRecords(r) { GM_setValue(RECORDS_KEY, r); } function getBookTime(k) { return Math.max(0, parseInt(loadRecords()[k], 10) || 0); } function setBookTime(k, s) { const r = loadRecords(); r[k] = Math.max(0, Math.floor(s)); saveRecords(r); } // --- 常用工具 --- function readCfg() { const r = GM_getValue(KEY, D); if (!r || typeof r !== 'object') return { ...D }; const sec = parseInt(r.readerSec, 10); return { posX: parseInt(r.posX,10)||D.posX, posY: parseInt(r.posY,10)||D.posY, readerSec: sec > 0 ? sec : D.readerSec, readerAutoStart: r.readerAutoStart !== false }; } function getActivePageName() { const activePage = document.querySelector('.page-name.active'); if (!activePage) return ''; const textEl = activePage.querySelector('.text span') || activePage.querySelector('.text'); return textEl ? trimName(textEl.textContent) : ''; } function getBookDisplayName() { const section = getActiveSectionName(); const page = getActivePageName(); if (!section && !page) return '未识别'; return page ? section + ' - ' + page : section; } function koUnwrap(val) { return typeof val === 'function' ? val() : val; } function trimName(s) { return (s || '').replace(/\s+/g, ' ').trim(); } function getServerSideBookTimes() { const vm = PAGE_WIN.koLearnCourseViewModel; if (!vm) return null; const course = koUnwrap(vm.course); if (!course) return null; const chapters = koUnwrap(course.chapters); if (!chapters) return null; const result = {}; chapters.forEach(function(chapter) { const sections = koUnwrap(chapter.sections); if (!sections) return; sections.forEach(function(section) { let name = ''; try { name = trimName(koUnwrap(section.name)); } catch(e) {} if (!name) return; const key = cachedCourseId ? cachedCourseId + '|' + name : name; let total = 0; try { const secRec = koUnwrap(section.record); if (secRec && secRec.sectionStudyTime !== undefined) { total = koUnwrap(secRec.sectionStudyTime) || 0; } } catch(e) {} if (total === 0) { const pages = koUnwrap(section.pages); if (pages) { pages.forEach(function(page) { try { const record = koUnwrap(page.record); if (record) { total += (koUnwrap(record.studyTime) || 0) + (koUnwrap(record.lastStudyTime) || 0); } } catch(e) {} }); } } if (total > 0) result[key] = total; }); }); return result; } function syncServerTime(bookKey, accumulated, logFn) { const serverTimes = getServerSideBookTimes(); if (!serverTimes) return accumulated; const records = loadRecords(); let updated = false; Object.keys(serverTimes).forEach(function(key) { const serverSec = serverTimes[key]; const localSec = records[key] || 0; if (serverSec > localSec) { records[key] = serverSec; updated = true; if (logFn) logFn('服务端同步:' + key + ' ' + fmt(localSec) + ' → ' + fmt(serverSec)); } }); if (updated) saveRecords(records); if (bookKey && serverTimes[bookKey] && serverTimes[bookKey] > accumulated) { accumulated = serverTimes[bookKey]; } return accumulated; } function fmt(t) { t = Math.max(0, t); return `${String(Math.floor(t/3600)).padStart(2,'0')}:${String(Math.floor(t%3600/60)).padStart(2,'0')}:${String(t%60).padStart(2,'0')}`; } // --- 主页面(课程页) --- function initHost() { let bookKey = getBookKey(); let accumulated = getBookTime(bookKey); let sessionStart = Date.now(); let lastSave = accumulated; let timer = null; let drag = false, dx, dy; const cfg = readCfg(); let currentPageId = null; let pageStartTime = Date.now(); let cachedFlatList = []; let flatListDirty = true; let holdPageId = null; function getFlatList(vm) { if (flatListDirty) { cachedFlatList = buildFlatPageList(vm); flatListDirty = false; } return cachedFlatList; } function getCurrentPageIdFromVM(vm) { try { return pageId(vm.currentPage?.()); } catch(e) { return null; } } function pageId(page) { if (!page) return null; return typeof page.id === 'function' ? page.id() : page.id; } function isPageComplete(page) { try { const record = koUnwrap(page.record); return record ? !!koUnwrap(record.status) : false; } catch(e) { return false; } } function getMoveLabel(fromItem, toItem) { if (!fromItem || !toItem) return '切换'; if (pageId(fromItem.chapter) !== pageId(toItem.chapter)) return '切换下一章'; if (pageId(fromItem.section) !== pageId(toItem.section)) return '切换下一本书'; return '切换下一节'; } function formatMoveTarget(item) { if (!item) return '(未知)'; const chapterName = trimName(koUnwrap(item.chapter && item.chapter.name)); const sectionName = trimName(koUnwrap(item.section && item.section.name)); const pageName = trimName(koUnwrap(item.page && item.page.name)); if (chapterName && sectionName) return chapterName + ' / ' + sectionName + ' - ' + pageName; if (sectionName) return sectionName + ' - ' + pageName; return pageName || '(未知)'; } document.head.appendChild(Object.assign(document.createElement('style'), { textContent: '#dgut-single-helper-panel{background:rgba(28,28,30,.82);backdrop-filter:blur(28px);-webkit-backdrop-filter:blur(28px);color:#e5e5e7;padding:0;border-radius:16px;position:fixed;z-index:100000;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Helvetica Neue",Arial,sans-serif;min-width:230px;box-shadow:0 12px 40px rgba(0,0,0,.5),0 0 0 1px rgba(255,255,255,.08);user-select:none;overflow:hidden}#dgut-drag-handle{cursor:move;padding:9px 14px;background:rgba(255,255,255,.04);border-bottom:1px solid rgba(255,255,255,.07);display:flex;align-items:center;justify-content:space-between;gap:8px}#dgut-drag-handle h4{margin:0;background:linear-gradient(90deg,#007aff,#34c759);-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-size:12px;font-weight:800;letter-spacing:.5px;text-transform:uppercase}#dgut-collapse-btn{cursor:pointer;font-size:14px;font-weight:700;color:rgba(255,255,255,.4);width:20px;height:20px;display:flex;align-items:center;justify-content:center;border-radius:5px;transition:background .15s,color .15s}#dgut-collapse-btn:hover{background:rgba(255,255,255,.1);color:#fff}#dgut-single-helper-panel.collapsed{min-width:200px}#dgut-single-helper-panel.collapsed .dgut-panel-body>:not(#timer-display):not(#progress-bar-wrap):not(#btn-pause-wrap){display:none}#dgut-single-helper-panel.collapsed .dgut-panel-body{padding:12px}#dgut-single-helper-panel.collapsed #timer-display{font-size:28px;margin:0 0 6px}#dgut-single-helper-panel.collapsed #btn-pause-wrap{margin-bottom:0;margin-top:8px}#dgut-single-helper-panel.collapsed .dgut-btn-primary{height:30px;font-size:12px}.dgut-panel-body{padding:14px}#timer-display{font-weight:800;color:#fff;font-size:34px;font-variant-numeric:tabular-nums;letter-spacing:1px;line-height:1;text-align:center;margin:2px 0 8px;text-shadow:0 2px 12px rgba(0,122,255,.3)}#progress-bar-wrap{height:4px;background:rgba(255,255,255,.1);border-radius:2px;margin:6px 0;overflow:hidden}#progress-bar-fill{height:100%;background:linear-gradient(90deg,#007aff,#34c759);border-radius:2px;transition:width .6s cubic-bezier(.34,1.56,.64,1);box-shadow:0 0 8px rgba(0,122,255,.3)}#progress-text{font-size:10px;font-weight:600;color:rgba(255,255,255,.4);text-align:center;margin-bottom:8px}#book-name-display{font-size:11px;color:rgba(255,255,255,.55);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;margin-bottom:10px;padding:3px 8px;background:rgba(255,255,255,.03);border-radius:6px}#btn-pause-wrap{margin-bottom:12px}.dgut-btn{border:none;border-radius:9px;cursor:pointer;font-size:13px;font-weight:600;transition:all .15s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:center;outline:0}.dgut-btn:hover{filter:brightness(1.08)}.dgut-btn:active{transform:scale(.97)}.dgut-btn-primary{color:#fff;width:100%;height:36px;background:linear-gradient(135deg,#43e97b,#38f9d7);box-shadow:0 4px 12px rgba(67,233,123,.25)}.dgut-btn-ghost{color:rgba(255,255,255,.85);background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.12);height:24px;padding:0 10px;font-size:11px;font-weight:500}.dgut-btn-ghost:hover{background:rgba(255,255,255,.13);border-color:rgba(255,255,255,.2)}#auto-row,#server-row{display:flex;align-items:center;gap:8px;margin-bottom:8px;font-size:11px;color:rgba(255,255,255,.5)}#server-row #server-time-display{flex:1;min-width:0}#auto-row .row-label{color:rgba(255,255,255,.4);font-size:11px;flex-shrink:0}#auto-row .row-unit{font-size:11px;color:rgba(255,255,255,.4);margin-right:auto}#reader-sec{width:42px;background:rgba(0,0,0,.25);border:1px solid rgba(255,255,255,.1);border-radius:5px;color:#fff;text-align:center;padding:3px;font-size:12px;outline:0;transition:border-color .15s}#reader-sec:focus{border-color:#007aff}#reader-status{display:flex;align-items:center;gap:6px;font-size:10px;color:rgba(255,255,255,.4);margin-bottom:10px}#status-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;background:#ffcc80}#status-dot.active{background:#34c759;box-shadow:0 0 6px #34c759;animation:dgut-pulse 2s infinite}@keyframes dgut-pulse{0%,100%{opacity:1}50%{opacity:.5}}#dgut-log-header{display:flex;align-items:center;justify-content:space-between;cursor:pointer;margin-bottom:6px;padding-top:8px;border-top:1px solid rgba(255,255,255,.06)}#dgut-log-title{font-size:10px;color:rgba(255,255,255,.35);text-transform:uppercase;letter-spacing:.5px;font-weight:700}#dgut-log-toggle{font-size:10px;color:rgba(255,255,255,.25)}#dgut-log-container{height:90px;overflow-y:auto;background:rgba(0,0,0,.22);border-radius:7px;padding:6px 8px;font-size:10px;font-family:Consolas,monospace;line-height:1.5;scrollbar-width:thin;transition:all .25s ease}#dgut-log-container.collapsed{height:0;padding:0;opacity:0;overflow:hidden}#dgut-log-container::-webkit-scrollbar{width:4px}#dgut-log-container::-webkit-scrollbar-thumb{background:rgba(255,255,255,.1);border-radius:2px}.dgut-log-line{padding:1px 0;color:rgba(255,255,255,.55)}.dgut-log-time{color:rgba(255,255,255,.18);margin-right:6px}' })); const p = Object.assign(document.createElement('div'), { id: 'dgut-single-helper-panel', style: `top:${cfg.posY}px;right:${cfg.posX}px` }); p.innerHTML = `

DGUT 求是阅读助手

${fmt(accumulated)}
0%
${getBookDisplayName()}
间隔 秒/页
就绪
服务端: --
日志
`; document.body.appendChild(p); const td = document.getElementById('timer-display'); const bd = document.getElementById('book-name-display'); const sd = document.getElementById('server-time-display'); const pb = document.getElementById('btn-pause'); const sb = document.getElementById('btn-sync-server'); let lastServerSync = 0; const SYNC_INTERVAL = 300; function status(t, c) { const st = document.getElementById('status-text'); const dot = document.getElementById('status-dot'); if (st) st.textContent = t; if (dot) { dot.style.background = c || '#ffcc80'; dot.className = c === '#34c759' ? 'active' : ''; } } function log(msg) { const c = document.getElementById('dgut-log-container'); if(!c) return; const n = new Date(); const t = `${String(n.getHours()).padStart(2,'0')}:${String(n.getMinutes()).padStart(2,'0')}:${String(n.getSeconds()).padStart(2,'0')}`; const l = document.createElement('div'); l.className = 'dgut-log-line'; l.innerHTML = `[${t}]${msg}`; c.appendChild(l); c.scrollTop = c.scrollHeight; while(c.children.length > 100) c.removeChild(c.firstChild); } function saveCfg() { GM_setValue(KEY, cfg); } function updateTimerDisplay(sec) { td.textContent = fmt(sec); const pct = Math.min(100, Math.floor(sec / MIN_BOOK_SEC * 100)); const bar = document.getElementById('progress-bar-fill'); const pctEl = document.getElementById('progress-text'); if (bar) bar.style.width = pct + '%'; if (pctEl) pctEl.textContent = pct + '%'; } function getTotal() { return accumulated + Math.floor((Date.now() - sessionStart) / 1000); } function persist() { const t = getTotal(); setBookTime(bookKey, t); lastSave = t; log('存档:' + fmt(t)); } function syncReader() { saveCfg(); const payload = { type: MSG, intervalSec: cfg.readerSec, autoStart: cfg.readerAutoStart }; let n = 0; Array.from(document.querySelectorAll('iframe')).forEach(f => { try { if(f.contentWindow){ f.contentWindow.postMessage(payload, '*'); n++; } } catch(e) {} }); let txt; if (n > 0) { txt = cfg.readerAutoStart ? '阅读中·'+cfg.readerSec+'秒/页' : '已暂停·'+cfg.readerSec+'秒/页'; } else { txt = '等待阅读器连接'; } status(txt, n > 0 ? '#34c759' : '#ffcc80'); } function saveReader() { const s = parseInt(document.getElementById('reader-sec').value,10); if (s > 0) { cfg.readerSec = s; syncReader(); log('自动操作间隔已设为 ' + s + ' 秒/页'); } else alert('请输入大于 0 的数字'); } function solveModal() { const b1 = document.querySelector('button.btn-submit'); if(b1&&b1.offsetParent!==null) { b1.click(); log('自动关闭弹窗 (btn-submit)'); } const b2 = document.querySelector('#alertModal .btn-submit, .modal.fade.in .btn-hollow, .modal.in .btn-primary'); if(b2) { const r = b2.getBoundingClientRect(); if (r.width > 0 || r.height > 0) { b2.click(); log('自动关闭弹窗 (modal)'); } } } function solveChapterModal() { const modal = document.querySelector('.stat-page.chapter-stat'); if (!modal) return false; const rect = modal.getBoundingClientRect(); if (rect.width === 0 && rect.height === 0) return false; const btns = modal.querySelectorAll('.stat-next .btn-hollow'); if (btns.length === 0) return false; if (getTotal() < MIN_BOOK_SEC) { btns[0].click(); pageStartTime = Date.now(); log('弹窗:未满4h,留在本章'); } else if (btns.length > 1) { btns[1].click(); currentPageId = null; pageStartTime = Date.now(); log('弹窗:已满4h,切换下一章'); } else { btns[0].click(); pageStartTime = Date.now(); log('弹窗:已满4h,已是最后一章,留在本章'); } return true; } let antiDetectTimer = null; function setupAntiDetect() { antiDetectTimer = setInterval(() => { document.dispatchEvent(new MouseEvent('mousemove', { clientX: 100 + Math.random()*500, clientY: 100 + Math.random()*300, bubbles: false })); }, 30000); } function tick() { solveModal(); const modalHandled = solveChapterModal(); const currentKey = getBookKey(); if (currentKey !== bookKey) { setBookTime(bookKey, getTotal()); bookKey = currentKey; accumulated = getBookTime(bookKey); sessionStart = Date.now(); lastSave = accumulated; flatListDirty = true; holdPageId = null; const dispName = getBookDisplayName(); bd.textContent = dispName; bd.title = bookKey; refreshServerDisplay(); log('当前书目:' + dispName); } const vm = PAGE_WIN.koLearnCourseViewModel; if(!modalHandled && vm && vm.currentPage && cfg.readerAutoStart) { const flatList = getFlatList(vm); const page = vm.currentPage(); const pId = pageId(page); if(pId && pId !== currentPageId) { const prevPid = currentPageId; const prevIdx = prevPid ? findPageIndex(flatList, prevPid) : -1; const currentIdx = findPageIndex(flatList, pId); const prevItem = prevIdx >= 0 ? flatList[prevIdx] : null; const currentItem = currentIdx >= 0 ? flatList[currentIdx] : null; currentPageId = pId; pageStartTime = Date.now(); const moveLabel = getMoveLabel(prevItem, currentItem); log(moveLabel + ':' + formatMoveTarget(currentItem)); const dispName = getBookDisplayName(); bd.textContent = dispName; bd.title = bookKey; } if (getTotal() >= MIN_BOOK_SEC) { if(currentPageId && Date.now() - pageStartTime >= NAV_SEC * 1000) { const next = vm.nextPageName?.(); const currentIdx = findPageIndex(flatList, currentPageId); const currentItem = currentIdx >= 0 ? flatList[currentIdx] : null; const nextFlatItem = currentIdx >= 0 && currentIdx < flatList.length - 1 ? flatList[currentIdx + 1] : null; const isNoMore = !next || next === vm.i18nMessageText?.()?.noMore; const crossesSection = !!(currentItem && nextFlatItem && pageId(currentItem.section) !== pageId(nextFlatItem.section)); const nextMoveLabel = getMoveLabel(currentItem, nextFlatItem); const isChapterEnd = isNoMore || (next && next.includes('统计')) || crossesSection; if (isChapterEnd) { const result = advanceToNextSection(vm, currentPageId); if (result.success) { holdPageId = null; currentPageId = null; pageStartTime = Date.now(); log('已满4h,' + result.moveLabel + ':' + result.targetText); setTimeout(function() { syncReader(); }, 1500); } else if (result.atEnd) { stopReading(); log('全部书目已读完'); } } else { vm.goNextPage(); pageStartTime = Date.now(); log(nextMoveLabel + ':' + formatMoveTarget(nextFlatItem)); } } } else { ensureHoldPage(vm, flatList); } } const total = getTotal(); updateTimerDisplay(total); if (total - lastSave >= SAVE_INTERVAL) persist(); if (Date.now() - lastServerSync >= SYNC_INTERVAL * 1000) { const newAcc = syncServerTime(bookKey, accumulated, log); if (newAcc !== accumulated) { sessionStart = Date.now(); lastSave = newAcc; } accumulated = newAcc; lastServerSync = Date.now(); refreshServerDisplay(); } } function startReading() { if (timer) return; sessionStart = Date.now(); timer = setInterval(tick, 1000); pb.textContent = '暂停'; pb.style.background = 'linear-gradient(135deg, #475569, #64748b)'; pb.style.boxShadow = '0 4px 12px rgba(71,85,105,.25)'; cfg.readerAutoStart = true; const vm = PAGE_WIN.koLearnCourseViewModel; if(vm && vm.currentPage) { const p = vm.currentPage(); currentPageId = pageId(p); pageStartTime = Date.now(); } syncReader(); log('开始阅读'); } function stopReading() { if (!timer) return; clearInterval(timer); timer = null; persist(); pb.textContent = '开始'; pb.style.background = ''; pb.style.boxShadow = ''; cfg.readerAutoStart = false; syncReader(); log('暂停阅读'); } function refreshServerDisplay() { try { const st = getServerSideBookTimes(); if (st && st[bookKey]) { sd.textContent = '服务端: ' + fmt(st[bookKey]); return true; } else if (st) { sd.textContent = '服务端: 暂无记录'; return true; } else { sd.textContent = '服务端: 获取失败'; return false; } } catch(e) { console.error('[DGUT Reader] refreshServerDisplay error:', e); sd.textContent = '服务端: 出错'; return false; } } function doSync() { refreshServerDisplay(); const before = accumulated; accumulated = syncServerTime(bookKey, accumulated, log); sessionStart = Date.now(); lastSave = accumulated; lastServerSync = Date.now(); updateTimerDisplay(accumulated); refreshServerDisplay(); if (accumulated > before) { log('已从服务端同步,当前累计: ' + fmt(accumulated)); } else { log('服务端数据已是最新'); } } function buildFlatPageList(vm) { const course = koUnwrap(vm.course); const chapters = koUnwrap(course.chapters); if (!chapters) return []; const flatList = []; chapters.forEach(function(chapter) { const sections = koUnwrap(chapter.sections); if (!sections) return; sections.forEach(function(section) { if (koUnwrap(section.isHide)) return; const pages = koUnwrap(section.pages); if (!pages) return; pages.forEach(function(page) { flatList.push({ page: page, section: section, chapter: chapter }); }); }); }); return flatList; } function findPageIndex(flatList, pid) { return flatList.findIndex(function(item) { const id = pageId(item.page); return String(id) === String(pid); }); } function advanceToNextSection(vm, currentPageId) { const flatList = getFlatList(vm); const currentIdx = findPageIndex(flatList, currentPageId); if (currentIdx >= 0 && currentIdx < flatList.length - 1) { const current = flatList[currentIdx]; const next = flatList[currentIdx + 1]; vm.selectPage(next.page, next.section, next.chapter); return { success: true, moveLabel: getMoveLabel(current, next), targetText: formatMoveTarget(next) }; } return { success: false, atEnd: flatList.length > 0 }; } function ensureHoldPage(vm, flatList) { if (!currentPageId) return; const currentIdx = findPageIndex(flatList, currentPageId); if (currentIdx < 0) return; const currentItem = flatList[currentIdx]; const secId = pageId(currentItem.section); const sameSectionItems = flatList.filter(function(item) { return pageId(item.section) === secId; }); if (sameSectionItems.length === 0) return; if (!isPageComplete(currentItem.page)) { if (holdPageId !== currentPageId) { holdPageId = currentPageId; log('未满4h,停留在当前节刷时长:' + trimName(koUnwrap(currentItem.page.name))); } return; } let target = sameSectionItems.find(function(item) { return !isPageComplete(item.page); }); if (!target) target = sameSectionItems[sameSectionItems.length - 1]; const targetPid = pageId(target.page); if (targetPid === currentPageId) { holdPageId = currentPageId; return; } if (holdPageId === targetPid) return; vm.selectPage(target.page, target.section, target.chapter); holdPageId = targetPid; currentPageId = null; pageStartTime = Date.now(); log('未满4h,当前节已完成,切换并停留:' + trimName(koUnwrap(target.page.name))); setTimeout(function() { syncReader(); }, 1500); } const onDragMove = e => { const l = e.clientX-dx, t = e.clientY-dy; Object.assign(p.style, { left: l+'px', right: 'auto', top: t+'px' }); cfg.posX = Math.max(0, window.innerWidth-(l+p.offsetWidth)); cfg.posY = Math.max(0, t); }; const onDragUp = () => { drag = false; document.removeEventListener('mousemove', onDragMove); document.removeEventListener('mouseup', onDragUp); saveCfg(); }; document.getElementById('dgut-drag-handle').addEventListener('mousedown', e => { drag = true; dx = e.clientX-p.offsetLeft; dy = e.clientY-p.offsetTop; document.addEventListener('mousemove', onDragMove); document.addEventListener('mouseup', onDragUp); }); document.getElementById('reader-sec').addEventListener('keydown', e => { if(e.key==='Enter') saveReader(); }); document.getElementById('btn-apply-reader').addEventListener('click', saveReader); pb.addEventListener('click', () => { timer ? stopReading() : startReading(); }); sb.addEventListener('click', doSync); const collapseBtn = document.getElementById('dgut-collapse-btn'); collapseBtn.addEventListener('mousedown', e => e.stopPropagation()); collapseBtn.addEventListener('click', e => { e.stopPropagation(); const collapsed = p.classList.toggle('collapsed'); collapseBtn.textContent = collapsed ? '+' : '−'; }); document.getElementById('dgut-log-header').addEventListener('click', function() { const c = document.getElementById('dgut-log-container'); const t = document.getElementById('dgut-log-toggle'); if (c.classList.contains('collapsed')) { c.classList.remove('collapsed'); t.textContent = '▼'; } else { c.classList.add('collapsed'); t.textContent = '▶'; } }); updateTimerDisplay(accumulated); log('DGUT 阅读助手已启动'); log('当前书目:' + getBookDisplayName()); if(cfg.readerAutoStart) { startReading(); } else { sessionStart = Date.now(); pb.textContent = '开始'; } syncReader(); window.addEventListener('message', function(e) { if (e.data && e.data.type === 'DGUT_LOG') { log('[iframe] ' + e.data.text); } }); setupAntiDetect(); let startupSyncRetries = 0; function startupSync() { const newAcc = syncServerTime(bookKey, accumulated, null); if (newAcc !== accumulated || startupSyncRetries === 0) { if (newAcc !== accumulated) { sessionStart = Date.now(); lastSave = newAcc; } accumulated = newAcc; lastServerSync = Date.now(); updateTimerDisplay(accumulated); } const hasServerData = refreshServerDisplay(); if (!hasServerData && startupSyncRetries < 10) { startupSyncRetries++; setTimeout(startupSync, 3000); } else if (hasServerData) { log('服务端时长已同步'); } } setTimeout(startupSync, 3000); window.addEventListener('load', () => { setTimeout(syncReader,1200); setTimeout(syncReader,2600); }, { once: true }); } // --- 阅读器 iframe 页 --- function initReader() { if(window.__DGUT_SINGLE_FILE_READER_INITED__) return; window.__DGUT_SINGLE_FILE_READER_INITED__ = true; let timer = null, state, lastFlipLogAt = 0, lastLoopBack = 0; const LOG = 'DGUT_LOG'; function rlog(msg) { console.log('[DGUT Reader]', msg); try { window.parent.postMessage({ type: LOG, text: msg }, '*'); } catch(e) {} } function clickNext() { const b = document.querySelector('#nextBtn'); if(!b || b.style.display === 'none' || b.disabled) return; const pageIdxEl = document.getElementById('pageIndex'); const pageCntEl = document.getElementById('pageCount'); if (pageIdxEl && pageCntEl) { const cur = parseInt(pageIdxEl.innerHTML, 10); const total = parseInt(pageCntEl.innerHTML, 10); if (total > 0 && cur >= total) { const now = Date.now(); if (now - lastLoopBack < 3000) return; lastLoopBack = now; try { const pw = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; if (typeof pw.goPage === 'function') { pw.goPage(0); } else if (typeof goPage === 'function') { goPage(0); } else { const s = document.createElement('script'); s.textContent = 'goPage(0);'; document.head.appendChild(s); setTimeout(function() { if (s.parentNode) s.parentNode.removeChild(s); }, 100); } lastFlipLogAt = 0; rlog('已至末页,回到第一页循环'); return; } catch(e) { rlog('回到第一页失败:' + e.message); } } } b.click(); const now = Date.now(); if (now - lastFlipLogAt >= 60000) { lastFlipLogAt = now; rlog('翻页'); } } function stop() { if(!timer) return; clearInterval(timer); timer = null; rlog('翻页定时器已停止'); } function start(s) { if(timer) return; timer = setInterval(clickNext, s*1000); rlog('翻页定时器已启动:' + s + '秒/页'); } function apply(s, auto) { stop(); if(auto!==false) start(s); } window.addEventListener('message', e => { const d = e.data; if(!d||d.type!==MSG) return; state = readCfg(); const sec = parseInt(d.intervalSec, 10); state.readerSec = sec > 0 ? sec : D.readerSec; state.readerAutoStart = d.autoStart !== false; GM_setValue(KEY, state); rlog('收到同步:' + sec + '秒/页,' + (state.readerAutoStart ? '自动' : '暂停')); apply(state.readerSec, state.readerAutoStart); }); function tryStart() { state = readCfg(); apply(state.readerSec, state.readerAutoStart); } if (document.readyState === 'complete') { setTimeout(tryStart, 500); } else { window.addEventListener('load', () => setTimeout(tryStart, 1200), { once: true }); } } function bootstrapReader() { const init = () => document.querySelector('#nextBtn') ? (initReader(), true) : false; if(init()) { console.log('[DGUT Reader] 阅读器已连接'); return; } let n = 0, t = setInterval(() => { n++; if(init()||n>=20) clearInterval(t); }, 500); window.addEventListener('load', () => setTimeout(init, 1200), { once: true }); } })();