// ==UserScript== // @name 视频下载器(x版) // @namespace http://tampermonkey.net/ // @version 24.0 // @description 支持Via菜单,自动读取当前网址,防刷新丢失,带开关 // @author AI // @match *://*/* // @grant GM_download // @grant GM_notification // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @connect * // ==/UserScript== (function() { 'use strict'; let currentParsedVideos = []; let lastUrl = window.location.href; // ==================== 1. 注册 Via 菜单按钮 ==================== if (typeof GM_registerMenuCommand !== 'undefined') { GM_registerMenuCommand("🎬 解析当前网页视频", startParseCurrentPage); GM_registerMenuCommand("🙈 隐藏/显示悬浮球", toggleFab); } // ==================== 2. 悬浮球显隐控制 ==================== function toggleFab() { const fab = document.getElementById('sniffer-fab'); if (!fab) return; const isHidden = fab.style.display === 'none' || fab.style.display === ''; if (isHidden) { fab.style.display = 'flex'; localStorage.setItem('sniffer_fab_hidden', 'false'); if (typeof GM_notification !== 'undefined') GM_notification({ text: "悬浮球已显示", timeout: 1500 }); } else { fab.style.display = 'none'; localStorage.setItem('sniffer_fab_hidden', 'true'); if (typeof GM_notification !== 'undefined') GM_notification({ text: "悬浮球已隐藏", timeout: 1500 }); } } // ==================== 3. 创建基础UI ==================== function createBaseUI() { if (document.getElementById('sniffer-fab')) return; const isHidden = localStorage.getItem('sniffer_fab_hidden') === 'true'; // 悬浮球 const fab = document.createElement('div'); fab.id = 'sniffer-fab'; fab.innerHTML = '🎬'; fab.style.cssText = ` position: fixed !important; bottom: 80px !important; right: 15px !important; width: 45px !important; height: 45px !important; background: #1e88e5 !important; color: white !important; border-radius: 50% !important; display: ${isHidden ? 'none' : 'flex'} !important; align-items: center !important; justify-content: center !important; font-size: 22px !important; box-shadow: 0 4px 10px rgba(0,0,0,0.3) !important; z-index: 9999999 !important; cursor: pointer !important; `; document.body.appendChild(fab); // 主面板 const panel = document.createElement('div'); panel.id = 'sniffer-panel'; panel.style.cssText = ` position: fixed !important; bottom: 135px !important; right: 15px !important; width: 320px !important; background: #fff !important; border-radius: 10px !important; box-shadow: 0 4px 15px rgba(0,0,0,0.2) !important; padding: 15px !important; z-index: 9999999 !important; display: none !important; flex-direction: column !important; max-height: 60vh !important; overflow-y: auto !important; box-sizing: border-box !important; font-family: sans-serif !important; `; document.body.appendChild(panel); fab.addEventListener('click', () => { panel.style.display = panel.style.display === 'none' ? 'flex' : 'none'; if (panel.style.display === 'flex') startParseCurrentPage(); }); } // ==================== 4. “哨兵”逻辑(防刷新丢失核心) ==================== setInterval(() => { // 1. 如果悬浮窗不见了,立刻重新画一个 if (!document.getElementById('sniffer-fab')) { createBaseUI(); } // 2. 如果网址变了(比如B站切换视频),但页面没刷新,面板里如果有旧数据就清理一下 if (window.location.href !== lastUrl) { lastUrl = window.location.href; currentParsedVideos = []; // 清空旧数据 // 顺便把面板关了,防止显示上个视频的数据 const panel = document.getElementById('sniffer-panel'); if (panel) panel.style.display = 'none'; } }, 2000); // 每2秒检查一次 // ==================== 5. 核心解析逻辑 ==================== function startParseCurrentPage() { const panel = document.getElementById('sniffer-panel'); if (!panel) return; panel.style.display = 'flex'; const currentUrl = window.location.href; panel.innerHTML = `
解析当前网页 ×
读取地址:
${currentUrl}
`; document.getElementById('panel-close').addEventListener('click', () => panel.style.display = 'none'); currentParsedVideos = []; if (currentUrl.includes('twitter.com') || currentUrl.includes('x.com')) { const match = currentUrl.match(/status\/(\d+)/); if (!match) return renderError("当前页面不是推文详情页。请点开具体的推文(网址包含 /status/),再执行解析。"); parseTwitter(match[1]); return; } if (currentUrl.includes('bilibili.com')) { const bvMatch = currentUrl.match(/(BV\w+)/); if (!bvMatch) return renderError("无法识别BV号。请打开具体的B站视频播放页。"); parseBilibili(bvMatch[1]); return; } const videos = document.querySelectorAll('video'); let foundCount = 0; videos.forEach(v => { if (v.src && v.src.includes('.mp4')) { currentParsedVideos.push({ url: v.src, quality: '网页视频 (MP4直链)' }); foundCount++; } }); if (foundCount > 0) renderModalResults(); else renderError("当前网页暂不支持自动解析。建议在推特或B站页面使用。"); } function parseTwitter(tweetId) { if (typeof GM_xmlhttpRequest === 'undefined') return renderError("Via未开放跨域权限,无法解析。"); GM_xmlhttpRequest({ method: "GET", url: `https://cdn.syndication.twimg.com/tweet-result?id=${tweetId}&token=a`, onload: function(res) { try { const data = JSON.parse(res.responseText); if (data.video && data.video.variants) { data.video.variants.forEach(v => { if (v.type === 'video/mp4') { let q = '标清'; if (v.src.includes('1080')) q = '超清 (1080P)'; else if (v.src.includes('720')) q = '高清 (720P)'; else if (v.src.includes('480')) q = '标清 (480P)'; currentParsedVideos.push({ url: v.src, quality: `推特视频 ${q}` }); } }); if (currentParsedVideos.length > 0) renderModalResults(); else renderError("该推文不包含视频,或需要登录才能查看。"); } else { renderError("该推文未包含视频,或需要登录才能查看。"); } } catch(e) { renderError("解析失败,可能是推文被保护或接口变动。"); } }, onerror: () => renderError("网络请求失败,请检查网络连接。") }); } function parseBilibili(bvid) { if (typeof GM_xmlhttpRequest === 'undefined') return renderError("Via未开放跨域权限,无法解析。"); GM_xmlhttpRequest({ method: "GET", url: `https://api.bilibili.com/x/web-interface/view?bvid=${bvid}`, onload: function(res) { try { const info = JSON.parse(res.responseText).data; GM_xmlhttpRequest({ method: "GET", url: `https://api.bilibili.com/x/player/playurl?bvid=${bvid}&cid=${info.cid}&qn=80&fnval=16`, headers: { "Referer": "https://www.bilibili.com/" }, onload: function(res2) { try { const playData = JSON.parse(res2.responseText).data; if (playData && playData.dash) { currentParsedVideos.push({ url: playData.dash.video[0].baseUrl, quality: 'B站视频轨(需合并音频)' }); if (playData.dash.audio && playData.dash.audio.length > 0) { currentParsedVideos.push({ url: playData.dash.audio[0].baseUrl, quality: 'B站音频轨(需合并)' }); } renderModalResults(); } else renderError("B站拒绝解析,可能触发风控。"); } catch(e) { renderError("B站流解析出错"); } } }); } catch(e) { renderError("B站信息获取失败"); } } }); } function renderError(msg) { const panel = document.getElementById('sniffer-panel'); if (!panel) return; panel.innerHTML = `
提示 ×
${msg}
`; document.getElementById('panel-close').addEventListener('click', () => panel.style.display = 'none'); } function renderModalResults() { const panel = document.getElementById('sniffer-panel'); if (!panel) return; let html = `
发现视频清晰度 ×
`; currentParsedVideos.forEach((v) => { html += `
${v.quality}
`; }); panel.innerHTML = html; document.getElementById('panel-close').addEventListener('click', () => panel.style.display = 'none'); panel.querySelectorAll('.modal-play-btn').forEach(btn => { btn.addEventListener('click', function() { playVideoPreview(this.getAttribute('data-url')); }); }); panel.querySelectorAll('.modal-dl-btn').forEach(btn => { btn.addEventListener('click', function() { downloadVideo(this.getAttribute('data-url')); panel.style.display = 'none'; }); }); } function playVideoPreview(url) { const playModal = document.createElement('div'); playModal.style.cssText = ` position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background: #000 !important; z-index: 10000000 !important; display: flex !important; align-items: center !important; justify-content: center !important; flex-direction: column !important; `; playModal.innerHTML = ` `; document.body.appendChild(playModal); document.getElementById('close-play').addEventListener('click', () => document.body.removeChild(playModal)); } function downloadVideo(url) { if (typeof GM_download === 'undefined') { alert("Via未开放下载权限,将尝试用浏览器接管。"); window.open(url, '_blank'); return; } if (typeof GM_notification !== 'undefined') GM_notification({ text: "开始下载视频...", timeout: 2000 }); GM_download({ url: url, name: `视频_${Date.now()}.mp4`, headers: { "Referer": window.location.href, "User-Agent": navigator.userAgent }, onerror: function() { if (typeof GM_notification !== 'undefined') GM_notification({ text: "下载失败,链接已复制", timeout: 2000 }); const input = document.createElement('input'); input.value = url; document.body.appendChild(input); input.select(); document.execCommand('copy'); document.body.removeChild(input); alert("下载失败,链接已复制到剪贴板,请去Via下载栏粘贴。"); } }); } // ==================== 初始化 ==================== if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createBaseUI); } else { createBaseUI(); } })();