您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在哔哩哔哩视频页面添加下载按钮
当前为
// ==UserScript== // @name bilibili哔哩哔哩视频下载按钮 // @namespace http://tampermonkey.net/ // @version 0.5 // @description 在哔哩哔哩视频页面添加下载按钮 // @match https://www.bilibili.com/video/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // 定义一些常用的解析接口 const PARSE_APIS = [ 'https://api.injahow.cn/bparse/', 'https://jx.jsonplayer.com/player/', 'https://jx.bozrc.com:4433/player/', 'https://jx.parwix.com:4433/player/' ]; function createDownloadButton() { const downloadBtn = document.createElement('button'); downloadBtn.textContent = '下载'; downloadBtn.style.cssText = ` margin-left: 10px; padding: 5px 12px; background: #00aeec; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 13px; height: 32px; line-height: 18px; min-width: 50px; `; downloadBtn.onclick = startDownload; return downloadBtn; } // 获取视频基本信息 function getBiliVideoInfo() { try { // 首先尝试从 window.__INITIAL_STATE__ 获取视频信息 let initialState = window.__INITIAL_STATE__; let videoData = initialState?.videoData; // 如果上面的方式失败,尝试从其他可能的数据源获取 if (!videoData) { // 尝试从 window.__playinfo__ 获取 const playInfo = window.__playinfo__; if (playInfo) { videoData = { bvid: document.querySelector('meta[itemprop="url"]')?.content?.split('/').pop(), aid: playInfo.aid, cid: playInfo.cid, title: document.querySelector('h1.video-title')?.textContent?.trim(), desc: document.querySelector('.desc-info-text')?.textContent?.trim(), pic: document.querySelector('meta[itemprop="image"]')?.content, owner: { name: document.querySelector('.up-name')?.textContent?.trim(), face: document.querySelector('.up-avatar img')?.src, mid: document.querySelector('.up-name')?.href?.match(/\d+/)?.[0] } }; } } // 如果还是没有数据,尝试从URL和页面元素获取 if (!videoData) { const bvid = location.pathname.match(/BV\w+/)?.[0]; videoData = { bvid: bvid, title: document.title.replace(' - 哔哩哔哩', '').trim(), pic: document.querySelector('meta[property="og:image"]')?.content, desc: document.querySelector('meta[property="og:description"]')?.content, owner: { name: document.querySelector('.up-name')?.textContent?.trim(), face: document.querySelector('.up-avatar img')?.src, mid: document.querySelector('.up-name')?.href?.match(/\d+/)?.[0] } }; } if (!videoData || !videoData.bvid) { throw new Error('无法获取视频信息'); } // 确保返回的对象包含所有必要的字段 return { bvid: videoData.bvid, pic: videoData.pic || '', title: videoData.title || document.title, pubdate: videoData.pubdate, desc: videoData.desc || '', duration: videoData.duration, owner: { mid: videoData.owner?.mid || '', name: videoData.owner?.name || '未知用户', face: videoData.owner?.face || '' }, aid: videoData.aid, cid: videoData.cid || videoData.pages?.[0]?.cid }; } catch (error) { console.error('获取视频信息失败:', error); // 添加更详细的错误信息 console.log('当前页面URL:', location.href); console.log('window.__INITIAL_STATE__:', window.__INITIAL_STATE__); console.log('window.__playinfo__:', window.__playinfo__); throw error; } } // 使用bilibili官方接口解析视频 async function getVideoUrl(aid, cid, quality) { const apiUrl = 'https://api.bilibili.com/x/player/playurl'; const params = { otype: 'json', platform: 'html5', avid: aid, cid: cid, qn: quality || window.__playinfo__?.data?.accept_quality?.[0] || 80, fnver: 0, fnval: 4048, high_quality: window.__playinfo__?.data?.quality || 1 }; const queryString = Object.entries(params) .map(([key, value]) => `${key}=${value}`) .join('&'); const response = await fetch(`${apiUrl}?${queryString}`, { credentials: 'include' }); const data = await response.json(); if (data.code !== 0) { throw new Error(data.message || '获取下载地址失败'); } return data.data.durl[0].url; } // 使用第三方接口解析视频 async function parseVideoUrl(bvid, apiIndex = 0, usedQuality = null) { if (apiIndex >= PARSE_APIS.length) { throw new Error('所有解析接口都失败了'); } try { // 如果没有传入清晰度,使用当前播放器的清晰度设置 const quality = usedQuality || window.__playinfo__?.data?.quality || 80; // 构建API URL,添加清晰度参数 const apiUrl = `${PARSE_APIS[apiIndex]}?bv=${bvid}&q=${quality}`; console.log(`尝试解析接口${apiIndex + 1},清晰度: ${quality}`); const response = await fetch(apiUrl); const data = await response.json(); if (!data.url && !data.data?.url) { // 如果当前清晰度失败且不是1080P,尝试降级到1080P if (quality !== 80) { console.log(`清晰度${quality}解析失败,尝试1080P`); return parseVideoUrl(bvid, apiIndex, 80); } throw new Error('解析接口返回数据格式错误'); } return { url: data.url || data.data.url, quality: quality }; } catch (error) { return parseVideoUrl(bvid, apiIndex + 1, usedQuality); } } // 构造下载信息 async function constructDownloadInfo() { try { const videoInfo = getBiliVideoInfo(); let downloadUrl; let usedQuality; // 添加变量记录使用的清晰度 try { if (videoInfo.aid && videoInfo.cid) { const quality = window.__playinfo__?.data?.accept_quality?.[0] || 80; downloadUrl = await getVideoUrl(videoInfo.aid, videoInfo.cid, quality); usedQuality = quality; // 记录官方API使用的清晰度 } } catch (error) { // 官方API失败时静默切换到备用接口 } if (!downloadUrl) { const result = await parseVideoUrl(videoInfo.bvid, 0, window.__playinfo__?.data?.quality); downloadUrl = result.url; usedQuality = result.quality; } return { bvid: videoInfo.bvid, downloadUrl: downloadUrl, title: videoInfo.title, desc: videoInfo.desc, pic: videoInfo.pic, aid: videoInfo.aid, cid: videoInfo.cid, owner: videoInfo.owner, face: videoInfo.face, downloadUrl, usedQuality, // 将清晰度信息添加到返回对象中 }; } catch (error) { throw error; } } // 开始下载 async function startDownload() { try { const downloadInfo = await constructDownloadInfo(); // 在控制台打印下载信息 console.group('视频下载信息'); console.log('标题:', downloadInfo.title); console.log('描述:', downloadInfo.desc); console.log('封面:', downloadInfo.pic); console.log('下载地址:', downloadInfo.downloadUrl); console.log('UP主:', downloadInfo.owner?.name); console.log('UP主头像:', downloadInfo.owner?.face); console.log('BV号:', downloadInfo.bvid); console.log('AV号:', downloadInfo.aid); console.log('CID:', downloadInfo.cid); // 添加清晰度相关信息 console.group('清晰度信息'); console.log('支持的清晰度列表:', window.__playinfo__?.data?.accept_quality?.map(qn => ({ qn, desc: { 120: '4K', 116: '1080P60帧', 112: '1080P+高码率', 80: '1080P', 64: '720P', 32: '480P', 16: '360P' }[qn] || `未知(${qn})` }))); console.log('当前播放清晰度:', window.__playinfo__?.data?.quality); if (downloadInfo.isOfficialApi) { console.log('下载使用的清晰度:', `${downloadInfo.usedQuality} (${ { 120: '4K', 116: '1080P60帧', 112: '1080P+高码率', 80: '1080P', 64: '720P', 32: '480P', 16: '360P' }[downloadInfo.usedQuality] || '未知清晰度' })`); console.log('使用接口: 官方API'); } else { console.log('下载使用的清晰度:', `${downloadInfo.usedQuality} (${ { 120: '4K', 116: '1080P60帧', 112: '1080P+高码率', 80: '1080P', 64: '720P', 32: '480P', 16: '360P' }[downloadInfo.usedQuality] || '未知清晰度' })`); console.log('使用接口: 第三方接口'); console.log('提示: 如需更高清晰度,建议登录后使用官方API下载'); } console.groupEnd(); console.groupEnd(); // 构建URL参数 const params = new URLSearchParams(); params.append('title', downloadInfo.title || ''); params.append('desc', downloadInfo.desc || ''); params.append('pic', downloadInfo.pic || ''); params.append('downloadUrl', downloadInfo.downloadUrl); params.append('owner', downloadInfo.owner?.name || ''); params.append('face', downloadInfo.owner?.face || ''); // 构建完整的URL地址 const baseUrl = 'https://saveany.cn/get_video_info.html'; const finalUrl = `${baseUrl}?${params.toString()}`; // 在控制台打印最终URL console.log('最终请求URL:', finalUrl); // 打开新窗口 const downloadWindow = window.open(finalUrl, '_blank'); if (downloadWindow) { downloadWindow.focus(); } else { alert('下载窗口被浏览器阻止,请允许弹出窗口后重试。'); } } catch (error) { console.error('下载失败:', error); alert('下载失败: ' + error.message); } } function addDownloadButton() { const targetArea = document.querySelector("#bilibili-player > div > div > div.bpx-player-primary-area > div.bpx-player-sending-area > div"); if (targetArea && !targetArea.querySelector('.download-btn')) { const downloadBtn = createDownloadButton(); downloadBtn.classList.add('download-btn'); targetArea.appendChild(downloadBtn); } } function observeDOM() { const targetNode = document.body; const config = { childList: true, subtree: true }; const observer = new MutationObserver((mutationsList, observer) => { for(let mutation of mutationsList) { if (mutation.type === 'childList') { addDownloadButton(); } } }); observer.observe(targetNode, config); } // 初始尝试添加按钮 window.addEventListener('load', () => { addDownloadButton(); observeDOM(); }); // 定期检查并尝试重新添加按钮 setInterval(addDownloadButton, 5000); })();