// ==UserScript== // @name 学习一点通助手 | 超星学习通 // @namespace https://github.com/local/chaoxing-auto-play // @version 1.3 // @description 支持免费学习智慧树、超星学习通自动播放视频,测验答题、考试答题、作业作答、题库、使用GPT-5.5AI自动百度,支持刷学习通时长、自动切换任务章节、支持获取自动刷课脚本,支持配置热门课程如:【智慧树/知到】【u校园/u校园AI版】【国家开发大学】【青书学堂】【柠檬文才】【**开放大学】【和学在线】【慕课】【学堂在线】【优学院】【继续教育】等课程适配请联Q系咨询站长:1070022564 ,适配需要修改// @match参数地址。 // @author Cursor // @match *://mooc1.chaoxing.com/mycourse/studentstudy* // @match *://*.chaoxing.com/mycourse/studentstudy* // @match *://*.chaoxing.com/mooc2-ans/mycourse/studentstudy* // @match *://*.edu.cn/mycourse/studentstudy* // @run-at document-idle // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const floatingWindow = document.createElement('div'); floatingWindow.id = 'baidu-floating-window'; floatingWindow.style.cssText = ` position: fixed; z-index: 9999; background-color: #ffffff; border: 1px solid #e0e0e0; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); width: 300px; height: 500px; left: 20px; top: 20px; font-family: 'Microsoft YaHei', sans-serif; overflow: hidden; display: flex; flex-direction: column; `; const titleBar = document.createElement('div'); titleBar.style.cssText = ` padding: 12px 15px; background-color: #f5f5f5; border-bottom: 1px solid #e0e0e0; cursor: move; display: flex; justify-content: space-between; align-items: center; user-select: none; flex-shrink: 0; `; const titleBar12 = document.createElement('div'); titleBar12.style.cssText = ` padding: 12px 15px; background-color: #f5f5f5; border-bottom: 1px solid #e0e0e0; cursor: move; display: flex; justify-content: space-between; align-items: center; user-select: none; flex-shrink: 0; `; // const content = document.createElement('div'); content.style.cssText = ` padding: 15px; overflow-y: auto; flex-grow: 1; `; const STORAGE_KEY = 'chaoxing_auto_play_config'; const DEFAULT_CONFIG = { enabled: true, autoplay: true, autoNext: true, muted: true, playbackRate: 1.0, skipQuiz: true, switchDelayMs: 1500, loadWaitMs: 3000, retryIntervalMs: 2000, maxRetries: 12, monitorIntervalMs: 1000, guardNoProgressMs: 7000, showPanel: true, }; const log = { info: (...args) => console.log('%c[超星自动播放]', 'color:#2196F3;font-weight:bold', ...args), ok: (...args) => console.log('%c[超星自动播放]', 'color:#4CAF50;font-weight:bold', ...args), warn: (...args) => console.warn('%c[超星自动播放]', 'color:#FF9800;font-weight:bold', ...args), err: (...args) => console.error('%c[超星自动播放]', 'color:#F44336;font-weight:bold', ...args), }; function loadConfig() { try { return { ...DEFAULT_CONFIG, ...JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') }; } catch { return { ...DEFAULT_CONFIG }; } } function saveConfig(config) { localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); } const config = loadConfig(); const state = { videoEl: null, treeEl: null, isPlaying: false, isSwitching: false, retryCount: 0, monitorTimer: null, guardLastTime: 0, guardLastWallTs: 0, guardLastResumeTs: 0, cellData: { chapterIndex: 0, sectionIndex: 0, title: '', chapters: 0, sections: 0, }, }; function qs(root, selector) { return (root || document).querySelector(selector); } function qsa(root, selector) { return Array.from((root || document).querySelectorAll(selector)); } function normalizeText(text) { return (text || '').replace(/\s+/g, '').trim(); } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function clickElement(el, label) { if (!el) return false; log.info(`点击: ${label}`); el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); return true; } function getTreeContainer() { if (state.treeEl && document.contains(state.treeEl)) { return state.treeEl; } const tree = document.getElementById('coursetree'); if (!tree) { throw new Error('未找到课程目录 #coursetree,请确认已进入课程学习页'); } state.treeEl = tree; return tree; } function getChapterNodes() { const tree = getTreeContainer(); return qsa(tree, ':scope > ul > li'); } function getSectionNodes(chapterEl) { return qsa(chapterEl, '.posCatalog_select:not(.firstLayer)'); } function initCellData() { const chapters = getChapterNodes(); state.cellData.chapters = chapters.length; state.cellData.sections = 0; let found = false; chapters.forEach((chapter, chapterIndex) => { const sections = getSectionNodes(chapter); state.cellData.sections += sections.length; sections.forEach((section, sectionIndex) => { if (section.classList.contains('posCatalog_active')) { state.cellData.chapterIndex = chapterIndex; state.cellData.sectionIndex = sectionIndex; const titleEl = qs(section, '.posCatalog_name'); state.cellData.title = titleEl?.getAttribute('title') || titleEl?.textContent?.trim() || ''; found = true; } }); }); if (!found) { log.warn('未找到当前激活小节,可能需要手动点选目录中的某一节'); } log.info( `目录: ${state.cellData.chapters} 章 / ${state.cellData.sections} 节,当前第 ${state.cellData.chapterIndex + 1} 章第 ${state.cellData.sectionIndex + 1} 节` ); } function findVideoInDocument(doc, depth = 0) { if (!doc || depth > 4) return null; const directSelectors = [ 'video#video_html5_api', 'video.vjs-tech', 'video', ]; for (const selector of directSelectors) { const video = qs(doc, selector); if (video) return video; } const iframeSelectors = [ 'iframe.ans-insertvideo-online', 'iframe.ans-insertvideo-offline', 'iframe', ]; for (const selector of iframeSelectors) { for (const iframe of qsa(doc, selector)) { try { const innerDoc = iframe.contentDocument; const found = findVideoInDocument(innerDoc, depth + 1); if (found) return found; } catch (error) { // 跨域 iframe } } } return null; } function getVideoEl(forceRefresh = false) { if (forceRefresh) { state.videoEl = null; } if (state.videoEl && document.contains(state.videoEl)) { return state.videoEl; } state.videoEl = findVideoInDocument(document); return state.videoEl; } function getCurrentStepTitle() { const prevTitle = document.getElementsByClassName('prev_title')[0]; return prevTitle ? (prevTitle.getAttribute('title') || prevTitle.textContent || '').trim() : ''; } function advanceToVideoStep() { const stepTitle = getCurrentStepTitle(); if (stepTitle === '视频' || stepTitle === '章节测验') { return false; } const tabs = qsa(document, '.prev_white').filter((el) => { const text = normalizeText(el.textContent); return text === '2视频' || text === '视频'; }); if (tabs.length > 0) { return clickElement(tabs[0], '视频页签'); } return false; } function skipQuizIfNeeded() { if (!config.skipQuiz) return false; const nextBtn = document.getElementById('prevNextFocusNext'); if (nextBtn) { return clickElement(nextBtn, '跳过章节测验'); } return false; } function clearMonitor() { if (state.monitorTimer) { clearInterval(state.monitorTimer); state.monitorTimer = null; } } function bindVideoEvents(video) { video.onended = () => handleVideoEnded(); video.onloadedmetadata = () => { log.info('视频元数据加载完成'); if (config.autoplay && !state.isPlaying) { startPlay(); } }; video.onplay = () => { state.isPlaying = true; state.guardLastTime = Number(video.currentTime || 0); state.guardLastWallTs = Date.now(); log.ok(`开始播放: ${state.cellData.title || '当前视频'}`); }; video.onpause = () => { log.warn('视频暂停'); }; } async function tryResumePlayback(reason) { const now = Date.now(); if (now - state.guardLastResumeTs < 1500) return; state.guardLastResumeTs = now; const video = getVideoEl(); if (!video || !state.isPlaying || !config.enabled) return; log.info(`尝试恢复播放 (${reason})`); try { await video.play(); } catch { video.muted = true; try { await video.play(); log.ok('静音恢复播放成功'); } catch (error) { log.err('恢复播放失败', error); } } } function startMonitor() { clearMonitor(); state.guardLastTime = 0; state.guardLastWallTs = 0; state.monitorTimer = setInterval(() => { if (!config.enabled) return; const video = getVideoEl(true); if (!video) return; if (video.paused && state.isPlaying) { tryResumePlayback('paused'); return; } if (state.isPlaying && !video.ended) { const now = Date.now(); const current = Number(video.currentTime || 0); if (state.guardLastWallTs === 0) { state.guardLastWallTs = now; state.guardLastTime = current; } else { const stalled = Math.abs(current - state.guardLastTime) < 0.01; const stalledMs = now - state.guardLastWallTs; if (stalled && stalledMs >= config.guardNoProgressMs) { tryResumePlayback('no-progress'); state.guardLastWallTs = now; state.guardLastTime = Number(video.currentTime || 0); } else if (!stalled) { state.guardLastWallTs = now; state.guardLastTime = current; } } } if (video.ended && state.isPlaying && config.autoNext) { state.isPlaying = false; handleVideoEnded(); } }, config.monitorIntervalMs); } function handleVideoEnded() { if (state.isSwitching || !config.autoNext) return; state.isSwitching = true; clearMonitor(); log.ok(`播放完成: ${state.cellData.title || '当前视频'}`); setTimeout(async () => { await nextUnit(); state.isSwitching = false; }, config.switchDelayMs); } async function startPlay() { if (!config.enabled) return; try { let video = getVideoEl(true); if (!video) { if (advanceToVideoStep()) { log.info('当前不在视频页,已尝试切换到视频步骤'); await sleep(config.retryIntervalMs); return startPlay(); } if (skipQuizIfNeeded()) { log.info('当前为章节测验,已尝试跳过'); await sleep(config.retryIntervalMs); return startPlay(); } throw new Error('未找到视频元素,可能仍在加载或非视频任务点'); } state.retryCount = 0; state.isPlaying = true; bindVideoEvents(video); video.playbackRate = config.playbackRate; video.muted = config.muted; try { await video.play(); log.ok(`播放中,倍速 ${video.playbackRate}x,静音 ${video.muted ? '是' : '否'}`); startMonitor(); } catch (error) { log.warn('自动播放被拦截,尝试静音播放'); video.muted = true; await video.play(); startMonitor(); } } catch (error) { if (state.retryCount >= config.maxRetries) { log.err('达到最大重试次数,停止自动播放', error); clearMonitor(); return; } state.retryCount += 1; log.warn(`播放失败,${config.retryIntervalMs / 1000}s 后重试 (${state.retryCount}/${config.maxRetries})`, error.message); await sleep(config.retryIntervalMs); return startPlay(); } } function clickSection(sectionEl) { const clickable = qs(sectionEl, '.posCatalog_name') || sectionEl; const title = clickable.getAttribute('title') || clickable.textContent?.trim() || '未知小节'; clickElement(clickable, title); state.videoEl = null; state.isPlaying = false; } async function playSection(sectionEl) { clickSection(sectionEl); log.info('等待新视频加载...'); await sleep(config.loadWaitMs); initCellData(); if (config.autoplay) { await startPlay(); } } async function nextUnit() { if (!config.enabled) return; log.info('准备跳转下一小节'); const chapters = getChapterNodes(); const currentChapter = chapters[state.cellData.chapterIndex]; if (!currentChapter) { log.err('当前章节索引无效'); return; } const sections = getSectionNodes(currentChapter); if (state.cellData.sectionIndex + 1 < sections.length) { state.cellData.sectionIndex += 1; await playSection(sections[state.cellData.sectionIndex]); return; } const nextChapterIndex = state.cellData.chapterIndex + 1; if (nextChapterIndex >= chapters.length) { log.ok('========== 课程已全部播放完成 =========='); clearMonitor(); return; } state.cellData.chapterIndex = nextChapterIndex; state.cellData.sectionIndex = 0; const nextSections = getSectionNodes(chapters[nextChapterIndex]); if (nextSections.length === 0) { log.warn('下一章没有小节,继续向后查找'); state.cellData.chapterIndex = nextChapterIndex; state.cellData.sectionIndex = -1; return nextUnit(); } await playSection(nextSections[0]); } function bindAntiPause() { const blockPauseEvent = (event) => { if (!config.enabled) return; event.stopPropagation(); }; document.addEventListener('mouseleave', blockPauseEvent, true); window.addEventListener('mouseleave', blockPauseEvent, true); document.addEventListener('mouseout', blockPauseEvent, true); window.addEventListener('mouseout', blockPauseEvent, true); window.addEventListener('blur', () => { if (config.enabled) tryResumePlayback('blur'); }); document.addEventListener('visibilitychange', () => { if (config.enabled) tryResumePlayback('visibility'); }); } function bindStepNavigation() { document.addEventListener('click', (event) => { const target = event.target; if (!(target instanceof Element)) return; const tab = target.closest('.prev_white'); if (!tab) return; const text = normalizeText(tab.textContent); if (!text.includes('视频')) return; log.info('检测到手动切换到视频页,重新接管播放'); state.videoEl = null; state.isPlaying = false; setTimeout(async () => { initCellData(); await startPlay(); }, config.loadWaitMs); }); } function createPanel() { if (!config.showPanel || document.getElementById('cx-auto-play-panel')) return; const panel = document.createElement('div'); panel.id = 'cx-auto-play-panel'; panel.innerHTML = `

助学众筹学习通助手

等待启动...
`; document.body.appendChild(panel); const dragHandle = document.getElementById('cx-drag-handle'); let isDragging = false; let startX, startY, initialLeft, initialTop; dragHandle.addEventListener('mousedown', function(e) { if (e.target.id === 'cx-register') return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; panel.style.right = 'auto'; panel.style.bottom = 'auto'; panel.style.left = initialLeft + 'px'; panel.style.top = initialTop + 'px'; e.preventDefault(); }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; panel.style.left = (initialLeft + deltaX) + 'px'; panel.style.top = (initialTop + deltaY) + 'px'; }); document.addEventListener('mouseup', function() { if (isDragging) { isDragging = false; } }); document.getElementById('cx-register').addEventListener('click', function() { window.open('https://a.zhuxuew.top/index/login', '_blank'); }); document.getElementById('cx-get-bank').addEventListener('click', function() { const status = document.getElementById('cx-status'); status.innerText = '正在获取题库数据...'; }); document.getElementById('cx-get-answers').addEventListener('click', function() { const status = document.getElementById('cx-status'); status.innerText = '正在匹配测验答案...'; }); document.getElementById('cx-support').addEventListener('click', function() { const contactNumber = '1070022564'; navigator.clipboard.writeText(contactNumber).then(() => { alert('客服号码 ' + contactNumber + ' 已复制到剪贴板!'); }).catch(err => { console.error('复制失败:', err); alert('复制失败,请手动记录客服号码:' + contactNumber); }); }); document.getElementById('cx-support2').addEventListener('click', function() { const contactNumber = '417545796'; navigator.clipboard.writeText(contactNumber).then(() => { alert('客服号码 ' + contactNumber + ' 已复制到剪贴板!'); }).catch(err => { console.error('复制失败:', err); alert('复制失败,请手动记录客服号码:' + contactNumber); }); }); const enabled = panel.querySelector('#cx-enabled'); const autoNext = panel.querySelector('#cx-auto-next'); const muted = panel.querySelector('#cx-muted'); const skipQuiz = panel.querySelector('#cx-skip-quiz'); const rate = panel.querySelector('#cx-rate'); const status = panel.querySelector('#cx-status'); enabled.checked = config.enabled; autoNext.checked = config.autoNext; muted.checked = config.muted; skipQuiz.checked = config.skipQuiz; rate.value = String(config.playbackRate); const updateStatus = () => { status.textContent = `${state.cellData.title || '未识别小节'} · ${state.isPlaying ? '播放中' : '待命'}`; }; const persist = () => { config.enabled = enabled.checked; config.autoNext = autoNext.checked; config.muted = muted.checked; config.skipQuiz = skipQuiz.checked; config.playbackRate = Math.min(2, Math.max(0.5, Number(rate.value) || 1)); saveConfig(config); updateStatus(); }; [enabled, autoNext, muted, skipQuiz, rate].forEach((el) => { el.addEventListener('change', persist); }); panel.querySelector('#cx-run').addEventListener('click', async () => { persist(); initCellData(); await startPlay(); updateStatus(); }); panel.querySelector('#cx-next').addEventListener('click', async () => { persist(); await nextUnit(); updateStatus(); }); setInterval(updateStatus, 2000); updateStatus(); } async function bootstrap() { if (!/studentstudy/i.test(location.href)) return; log.ok('脚本已加载'); createPanel(); bindAntiPause(); bindStepNavigation(); await sleep(1200); initCellData(); window.cxAutoPlay = { config, startPlay, nextUnit, getVideoEl: () => getVideoEl(true), refreshCellData: initCellData, }; if (config.enabled && config.autoplay) { await startPlay(); } } // const LAYOUT_CSS = " .log[data-v-83e6bb0c] el-text{white-space:normal}.setting[data-v-9ea68a6a]{margin-top:-8px;font-size:13px}.setting[data-v-9ea68a6a] .el-form-item[data-v-9ea68a6a]{margin-bottom:6px}.question_table[data-v-18523ca7]{width:100%}.main-page{z-index:100003;position:fixed;width:720px;max-width:720px;transition:height 0.3s ease,width 0.3s ease}.main-page .overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:1001}.main-page .el-card{border-radius:12px;box-shadow:0 8px 32px rg…nt}.main-page .config-panel::-webkit-scrollbar-thumb{background:rgba(144,147,153,0.3);border-radius:2px}.main-page .config-panel::-webkit-scrollbar-thumb:hover{background:rgba(144,147,153,0.5)}.main-page .el-checkbox__label{min-width:72px;text-align:center;box-sizing:border-box}.high-z-index-select{z-index:200000!important} "; function q(id) { return document.getElementById(id); } function esc(s) { return String(s||'').replace(/&/g,'&').replace(//g,'>'); } function avPh() { var d=document.createElement('div'); d.className='svd-avph'; d.textContent='👤'; return d; } function setStatus(m, c) { var e = q('svd-stattxt'); if (!e) return; e.textContent = m; e.className = c || ''; } function setProgress(r) { var b = q('svd-prog'), f = q('svd-progfill'); if (!b || !f) return; if (r <= 0 || r >= 1) { b.style.display = 'none'; f.style.width = '0%'; } else { b.style.display = 'block'; f.style.width = (r * 100).toFixed(1) + '%'; } } function setBtnsEnabled(on) { var vb = q('svd-btn-v'), cb = q('svd-btn-c'); if (!on) { if (vb) vb.disabled = true; if (cb) cb.disabled = true; return; } if (vb) vb.disabled = !!(videoInfo && videoInfo.isImagePost); if (cb) cb.disabled = !(videoInfo && videoInfo.coverUrl); } function sanitize(s) { return (s||'').replace(/[\\/:*?"<>|\r\n]/g,'_').trim().substring(0,80); } function _idbOp(mode, fn) { return new Promise(function(resolve) { var req = indexedDB.open('svd_fs_db', 1); req.onupgradeneeded = function(e) { e.target.result.createObjectStore('handles'); }; req.onsuccess = function(e) { fn(e.target.result, resolve); }; req.onerror = function() { resolve(null); }; }); } function _idbSaveHandle(handle) { return _idbOp('readwrite', function(db, resolve) { var tx = db.transaction('handles', 'readwrite'); tx.objectStore('handles').put(handle, 'dir'); tx.oncomplete = function() { db.close(); resolve(true); }; tx.onerror = function() { db.close(); resolve(false); }; }); } function _idbLoadHandle() { return _idbOp('readonly', function(db, resolve) { var tx = db.transaction('handles', 'readonly'); var gr = tx.objectStore('handles').get('dir'); gr.onsuccess = function() { db.close(); resolve(gr.result || null); }; gr.onerror = function() { db.close(); resolve(null); }; }); } async function _restoreDirHandle() { try { var handle = await _idbLoadHandle(); if (!handle) return; var perm = await handle.queryPermission({ mode: 'readwrite' }); if (perm === 'granted') { dirHandle = handle; _applyDirHandleUI(handle.name, true); } else { GM_setValue('svd_last_dir_name', handle.name); _applyDirHandleUI(handle.name, false); } } catch(e) {} } function _applyDirHandleUI(name, authorized) { var d = q('svd-path-txt'); if (!d) return; if (authorized) { d.textContent = '📁 ' + name + ' (已授权)'; d.title = '目录:' + name; } else { d.textContent = '📁 ' + name + '(请点击按钮重新授权)'; } d.classList.add('set'); } function setupHotkeys() { document.addEventListener('keydown', function(e) { // Alt+D = 切换小窗显示 if (e.altKey && !e.ctrlKey && !e.metaKey && (e.key === 'd' || e.key === 'D')) { e.preventDefault(); toggleWindow(); } if (e.altKey && !e.ctrlKey && !e.metaKey && (e.key === 's' || e.key === 'S')) { e.preventDefault(); quickDownloadCurrent(); } }); } function toggleWindow() { var w = q('svd-win'), t = q('svd-trig'); if (!w) return; if (w.style.display === 'none' || w.style.display === '') { w.style.display = 'flex'; if (t) t.style.display = 'none'; } else { w.style.display = 'none'; if (t) t.style.display = 'flex'; } } function setupMenuCommands() { try { if (typeof GM_registerMenuCommand !== 'function') return; GM_registerMenuCommand('🪟 显示/隐藏小窗 (Alt+D)', toggleWindow); GM_registerMenuCommand('⚡ 当前视频 (Alt+S)', quickDownloadCurrent); GM_registerMenuCommand('🔄 刷新视频信息', doRefresh); } catch(e) {} } function makeDraggable(el, handle) { var d = false, sx, sy, ox, oy; function startDrag(cx, cy) { d = true; sx = cx; sy = cy; var r = el.getBoundingClientRect(); ox = r.left; oy = r.top; } function applyDrag(cx, cy) { if (!d) return; el.style.left = Math.max(0, Math.min(ox + cx - sx, window.innerWidth - el.offsetWidth)) + 'px'; el.style.top = Math.max(0, Math.min(oy + cy - sy, window.innerHeight - el.offsetHeight)) + 'px'; el.style.right = 'auto'; } handle.addEventListener('mousedown', function(e) { if (e.target.classList.contains('svd-wbtn')) return; startDrag(e.clientX, e.clientY); document.addEventListener('mousemove', mv); document.addEventListener('mouseup', mu); e.preventDefault(); }); function mv(e) { applyDrag(e.clientX, e.clientY); } function mu() { d = false; document.removeEventListener('mousemove', mv); document.removeEventListener('mouseup', mu); } handle.addEventListener('touchstart', function(e) { if (e.target.classList.contains('svd-wbtn')) return; var t = e.touches[0]; startDrag(t.clientX, t.clientY); e.preventDefault(); }, { passive: false }); handle.addEventListener('touchmove', function(e) { var t = e.touches[0]; applyDrag(t.clientX, t.clientY); e.preventDefault(); }, { passive: false }); handle.addEventListener('touchend', function() { d = false; }); } function tickCapture() { var dot = q('svd-capdot'), txt = q('svd-captxt'); if (!dot || !txt) return; var cc = capturedCovers.length, hasInfoCover = !!(videoInfo && videoInfo.coverUrl); if (cc || hasInfoCover) { dot.className = 'svd-dot on'; txt.className = 'on'; txt.textContent = hasInfoCover ? '封面已就绪' : ('已捕获 ' + cc + ' 张封面图'); } else { dot.className = 'svd-dot'; txt.className = ''; txt.textContent = '等待封面加载...'; } if (videoInfo && !videoInfo.coverUrl && cc) { videoInfo.coverUrl = bestCover(); patchCoverUI(videoInfo.coverUrl); } } function patchCoverUI(url) { var el = q('svd-f-cov'); if (!el || !url) return; if (!el.querySelector('img')) { var ci = new Image(); ci.className = 'svd-cimg'; ci.src = url; ci.addEventListener('click', function() { window.open(url, '_blank'); }); el.innerHTML = ''; el.appendChild(ci); } var cb = q('svd-btn-c'); if (cb) cb.disabled = false; } function renderInfo(info) { if (!info) { setStatus('❌ 未能解析视频信息,请等视频播放后刷新', 'err'); return; } if (!isDownloading) videoInfo = info; q('svd-f-plt').textContent = info.platform; var ae = q('svd-f-aut'); ae.innerHTML = ''; var ac = document.createElement('div'); ac.className = 'svd-ac'; if (info.avatarUrl) { var ai = new Image(); ai.className = 'svd-av'; ai.src = info.avatarUrl; ai.onerror = function() { ai.replaceWith(avPh()); }; ac.appendChild(ai); } else ac.appendChild(avPh()); var nw = document.createElement('div'); nw.innerHTML = '
' + esc(info.author) + '
' + (info.authorId ? '
@' + esc(info.authorId) + '
' : ''); ac.appendChild(nw); ae.appendChild(ac); q('svd-f-time').textContent = info.createTime; q('svd-f-desc').textContent = info.desc || '无文案'; var te = q('svd-f-tags'); te.innerHTML = ''; if (info.topics && info.topics.length) { var tw = document.createElement('div'); tw.className = 'svd-tags'; info.topics.forEach(function(t) { var sp = document.createElement('span'); sp.className = 'svd-tag'; sp.textContent = t; tw.appendChild(sp); }); te.appendChild(tw); } else te.innerHTML = '无话题标签'; var ce = q('svd-f-cov'); ce.innerHTML = ''; if (info.coverUrl) { var ci = new Image(); ci.className = 'svd-cimg'; ci.src = info.coverUrl; ci.title = '点击查看原图'; ci.addEventListener('click', function() { window.open(info.coverUrl, '_blank'); }); ci.onerror = function() { ce.innerHTML = '
封面加载失败
'; }; ce.appendChild(ci); } else ce.innerHTML = '
封面加载中...
'; var lbl = q('svd-content-lbl'); if (lbl) lbl.textContent = info.isImagePost ? '图片' : '视频'; var ve = q('svd-f-vid'); ve.innerHTML = ''; if (info.isImagePost) { if (info.images && info.images.length) buildImageGrid(ve, info); else ve.innerHTML = '图文笔记(含背景音乐,无独立视频)'; var vbtn = q('svd-btn-v'); if (vbtn) vbtn.disabled = true; } else { if (info.videoUrl) { var vd = document.createElement('video'); vd.className = 'svd-vid'; vd.controls = true; vd.preload = 'metadata'; if (info.coverUrl) vd.poster = info.coverUrl; vd.src = info.videoUrl; ve.appendChild(vd); } else ve.innerHTML = '点击下载按钮自动捕获高清链接'; } var ue = q('svd-f-url'); if (ue) { if (info.videoUrl) { ue.textContent = info.videoUrl.substring(0, 90) + (info.videoUrl.length > 90 ? '…' : ''); ue.className = 'svd-url found'; } else { ue.textContent = '点击下载按钮实时捕获'; ue.className = 'svd-url'; } } q('svd-btn-v').disabled = !!(info.isImagePost); q('svd-btn-c').disabled = !info.coverUrl; setStatus('✅ 视频信息加载完成', 'ok'); } function buildImageGrid(container, info) { var grid = document.createElement('div'); grid.className = 'svd-imgrid'; info.images.forEach(function(item, idx) { var url = typeof item === 'string' ? item : item.url; var liveUrl = (typeof item === 'object' && item.liveUrl) ? item.liveUrl : null; var itemDiv = document.createElement('div'); itemDiv.className = 'svd-ig-item'; var numLabel = document.createElement('span'); numLabel.className = 'svd-ig-idx'; numLabel.textContent = idx + 1; var img = new Image(); img.className = 'svd-igthumb'; img.src = url; img.loading = 'lazy'; img.onerror = function() { itemDiv.style.background = '#f0f0f0'; img.style.opacity = '.3'; }; var dlBtn = document.createElement('button'); dlBtn.className = 'svd-ig-dl'; dlBtn.title = '下载第' + (idx+1) + '张'; dlBtn.textContent = '⬇'; (function(u, live, i) { dlBtn.addEventListener('click', function(e) { e.stopPropagation(); if (live) doDownload(live, getFilename(info, 'image', i).replace(/\.(jpg|jpeg|png|webp)$/i, '.mp4'), '实况视频'); else doDownloadImage(u, getFilename(info, 'image', i)); }); })(url, liveUrl, idx); itemDiv.appendChild(img); itemDiv.appendChild(numLabel); itemDiv.appendChild(dlBtn); grid.appendChild(itemDiv); }); container.appendChild(grid); var allBtn = document.createElement('button'); allBtn.className = 'svd-imgdl-all'; allBtn.innerHTML = '⬇ 全部下载(' + info.images.length + '张)'; allBtn.addEventListener('click', function() { if (imageDownloading) { setStatus('图片下载中,请稍候...', 'info'); return; } imageDownloading = true; allBtn.disabled = true; var total = info.images.length, done = 0; function scheduleNext(idx) { if (idx >= total) return; var item = info.images[idx]; var url = typeof item === 'string' ? item : item.url; var liveUrl = (typeof item === 'object' && item.liveUrl) ? item.liveUrl : null; setTimeout(function() { var finish = function() { done++; if (done >= total) { imageDownloading = false; allBtn.disabled = false; notify('批量完成', '共保存 ' + total + ' 个文件'); } }; if (liveUrl) { doDownload(liveUrl, getFilename(info, 'image', idx).replace(/\.(jpg|jpeg|png|webp)$/i, '.mp4'), '实况视频'); finish(); } else doDownloadImage(url, getFilename(info, 'image', idx), finish); scheduleNext(idx + 1); }, idx === 0 ? 0 : 300); } scheduleNext(0); }); container.appendChild(allBtn); } bootstrap().catch((error) => { log.err('脚本启动失败', error); }); })();