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