// ==UserScript== // @name 短视频平台去水印下载 Pro (高清修复版)【支持抖音🎶网页版、小红书📚网页版下载 // @name:zh-CN 短视频平台去水印下载 Pro (高清修复版)【支持抖音🎶网页版、小红书📚网页版下载 // @version 3.0.0 // @license MIT // @description 支持抖音和小红书平台,高清无水印下载 // @author LightDownload // @match *://*.douyin.com/* // @match *://*.xiaohongshu.com/* // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant unsafeWindow // @connect * // @run-at document-start // @namespace https://greasyfork.org/ // ==/UserScript== (function () { 'use strict'; /* ══════════════════════════════════════════════════════════ STATE ══════════════════════════════════════════════════════════ */ var dirHandle = null; var videoInfo = null; var isDownloading = false; var lastNavUrl = ''; var capturedCovers = []; var originalFetch = unsafeWindow.fetch; var originalXHROpen = unsafeWindow.XMLHttpRequest.prototype.open; // 小红书专用数据存储 var xhsNoteData = null; // 平台检测 function isDouyin() { return location.hostname.includes('douyin.com'); } function isXiaohongshu() { return location.hostname.includes('xiaohongshu.com'); } // 抖音的 IntersectionObserver 相关(仅抖音使用) var _itemObserver = null; var _currentItemKey = null; function getItemKey(el) { return el.getAttribute('data-aweme-id') || el.getAttribute('data-id') || el.dataset.awemeid || el.dataset.id || (el.parentElement ? Array.prototype.indexOf.call(el.parentElement.children, el) : 0) + '_i'; } function setupItemObserver() { if (!isDouyin()) return; if (_itemObserver) { _itemObserver.disconnect(); _itemObserver = null; } var items = document.querySelectorAll('.page-recommend-container,.dySwiperSlide[data-e2e="feed-item"]'); if (!items.length) return; _itemObserver = new IntersectionObserver(function(entries) { entries.forEach(function(entry) { if (entry.intersectionRatio >= 0.65) { var key = getItemKey(entry.target); if (key && key !== _currentItemKey) { _currentItemKey = key; capturedCovers = []; } } }); }, { threshold: [0.65] }); Array.prototype.forEach.call(items, function(item) { _itemObserver.observe(item); }); } var _domWatcher = null; function watchForNewItems() { if (!isDouyin()) return; if (_domWatcher) return; _domWatcher = new MutationObserver(function(m) { for (var i=0;i 20) capturedCovers.pop(); } function bestCover() { if (!capturedCovers.length) return null; var now = Date.now(); var fresh = capturedCovers.filter(function(c){ return now - c.ts < 90000; }); return (fresh.length ? fresh[0] : capturedCovers[0]).url; } // 小红书数据提取(从 feed 接口响应中解析) function extractXiaohongshuFromResponse(responseText) { try { var data = JSON.parse(responseText); var items = data?.data?.items; if (!items || !items.length) return; var note = items[0].note_card; if (!note) return; // 提取图片列表 var images = []; if (note.image_list) { images = note.image_list.map(function(img) { var url = img.url_default || (img.info_list && img.info_list[0] && img.info_list[0].url) || ''; var liveUrl = ''; if (img.live_photo && img.stream && img.stream.h264 && img.stream.h264[0]) { liveUrl = img.stream.h264[0].master_url || ''; } return { url: url, liveUrl: liveUrl }; }); } // 提取视频链接 var videoUrl = ''; if (note.video) { var stream = note.video.media?.stream; var h265 = stream?.h265?.[0]?.master_url; var h264 = stream?.h264?.[0]?.master_url; videoUrl = h265 || h264 || ''; } // 封面 var cover = note.image_list?.[0]?.url_default || note.video?.image?.thumbnail || ''; xhsNoteData = { platform: '小红书', author: note.user?.nickname || note.user?.nick_name || '未知作者', authorId: note.user?.user_id || '', avatarUrl: note.user?.avatar || '', desc: note.title || note.desc || '无文案', createTime: note.time ? new Date(note.time).toLocaleString('zh-CN', {hour12: false}) : '未知', coverUrl: cover, videoUrl: videoUrl, isImagePost: !videoUrl && images.length > 0, images: images, noteId: note.note_id || '', ipLocation: note.ip_location || '', topics: [] // 小红书无话题 }; renderInfo(xhsNoteData); } catch(e) { console.warn('[XHS] 解析笔记数据失败:', e); } } // 拦截 fetch 和 XHR(封面 + 小红书数据) (function setupInterceptors() { // 劫持 fetch(封面捕获) try { unsafeWindow.fetch = function() { var a = Array.prototype.slice.call(arguments); captureCoverUrl(typeof a[0] === 'string' ? a[0] : (a[0] && a[0].url) || ''); return originalFetch.apply(this, a); }; } catch(e) {} // 劫持 XHR open 记录 url,并劫持 send 捕获小红书数据 var originalXHROpenLocal = unsafeWindow.XMLHttpRequest.prototype.open; unsafeWindow.XMLHttpRequest.prototype.open = function(method, url) { this._url = url; // 同时捕获封面 captureCoverUrl(typeof url === 'string' ? url : ''); return originalXHROpenLocal.apply(this, arguments); }; var originalXHRSend = unsafeWindow.XMLHttpRequest.prototype.send; unsafeWindow.XMLHttpRequest.prototype.send = function() { var self = this; var url = self._url; // 小红书 feed 接口拦截 if (isXiaohongshu() && url && url.includes('/api/sns/web/v1/feed')) { self.addEventListener('load', function() { if (self.status === 200) { extractXiaohongshuFromResponse(self.responseText); } }); } return originalXHRSend.apply(this, arguments); }; // 小红书详情页直接从 __INITIAL_STATE__ 提取(备用) if (isXiaohongshu() && location.pathname.startsWith('/explore/')) { setTimeout(function() { try { var state = unsafeWindow.__INITIAL_STATE__; if (state && state.note) { var noteId = location.pathname.split('/').pop(); var note = state.note.noteDetailMap[noteId].note; if (note) { var images = []; if (note.image_list) { images = note.image_list.map(function(img) { var url = img.url_default || (img.info_list && img.info_list[0] && img.info_list[0].url) || ''; var liveUrl = ''; if (img.live_photo && img.stream && img.stream.h264 && img.stream.h264[0]) { liveUrl = img.stream.h264[0].master_url || ''; } return { url: url, liveUrl: liveUrl }; }); } var videoUrl = ''; if (note.video) { var stream = note.video.media?.stream; videoUrl = stream?.h265?.[0]?.master_url || stream?.h264?.[0]?.master_url || ''; } xhsNoteData = { platform: '小红书', author: note.user?.nickname || note.user?.nick_name || '未知作者', authorId: note.user?.user_id || '', avatarUrl: note.user?.avatar || '', desc: note.title || note.desc || '无文案', createTime: note.time ? new Date(note.time).toLocaleString('zh-CN', {hour12: false}) : '未知', coverUrl: note.image_list?.[0]?.url_default || note.video?.image?.thumbnail || '', videoUrl: videoUrl, isImagePost: !videoUrl && images.length > 0, images: images, noteId: note.note_id || noteId, ipLocation: note.ip_location || '', topics: [] }; renderInfo(xhsNoteData); } } } catch(e) {} }, 1200); } })(); /* ══════════════════════════════════════════════════════════ ② 抖音专用:查找当前Feed Item ══════════════════════════════════════════════════════════ */ function findCurrentFeedItem() { if (!isDouyin()) return null; var vids = Array.prototype.slice.call(document.querySelectorAll('video')); var playing = null; for (var i = 0; i < vids.length; i++) { if (!vids[i].paused && vids[i].readyState >= 2 && vids[i].currentTime > 0) { playing = vids[i]; break; } } if (!playing && vids.length) { var bestV = -1; for (var j = 0; j < vids.length; j++) { var r = vids[j].getBoundingClientRect(); var v = Math.min(r.bottom, window.innerHeight) - Math.max(r.top, 0); if (v > bestV) { bestV = v; playing = vids[j]; } } } if (playing) { var el = playing.parentElement; while (el && el !== document.body) { if (el.classList.contains('page-recommend-container') || el.dataset.e2e === 'feed-item') return el; if (el.id === 'slideMode') return el; if (el.classList.contains('playerControlHeight')) return el; el = el.parentElement; } } var items = Array.prototype.slice.call( document.querySelectorAll('.page-recommend-container,.dySwiperSlide[data-e2e="feed-item"],#slideMode,.playerControlHeight') ); var best = null, bestVis = -1; for (var k = 0; k < items.length; k++) { var rr = items[k].getBoundingClientRect(); var vv = Math.min(rr.bottom, window.innerHeight) - Math.max(rr.top, 0); if (vv > bestVis) { bestVis = vv; best = items[k]; } } return best; } /* ══════════════════════════════════════════════════════════ ③ 抖音图文检测 ══════════════════════════════════════════════════════════ */ function hasImageTextBadge(container) { if (!container) return false; var all = container.querySelectorAll('span,div,a,em,i,button'); for (var i = 0; i < all.length; i++) { var t = all[i].textContent.trim(); if (t === '图文') return true; } return !!( container.querySelector('.xgplayer-progress-outer.picture') || container.querySelector('[class*="pictureMode"]') || container.querySelector('[class*="PicTextTag"]') || container.querySelector('[class*="picTextTag"]') || container.querySelector('[class*="imageTextTag"]') || container.querySelector('[class*="ImageTextTag"]') || container.querySelector('[class*="img-text-tag"]') || container.getAttribute('data-type') === 'image' ); } function detectImagePost(container) { if (!container) return { isImage: false, images: [] }; var isPic = hasImageTextBadge(container); if (!isPic) return { isImage: false, images: [] }; var images = extractSlideImages(container); return { isImage: true, images: images }; } function extractSlideImages(container) { var images = []; var seen = {}; var slideSelectors = [ '[class*="swiper-slide"]', '[class*="SwiperSlide"]', '[class*="slide-item"]', '[class*="ImageItem"]', '[class*="imageItem"]', '[class*="pic-item"]' ].join(','); var slides = container.querySelectorAll(slideSelectors); if (slides.length >= 2) { for (var i = 0; i < slides.length; i++) { var img = bestImgInSlide(slides[i]); if (img && !seen[img]) { seen[img] = true; images.push(img); } } if (images.length >= 2) return images; } var wrappers = container.querySelectorAll( '[class*="swiper-wrapper"],[class*="SwiperWrapper"],[class*="carousel"],[class*="Carousel"]' ); if (wrappers.length) { var wrapper = wrappers[0]; var imgs = wrapper.querySelectorAll('img'); for (var j = 0; j < imgs.length; j++) { if (isContentImg(imgs[j])) { var src = imgs[j].src || imgs[j].getAttribute('data-src') || ''; if (src && !seen[src]) { seen[src] = true; images.push(src); } } } if (images.length >= 2) return images; } return images; } function bestImgInSlide(slide) { var imgs = slide.querySelectorAll('img'); for (var i = 0; i < imgs.length; i++) { if (isContentImg(imgs[i])) return imgs[i].src || imgs[i].getAttribute('data-src') || ''; } return null; } function isContentImg(img) { if (!img) return false; var src = img.src || img.getAttribute('data-src') || ''; if (!src || /^(blob:|data:)/.test(src) || src.length < 20) return false; if (/avatar|emoji|icon|logo|dot|indicator/i.test(src)) return false; if (/avatar|emoji|icon|logo|dot|indicator/i.test(img.className || '')) return false; var w = img.naturalWidth || img.width || (img.getBoundingClientRect && img.getBoundingClientRect().width) || 0; var h = img.naturalHeight || img.height || (img.getBoundingClientRect && img.getBoundingClientRect().height) || 0; if (w > 0 && w < 50) return false; if (h > 0 && h < 50) return false; var par = img.parentElement; for (var i = 0; i < 3 && par; i++) { if (/avatar|Avatar|userPhoto/i.test(par.className || '')) return false; par = par.parentElement; } return true; } /* ══════════════════════════════════════════════════════════ ④ 抖音封面提取 ══════════════════════════════════════════════════════════ */ function extractCover(container) { if (!container) return bestCover() || ''; var xgPoster = container.querySelector( '.xgplayer-poster,.xg-poster,[class*="xgplayer-poster"],[class*="xg-poster"]' ); if (xgPoster) { var bgStyle = xgPoster.getAttribute('style') || ''; var bgMatch = bgStyle.match(/background(?:-image)?\s*:\s*url\(["']?(https?:[^"')]+)["']?\)/); if (!bgMatch) { var computedBg = window.getComputedStyle(xgPoster).backgroundImage; bgMatch = computedBg.match(/url\(["']?(https?:[^"')]+)["']?\)/); } if (bgMatch) return bgMatch[1]; var xgImg = xgPoster.querySelector('img'); if (xgImg && xgImg.src && !/^(blob:|data:)/.test(xgImg.src)) return xgImg.src; } var coverSels = ['img[class*="cover"]','img[class*="Cover"]','img[class*="poster"]','img[class*="Poster"]','img[class*="thumb"]','img[class*="Thumb"]']; for (var s = 0; s < coverSels.length; s++) { var ci = container.querySelector(coverSels[s]); if (ci && isContentImg(ci)) return ci.src; } var imgs = container.querySelectorAll('img'); for (var i = 0; i < imgs.length; i++) { var src = imgs[i].src || imgs[i].getAttribute('data-src') || ''; if (isContentImg(imgs[i]) && src) return src; } var bgEls = container.querySelectorAll('[class*="cover"],[class*="poster"],[class*="mask"]'); for (var b = 0; b < bgEls.length; b++) { var bg = window.getComputedStyle(bgEls[b]).backgroundImage; var m = bg.match(/url\(["']?(https?:[^"')]+)["']?\)/); if (m) return m[1]; } return bestCover() || ''; } /* ══════════════════════════════════════════════════════════ ⑤ 抖音高清视频链接提取(从RENDER_DATA中取最高码率) ══════════════════════════════════════════════════════════ */ function getHighQualityVideoUrlFromRenderData() { if (!isDouyin()) return null; try { var el = document.getElementById('RENDER_DATA'); if (!el) return null; var raw = JSON.parse(decodeURIComponent(el.textContent)); var detail = null; for (var key in raw) { if (key === '_location' || key === 'app' || key === '11') continue; if (raw[key] && raw[key].videoDetail) { detail = raw[key].videoDetail; break; } } if (!detail) return null; var video = detail.video; if (!video) return null; var bitrates = video.bitRate || []; if (bitrates.length > 0) { bitrates.sort(function(a, b) { return (b.bit_rate || 0) - (a.bit_rate || 0); }); var best = bitrates[0]; if (best && best.play_addr) { var urlList = best.play_addr.url_list || []; if (urlList.length > 0) { return urlList[0].replace(/^\/\//, 'https://'); } } } var playAddr = video.play_addr; if (playAddr && playAddr.url_list && playAddr.url_list.length > 0) { return playAddr.url_list[0].replace(/^\/\//, 'https://'); } var h264 = video.h264_packed_addr; if (h264 && h264.url_list && h264.url_list.length > 0) { return h264.url_list[0].replace(/^\/\//, 'https://'); } return null; } catch (e) { console.warn('[SVD] 解析高清链接失败:', e); return null; } } /* ══════════════════════════════════════════════════════════ ⑥ 下载核心(优先高清链接,降级劫持) ══════════════════════════════════════════════════════════ */ var HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', 'Origin': isDouyin() ? 'https://www.douyin.com' : 'https://www.xiaohongshu.com', 'Referer': isDouyin() ? 'https://www.douyin.com/' : 'https://www.xiaohongshu.com/' }; function getCurrentVideoElement() { var container = findCurrentFeedItem() || document.querySelector('.playerControlHeight') || document.getElementById('slideMode'); if (!container) return null; return container.querySelector('video'); } function getVideoUrlFromDom(videoEl) { if (!videoEl) return null; if (videoEl.currentSrc && !/^blob:/.test(videoEl.currentSrc)) { var cur = videoEl.currentSrc; try { cur = decodeURI(cur); } catch(e) {} if (cur.indexOf('douyin') > -1 || cur.indexOf('.mp4') > -1) return cur; } var sources = videoEl.querySelectorAll('source'); for (var i = 0; i < sources.length; i++) { var s = sources[i].getAttribute('src') || ''; if (!s || /^blob:/.test(s)) continue; try { s = decodeURI(s); } catch(e) {} if (s.indexOf('douyin') > -1 || s.indexOf('.mp4') > -1) return s; } var attrSrc = videoEl.getAttribute('src'); if (attrSrc && !/^blob:/.test(attrSrc)) { try { attrSrc = decodeURI(attrSrc); } catch(e) {} if (attrSrc.indexOf('douyin') > -1 || attrSrc.indexOf('.mp4') > -1) return attrSrc; } return null; } function resolveVideoUrl(rawUrl, callback) { if (!rawUrl) { callback(null); return; } var method = navigator.userAgent.indexOf('Firefox') !== -1 ? 'GET' : 'HEAD'; GM_xmlhttpRequest({ method: method, url: rawUrl, headers: HEADERS, timeout: 20000, onload: function(res) { if (res.status === 200 || res.status === 401) { callback(rawUrl); } else if (res.status === 302) { var locationMatch = (res.responseHeaders || '').match(/[Ll]ocation:\s*(.+)/); if (locationMatch) callback(locationMatch[1].trim()); else callback(rawUrl); } else callback(rawUrl); }, onerror: function() { callback(rawUrl); } }); } function startDownloadWithUrl(rawUrl) { if (!videoInfo) { videoInfo = extractVideoInfo() || { author: '未知', desc: '视频' }; } videoInfo.videoUrl = rawUrl; isDownloading = true; setBtnsEnabled(false); setStatus('正在解析视频地址...', 'info'); setProgress(0.02); resolveVideoUrl(rawUrl, function(finalUrl) { if (!finalUrl) { finishDL(false, '无法解析视频地址'); return; } var filename = getFilename(videoInfo, 'video'); fetchBlob(finalUrl, filename, '视频'); }); } function doDownloadVideo() { if (isDownloading) { setStatus('下载进行中...', 'info'); return; } if (isDouyin()) { // 抖音:优先高清链接 var hdUrl = getHighQualityVideoUrlFromRenderData(); if (hdUrl) { console.log('[SVD] 使用高清链接:', hdUrl); startDownloadWithUrl(hdUrl); return; } var videoEl = getCurrentVideoElement(); var domUrl = getVideoUrlFromDom(videoEl); if (domUrl) { startDownloadWithUrl(domUrl); return; } // 劫持模式 setStatus('🔍 正在捕获视频链接...', 'info'); var captureDone = false; var captureTimer = null; var originalFetchLocal = unsafeWindow.fetch; var originalXHROpenLocal = unsafeWindow.XMLHttpRequest.prototype.open; function captureAndRestore(url) { if (captureDone) return; captureDone = true; unsafeWindow.fetch = originalFetchLocal; unsafeWindow.XMLHttpRequest.prototype.open = originalXHROpenLocal; if (captureTimer) clearTimeout(captureTimer); var decodedUrl; try { decodedUrl = decodeURI(url); } catch(e) { decodedUrl = url; } console.log('[SVD] 劫持捕获:', decodedUrl); startDownloadWithUrl(decodedUrl); } unsafeWindow.fetch = function() { var args = Array.prototype.slice.call(arguments); var url = typeof args[0] === 'string' ? args[0] : (args[0] && args[0].url) || ''; if (url && typeof url === 'string' && url.indexOf('douyin') > -1 && (url.indexOf('.mp4') > -1 || url.indexOf('mime_type=video') > -1)) { captureAndRestore(url); } return originalFetchLocal.apply(this, args); }; unsafeWindow.XMLHttpRequest.prototype.open = function(method, url) { if (typeof url === 'string' && url.indexOf('douyin') > -1 && (url.indexOf('.mp4') > -1 || url.indexOf('mime_type=video') > -1)) { captureAndRestore(url); } return originalXHROpenLocal.apply(this, arguments); }; if (videoEl) videoEl.load(); captureTimer = setTimeout(function() { if (!captureDone) { unsafeWindow.fetch = originalFetchLocal; unsafeWindow.XMLHttpRequest.prototype.open = originalXHROpenLocal; setStatus('❌ 捕获超时,请重试或刷新页面', 'err'); isDownloading = false; setBtnsEnabled(true); setProgress(0); } }, 8000); } else if (isXiaohongshu()) { // 小红书:直接使用已提取的视频链接 if (!videoInfo || !videoInfo.videoUrl) { setStatus('❌ 未找到视频链接', 'err'); return; } startDownloadWithUrl(videoInfo.videoUrl); } } function doDownloadCover() { var url = (videoInfo && videoInfo.coverUrl) || bestCover(); if (!url) { setStatus('未找到封面', 'err'); return; } doDownload(url, getFilename(videoInfo || {author:'未知',desc:'封面'}, 'cover'), '封面'); } function doDownload(url, filename, label) { if (isDownloading){ setStatus('下载进行中...','info'); return; } isDownloading=true; setBtnsEnabled(false); setStatus('正在验证 '+label+' ...','info'); setProgress(0.02); var method=navigator.userAgent.indexOf('Firefox')!==-1?'GET':'HEAD'; GM_xmlhttpRequest({ method:method, url:url, headers:HEADERS, timeout:20000, onload:function(r){ var fu=url; if(r.status===302){ var m=(r.responseHeaders||'').match(/[Ll]ocation:\s*(.+)/); if(m)fu=m[1].trim(); } else if(r.status===403){ finishDL(false,'链接已失效,请等视频重新加载后下载'); return; } fetchBlob(fu, filename, label); }, onerror:function(){ fetchBlob(url,filename,label); } }); } function doDownloadImage(url, filename) { setStatus('正在下载图片: '+filename,'info'); GM_xmlhttpRequest({ method:'GET', url:url, headers:HEADERS, responseType:'blob', timeout:60000, onload:function(res){ if(res.status===200){ saveFile(res.response, filename).then(function(r){ setStatus((r.ok?'✅ 已保存: ':'❌ ')+filename, r.ok?'ok':'err'); }); } else { setStatus('❌ 图片下载失败 HTTP '+res.status,'err'); } }, onerror:function(){ setStatus('❌ 图片下载出错','err'); } }); } function fetchBlob(url, filename, label) { setStatus('正在下载 '+label+'...','info'); GM_xmlhttpRequest({ method:'GET', url:url, headers:HEADERS, responseType:'blob', timeout:3600000, onprogress:function(e){ if(e.lengthComputable){ setProgress(e.loaded/e.total); setStatus('⬇ '+(e.loaded/1048576).toFixed(1)+' / '+(e.total/1048576).toFixed(1)+' MB','info'); } }, onload:function(res){ if(res.status===200){ setStatus('正在保存...','info'); saveFile(res.response, filename).then(function(r){ finishDL(r.ok, r.ok?(r.method==='fs'?'已存入目录: '+filename:'已触发下载: '+filename):r.error); }); } else finishDL(false,'HTTP '+res.status); }, onerror:function(e){ finishDL(false,e.error||'网络错误'); } }); } function finishDL(ok,msg){ isDownloading=false; setProgress(0); setBtnsEnabled(true); setStatus((ok?'✅ ':'❌ ')+msg,ok?'ok':'err'); } /* ══════════════════════════════════════════════════════════ ⑦ 信息提取(统一入口) ══════════════════════════════════════════════════════════ */ function extractFromRenderData() { // 抖音专用 if (!isDouyin()) return null; try { var el = document.getElementById('RENDER_DATA'); if (!el) return null; var raw = JSON.parse(decodeURIComponent(el.textContent)); var detail = null; for (var key in raw) { if (key === '_location' || key === 'app' || key === '11') continue; if (raw[key] && raw[key].videoDetail) { detail = raw[key].videoDetail; break; } } if (!detail) return null; var author = detail.authorInfo || detail.author || {}; var video = detail.video || {}; var textExtra = detail.textExtra || []; var coverList = ((video.cover||{}).urlList) || ((video.dynamicCover||{}).urlList) || ((video.originCover||{}).urlList) || []; var coverUrl = coverList[0] || bestCover() || ''; var avList = ((author.avatarThumb||{}).urlList) || ((author.avatarMedium||{}).urlList) || []; var avatarUrl = avList[0] || ''; var topics = textExtra.filter(function(t){ return t.hashtagName; }).map(function(t){ return '#'+t.hashtagName; }); var createTime= detail.createTime ? new Date(detail.createTime*1000).toLocaleString('zh-CN',{hour12:false}) : '未知'; var hasImages = !!(detail.imageAlbumMusicInfo || (detail.images && detail.images.length)); var container = findCurrentFeedItem(); var isImagePost = hasImages || (container && hasImageTextBadge(container)); var images = isImagePost ? (detail.images||[]).map(function(img){ return ((img.urlList||[])[0])||''; }).filter(Boolean) : []; return { platform:'抖音', author:author.nickname||author.uniqueId||'未知作者', authorId:author.uniqueId||'', avatarUrl:avatarUrl, createTime:createTime, desc:detail.desc||detail.title||'无文案', topics:topics, coverUrl:coverUrl, videoUrl: null, isImagePost: isImagePost, images: images }; } catch(e) { console.warn('[SVD] RENDER_DATA:', e); return null; } } function extractVideoInfo() { if (isDouyin()) { var rd = extractFromRenderData(); if (rd) { var videoEl = getCurrentVideoElement(); if (videoEl) rd.videoUrl = getVideoUrlFromDom(videoEl); return rd; } var sm = document.getElementById('slideMode'); if (sm) return extractFromContainer(sm, false); var pc = document.querySelector('.playerControlHeight'); if (pc) return extractFromContainer(pc, true); var fi = findCurrentFeedItem(); if (fi) return extractFromFeedItem(fi); return null; } else if (isXiaohongshu()) { return xhsNoteData; } return null; } function extractFromFeedItem(item) { var author = ((item.querySelector('.account-name')||{textContent:''}).textContent||'').trim() || '未知作者'; var avEl = item.querySelector('[class*="avatar"] img,.avatar img,img[class*="Avatar"]'); var avatarUrl = avEl ? avEl.src : ''; var desc = '无文案'; var te = item.querySelector('.title'); if (te) { var sp=te.querySelector('span'); desc=((sp&&sp.textContent)||te.textContent||'').replace('展开','').trim(); } var topics = []; var tags = item.querySelectorAll('a[href*="hashtag"],[class*="Tag"],[class*="tag"]'); for (var i=0;i|\r\n]/g,'_').trim().substring(0,80); } function getFilename(info, type, idx) { var customName = ''; var inputEl = q('svd-filename-input'); if (inputEl) { customName = inputEl.value.trim(); } if (customName !== '') { var safeCustom = sanitize(customName); if (type === 'video') return safeCustom + '.mp4'; if (type === 'cover') return safeCustom + '_封面.jpg'; if (type === 'image') return safeCustom + '_图文第'+(idx+1)+'张.jpg'; return safeCustom + '.bin'; } var a = sanitize(info.author); var d = sanitize((info.desc || '').substring(0,25)); if (type === 'video') return a + '-' + d + '.mp4'; if (type === 'cover') return a + '-封面.jpg'; if (type === 'image') return a + '-图文第'+(idx+1)+'张.jpg'; return a + '-文件.bin'; } async function saveFile(blob, filename) { if (!dirHandle) { return saveViaGM(blob, filename); } try { const parts = filename.split('/'); const finalName = parts.pop(); let currentHandle = dirHandle; for (const part of parts) { if (!part) continue; currentHandle = await currentHandle.getDirectoryHandle(part, { create: true }); } const fileHandle = await currentHandle.getFileHandle(finalName, { create: true }); const writable = await fileHandle.createWritable(); await writable.write(blob); await writable.close(); return { ok: true, method: 'fs' }; } catch (e) { console.warn('[SVD] 文件系统写入失败,回退到 GM_download:', e); return saveViaGM(blob, filename); } } function saveViaGM(blob, filename) { return new Promise(function(res) { var bu=URL.createObjectURL(blob); GM_download({url:bu,name:filename, onload: function(){ URL.revokeObjectURL(bu); res({ok:true,method:'gm'}); }, onerror: function(e){ URL.revokeObjectURL(bu); res({ok:false,error:e.error||'失败'}); } }); }); } /* ══════════════════════════════════════════════════════════ ⑨ UI样式与构建 ══════════════════════════════════════════════════════════ */ var THEME = { bg: '#ffffff', bg2: '#f7f8fa', bg3: '#f0f2f5', border: '#e4e6ea', border2: '#ced0d4', text: '#1c1e21', text2: '#65676b', text3: '#a8aaad', accent: '#fe2c55', accentBg: 'rgba(254,44,85,0.08)', purple: '#6366f1', green: '#16a34a', red: '#dc2626', blue: '#2563eb', shadow: '0 4px 24px rgba(0,0,0,0.10), 0 1px 4px rgba(0,0,0,0.06)' }; function injectStyles() { if (document.getElementById('svd-styles')) return; var s = document.createElement('style'); s.id = 'svd-styles'; var rules = [ '#svd-win{', ' position:fixed;top:58px;right:18px;width:410px;max-height:92vh;', ' overflow-y:auto;overflow-x:hidden;', ' background:'+THEME.bg+';', ' border:1px solid '+THEME.border+';border-radius:16px;', ' box-shadow:'+THEME.shadow+';', ' z-index:2147483647;', ' font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;', ' font-size:13px;color:'+THEME.text+';', ' scrollbar-width:thin;scrollbar-color:'+THEME.border+' transparent;', '}', '#svd-win::-webkit-scrollbar{width:5px}', '#svd-win::-webkit-scrollbar-thumb{background:'+THEME.border2+';border-radius:3px}', '#svd-win.svd-mini{max-height:52px;overflow:hidden}', '#svd-hdr{', ' position:sticky;top:0;z-index:5;', ' display:flex;align-items:center;gap:10px;', ' padding:14px 16px;', ' background:'+THEME.bg+';', ' border-bottom:1px solid '+THEME.border+';', ' border-radius:16px 16px 0 0;cursor:move;', '}', '.svd-badge{', ' width:28px;height:28px;border-radius:9px;', ' background:'+THEME.accent+';', ' display:flex;align-items:center;justify-content:center;flex-shrink:0;', ' box-shadow:0 2px 6px rgba(254,44,85,.35);', '}', '.svd-badge svg{width:15px;height:15px;fill:#fff}', '.svd-title{flex:1;font-weight:700;font-size:14px;color:'+THEME.text+';letter-spacing:-.2px}', '.svd-title em{color:'+THEME.accent+';font-style:normal}', '.svd-wbtns{display:flex;gap:6px}', '.svd-wbtn{', ' width:22px;height:22px;border-radius:50%;border:none;cursor:pointer;', ' font-size:11px;font-weight:700;line-height:1;', ' display:flex;align-items:center;justify-content:center;', ' transition:transform .15s,filter .15s;', '}', '.svd-wbtn:hover{transform:scale(1.15)}', '.svd-wbtn-min{background:#fdbc40;color:#5a3900}', '.svd-wbtn-x{background:#fd5f57;color:#5a0000}', '.svd-sec{padding:12px 16px;border-bottom:1px solid '+THEME.border+';background:'+THEME.bg+'}', '.svd-sec-lbl{font-size:10px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:'+THEME.text3+';margin-bottom:8px}', '#svd-path-row{display:flex;align-items:center;gap:8px}', '#svd-path-txt{', ' flex:1;padding:7px 10px;', ' background:'+THEME.bg2+';border:1px solid '+THEME.border+';border-radius:8px;', ' color:'+THEME.text3+';font-size:11.5px;', ' overflow:hidden;text-overflow:ellipsis;white-space:nowrap;', '}', '#svd-path-txt.set{color:'+THEME.green+';border-color:rgba(22,163,74,.3);background:rgba(22,163,74,.06)}', '#svd-path-btn{', ' padding:7px 13px;', ' background:'+THEME.purple+';', ' border:none;border-radius:8px;', ' color:#fff;font-size:11.5px;font-weight:600;', ' cursor:pointer;white-space:nowrap;', ' box-shadow:0 2px 8px rgba(99,102,241,.3);', ' transition:opacity .15s,transform .15s;', '}', '#svd-path-btn:hover{opacity:.88;transform:translateY(-1px)}', '#svd-capbar{', ' display:flex;align-items:center;gap:7px;', ' padding:7px 16px;', ' background:'+THEME.bg2+';', ' border-bottom:1px solid '+THEME.border+';', ' font-size:11px;', '}', '.svd-dot{width:7px;height:7px;border-radius:50%;background:'+THEME.text3+';flex-shrink:0;transition:background .3s}', '.svd-dot.on{background:#16a34a;box-shadow:0 0 5px rgba(22,163,74,.6);animation:svd-pulse 1.5s infinite}', '@keyframes svd-pulse{0%,100%{opacity:1}50%{opacity:.45}}', '#svd-captxt{color:'+THEME.text3+'}', '#svd-captxt.on{color:'+THEME.green+'}', '#svd-refresh{', ' width:100%;padding:8px;', ' background:'+THEME.bg2+';border:1px solid '+THEME.border+';border-radius:8px;', ' color:'+THEME.text2+';font-size:12px;font-weight:500;', ' cursor:pointer;display:flex;align-items:center;justify-content:center;gap:6px;', ' transition:background .15s,border-color .15s;', '}', '#svd-refresh:hover{background:'+THEME.bg3+';border-color:'+THEME.border2+'}', '#svd-tbl{padding:4px 16px 12px;background:'+THEME.bg+';border-bottom:1px solid '+THEME.border+'}', '.svd-row{display:grid;grid-template-columns:64px 1fr;gap:10px;padding:8px 0;border-bottom:1px solid '+THEME.bg3+';align-items:start}', '.svd-row:last-child{border-bottom:none}', '.svd-lbl{font-size:11.5px;font-weight:600;color:'+THEME.text3+';padding-top:1px}', '.svd-val{font-size:12.5px;color:'+THEME.text+';line-height:1.5}', '.svd-ac{display:flex;align-items:center;gap:9px}', '.svd-av{width:34px;height:34px;border-radius:50%;object-fit:cover;flex-shrink:0;border:2px solid '+THEME.accentBg+'}', '.svd-avph{width:34px;height:34px;border-radius:50%;background:'+THEME.bg3+';display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}', '.svd-aname{font-size:13px;font-weight:600;color:'+THEME.text+'}', '.svd-aid{font-size:11px;color:'+THEME.text3+';margin-top:1px}', '.svd-desc{max-height:60px;overflow-y:auto;font-size:12.5px;color:'+THEME.text2+';line-height:1.6;word-break:break-word;scrollbar-width:thin}', '.svd-tags{display:flex;flex-wrap:wrap;gap:4px}', '.svd-tag{padding:2px 8px;background:'+THEME.accentBg+';border:1px solid rgba(254,44,85,.2);border-radius:100px;color:'+THEME.accent+';font-size:11px;font-weight:500}', '.svd-cimg{width:100%;max-height:130px;object-fit:cover;border-radius:10px;display:block;cursor:pointer;transition:opacity .2s}', '.svd-cimg:hover{opacity:.85}', '.svd-cph{width:100%;height:70px;background:'+THEME.bg2+';border:1.5px dashed '+THEME.border+';border-radius:10px;display:flex;align-items:center;justify-content:center;color:'+THEME.text3+';font-size:12px}', '.svd-vid{width:100%;max-height:180px;border-radius:10px;background:#000;display:block;object-fit:contain}', '.svd-imgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}', '.svd-ig-item{position:relative;border-radius:8px;overflow:hidden;cursor:pointer;background:'+THEME.bg2+'}', '.svd-igthumb{width:100%;aspect-ratio:1;object-fit:cover;display:block;transition:transform .2s}', '.svd-ig-item:hover .svd-igthumb{transform:scale(1.04)}', '.svd-ig-dl{', ' position:absolute;bottom:5px;right:5px;', ' background:rgba(255,255,255,.9);backdrop-filter:blur(4px);', ' border:none;border-radius:6px;', ' width:26px;height:26px;', ' display:flex;align-items:center;justify-content:center;', ' cursor:pointer;font-size:13px;', ' opacity:0;transform:translateY(4px);', ' transition:opacity .18s,transform .18s;', ' box-shadow:0 1px 4px rgba(0,0,0,.15);', '}', '.svd-ig-item:hover .svd-ig-dl{opacity:1;transform:translateY(0)}', '.svd-ig-idx{', ' position:absolute;top:5px;left:6px;', ' background:rgba(0,0,0,.45);', ' color:#fff;font-size:10px;font-weight:600;', ' padding:1px 5px;border-radius:4px;', '}', '.svd-imgdl-all{', ' margin-top:7px;width:100%;padding:7px;', ' background:'+THEME.bg2+';border:1px solid '+THEME.border+';border-radius:8px;', ' color:'+THEME.text2+';font-size:11.5px;font-weight:500;', ' cursor:pointer;display:flex;align-items:center;justify-content:center;gap:5px;', ' transition:background .15s;', '}', '.svd-imgdl-all:hover{background:'+THEME.bg3+'}', '.svd-url{font-size:10.5px;color:'+THEME.text3+';word-break:break-all;background:'+THEME.bg2+';border-radius:6px;padding:5px 8px;max-height:36px;overflow:hidden}', '.svd-url.found{color:'+THEME.green+';background:rgba(22,163,74,.07)}', '.svd-na{color:'+THEME.text3+';font-style:italic;font-size:11.5px}', '#svd-actions{display:flex;gap:8px;padding:12px 16px;background:'+THEME.bg+';border-bottom:1px solid '+THEME.border+'}', '.svd-abtn{', ' flex:1;padding:10px 8px;border:none;border-radius:10px;', ' font-size:12.5px;font-weight:700;cursor:pointer;', ' display:flex;align-items:center;justify-content:center;gap:5px;', ' transition:transform .15s,box-shadow .15s,opacity .15s;', '}', '.svd-abtn:hover{transform:translateY(-1px);box-shadow:0 4px 14px rgba(0,0,0,.12)}', '.svd-abtn:disabled{opacity:.38;cursor:not-allowed;transform:none;box-shadow:none}', '.svd-dlv{background:'+THEME.accent+';color:#fff}', '.svd-dlc{background:'+THEME.bg2+';color:'+THEME.text2+';border:1.5px solid '+THEME.border+'}', '#svd-statarea{padding:9px 16px 13px;background:'+THEME.bg+'}', '#svd-prog{height:3px;background:'+THEME.bg3+';border-radius:2px;margin-bottom:7px;overflow:hidden;display:none}', '#svd-progfill{height:100%;width:0%;background:'+THEME.accent+';border-radius:2px;transition:width .2s}', '#svd-stattxt{font-size:11px;color:'+THEME.text3+';text-align:center;min-height:16px}', '#svd-stattxt.ok{color:'+THEME.green+'}', '#svd-stattxt.err{color:'+THEME.red+'}', '#svd-stattxt.info{color:'+THEME.blue+'}', '#svd-trig{position:fixed;bottom:108px;right:18px;width:46px;height:46px;border-radius:50%;', ' background:'+THEME.accent+';border:none;cursor:pointer;z-index:2147483646;', ' display:none;align-items:center;justify-content:center;', ' box-shadow:0 4px 16px rgba(254,44,85,.4);transition:transform .2s;}', '#svd-trig:hover{transform:scale(1.1)}', '#svd-trig svg{width:22px;height:22px;fill:#fff}', '/* 透明度滑块 */', '#svd-opacity-slider {', ' -webkit-appearance: none;', ' appearance: none;', ' width: 80px;', ' height: 4px;', ' background: '+THEME.border2+';', ' border-radius: 10px;', ' outline: none;', ' margin: 0 8px;', ' cursor: pointer;', '}', '#svd-opacity-slider::-webkit-slider-thumb {', ' -webkit-appearance: none;', ' appearance: none;', ' width: 14px;', ' height: 14px;', ' background: '+THEME.accent+';', ' border-radius: 50%;', ' cursor: pointer;', ' box-shadow: 0 1px 4px rgba(0,0,0,0.2);', ' border: 2px solid white;', '}', '#svd-opacity-slider::-moz-range-thumb {', ' width: 14px;', ' height: 14px;', ' background: '+THEME.accent+';', ' border-radius: 50%;', ' cursor: pointer;', ' box-shadow: 0 1px 4px rgba(0,0,0,0.2);', ' border: 2px solid white;', '}', '#svd-opacity-slider::-moz-range-track {', ' height: 4px;', ' background: '+THEME.border2+';', ' border-radius: 10px;', '}', '/* 作者归属栏 */', '#svd-author-bar{', ' padding:8px 16px 12px;', ' display:flex;align-items:center;justify-content:space-between;', ' border-top:1px solid '+THEME.border+';', ' background:'+THEME.bg+';', ' font-size:11px;', '}', '.svd-author{', ' color:'+THEME.text3+';', '}', '.svd-author a{', ' color:'+THEME.accent+';', ' text-decoration:none;', ' font-weight:600;', '}', '.svd-donate{', ' color:'+THEME.purple+';', ' cursor:pointer;', ' font-weight:500;', ' background:'+THEME.accentBg+';', ' padding:3px 8px;', ' borderRadius:12px;', ' transition:background .15s;', '}', '.svd-donate:hover{', ' background:rgba(254,44,85,0.15);', '}', '#svd-donate-modal{', ' position:fixed;top:0;left:0;width:100%;height:100%;', ' background:rgba(0,0,0,0.5);z-index:2147483648;', ' display:none;align-items:center;justify-content:center;', '}', '#svd-donate-content{', ' background:white;padding:24px 30px;border-radius:20px;', ' text-align:center;max-width:360px;', ' box-shadow:0 10px 40px rgba(0,0,0,0.2);', '}', '#svd-donate-content img{', ' width:280px;height:280px;margin:15px 0;', ' border-radius:12px;', '}', '#svd-donate-close{', ' margin-top:10px;padding:6px 20px;', ' background:'+THEME.accent+';color:white;border:none;border-radius:20px;', ' cursor:pointer;', '}' ]; s.textContent = rules.join(''); document.head.appendChild(s); } function buildWindow() { if (document.getElementById('svd-win')) return; var w = document.createElement('div'); w.id = 'svd-win'; var platformTitle = isDouyin() ? '短视频下载 Pro' : '小红书下载 Pro'; w.innerHTML = [ '
', '
', ' ' + platformTitle + '', ' ', '
', '
', '
', '
📁 保存路径
', '
未选择目录,将使用浏览器默认下载
', '
', '
', '
✏️ 文件命名 (留空则自动生成)
', ' ', '
', '
等待封面加载...
', '
', '
', '
平台
', '
作者
', '
发布时间
', '
发布文案
', '
话题
', '
封面
', '
视频
', '
链接
等待捕获...
', '
', '
', ' ', ' ', '
', '
点击「刷新」读取当前视频信息
', '
', ' By 花海🌸', ' ❤️ 赞赏支持', '
' ].join(''); document.body.appendChild(w); bindEvents(w); makeDraggable(w, w.querySelector('#svd-hdr')); createDonateModal(); } function createDonateModal() { if (document.getElementById('svd-donate-modal')) return; var modal = document.createElement('div'); modal.id = 'svd-donate-modal'; modal.innerHTML = [ '
', '

赞赏支持

', '

如果觉得好用,请作者喝杯咖啡☕

', ' 赞赏码', '

微信

', ' ', '
' ].join(''); document.body.appendChild(modal); document.getElementById('svd-donate-btn').addEventListener('click', function() { modal.style.display = 'flex'; }); document.getElementById('svd-donate-close').addEventListener('click', function() { modal.style.display = 'none'; }); modal.addEventListener('click', function(e) { if (e.target === modal) modal.style.display = 'none'; }); } function bindEvents(w) { w.querySelector('.svd-wbtn-min').addEventListener('click', function(){ w.classList.toggle('svd-mini'); }); w.querySelector('.svd-wbtn-x').addEventListener('click', function(){ w.style.display='none'; var t=q('svd-trig'); if(t) t.style.display='flex'; }); w.querySelector('#svd-path-btn').addEventListener('click', async function(){ if (!window.showDirectoryPicker) { setStatus('浏览器不支持目录选择', 'err'); return; } try { const dh = await window.showDirectoryPicker({ mode: 'readwrite' }); dirHandle = dh; // 仅能获取目录名,无法获取完整物理路径 const dirName = dh.name; const d = q('svd-path-txt'); d.textContent = '📁 ' + dirName + ' (已授权)'; d.classList.add('set'); d.title = '目录名:' + dirName + '\n浏览器限制,无法显示完整路径'; setStatus('下载目录: ' + dirName + ' (授权成功)', 'ok'); } catch (e) { if (e.name !== 'AbortError') { setStatus('目录选择失败: ' + e.message, 'err'); } } }); w.querySelector('#svd-refresh').addEventListener('click', function(){ doRefresh(); }); w.querySelector('#svd-btn-v').addEventListener('click', function(){ doDownloadVideo(); }); w.querySelector('#svd-btn-c').addEventListener('click', function(){ doDownloadCover(); }); // ========== 透明度滑块 ========== var opacitySlider = w.querySelector('#svd-opacity-slider'); if (opacitySlider) { var savedOpacity = GM_getValue('svd_win_opacity', 1.0); opacitySlider.value = savedOpacity; w.style.opacity = savedOpacity; opacitySlider.addEventListener('input', function(e) { var val = parseFloat(e.target.value); w.style.opacity = val; GM_setValue('svd_win_opacity', val); }); opacitySlider.addEventListener('mousedown', function(e) { e.stopPropagation(); }); } } function renderInfo(info) { videoInfo=info; if (!info){ setStatus('❌ 未能解析视频信息,请等视频播放后刷新','err'); return; } 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){ var u2=info.videoUrl; if(u2){ ue.textContent=u2.substring(0,90)+(u2.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) { var filename = getFilename(info, 'image', i).replace(/\.(jpg|jpeg|png|webp)$/i, '.mp4'); doDownload(live, filename, '实况视频'); } 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(){ info.images.forEach(function(item, i){ var url = typeof item === 'string' ? item : item.url; var liveUrl = (typeof item === 'object' && item.liveUrl) ? item.liveUrl : null; setTimeout(function(){ if (liveUrl) { var filename = getFilename(info, 'image', i).replace(/\.(jpg|jpeg|png|webp)$/i, '.mp4'); doDownload(liveUrl, filename, '实况视频'); } else { doDownloadImage(url, getFilename(info, 'image', i)); } }, i*300); }); }); container.appendChild(allBtn); } function avPh(){ var d=document.createElement('div'); d.className='svd-avph'; d.textContent='👤'; return d; } function q(id){ return document.getElementById(id); } function esc(s){ return String(s||'').replace(/&/g,'&').replace(//g,'>'); } 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 tickCapture() { var dot=q('svd-capdot'), txt=q('svd-captxt'); if(!dot||!txt) return; var cc = capturedCovers.length; if (cc){ dot.className='svd-dot on'; txt.className='on'; txt.textContent='已捕获 '+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 makeDraggable(el, handle) { var d=false,sx,sy,ox,oy; handle.addEventListener('mousedown',function(e){ if(e.target.classList.contains('svd-wbtn')) return; d=true; sx=e.clientX; sy=e.clientY; var r=el.getBoundingClientRect(); ox=r.left; oy=r.top; document.addEventListener('mousemove',mv); document.addEventListener('mouseup',mu); e.preventDefault(); }); function mv(e){ if(!d)return; el.style.left=(ox+e.clientX-sx)+'px'; el.style.top=(oy+e.clientY-sy)+'px'; el.style.right='auto'; } function mu(){ d=false; document.removeEventListener('mousemove',mv); document.removeEventListener('mouseup',mu); } } function buildTrigger(){ if (q('svd-trig')) return; var btn=document.createElement('button'); btn.id='svd-trig'; btn.title='打开下载助手'; btn.innerHTML=''; btn.addEventListener('click',function(){ var w=q('svd-win'); if(w){w.style.display='';btn.style.display='none';} }); document.body.appendChild(btn); } function doRefresh(){ setStatus('正在读取视频信息...','info'); setTimeout(function(){ var info = extractVideoInfo(); if (info) renderInfo(info); else setStatus('❌ 未能解析视频信息,请等视频播放后刷新', 'err'); }, 350); } function bootstrap(){ injectStyles(); buildWindow(); buildTrigger(); if (isDouyin()) { watchForNewItems(); setTimeout(function(){ setupItemObserver(); doRefresh(); }, 1800); } else { setTimeout(function(){ doRefresh(); }, 1500); } } if (document.readyState==='loading') document.addEventListener('DOMContentLoaded',function(){ setTimeout(bootstrap,500); }); else setTimeout(bootstrap,500); setInterval(function(){ if (!q('svd-win')) buildWindow(); if (!q('svd-trig')) buildTrigger(); if (!q('svd-styles')) injectStyles(); tickCapture(); var cur=location.href; if (cur!==lastNavUrl){ lastNavUrl=cur; capturedCovers=[]; xhsNoteData = null; setTimeout(doRefresh, isDouyin() ? 1800 : 1000); } }, 800); })();