// ==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);
});
})();