// ==UserScript== // @name WeLearn 时长助手 // @namespace https://bbs.tampermonkey.net.cn/ // @version 1.0.5 // @author 恶搞之家 (modified by AI) // @description 自动刷Welearn时长,自动章节跳转和循环,可配置滑动间隔并在submit按钮和direction区域间交替滑动。 // @match *://welearn.sflep.com/Student/StudyCourse.aspx* // @match *://welearn.sflep.com/student/StudyCourse.aspx* // @match *://welearn.sflep.com/student/studyCourse.aspx* // @match *://welearn.sflep.com/student/Studycourse.aspx* // @match *://welearn.sflep.com/student/studycourse.aspx* // @match *://welearn.sflep.com/course/trycourse.aspx* // @grant none // @run-at document-idle // ==/UserScript== (function () { 'use strict'; window.wtb_waitTimerActive = false; window.wtb_waitTimerInterval = null; window.wtb_defaultWaitTimeMinutes = 30; window.wtb_defaultScrollIntervalMinutes = 10; window.wtb_autoCycleActive = false; window.wtb_lastScrollTarget = 'image'; const wtb_sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const wtb_createEvent = (type, options = {}) => new Event(type, { bubbles: true, cancelable: true, ...options }); async function tryScrollToElement(docContext, windowContext, selector, elementName) { const maxRetries = 3; const retryDelay = 300; let element = null; for (let i = 0; i < maxRetries; i++) { element = docContext.querySelector(selector); if (element) { const style = windowContext.getComputedStyle(element); if (style.display !== 'none' && style.visibility !== 'hidden' && parseFloat(style.opacity) > 0 && element.getClientRects().length > 0) { console.log(`[WTB] ${elementName} found and visible on attempt ${i + 1}.`); break; } else { console.log(`[WTB] ${elementName} found on attempt ${i + 1} but appears hidden (display: ${style.display}, visibility: ${style.visibility}, opacity: ${style.opacity}, rects: ${element.getClientRects().length}).`); element = null; } } if (i < maxRetries - 1 && !element) { console.log(`[WTB] ${elementName} not found/visible on attempt ${i + 1}/${maxRetries}. Waiting ${retryDelay}ms.`); await wtb_sleep(retryDelay); } } if (element) { try { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); console.log(`[WTB] Scrolled to ${elementName}.`); await wtb_sleep(1000); return true; } catch (e) { console.error(`[WTB] Error scrolling to ${elementName} (selector: ${selector}):`, e); return false; } } else { console.log(`[WTB] ${elementName} (selector: ${selector}) not found or not visible after retries.`); return false; } } async function wtb_autoScrollPage() { console.log('[WTB] Auto scroll initiated.'); const iframe = document.getElementById('contentFrame'); let docCtx = document; let winCtx = window; if (iframe) { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; if (iframeDoc && iframe.contentWindow) { docCtx = iframeDoc; winCtx = iframe.contentWindow; console.log('[WTB] Scrolling within iframe#contentFrame.'); } else { console.log('[WTB] Could not fully access iframe#contentFrame. Using main page for scroll.'); } } catch (e) { console.error('[WTB] Error accessing iframe for scrolling: ', e); console.log('[WTB] Using main page for scroll due to error.'); } } else { console.log('[WTB] iframe#contentFrame not found. Using main page for scroll.'); } const submitSelector = 'a[data-controltype="submit"].cmd.cmd_submit'; const imageSelector = 'div.direction'; let scrolledToSpecific = false; if (window.wtb_lastScrollTarget === 'image') { console.log('[WTB] Last scroll was to image (or initial). Attempting to scroll to submit button.'); if (await tryScrollToElement(docCtx, winCtx, submitSelector, 'Submit Button')) { window.wtb_lastScrollTarget = 'submit'; scrolledToSpecific = true; } else { console.log('[WTB] Failed to scroll to submit button, trying image/direction div as fallback.'); if (await tryScrollToElement(docCtx, winCtx, imageSelector, 'Image/Direction Div')) { window.wtb_lastScrollTarget = 'image'; scrolledToSpecific = true; } } } else { console.log('[WTB] Last scroll was to submit. Attempting to scroll to image/direction div.'); if (await tryScrollToElement(docCtx, winCtx, imageSelector, 'Image/Direction Div')) { window.wtb_lastScrollTarget = 'image'; scrolledToSpecific = true; } else { console.log('[WTB] Failed to scroll to image/direction div, trying submit button as fallback.'); if (await tryScrollToElement(docCtx, winCtx, submitSelector, 'Submit Button')) { window.wtb_lastScrollTarget = 'submit'; scrolledToSpecific = true; } } } if (!scrolledToSpecific) { console.log('[WTB] Neither specific element found/scrolled. Performing generic scroll.'); await wtb_performGenericScroll(docCtx, winCtx); } } async function wtb_performGenericScroll(docContext, windowContext) { const scrollAmount = windowContext.innerHeight / 4; const currentScroll = windowContext.scrollY; const bodyElement = docContext.body; if (!bodyElement) { console.log('[WTB] Document body not found for generic scroll.'); return; } const maxScroll = bodyElement.scrollHeight - windowContext.innerHeight; let targetScroll = currentScroll + scrollAmount; if (targetScroll > maxScroll) { targetScroll = Math.max(0, currentScroll - scrollAmount); } if (maxScroll > 0 && currentScroll !== targetScroll) { windowContext.scrollTo({ top: targetScroll, behavior: 'smooth' }); await wtb_sleep(2000); if (targetScroll > currentScroll && targetScroll > 100) { windowContext.scrollTo({ top: targetScroll - 50, behavior: 'smooth' }); } else if (targetScroll < currentScroll && currentScroll > 100) { windowContext.scrollTo({ top: targetScroll + 50, behavior: 'smooth' }); } console.log('[WTB] Generic page scroll completed within the given context.'); } else { console.log('[WTB] No scroll needed for generic scroll (page too short or already at target) within the given context.'); } } async function wtb_executeWaitTimer(callback) { const timeInput = document.getElementById('wtb_waitTimeInput'); const countdownDisplay = document.getElementById('wtb_countdownDisplay'); const scrollIntervalInput = document.getElementById('wtb_scrollIntervalInput'); let waitTimeMinutes = parseInt(timeInput?.value || window.wtb_defaultWaitTimeMinutes); if (isNaN(waitTimeMinutes) || waitTimeMinutes < 1) { waitTimeMinutes = window.wtb_defaultWaitTimeMinutes; if (timeInput) timeInput.value = window.wtb_defaultWaitTimeMinutes.toString(); } else { window.wtb_defaultWaitTimeMinutes = waitTimeMinutes; } let scrollIntervalMinutes = parseInt(scrollIntervalInput?.value || window.wtb_defaultScrollIntervalMinutes); if (isNaN(scrollIntervalMinutes) || scrollIntervalMinutes < 1) { scrollIntervalMinutes = window.wtb_defaultScrollIntervalMinutes; if (scrollIntervalInput) scrollIntervalInput.value = window.wtb_defaultScrollIntervalMinutes.toString(); } else { window.wtb_defaultScrollIntervalMinutes = scrollIntervalMinutes; } if (waitTimeMinutes <= 0) { if (typeof callback === 'function') await callback(); return; } let totalWaitTimeInSeconds = waitTimeMinutes * 60; let remainingTimeInSeconds = totalWaitTimeInSeconds; const scrollIntervalSeconds = scrollIntervalMinutes * 60; if (countdownDisplay) { const minutes = Math.floor(remainingTimeInSeconds / 60); const seconds = remainingTimeInSeconds % 60; countdownDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; countdownDisplay.style.color = '#80cbc4'; countdownDisplay.classList.remove('wtb-status-update'); } if (window.wtb_waitTimerInterval) { clearInterval(window.wtb_waitTimerInterval); } window.wtb_waitTimerActive = true; wtb_updateTimerButtonState(); if (totalWaitTimeInSeconds > scrollIntervalSeconds) { await wtb_autoScrollPage(); } return new Promise(resolve => { window.wtb_waitTimerInterval = setInterval(async () => { remainingTimeInSeconds--; const elapsedTimeInSeconds = totalWaitTimeInSeconds - remainingTimeInSeconds; if (countdownDisplay) { const minutes = Math.floor(remainingTimeInSeconds / 60); const seconds = remainingTimeInSeconds % 60; countdownDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; if (remainingTimeInSeconds <= 10 && remainingTimeInSeconds > 0) { countdownDisplay.style.color = '#ff6b6b'; } } if (window.wtb_waitTimerActive && elapsedTimeInSeconds > 0 && elapsedTimeInSeconds % scrollIntervalSeconds === 0) { if (remainingTimeInSeconds > 5) { console.log(`[WTB] Periodic scroll trigger. Elapsed: ${elapsedTimeInSeconds}s. Interval: ${scrollIntervalSeconds}s. Remaining: ${remainingTimeInSeconds}s. Calling wtb_autoScrollPage.`); await wtb_autoScrollPage(); } else { console.log(`[WTB] Periodic scroll trigger. Elapsed: ${elapsedTimeInSeconds}s. Interval: ${scrollIntervalSeconds}s. Remaining: ${remainingTimeInSeconds}s. SKIPPED (too close to end).`); } } if (remainingTimeInSeconds <= 0) { clearInterval(window.wtb_waitTimerInterval); window.wtb_waitTimerInterval = null; window.wtb_waitTimerActive = false; wtb_updateTimerButtonState(); if (countdownDisplay) { countdownDisplay.textContent = '完成!'; countdownDisplay.style.color = '#4CAF50'; countdownDisplay.classList.add('wtb-status-update'); setTimeout(() => { if (countdownDisplay) { countdownDisplay.textContent = '未开始'; countdownDisplay.style.color = '#80cbc4'; countdownDisplay.classList.remove('wtb-status-update'); } }, 3000); } if (typeof callback === 'function') await callback(); resolve(); } }, 1000); }); } function wtb_cancelWaitTimer() { if (window.wtb_waitTimerInterval) { clearInterval(window.wtb_waitTimerInterval); window.wtb_waitTimerInterval = null; } window.wtb_waitTimerActive = false; window.wtb_autoCycleActive = false; wtb_updateTimerButtonState(); const countdownDisplay = document.getElementById('wtb_countdownDisplay'); if (countdownDisplay) { countdownDisplay.textContent = '已取消'; countdownDisplay.style.color = '#ff9800'; countdownDisplay.classList.add('wtb-status-update'); setTimeout(() => { if (countdownDisplay) { countdownDisplay.textContent = '未开始'; countdownDisplay.style.color = '#80cbc4'; countdownDisplay.classList.remove('wtb-status-update'); } }, 3000); } console.log('[WTB] 计时器已取消。'); return false; } async function wtb_autoNextSection() { console.log('[WTB] 准备自动跳转到下一章节 (将在主页面执行操作)...'); await wtb_sleep(1500); let transitionStarted = false; if (typeof window.NextSCO === 'function') { try { console.log('[WTB] 尝试在主页面直接执行NextSCO函数'); window.NextSCO(); transitionStarted = true; console.log('[WTB] NextSCO函数已在主页面执行。'); } catch (e) { console.log('[WTB] 在主页面执行NextSCO函数失败:', e); } } if (!transitionStarted) { const navLinks = document.querySelectorAll('a[href*="javascript:NextSCO()"], a[href*="javascript:PrevSCO()"]'); if (navLinks.length > 0) { const nextLink = Array.from(navLinks).find(link => link.href.includes('NextSCO') && !link.href.includes('PrevSCO') && (link.offsetParent !== null || link.clientWidth > 0 || link.clientHeight > 0) ); if (nextLink) { console.log('[WTB] 找到NextSCO链接,在主页面尝试点击'); nextLink.click(); transitionStarted = true; } else { console.log('[WTB] NextSCO链接在主页面找到,但不可见或不符合条件。'); } } else { console.log('[WTB] 未在主页面找到NextSCO链接。'); } } if (transitionStarted) { console.log('[WTB] 已成功触发跳转,等待新页面加载。'); await wtb_sleep(3000); return true; } console.log('[WTB] 所有在主页面的跳转尝试都失败。'); return false; } async function wtb_runCycle() { if (!window.wtb_autoCycleActive) { console.log('[WTB] Auto-cycle is not active. Stopping.'); wtb_updateTimerButtonState(); return; } console.log('[WTB] Starting a new cycle leg: Running timer.'); try { await wtb_executeWaitTimer(async () => { if (!window.wtb_autoCycleActive) { console.log('[WTB] Timer finished, but auto-cycle was stopped during timer. No transition.'); return; } console.log('[WTB] Timer finished. Attempting to go to next section.'); const navigationSuccess = await wtb_autoNextSection(); if (navigationSuccess && window.wtb_autoCycleActive) { console.log('[WTB] Navigation successful. Waiting for page load and then restarting cycle...'); const countdownDisplay = document.getElementById('wtb_countdownDisplay'); if (countdownDisplay) { countdownDisplay.textContent = '加载中...'; countdownDisplay.style.color = '#80cbc4'; countdownDisplay.classList.remove('wtb-status-update'); } await wtb_sleep(5000); if (window.wtb_autoCycleActive) { console.log('[WTB] Restarting cycle.'); wtb_runCycle(); } else { console.log('[WTB] Auto-cycle stopped during page load wait.'); wtb_updateTimerButtonState(); } } else if (!navigationSuccess && window.wtb_autoCycleActive) { console.log('[WTB] Navigation failed. Stopping auto-cycle.'); window.wtb_autoCycleActive = false; wtb_updateTimerButtonState(); const cdDisplay = document.getElementById('wtb_countdownDisplay'); if (cdDisplay) { cdDisplay.textContent = '跳转失败'; cdDisplay.style.color = '#ff6b6b'; } } else { console.log('[WTB] Navigation was successful but auto-cycle was stopped, or navigation failed and cycle already stopped.'); wtb_updateTimerButtonState(); } }); } catch (error) { console.error('[WTB] Error during wtb_executeWaitTimer in wtb_runCycle:', error); window.wtb_autoCycleActive = false; wtb_updateTimerButtonState(); wtb_cancelWaitTimer(); } } function wtb_createSimpleUI() { const panelId = 'wtbHelperPanel'; if (document.getElementById(panelId)) { console.log('[WTB] UI Panel already exists.'); return document.getElementById(panelId); } const globalStyle = document.createElement('style'); globalStyle.id = 'wtb-helper-style'; globalStyle.textContent = ` #${panelId} { position: fixed; top: 20px; left: 20px; z-index: 9999; background: rgba(35, 35, 38, 0.88); backdrop-filter: blur(10px) saturate(150%); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,0.3); padding: 15px; color: #dadada; font-size: 13px; width: 260px; display: flex; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.1); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; transition: transform 0.3s ease, box-shadow 0.3s ease; } #${panelId}:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(0,0,0,0.35); } .${panelId}-title-bar { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255,255,255,0.12); padding-bottom: 8px; margin-bottom: 5px; cursor: grab; } .${panelId}-title { font-weight: 500; font-size: 16px; color: #f0f0f0; } .${panelId}-minimize-btn { background: rgba(255,255,255,0.1); border: none; color: #bbb; font-size: 16px; font-weight: bold; cursor: pointer; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.25s ease; } .${panelId}-minimize-btn:hover { background-color: rgba(255,255,255,0.2); color: #fff; transform: scale(1.1); } .${panelId}-content { display: flex; flex-direction: column; gap: 12px; } .${panelId}-row { display: flex; align-items: center; gap: 8px; } .${panelId}-label { flex-shrink: 0; font-size: 13px; } .${panelId}-input { width: 60px; padding: 6px 8px; border-radius: 5px; border: 1px solid rgba(255,255,255,0.2); background-color: rgba(0,0,0,0.25); text-align: center; font-weight: 500; color: #e0e0e0; transition: all 0.2s ease; } .${panelId}-input:focus { border-color: #00aaff; background-color: rgba(0,0,0,0.35); box-shadow: 0 0 7px rgba(0,170,255,0.4); } .${panelId}-countdown { font-weight: 500; font-size: 14px; color: #80cbc4; margin-left: auto; min-width: 50px; text-align: right; } .${panelId}-button { flex: 1; padding: 8px 12px; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; font-size: 13px; transition: all 0.2s ease; background: linear-gradient(135deg, #0088cc 0%, #005599 100%); color: white; text-shadow: 0 1px 1px rgba(0,0,0,0.2); } .${panelId}-button:hover { background: linear-gradient(135deg, #0099e6 0%, #0066b3 100%); transform: translateY(-1px); box-shadow: 0 3px 10px rgba(0,136,204,0.25); } .${panelId}-button:active { transform: scale(0.98); } .${panelId}-button.stop { background: linear-gradient(135deg, #cc4444 0%, #aa2222 100%); } .${panelId}-button.stop:hover { background: linear-gradient(135deg, #dd5555 0%, #bb3333 100%); box-shadow: 0 3px 10px rgba(204,68,68,0.25); } .${panelId}-status { font-size: 11px; color: #888; text-align: center; padding-top: 5px; border-top: 1px solid rgba(255,255,255,0.08); margin-top: 5px; } .wtb-status-update { animation: wtbStatusPop 0.4s ease-out; } @keyframes wtbStatusPop { 0% { opacity:0; transform: scale(0.9); } 100% { opacity:1; transform: scale(1); } } `; document.head.appendChild(globalStyle); const mainPanel = document.createElement('div'); mainPanel.id = panelId; const titleBar = document.createElement('div'); titleBar.className = `${panelId}-title-bar`; const title = document.createElement('div'); title.className = `${panelId}-title`; title.textContent = 'WeLearn 时长助手'; const minimizeBtn = document.createElement('button'); minimizeBtn.className = `${panelId}-minimize-btn`; minimizeBtn.textContent = '-'; const contentDiv = document.createElement('div'); contentDiv.className = `${panelId}-content`; minimizeBtn.onclick = () => { if (contentDiv.style.display === 'none') { contentDiv.style.display = 'flex'; minimizeBtn.textContent = '-'; } else { contentDiv.style.display = 'none'; minimizeBtn.textContent = '+'; } }; titleBar.appendChild(title); titleBar.appendChild(minimizeBtn); mainPanel.appendChild(titleBar); mainPanel.appendChild(contentDiv); const timerRow = document.createElement('div'); timerRow.className = `${panelId}-row`; const timerLabel = document.createElement('span'); timerLabel.className = `${panelId}-label`; timerLabel.textContent = '刷课时长:'; const timeInput = document.createElement('input'); timeInput.id = 'wtb_waitTimeInput'; timeInput.className = `${panelId}-input`; timeInput.type = 'number'; timeInput.min = '1'; timeInput.value = window.wtb_defaultWaitTimeMinutes.toString(); const minutesLabel = document.createElement('span'); minutesLabel.className = `${panelId}-label`; minutesLabel.textContent = '分钟'; const countdownDisplay = document.createElement('div'); countdownDisplay.id = 'wtb_countdownDisplay'; countdownDisplay.className = `${panelId}-countdown`; countdownDisplay.textContent = '未开始'; timerRow.appendChild(timerLabel); timerRow.appendChild(timeInput); timerRow.appendChild(minutesLabel); timerRow.appendChild(countdownDisplay); contentDiv.appendChild(timerRow); timeInput.addEventListener('change', function() { const newTime = parseInt(this.value, 10); if (!isNaN(newTime) && newTime > 0) { window.wtb_defaultWaitTimeMinutes = newTime; } else { this.value = window.wtb_defaultWaitTimeMinutes.toString(); } }); const scrollIntervalRow = document.createElement('div'); scrollIntervalRow.className = `${panelId}-row`; const scrollIntervalLabel = document.createElement('span'); scrollIntervalLabel.className = `${panelId}-label`; scrollIntervalLabel.textContent = '滑动间隔:'; const scrollIntervalInput = document.createElement('input'); scrollIntervalInput.id = 'wtb_scrollIntervalInput'; scrollIntervalInput.className = `${panelId}-input`; scrollIntervalInput.type = 'number'; scrollIntervalInput.min = '1'; scrollIntervalInput.value = window.wtb_defaultScrollIntervalMinutes.toString(); const scrollMinutesLabel = document.createElement('span'); scrollMinutesLabel.className = `${panelId}-label`; scrollMinutesLabel.textContent = '分钟'; scrollIntervalRow.appendChild(scrollIntervalLabel); scrollIntervalRow.appendChild(scrollIntervalInput); scrollIntervalRow.appendChild(scrollMinutesLabel); contentDiv.appendChild(scrollIntervalRow); scrollIntervalInput.addEventListener('change', function() { const newInterval = parseInt(this.value, 10); if (!isNaN(newInterval) && newInterval > 0) { window.wtb_defaultScrollIntervalMinutes = newInterval; } else { this.value = window.wtb_defaultScrollIntervalMinutes.toString(); } }); const buttonsRow = document.createElement('div'); buttonsRow.className = `${panelId}-row`; const timerButton = document.createElement('button'); timerButton.id = 'wtb_timerButton'; timerButton.className = `${panelId}-button`; function timerButtonHandler() { if (window.wtb_autoCycleActive) { console.log('[WTB] User requested to stop auto-cycle.'); window.wtb_autoCycleActive = false; wtb_cancelWaitTimer(); } else { console.log('[WTB] User requested to start auto-cycle.'); window.wtb_autoCycleActive = true; wtb_updateTimerButtonState(); wtb_runCycle(); } } timerButton.onclick = timerButtonHandler; buttonsRow.appendChild(timerButton); contentDiv.appendChild(buttonsRow); const statusContainer = document.createElement('div'); statusContainer.className = `${panelId}-status`; statusContainer.textContent = '恶搞之家 (时长助手版)'; contentDiv.appendChild(statusContainer); let isDragging = false, initialX, initialY, xOffset = 0, yOffset = 0; titleBar.addEventListener('mousedown', e => { if (e.target === titleBar || e.target === title) { isDragging = true; initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; titleBar.style.cursor = 'grabbing'; } }); document.addEventListener('mousemove', e => { if (isDragging) { e.preventDefault(); xOffset = e.clientX - initialX; yOffset = e.clientY - initialY; mainPanel.style.transform = `translate3d(${xOffset}px, ${yPosOffset}px, 0)`; } }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; titleBar.style.cursor = 'grab'; } }); mainPanel.style.transform = 'translate3d(0px, 0px, 0)'; let yPosOffset = 0; titleBar.addEventListener('mousedown', e => { if (e.target === titleBar || e.target === title) { isDragging = true; initialX = e.clientX - parseFloat(mainPanel.style.transform.split(',')[0].split('(')[1] || '0'); initialY = e.clientY - parseFloat(mainPanel.style.transform.split(',')[1] || '0'); titleBar.style.cursor = 'grabbing'; } }); document.addEventListener('mousemove', e => { if (isDragging) { e.preventDefault(); const currentX = e.clientX - initialX; const currentY = e.clientY - initialY; mainPanel.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`; } }); return mainPanel; } function wtb_updateTimerButtonState() { const timerButton = document.getElementById('wtb_timerButton'); const panelId = 'wtbHelperPanel'; if (timerButton) { if (window.wtb_autoCycleActive) { timerButton.textContent = '停止自动循环'; timerButton.classList.add('stop'); } else { timerButton.textContent = '开始自动循环'; timerButton.classList.remove('stop'); } } } function wtb_removeExistingUI() { const panelId = 'wtbHelperPanel'; const existingPanel = document.getElementById(panelId); if (existingPanel) { existingPanel.remove(); } const existingStyle = document.getElementById('wtb-helper-style'); if (existingStyle) { existingStyle.remove(); } } function wtb_addButton() { if (window !== window.top) { console.log('[WTB] 在iframe中,不添加UI。'); return; } wtb_removeExistingUI(); const uiPanel = wtb_createSimpleUI(); if (uiPanel) { document.body.appendChild(uiPanel); wtb_updateTimerButtonState(); } } function wtb_handlePageLoad() { console.log('[WTB] 页面加载完成,初始化时长助手。'); if (window === window.top) { wtb_addButton(); } } function wtb_initialize() { if (window.wtb_scriptInitialized) { console.log('[WTB] 时长助手已初始化,跳过。'); return; } window.wtb_scriptInitialized = true; console.log('[WTB] 初始化 WeLearn 时长助手'); if (document.readyState === 'complete') { wtb_handlePageLoad(); } else { window.addEventListener('load', wtb_handlePageLoad); } } wtb_initialize(); })();