// ==UserScript== // @name 短视频去水印下载 Pro + 全站嗅探下载 // @name:zh-CN 短视频去水印下载 Pro + 全站嗅探下载 // @version 3.3.3 // @license MIT // @description 🎯 支持抖音、小红书网页版无水印高清下载。抖音:自动识别当前播放视频,一键下载无水印 MP4、封面图,支持图文帖多图批量保存,自动提取作者/标题/发布时间;小红书:支持视频笔记、图文笔记,自动解析无水印原图和视频链接,批量下载图片同时嗅探所有网页中的 M3U8、MP4 视频链接,可直接复制或下载。 // @tag 视频下载 视频嗅探 去水印 全平台下载 抖音 小红书 // @author LightDownload // @match *://*.douyin.com/* // @match *://*.xiaohongshu.com/* // @match *://*.bilibili.com/* // @match *://*/* // @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 imageDownloading = false; var capturedCovers = []; var xhsNoteData = null; var detectedUrls = new Set(); var sniffMemoryVault = []; var isSniffSectionVisible = GM_getValue('sniff_section_visible', false); /* ══════════════════════════════════════════════════════════ PLATFORM DETECTION ══════════════════════════════════════════════════════════ */ function isDouyin() { return location.hostname.includes('douyin.com'); } function isXiaohongshu() { return location.hostname.includes('xiaohongshu.com'); } function isBili() { return location.hostname.includes('bilibili.com'); } function isDyOrXhs() { return isDouyin() || isXiaohongshu(); } /* ══════════════════════════════════════════════════════════ UTILS ══════════════════════════════════════════════════════════ */ 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); } /** 从 URL 提取自然文件名,用于嗅探下载 */ function getUrlFilename(url) { try { var pathname = new URL(url).pathname; var parts = pathname.split('/').filter(Boolean); var last = parts[parts.length - 1] || ''; if (last) return decodeURIComponent(last); } catch(e) {} var m = url.match(/\/([^/?#]+)(?:\?|#|$)/); return m ? decodeURIComponent(m[1]) : 'video.mp4'; } function copyToClipboard(text) { if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text); return new Promise(function(resolve, reject) { var ta = document.createElement('textarea'); ta.value = text; ta.style.cssText = 'position:fixed;opacity:0'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); resolve(); } catch(e) { reject(e); } document.body.removeChild(ta); }); } /* ══════════════════════════════════════════════════════════ INDEXEDDB — 目录句柄持久化 ══════════════════════════════════════════════════════════ */ 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'); } /* ══════════════════════════════════════════════════════════ SNIFFER — 视频链接嗅探 ══════════════════════════════════════════════════════════ */ function getResTag(u) { u = u.toLowerCase(); if (u.includes('8k') || u.includes('4320')) return '[👑 8K] '; if (u.includes('4k') || u.includes('2160')) return '[💎 4K] '; if (u.includes('1080') || u.includes('1920')) return '[🔥 1080P] '; if (u.includes('720')) return '[🎬 720P] '; if (u.includes('480')) return '[📺 480P] '; return ''; } function tryRemoveWatermark(rawUrl) { try { var u = new URL(rawUrl, location.href); var wmParams = ['watermark','wm','x-watermark','x-wm','logo']; var changed = false; wmParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); var newPath = u.pathname.replace(/\/watermark\//i, '/'); if (newPath !== u.pathname) { u.pathname = newPath; changed = true; } return changed ? u.href : null; } catch(e) {} return null; } function addUrl(url, customTitle, isBiliBatch) { if (typeof url !== 'string') return; var fingerprint = url.split('?')[0]; if (detectedUrls.has(fingerprint)) return; if (!isBiliBatch && !/\.(m3u8|mp4|ts|m4s|mpd|m4v|mkv|webm|mov|avi|flv)(\?|$)/i.test(url) && !/\/video\/|\/vod\/|\/stream\/|\/playlist\/|m3u8/i.test(url)) return; if (url.includes('fragment') && (url.includes('.ts') || url.includes('seg-'))) return; if (window.self !== window.top) { window.top.postMessage({ type: 'VIDEO_SNIFF_ADD', url: url, customTitle: customTitle, isBiliBatch: isBiliBatch }, '*'); return; } detectedUrls.add(fingerprint); sniffMemoryVault.push({ url: url, customTitle: customTitle, isBiliBatch: isBiliBatch }); renderSingleSniffItem({ url: url, customTitle: customTitle, isBiliBatch: isBiliBatch }); updateSniffStatus(); if (customTitle && !isBiliBatch) { var cleanUrl = tryRemoveWatermark(url); if (cleanUrl) { var cleanFP = cleanUrl.split('?')[0]; if (!detectedUrls.has(cleanFP)) addUrl(cleanUrl, customTitle + '(去水印尝试)', false); } } } function renderSingleSniffItem(item) { var list = q('svd-m3u8-list'); if (!list) return; var li = document.createElement('li'); li.className = 'svd-m3u8-item'; var tag = item.isBiliBatch ? '[🎬 选集] ' : getResTag(item.url); var name = item.customTitle ? (tag + item.customTitle) : (tag + item.url.split('?')[0].substring(0, 60) + '...'); li.innerHTML = '' + '
' + '
' + name + '
' + '
' + '' + '' + '
'; li.querySelector('.svd-sniff-copy').onclick = function(e) { e.stopPropagation(); copyToClipboard(item.url).then(function() { setStatus('链接已复制', 'ok'); }).catch(function() { setStatus('复制失败', 'err'); }); }; li.querySelector('.svd-sniff-dl').onclick = function(e) { e.stopPropagation(); // 使用 URL 中的自然文件名,而非写死的通用名 downloadBestQuality(item.url, getUrlFilename(item.url), '嗅探视频'); }; li.querySelector('input').addEventListener('click', function(e) { e.stopPropagation(); }); list.prepend(li); } function captureVideoElementSrcs() { document.querySelectorAll('video').forEach(function(v) { var src = v.currentSrc || v.src; if (src && !/^(blob:|data:)/i.test(src)) addUrl(src); v.querySelectorAll('source').forEach(function(s) { var ss = s.getAttribute('src'); if (ss && !/^(blob:|data:)/i.test(ss)) addUrl(ss); }); }); } function updateSniffStatus() { if (isDyOrXhs()) return; if (isSniffSectionVisible) captureVideoElementSrcs(); if (isSniffSectionVisible) { var count = detectedUrls.size; setStatus(count > 0 ? ('✅ 已嗅探到 ' + count + ' 个链接,可下载或复制') : '📡 嗅探面板已开启,播放视频后自动捕获链接', count > 0 ? 'ok' : 'info'); } else { setStatus('📡 请打开嗅探面板以捕获视频链接', 'info'); } } /* ══════════════════════════════════════════════════════════ NETWORK INTERCEPTORS — 封面捕获 + 嗅探 + 小红书数据 ══════════════════════════════════════════════════════════ */ function isCoverUrl(url) { return /byteimg\.com\/img\/|douyinstatic\.com|pstatp\.com|xhscdn\.com|xiaohongshu\.com/.test(url) && /\.(jpe?g|webp|png)/.test(url) && !/avatar/.test(url); } function captureCoverUrl(rawUrl) { if (!rawUrl || typeof rawUrl !== 'string' || /^(blob:|data:)/.test(rawUrl)) return; var url; try { url = decodeURIComponent(rawUrl); } catch(e) { url = rawUrl; } if (isCoverUrl(url)) pushCoverCapture(url); } function pushCoverCapture(url) { var base = url.split('?')[0]; for (var i = 0; i < capturedCovers.length; i++) { if (capturedCovers[i].url.split('?')[0] === base) { capturedCovers[i].ts = Date.now(); return; } } capturedCovers.push({ url: url, ts: Date.now() }); capturedCovers.sort(function(a, b) { return b.ts - a.ts; }); if (capturedCovers.length > 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; } function extractXiaohongshuFromResponse(responseText) { try { var data = JSON.parse(responseText); var note = null; if (data && data.data) { note = data.data.note_card || data.data.note || (data.data.items && data.data.items.length && data.data.items[0].note_card) || null; } if (!note) return; _buildXhsNoteData(note, null); } catch(e) { console.warn('[XHS] 解析笔记数据失败:', e); } } /** 统一构建 XHS 笔记对象,避免重复代码 */ function _buildXhsNoteData(note, noteIdOverride) { var images = []; if (note.image_list) { images = note.image_list.map(function(img) { var infoList = img.info_list || []; var url = img.url_default || (infoList.length && infoList[infoList.length-1].url) || (infoList[0] && infoList[0].url) || ''; var liveUrl = (img.live_photo && img.stream && img.stream.h264 && img.stream.h264[0] && img.stream.h264[0].master_url) || ''; return { url: url, liveUrl: liveUrl }; }); } var videoUrl = ''; if (note.video) { var stream = (note.video.media && note.video.media.stream) || note.video.stream || null; videoUrl = (stream && stream.h265 && stream.h265[0] && stream.h265[0].master_url) || (stream && stream.h264 && stream.h264[0] && stream.h264[0].master_url) || ''; } xhsNoteData = { platform: '小红书', author: (note.user && (note.user.nickname || note.user.nick_name)) || '未知作者', authorId: (note.user && note.user.user_id) || '', avatarUrl: (note.user && note.user.avatar) || '', desc: note.title || note.desc || '无文案', createTime: note.time ? new Date(note.time).toLocaleString('zh-CN', {hour12:false}) : '未知', coverUrl: (note.image_list && note.image_list[0] && note.image_list[0].url_default) || (note.video && note.video.image && note.video.image.thumbnail) || '', videoUrl: videoUrl, isImagePost: !videoUrl && images.length > 0, images: images, noteId: note.note_id || noteIdOverride || '', ipLocation: note.ip_location || '', topics: [] }; renderInfo(xhsNoteData); } (function setupInterceptors() { // 劫持 fetch try { var _origFetch = unsafeWindow.fetch; unsafeWindow.fetch = function() { var a = Array.prototype.slice.call(arguments); var url = typeof a[0] === 'string' ? a[0] : (a[0] && a[0].url) || ''; captureCoverUrl(url); if (url) { try { var abs = new URL(url, location.href).href; if (/\.(m3u8|mp4)(\?|$)/i.test(abs)) addUrl(abs); } catch(e) {} } return _origFetch.apply(this, a); }; } catch(e) {} // 劫持 XHR var _origOpen = unsafeWindow.XMLHttpRequest.prototype.open; var _origSend = unsafeWindow.XMLHttpRequest.prototype.send; unsafeWindow.XMLHttpRequest.prototype.open = function(method, url) { this._url = url; captureCoverUrl(typeof url === 'string' ? url : ''); return _origOpen.apply(this, arguments); }; unsafeWindow.XMLHttpRequest.prototype.send = function() { var self = this, url = self._url; if (url) { try { var abs = new URL(url, location.href).href; if (/\.(m3u8|mp4)(\?|$)/i.test(abs)) addUrl(abs); } catch(e) {} } if (isXiaohongshu() && url && ( url.includes('/api/sns/web/v1/feed') || url.includes('/api/sns/web/v2/note/detail') || url.includes('/api/sns/web/v1/note/user_posted') || url.includes('/api/sns/web/v1/note/detail') )) { self.addEventListener('load', function() { if (self.status === 200) extractXiaohongshuFromResponse(self.responseText); }); } return _origSend.apply(this, arguments); }; // 小红书详情页备用:__INITIAL_STATE__ if (isXiaohongshu() && location.pathname.startsWith('/explore/')) { function tryExtractInitialState() { try { var state = unsafeWindow.__INITIAL_STATE__; if (!state || !state.note) return false; var noteId = location.pathname.split('/').pop(); var noteMap = state.note.noteDetailMap; var noteEntry = noteMap && noteMap[noteId] && noteMap[noteId].note; if (!noteEntry) return false; _buildXhsNoteData(noteEntry, noteId); return true; } catch(e) {} return false; } setTimeout(function() { if (!xhsNoteData) { if (!tryExtractInitialState()) setTimeout(function() { if (!xhsNoteData) tryExtractInitialState(); }, 2000); } }, 2500); } // B站批量扫描 if (isBili()) { window.scanBili = function() { var count = 0; document.querySelectorAll('.imageListItem_wrap__o28QW, .video-pod__item').forEach(function(el) { var bv = el.getAttribute('data-key'); var a = el.querySelector('a'); if (bv) { addUrl('https://www.bilibili.com/video/' + bv, el.querySelector('.title') && el.querySelector('.title').innerText.trim(), true); count++; } else if (a) { addUrl(new URL(a.getAttribute('href'), location.href).href, el.querySelector('.imageListItem_titleWrap__YTlLH') && el.querySelector('.imageListItem_titleWrap__YTlLH').getAttribute('title'), true); count++; } }); setStatus(count > 0 ? ('已扫描到 ' + count + ' 个选集') : '未找到可扫描的选集', count > 0 ? 'ok' : 'info'); }; } // 跨 iframe 消息接收 window.addEventListener('message', function(e) { if (e.data && e.data.type === 'VIDEO_SNIFF_ADD') { addUrl(e.data.url, e.data.customTitle, e.data.isBiliBatch); } }); })(); /* ══════════════════════════════════════════════════════════ DOUYIN OBSERVER ══════════════════════════════════════════════════════════ */ var _itemObserver = null, _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 = []; xhsNoteData = null; if (!isDownloading) setTimeout(doRefresh, 900); } } }); }, { threshold: [0.65] }); Array.prototype.forEach.call(items, function(item) { _itemObserver.observe(item); }); } var _domWatcher = null; function watchForNewItems() { if (!isDouyin() || _domWatcher) return; _domWatcher = new MutationObserver(function(m) { for (var i = 0; i < m.length; i++) { if (m[i].addedNodes.length) { setTimeout(setupItemObserver, 200); break; } } }); _domWatcher.observe(document.body, { childList: true, subtree: true }); } /* ══════════════════════════════════════════════════════════ VIDEO EXTRACTION — 抖音 ══════════════════════════════════════════════════════════ */ 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' || el.id === 'slideMode' || 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++) { if (all[i].textContent.trim() === '图文') return true; } return !!( container.querySelector('.xgplayer-progress-outer.picture') || container.querySelector('[class*="pictureMode"],[class*="PicTextTag"],[class*="picTextTag"]') || container.querySelector('[class*="imageTextTag"],[class*="ImageTextTag"],[class*="img-text-tag"]') || container.getAttribute('data-type') === 'image' ); } 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 || 0; var h = img.naturalHeight || img.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 extractSlideImages(container) { var images = [], seen = {}; var slideSelectors = '[class*="swiper-slide"],[class*="SwiperSlide"],[class*="slide-item"],[class*="ImageItem"],[class*="imageItem"],[class*="pic-item"]'; var slides = container.querySelectorAll(slideSelectors); if (slides.length >= 2) { for (var i = 0; i < slides.length; i++) { var img = (function(sl) { var imgs = sl.querySelectorAll('img'); for (var j = 0; j < imgs.length; j++) { if (isContentImg(imgs[j])) return imgs[j].src || imgs[j].getAttribute('data-src') || ''; } return null; })(slides[i]); if (img && !seen[img]) { seen[img] = true; images.push(img); } } if (images.length >= 2) return images; } var wrapper = container.querySelector('[class*="swiper-wrapper"],[class*="SwiperWrapper"],[class*="carousel"],[class*="Carousel"]'); if (wrapper) { 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); } } } } return images; } function detectImagePost(container) { if (!container || !hasImageTextBadge(container)) return { isImage: false, images: [] }; return { isImage: true, images: extractSlideImages(container) }; } 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?:[^"')]+)["']?\)/) || window.getComputedStyle(xgPoster).backgroundImage.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,同时提取视频信息和最高清视频URL */ 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 || []; // 提取最高清视频 URL(合并原 getHighQualityVideoUrlFromRenderData) var videoUrl = 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 && best.play_addr.url_list && best.play_addr.url_list.length) { videoUrl = best.play_addr.url_list[0].replace(/^\/\//, 'https://'); } } if (!videoUrl) { var pa = video.play_addr; if (pa && pa.url_list && pa.url_list.length) videoUrl = pa.url_list[0].replace(/^\/\//, 'https://'); } if (!videoUrl) { var h264 = video.h264_packed_addr; if (h264 && h264.url_list && h264.url_list.length) videoUrl = h264.url_list[0].replace(/^\/\//, 'https://'); } 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 topics = textExtra.filter(function(t) { return t.hashtagName; }).map(function(t) { return '#' + t.hashtagName; }); var rawTime = detail.create_time || detail.createTime; 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) { var list = img.urlList || []; return list[list.length - 1] || list[0] || ''; }).filter(Boolean) : []; return { platform: '抖音', author: author.nickname || author.uniqueId || '未知作者', authorId: author.uniqueId || '', avatarUrl: avList[0] || '', createTime: rawTime ? new Date(rawTime * 1000).toLocaleString('zh-CN', {hour12:false}) : '未知', desc: detail.desc || detail.title || '无文案', topics: topics, coverUrl: coverUrl, videoUrl: videoUrl, isImagePost: isImagePost, images: images }; } catch(e) { console.warn('[SVD] RENDER_DATA:', e); return null; } } function getHeaders() { var origin = location.origin, referer = location.href; if (isDouyin()) { origin = 'https://www.douyin.com'; referer = 'https://www.douyin.com/'; } else if (isXiaohongshu()) { origin = 'https://www.xiaohongshu.com'; referer = 'https://www.xiaohongshu.com/'; } return { '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': origin, 'Referer': referer }; } function getCurrentVideoElement() { var container = findCurrentFeedItem() || document.querySelector('.playerControlHeight') || document.getElementById('slideMode'); return container ? container.querySelector('video') : null; } function getVideoUrlFromDom(videoEl) { if (!videoEl) return null; var src = videoEl.currentSrc || videoEl.getAttribute('src') || ''; if (src && !/^(blob:|data:)/.test(src) && /^https?:/.test(src)) { try { return decodeURI(src); } catch(e) { return src; } } var sources = videoEl.querySelectorAll('source'); for (var i = 0; i < sources.length; i++) { var s = sources[i].getAttribute('src') || ''; if (s && !/^(blob:|data:)/.test(s) && /^https?:/.test(s)) { try { return decodeURI(s); } catch(e) { return s; } } } return null; } function extractVideoInfo() { if (isDouyin()) { var rd = extractFromRenderData(); if (rd) { // RENDER_DATA 已包含 videoUrl;若仍为 null 则从 DOM 补充 if (!rd.videoUrl) rd.videoUrl = getVideoUrlFromDom(getCurrentVideoElement()); 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 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 < tags.length; i++) { var tt = tags[i].textContent.trim(); if (tt && tt[0] === '#' && topics.indexOf(tt) === -1) topics.push(tt); } var imgPost = detectImagePost(item); var timeEl = item.querySelector('[class*="create-time"],[class*="createTime"],[data-e2e="video-time"],[class*="publish-time"]'); return { platform: '抖音', author: author, authorId: '', avatarUrl: avEl ? avEl.src : '', createTime: timeEl ? timeEl.textContent.trim() : '未知', desc: desc, topics: topics, coverUrl: extractCover(item), videoUrl: imgPost.isImage ? null : getVideoUrlFromDom(item.querySelector('video')), isImagePost: imgPost.isImage, images: imgPost.images }; } function extractFromContainer(container, isDetail) { var an = container.querySelector('.account-name,[class*="nickname"] span,[class*="accountName"] span'); var desc = '无文案'; if (isDetail) { var h1 = container.querySelector('h1'); if (h1) desc = h1.textContent.trim(); } else { var tn = container.querySelector('.title'); if (tn) desc = tn.textContent.replace('展开', '').trim(); } var imgPost = detectImagePost(container); var timeEl = container.querySelector('[class*="create-time"],[class*="createTime"],[data-e2e="video-time"],[class*="publish-time"]'); return { platform: '抖音', author: (an && an.textContent.trim()) || '未知作者', authorId: '', avatarUrl: container.querySelector('[class*="avatar"] img,.avatar img') ? container.querySelector('[class*="avatar"] img,.avatar img').src : '', createTime: timeEl ? timeEl.textContent.trim() : '未知', desc: desc, topics: [], coverUrl: extractCover(container), videoUrl: imgPost.isImage ? null : getVideoUrlFromDom(container.querySelector('video')), isImagePost: imgPost.isImage, images: imgPost.images }; } /* ══════════════════════════════════════════════════════════ DOWNLOAD CORE ══════════════════════════════════════════════════════════ */ function getFilename(info, type, idx) { var customName = ''; var inputEl = q('svd-filename-input'); if (inputEl) customName = inputEl.value.trim(); if (customName) { var sc = sanitize(customName); if (type === 'video') return sc + '.mp4'; if (type === 'cover') return sc + '_封面.jpg'; if (type === 'image') return sc + '_图文第' + (idx + 1) + '张.jpg'; return sc + '.bin'; } 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 resolveVideoUrl(rawUrl, callback) { if (!rawUrl) { callback(null); return; } var method = navigator.userAgent.indexOf('Firefox') !== -1 ? 'GET' : 'HEAD'; GM_xmlhttpRequest({ method: method, url: rawUrl, headers: getHeaders(), cookie: document.cookie, timeout: 20000, onload: function(res) { if (res.status === 200) callback(rawUrl); else if (res.status === 302) { var m = (res.responseHeaders || '').match(/[Ll]ocation:\s*(.+)/); callback(m ? m[1].trim() : rawUrl); } else if (res.status === 401 || res.status === 403) { finishDL(false, '链接已失效,请重新加载视频后下载'); } 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); var lockedFilename = getFilename(videoInfo, 'video'); var lockedAuthor = videoInfo.author || '未知'; var ue = q('svd-f-url'); if (ue && rawUrl) { ue.textContent = rawUrl.substring(0, 90) + (rawUrl.length > 90 ? '…' : ''); ue.className = 'svd-url found'; } resolveVideoUrl(rawUrl, function(finalUrl) { if (!finalUrl) { finishDL(false, '无法解析视频地址'); return; } fetchBlob(finalUrl, lockedFilename, '视频 · ' + lockedAuthor); }); } function doDownloadVideo() { if (isDownloading) { setStatus('下载进行中...', 'info'); return; } if (isDouyin()) { var rd = extractFromRenderData(); if (rd && rd.videoUrl) { startDownloadWithUrl(rd.videoUrl); return; } var domUrl = getVideoUrlFromDom(getCurrentVideoElement()); if (domUrl) { startDownloadWithUrl(domUrl); return; } // 动态捕获 isDownloading = true; setBtnsEnabled(false); setStatus('🔍 正在捕获视频链接...', 'info'); var captureDone = false, captureTimer = null; var origFetch = unsafeWindow.fetch; var origXHROpen = unsafeWindow.XMLHttpRequest.prototype.open; var origXHRSend = unsafeWindow.XMLHttpRequest.prototype.send; function captureAndRestore(url) { if (captureDone) return; captureDone = true; unsafeWindow.fetch = origFetch; unsafeWindow.XMLHttpRequest.prototype.open = origXHROpen; unsafeWindow.XMLHttpRequest.prototype.send = origXHRSend; if (captureTimer) clearTimeout(captureTimer); var decoded; try { decoded = decodeURI(url); } catch(e) { decoded = url; } startDownloadWithUrl(decoded); } 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 && (url.indexOf('.mp4') > -1 || url.indexOf('mime_type=video') > -1 || url.indexOf('douyinvod') > -1)) captureAndRestore(url); if (url) { try { var a = new URL(url, location.href).href; if (/\.(m3u8|mp4)(\?|$)/i.test(a)) addUrl(a); } catch(e) {} } return origFetch.apply(this, args); }; unsafeWindow.XMLHttpRequest.prototype.open = function(method, url) { if (typeof url === 'string' && (url.indexOf('.mp4') > -1 || url.indexOf('mime_type=video') > -1 || url.indexOf('douyinvod') > -1)) captureAndRestore(url); return origXHROpen.apply(this, arguments); }; var videoEl = getCurrentVideoElement(); if (videoEl) videoEl.load(); captureTimer = setTimeout(function() { if (!captureDone) { unsafeWindow.fetch = origFetch; unsafeWindow.XMLHttpRequest.prototype.open = origXHROpen; unsafeWindow.XMLHttpRequest.prototype.send = origXHRSend; setStatus('❌ 捕获超时,请重试或刷新页面', 'err'); isDownloading = false; setBtnsEnabled(true); setProgress(0); } }, 8000); } else if (isXiaohongshu()) { if (!videoInfo || !videoInfo.videoUrl) { setStatus('❌ 未找到视频链接', 'err'); return; } startDownloadWithUrl(videoInfo.videoUrl); } else { setStatus('请使用嗅探面板下载', 'info'); } } 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); GM_xmlhttpRequest({ method: navigator.userAgent.indexOf('Firefox') !== -1 ? 'GET' : 'HEAD', url: url, headers: getHeaders(), cookie: document.cookie, 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 || r.status === 401) { finishDL(false, '链接已失效,请等视频重新加载后下载'); return; } fetchBlob(fu, filename, label); }, onerror: function() { fetchBlob(url, filename, label); } }); } function doDownloadImage(url, filename, onComplete) { setStatus('正在下载图片: ' + filename, 'info'); GM_xmlhttpRequest({ method: 'GET', url: url, headers: getHeaders(), cookie: document.cookie, 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'); if (onComplete) onComplete(r.ok); }); } else { setStatus('❌ 图片下载失败 HTTP ' + res.status, 'err'); if (onComplete) onComplete(false); } }, onerror: function() { setStatus('❌ 图片下载出错', 'err'); if (onComplete) onComplete(false); } }); } function resolveBestM3u8Stream(m3u8Url, content) { var lines = content.split('\n'), bestBw = -1, bestUri = null, curBw = null; for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); if (line.startsWith('#EXT-X-STREAM-INF')) { var bwMatch = line.match(/BANDWIDTH=(\d+)/); curBw = bwMatch ? parseInt(bwMatch[1], 10) : 0; } else if (line && !line.startsWith('#') && curBw !== null) { if (curBw > bestBw) { bestBw = curBw; bestUri = line; } curBw = null; } } if (!bestUri) return null; try { return new URL(bestUri, m3u8Url).href; } catch(e) { return bestUri; } } function downloadBestQuality(rawUrl, filename, label) { if (!rawUrl) { setStatus('❌ 无效链接', 'err'); return; } if (/\.mp4(\?|$)/i.test(rawUrl) && !/\.m3u8/i.test(rawUrl)) { fetchBlob(rawUrl, filename, label); return; } setStatus('正在解析视频清晰度...', 'info'); GM_xmlhttpRequest({ method: 'GET', url: rawUrl, headers: getHeaders(), cookie: document.cookie, timeout: 30000, onload: function(res) { if (res.status === 200) { var best = resolveBestM3u8Stream(rawUrl, res.responseText); fetchBlob(best || rawUrl, filename, label); } else { fetchBlob(rawUrl, filename, label); } }, onerror: function() { fetchBlob(rawUrl, filename, label); } }); } function fetchBlob(url, filename, label) { setStatus('正在下载 ' + label + '...', 'info'); GM_xmlhttpRequest({ method: 'GET', url: url, headers: getHeaders(), cookie: document.cookie, responseType: 'blob', timeout: 600000, 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'); } async function saveFile(blob, filename) { if (!dirHandle) return saveViaGM(blob, filename); try { var parts = filename.split('/'), finalName = parts.pop(); var cur = dirHandle; for (var i = 0; i < parts.length; i++) { if (parts[i]) cur = await cur.getDirectoryHandle(parts[i], { create: true }); } var fileHandle = await cur.getFileHandle(finalName, { create: true }); var writable = await fileHandle.createWritable(); await writable.write(blob); await writable.close(); return { ok: true, method: 'fs' }; } catch(e) { console.warn('[SVD] FS写入失败,回退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 — STYLES ══════════════════════════════════════════════════════════ */ var T = { 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 (q('svd-styles')) return; var s = document.createElement('style'); s.id = 'svd-styles'; s.textContent = [ '#svd-win{position:fixed;top:58px;right:18px;width:410px;max-height:96vh;overflow-y:auto;overflow-x:hidden;resize:both;min-width:300px;max-width:680px;min-height:180px;background:'+T.bg+';border:1px solid '+T.border+';border-radius:16px;box-shadow:'+T.shadow+';z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;font-size:13px;color:'+T.text+';scrollbar-width:thin;scrollbar-color:'+T.border+' transparent}', '#svd-win::-webkit-scrollbar{width:5px}#svd-win::-webkit-scrollbar-thumb{background:'+T.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:'+T.bg+';border-bottom:1px solid '+T.border+';border-radius:16px 16px 0 0;cursor:move}', '.svd-badge{width:28px;height:28px;border-radius:9px;background:'+T.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:'+T.text+';letter-spacing:-.2px}.svd-title em{color:'+T.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}.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 '+T.border+';background:'+T.bg+'}', '.svd-sec-lbl{font-size:10px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:'+T.text3+';margin-bottom:8px}', '#svd-path-row{display:flex;align-items:center;gap:8px}', '#svd-path-txt{flex:1;padding:7px 10px;background:'+T.bg2+';border:1px solid '+T.border+';border-radius:8px;color:'+T.text3+';font-size:11.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}', '#svd-path-txt.set{color:'+T.green+';border-color:rgba(22,163,74,.3);background:rgba(22,163,74,.06)}', '#svd-path-btn{padding:7px 13px;background:'+T.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:'+T.bg2+';border-bottom:1px solid '+T.border+';font-size:11px}', '.svd-dot{width:7px;height:7px;border-radius:50%;background:'+T.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:'+T.text3+'}#svd-captxt.on{color:'+T.green+'}', '#svd-refresh{width:100%;padding:8px;background:'+T.bg2+';border:1px solid '+T.border+';border-radius:8px;color:'+T.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:'+T.bg3+';border-color:'+T.border2+'}', '#svd-tbl{padding:4px 16px 12px;background:'+T.bg+';border-bottom:1px solid '+T.border+'}', '.svd-row{display:grid;grid-template-columns:64px 1fr;gap:10px;padding:8px 0;border-bottom:1px solid '+T.bg3+';align-items:start}.svd-row:last-child{border-bottom:none}', '.svd-lbl{font-size:11.5px;font-weight:600;color:'+T.text3+';padding-top:1px}.svd-val{font-size:12.5px;color:'+T.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 '+T.accentBg+'}', '.svd-avph{width:34px;height:34px;border-radius:50%;background:'+T.bg3+';display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}', '.svd-aname{font-size:13px;font-weight:600;color:'+T.text+'}.svd-aid{font-size:11px;color:'+T.text3+';margin-top:1px}', '.svd-desc{max-height:60px;overflow-y:auto;font-size:12.5px;color:'+T.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:'+T.accentBg+';border:1px solid rgba(254,44,85,.2);border-radius:100px;color:'+T.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:'+T.bg2+';border:1.5px dashed '+T.border+';border-radius:10px;display:flex;align-items:center;justify-content:center;color:'+T.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:'+T.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:'+T.bg2+';border:1px solid '+T.border+';border-radius:8px;color:'+T.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:'+T.bg3+'}.svd-imgdl-all:disabled{opacity:.4;cursor:not-allowed}', '.svd-url{font-size:10.5px;color:'+T.text3+';word-break:break-all;background:'+T.bg2+';border-radius:6px;padding:5px 8px;max-height:36px;overflow:hidden}', '.svd-url.found{color:'+T.green+';background:rgba(22,163,74,.07)}', '.svd-na{color:'+T.text3+';font-style:italic;font-size:11.5px}', '#svd-actions{display:flex;gap:8px;padding:12px 16px;background:'+T.bg+';border-bottom:1px solid '+T.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:'+T.accent+';color:#fff}.svd-dlc{background:'+T.bg2+';color:'+T.text2+';border:1.5px solid '+T.border+'}', '#svd-statarea{padding:9px 16px 13px;background:'+T.bg+'}', '#svd-prog{height:3px;background:'+T.bg3+';border-radius:2px;margin-bottom:7px;overflow:hidden;display:none}', '#svd-progfill{height:100%;width:0%;background:'+T.accent+';border-radius:2px;transition:width .2s}', '#svd-stattxt{font-size:11px;color:'+T.text3+';text-align:center;min-height:16px}', '#svd-stattxt.ok{color:'+T.green+'}#svd-stattxt.err{color:'+T.red+'}#svd-stattxt.info{color:'+T.blue+'}', // 触发按钮:默认显示(flex),打开小窗后隐藏 '#svd-trig{position:fixed;bottom:108px;right:18px;width:46px;height:46px;border-radius:50%;background:'+T.accent+';border:none;cursor:pointer;z-index:2147483646;display:flex;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:'+T.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:'+T.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:'+T.accent+';border-radius:50%;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,0.2);border:2px solid white}', '#svd-author-bar{padding:8px 16px 12px;display:flex;align-items:center;justify-content:space-between;border-top:1px solid '+T.border+';background:'+T.bg+';font-size:11px}', '.svd-author{color:'+T.text3+'}.svd-author a{color:'+T.accent+';text-decoration:none;font-weight:600}', '.svd-donate{color:'+T.purple+';cursor:pointer;font-weight:500;background:'+T.accentBg+';padding:3px 8px;border-radius: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:'+T.accent+';color:white;border:none;border-radius:20px;cursor:pointer}', // 嗅探面板样式 '#svd-sniff-toggle-btn{width:calc(100% - 32px);margin:0 16px 10px;padding:8px;background:'+T.bg2+';border:1px solid '+T.border+';border-radius:8px;color:'+T.text2+';font-size:12px;font-weight:500;cursor:pointer;text-align:center}', '#svd-sniff-section{margin:0 16px 10px;border:1px solid '+T.border+';border-radius:10px;padding:8px;background:'+T.bg2+';display:none;max-height:300px;overflow-y:auto}', '#svd-sniff-section.visible{display:block}', '#svd-m3u8-list{list-style:none;padding:0;margin:0}', '.svd-m3u8-item{display:flex;align-items:center;padding:6px;background:rgba(128,128,128,0.08);margin-bottom:4px;border-radius:6px;border-left:3px solid '+T.accent+'}', '.svd-sniff-checkbox{margin-right:6px;width:14px;height:14px}', '.svd-sniff-url-content{flex:1}.svd-sniff-url-text{font-size:11px;word-break:break-all;line-height:1.3}', '.svd-sniff-actions{display:flex;gap:4px;margin-top:4px}', '.svd-sniff-actions button{padding:2px 8px;font-size:10px;border:none;border-radius:4px;cursor:pointer}', '.svd-sniff-copy{background:#3498db;color:#fff}.svd-sniff-dl{background:#27ae60;color:#fff}', '#svd-sniff-toolbar{display:flex;gap:4px;margin-bottom:6px}', '#svd-sniff-toolbar button{flex:1;padding:4px 2px;border:none;border-radius:5px;cursor:pointer;font-size:10px;font-weight:bold;color:#fff;background:#555}', '#svd-sniff-clear-btn{background:#e74c3c !important}' ].join(''); document.head.appendChild(s); } /* ══════════════════════════════════════════════════════════ UI — WINDOW BUILD ══════════════════════════════════════════════════════════ */ function buildWindow() { if (q('svd-win')) return; var w = document.createElement('div'); w.id = 'svd-win'; // 默认隐藏,点击触发按钮后打开 w.style.display = 'none'; var isDouyinOrXhs = isDyOrXhs(); var platformTitle = isDouyin() ? '短视频下载 Pro' : (isXiaohongshu() ? '小红书下载 Pro' : '视频嗅探 Pro'); var sniffHtml = (!isDouyinOrXhs) ? [ '', '
', '
', ' ', ' ', ' ', ' ', (isBili() ? ' ' : ''), '
', ' ', '
' ].join('') : ''; var dyXhsHtml = isDouyinOrXhs ? [ '
等待封面加载...
', '
', '
', '
平台
', '
作者
', '
发布时间
', '
发布文案
', '
话题
', '
封面
', '
视频
', '
链接
等待捕获...
', '
', '
', ' ', ' ', '
' ].join('') : ''; w.innerHTML = [ '
', '
', ' ' + platformTitle + '', ' ', '
', '
', '
', '
📁 保存路径
', '
未选择目录,将使用浏览器默认下载
', '
', '
', '
✏️ 文件命名 (留空则自动生成)
', ' ', '
', sniffHtml, dyXhsHtml, '
点击「刷新」读取当前视频信息
', '
', ' By 花海🌸', ' ❤️ 赞赏支持', '
' ].join(''); document.body.appendChild(w); bindEvents(w); makeDraggable(w, w.querySelector('#svd-hdr')); createDonateModal(); // 恢复嗅探面板状态(仅非抖音/XHS) if (!isDouyinOrXhs && isSniffSectionVisible) { var sec = q('svd-sniff-section'); if (sec) sec.classList.add('visible'); var btn = q('svd-sniff-toggle-btn'); if (btn) btn.textContent = '📡 关闭嗅探工具'; } sniffMemoryVault.forEach(function(item) { renderSingleSniffItem(item); }); } 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 createDonateModal() { if (q('svd-donate-modal')) return; var modal = document.createElement('div'); modal.id = 'svd-donate-modal'; modal.innerHTML = [ '
', '

赞赏支持

', '

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

', ' 赞赏码', '

微信

', ' ', '
' ].join(''); document.body.appendChild(modal); q('svd-donate-btn').addEventListener('click', function() { modal.style.display = 'flex'; }); q('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'; }); var pathBtn = w.querySelector('#svd-path-btn'); if (pathBtn) pathBtn.addEventListener('click', async function() { if (!window.showDirectoryPicker) { setStatus('浏览器不支持目录选择', 'err'); return; } try { var dh = await window.showDirectoryPicker({ mode: 'readwrite' }); dirHandle = dh; await _idbSaveHandle(dh); GM_setValue('svd_last_dir_name', dh.name); _applyDirHandleUI(dh.name, true); setStatus('下载目录: ' + dh.name + ' (授权成功)', 'ok'); } catch(e) { if (e.name !== 'AbortError') setStatus('目录选择失败: ' + e.message, 'err'); } }); var refreshBtn = w.querySelector('#svd-refresh'); if (refreshBtn) refreshBtn.addEventListener('click', doRefresh); var btnV = w.querySelector('#svd-btn-v'); if (btnV) btnV.addEventListener('click', doDownloadVideo); var btnC = w.querySelector('#svd-btn-c'); if (btnC) btnC.addEventListener('click', doDownloadCover); var slider = w.querySelector('#svd-opacity-slider'); if (slider) { var savedOpacity = GM_getValue('svd_win_opacity', 1.0); slider.value = savedOpacity; w.style.opacity = savedOpacity; slider.addEventListener('input', function(e) { var v = parseFloat(e.target.value); w.style.opacity = v; GM_setValue('svd_win_opacity', v); }); slider.addEventListener('mousedown', function(e) { e.stopPropagation(); }); } // 嗅探区域事件(仅非抖音/XHS 时存在相关元素) var sniffToggle = w.querySelector('#svd-sniff-toggle-btn'); if (sniffToggle) { sniffToggle.addEventListener('click', function() { var sec = q('svd-sniff-section'); if (!sec) return; isSniffSectionVisible = !sec.classList.contains('visible'); sec.classList.toggle('visible', isSniffSectionVisible); this.textContent = isSniffSectionVisible ? '📡 关闭嗅探工具' : '📡 打开视频嗅探工具'; GM_setValue('sniff_section_visible', isSniffSectionVisible); updateSniffStatus(); }); } var selAll = w.querySelector('#svd-sel-all-sniff'); if (selAll) selAll.addEventListener('click', function() { var cbs = w.querySelectorAll('.svd-sniff-checkbox'); var allChecked = Array.prototype.every.call(cbs, function(c) { return c.checked; }); Array.prototype.forEach.call(cbs, function(c) { c.checked = !allChecked; }); }); var copySelected = w.querySelector('#svd-sniff-copy-selected'); if (copySelected) copySelected.addEventListener('click', function() { var urls = []; Array.prototype.forEach.call(w.querySelectorAll('.svd-sniff-checkbox:checked'), function(c) { urls.push(c.dataset.url); }); if (urls.length) copyToClipboard(urls.join('\n')).then(function() { setStatus('已复制 ' + urls.length + ' 个链接', 'ok'); }); }); var dlSelected = w.querySelector('#svd-sniff-dl-selected'); if (dlSelected) dlSelected.addEventListener('click', function() { var checked = w.querySelectorAll('.svd-sniff-checkbox:checked'); Array.prototype.forEach.call(checked, function(cb, i) { setTimeout(function() { // 使用 URL 自然文件名 downloadBestQuality(cb.dataset.url, getUrlFilename(cb.dataset.url), '嗅探视频 ' + (i + 1)); }, i * 800); }); }); var clearBtn = w.querySelector('#svd-sniff-clear-btn'); if (clearBtn) clearBtn.addEventListener('click', function() { detectedUrls.clear(); sniffMemoryVault = []; var list = q('svd-m3u8-list'); if (list) list.innerHTML = ''; setStatus('已清空嗅探记录', 'info'); }); if (isBili()) { var scanBiliBtn = w.querySelector('#svd-scan-bili-btn'); if (scanBiliBtn) scanBiliBtn.addEventListener('click', function() { window.scanBili && window.scanBili(); }); } } 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 = Math.max(0, Math.min(ox + e.clientX - sx, window.innerWidth - el.offsetWidth)) + 'px'; el.style.top = Math.max(0, Math.min(oy + e.clientY - sy, window.innerHeight - el.offsetHeight)) + 'px'; el.style.right = 'auto'; } function mu() { d = false; document.removeEventListener('mousemove', mv); document.removeEventListener('mouseup', mu); } } /* ══════════════════════════════════════════════════════════ UI — RENDER INFO ══════════════════════════════════════════════════════════ */ 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; } }; 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); } /* ══════════════════════════════════════════════════════════ REFRESH & BOOTSTRAP ══════════════════════════════════════════════════════════ */ function doRefresh() { setStatus('正在读取视频信息...', 'info'); setTimeout(function() { if (!isDyOrXhs()) { updateSniffStatus(); return; } var info = extractVideoInfo(); if (info) renderInfo(info); else setStatus('❌ 未能解析视频信息,请等视频播放后刷新', 'err'); }, 350); } function bootstrap() { injectStyles(); buildWindow(); buildTrigger(); _restoreDirHandle(); if (isDouyin()) { watchForNewItems(); setTimeout(function() { setupItemObserver(); doRefresh(); }, 1800); } else { setTimeout(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(); if (isSniffSectionVisible && !isDyOrXhs()) captureVideoElementSrcs(); var cur = location.href; if (cur !== lastNavUrl) { lastNavUrl = cur; capturedCovers = []; xhsNoteData = null; imageDownloading = false; if (!isDownloading) setTimeout(doRefresh, isDouyin() ? 1800 : 1000); } }, 800); })();