// ==UserScript== // @name 短视频去水印下载 Pro (稳定提取版) // @name:zh-CN 短视频去水印下载 Pro (稳定提取版) // @version 2.5.2 // @license MIT // @description 抖音去水印下载 Pro — 采用稳定DOM提取+网络缓存双重保障,修复视频错乱与提取失败问题 // @author LightDownload (Merged & Fixed) // @match *://*.douyin.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 capturedVideos = []; var capturedCovers = []; 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 (_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; capturedVideos = []; capturedCovers = []; } } }); }, { threshold: [0.65] }); Array.prototype.forEach.call(items, function(item) { _itemObserver.observe(item); }); } var _domWatcher = null; function watchForNewItems() { if (_domWatcher) return; _domWatcher = new MutationObserver(function(m) { for (var i=0;i 0; } function isCoverUrl(url) { return /byteimg\.com\/img\/|douyinstatic\.com|pstatp\.com/.test(url) && /\.(jpe?g|webp|png)/.test(url) && !/avatar/.test(url); } function captureUrl(rawUrl) { if (!rawUrl || typeof rawUrl !== 'string') return; if (/^(blob:|data:)/.test(rawUrl)) return; var url; try { url = decodeURIComponent(rawUrl); } catch(e) { url = rawUrl; } if (isVideoUrl(url)) { pushCapture(capturedVideos, url, scoreVideoUrl(url)); } else if (isCoverUrl(url)) { pushCapture(capturedCovers, url, 0); } } function pushCapture(list, url, score) { var base = url.split('?')[0]; for (var i = 0; i < list.length; i++) { if (list[i].url.split('?')[0] === base) { list[i].ts = Date.now(); return; } } list.push({ url: url, score: score, ts: Date.now() }); list.sort(function(a, b) { return b.score !== a.score ? b.score - a.score : b.ts - a.ts; }); if (list.length > 40) list.pop(); } function bestVideo() { if (!capturedVideos.length) return null; var now = Date.now(); var fresh = capturedVideos.filter(function(c){ return now - c.ts < 90000; }); var pool = fresh.length ? fresh : capturedVideos; return pool[0].url; } 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; } (function setupInterceptors() { try { var oF = unsafeWindow.fetch.bind(unsafeWindow); unsafeWindow.fetch = function() { var a = Array.prototype.slice.call(arguments); captureUrl(typeof a[0] === 'string' ? a[0] : (a[0] && a[0].url) || ''); return oF.apply(unsafeWindow, a); }; } catch(e) {} try { var oO = unsafeWindow.XMLHttpRequest.prototype.open; unsafeWindow.XMLHttpRequest.prototype.open = function(m, url) { captureUrl(typeof url === 'string' ? url : ''); return oO.apply(this, arguments); }; } catch(e) {} })(); /* ══════════════════════════════════════════════════════════ ② 查找当前Feed Item(增强版) ══════════════════════════════════════════════════════════ */ function findCurrentFeedItem() { // 优先从正在播放的视频向上找 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; } } // 降级:取可视区域最大的 feed 容器 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) { 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() || ''; } /* ══════════════════════════════════════════════════════════ ⑤ 【修正】视频URL提取 —— 多重策略,确保能获取到链接 ══════════════════════════════════════════════════════════ */ function extractVideoUrl(container) { if (!container) { container = findCurrentFeedItem(); } if (!container) return null; var videoEl = container.querySelector('video'); if (!videoEl) return null; // 策略1:从 标签获取 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('douyinvod') > -1 || s.indexOf('.mp4') > -1) { return s; } } // 策略2:获取 video.currentSrc(播放器当前使用的源) 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; } } // 策略3:获取 video.src 属性 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; } } // 策略4:如果以上都失败,回退到网络捕获缓存(防止提取不到) var fallback = bestVideo(); if (fallback) { console.log('[SVD] DOM提取失败,使用网络捕获缓存:', fallback); return fallback; } return null; } // 处理302跳转,获取最终无水印直链 function resolveVideoUrl(rawUrl, callback) { if (!rawUrl) { callback(null); return; } var method = navigator.userAgent.indexOf('Firefox') !== -1 ? 'GET' : 'HEAD'; 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': 'https://www.douyin.com', 'Referer': 'https://www.douyin.com/' }; 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); } }); } /* ══════════════════════════════════════════════════════════ ⑥ RENDER_DATA 提取(保留,视频URL用实时提取) ══════════════════════════════════════════════════════════ */ function extractFromRenderData() { 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() { var rd = extractFromRenderData(); if (rd) { var container = findCurrentFeedItem() || document.querySelector('.playerControlHeight') || document.getElementById('slideMode'); rd.videoUrl = extractVideoUrl(container); 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; } 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 a=sanitize(info.author), 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'; } function saveFile(blob, filename) { if (dirHandle) { return dirHandle.getFileHandle(filename,{create:true}) .then(function(fh){ return fh.createWritable(); }) .then(function(wr){ return wr.write(blob).then(function(){ return wr.close(); }); }) .then(function(){ return {ok:true,method:'fs'}; }) .catch(function(){ return saveViaGM(blob,filename); }); } 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||'失败'}); } }); }); } // 【修正】视频下载:优先实时提取,若失败则提示 function doDownloadVideo() { if (isDownloading){ setStatus('下载进行中...','info'); return; } // 实时获取当前视频URL var container = findCurrentFeedItem() || document.querySelector('.playerControlHeight') || document.getElementById('slideMode'); var rawUrl = extractVideoUrl(container); if (!rawUrl) { setStatus('❌ 未找到视频链接,请确保视频正在播放', 'err'); return; } if (videoInfo) 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 || {author:'未知',desc:'视频'}, 'video'); fetchBlob(finalUrl, filename, '视频'); }); } 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'); } /* ══════════════════════════════════════════════════════════ ⑧ 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;', ' transition:max-height .3s cubic-bezier(.4,0,.2,1);', '}', '#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}' ]; 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'; w.innerHTML = [ '
', '
', ' 短视频下载 Pro', '
', '
', '
', '
📁 保存路径
', '
未选择目录,将使用浏览器默认下载
', '
', '
等待视频加载请求...
', '
', '
', '
平台
', '
作者
', '
发布时间
', '
发布文案
', '
话题
', '
封面
', '
视频
', '
链接
等待捕获...
', '
', '
', ' ', ' ', '
', '
点击「刷新」读取当前视频信息
' ].join(''); document.body.appendChild(w); bindEvents(w); makeDraggable(w, w.querySelector('#svd-hdr')); } 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', function(){ if (!window.showDirectoryPicker){ setStatus('浏览器不支持目录选择','err'); return; } window.showDirectoryPicker({mode:'readwrite'}).then(function(dh){ dirHandle=dh; var d=q('svd-path-txt'); d.textContent='📁 '+dh.name; d.classList.add('set'); setStatus('下载目录: '+dh.name,'ok'); }).catch(function(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(); }); } 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(url, idx){ var item=document.createElement('div'); item.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(){ item.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,i){ dlBtn.addEventListener('click',function(e){ e.stopPropagation(); doDownloadImage(u, getFilename(info,'image',i)); }); })(url,idx); item.appendChild(img); item.appendChild(numLabel); item.appendChild(dlBtn); grid.appendChild(item); }); 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(url,i){ setTimeout(function(){ 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 cv=capturedVideos.length, now=Date.now(); var fresh=cv&&(now-capturedVideos[0].ts<10000); if (cv){ dot.className='svd-dot on'; txt.className='on'; txt.textContent='已捕获 '+cv+' 条视频链接'+(fresh?' ✦':''); } else { dot.className='svd-dot'; txt.className=''; txt.textContent='等待视频加载请求...'; } if (videoInfo&&!videoInfo.isImagePost&&!videoInfo.videoUrl&&cv){ videoInfo.videoUrl = extractVideoUrl(findCurrentFeedItem()) || bestVideo(); patchVideoUI(videoInfo.videoUrl); } if (videoInfo&&!videoInfo.coverUrl&&capturedCovers.length){ videoInfo.coverUrl=bestCover(); patchCoverUI(videoInfo.coverUrl); } } function patchVideoUI(url){ var el=q('svd-f-vid'), ue=q('svd-f-url'); if(!el||!url) return; if (!el.querySelector('video')){ var vd=document.createElement('video'); vd.className='svd-vid'; vd.controls=true; vd.preload='metadata'; if(videoInfo&&videoInfo.coverUrl) vd.poster=videoInfo.coverUrl; vd.src=url; el.innerHTML=''; el.appendChild(vd); } if(ue){ ue.textContent=url.substring(0,90)+(url.length>90?'…':''); ue.className='svd-url found'; } var vb=q('svd-btn-v'); if(vb) vb.disabled=false; } 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(){ renderInfo(extractVideoInfo()); }, 350); } function bootstrap(){ injectStyles(); buildWindow(); buildTrigger(); watchForNewItems(); setTimeout(function(){ setupItemObserver(); doRefresh(); },1800); } 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; capturedVideos=[]; capturedCovers=[]; setTimeout(doRefresh,1800); } }, 800); })();