Download Bilibili videos with one click, clean and easy-to-use interface
- // ==UserScript==
- // @name bilibili哔哩哔哩视频下载 📥
- // @name:zh-CN 哔哩哔哩视频下载助手📥
- // @name:zh-TW 嗶哩嗶哩視頻下載助手📥
- // @name:en Bilibili Video Downloader 📥
- // @name:ja ビリビリ動画ダウンローダー 📥
- // @name:ko 비리비리 비디오 다운로더 📥
- // @namespace http://tampermonkey.net/
- // @version 0.8
- // @description 在哔哩哔哩视频页面添加下载按钮,支持多种清晰度和格式
- // @description:zh-CN 一键下载哔哩哔哩视频,界面简洁易用
- // @description:zh-TW 一鍵下載嗶哩嗶哩視頻,界面簡潔易用
- // @description:en Download Bilibili videos with one click, clean and easy-to-use interface
- // @description:ja ビリビリ動画を1クリックでダウンロード、シンプルで使いやすいインターフェース
- // @description:ko 비리비리 동영상 원클릭 다운로드, 깔끔하고 사용하기 쉬운 인터페이스
- // @author youhou
- // @match https://www.bilibili.com/video/*
- // @grant none
- // @license MIT
- // @homepage https://saveany.cn
- // @supportURL https://saveany.cn
- // @keywords bilibili,哔哩哔哩,视频下载,B站下载,bilibili下载,哔哩哔哩视频下载,B站视频下载器,bilibili视频下载,B站,下载视频,下载,ビリビリ,ダウンロード,비리비리,다운로드
- // @icon https://www.bilibili.com/favicon.ico
- // ==/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 {
- 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]
- }
- };
- }
- }
- 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;
- }
- }
- 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;
- const apiUrl = `${PARSE_APIS[apiIndex]}?bv=${bvid}&q=${quality}`;
- const response = await fetch(apiUrl);
- const data = await response.json();
- if (!data.url && !data.data?.url) {
- if (quality !== 80) {
- 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;
- }
- } catch (error) {
- }
- if (!downloadUrl) {
- const r###lt = await parseVideoUrl(videoInfo.bvid, 0, window.__playinfo__?.data?.quality);
- downloadUrl = r###lt.url;
- usedQuality = r###lt.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();
- 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 || '');
- const baseUrl = 'https://saveany.cn/get_video_info';
- const finalUrl = `${baseUrl}?${params.toString()}`;
- console.log('最终请求URL:', finalUrl);
- const downloadWindow = window.open(finalUrl, '_blank');
- if (downloadWindow) {
- downloadWindow.focus();
- }
- } 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);
- })();