您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
下载B站视频完整弹幕(携带WBI认证)
// ==UserScript== // @name Bilibili完整弹幕下载器(WBI认证版) // @namespace http://tampermonkey.net/ // @version 0.3 // @description 下载B站视频完整弹幕(携带WBI认证) // @author weiye // @match https://www.bilibili.com/video/* // @grant GM_xmlhttpRequest // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/protobuf.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js // ==/UserScript== (function() { 'use strict'; // WBI签名相关配置 const mixinKeyEncTab = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 ]; // 对 imgKey 和 subKey 进行字符顺序打乱编码 const getMixinKey = (orig) => mixinKeyEncTab.map(n => orig[n]).join('').slice(0, 32); // 为请求参数进行 wbi 签名 function encWbi(params, img_key, sub_key) { console.log('开始WBI签名, 参数:', params); const mixin_key = getMixinKey(img_key + sub_key); const curr_time = Math.round(Date.now() / 1000); const chr_filter = /[!'()*]/g; // 确保所有参数都是字符串 const safeParams = {}; for (const [key, value] of Object.entries(params)) { if (value !== undefined && value !== null) { // 先转换为字符串,再过滤特殊字符 safeParams[key] = String(value).replace(chr_filter, ''); } } // 添加 wts 字段 safeParams.wts = curr_time; // 按照 key 重排参数 const query = Object.keys(safeParams) .sort() .map(key => { return `${encodeURIComponent(key)}=${encodeURIComponent(safeParams[key])}`; }) .join('&'); // 使用 CryptoJS 计算 MD5 const wbi_sign = CryptoJS.MD5(query + mixin_key).toString(); console.log('WBI签名完成:', query + '&w_rid=' + wbi_sign); return query + '&w_rid=' + wbi_sign; } // 获取最新的 img_key 和 sub_key async function getWbiKeys() { console.log('开始获取WBI Keys...'); const response = await fetch('https://api.bilibili.com/x/web-interface/nav', { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', 'Referer': 'https://www.bilibili.com/' } }); const data = await response.json(); if (!data.data?.wbi_img) { throw new Error('无法获取WBI Keys'); } const { img_url, sub_url } = data.data.wbi_img; const img_key = img_url.slice(img_url.lastIndexOf('/') + 1, img_url.lastIndexOf('.')); const sub_key = sub_url.slice(sub_url.lastIndexOf('/') + 1, sub_url.lastIndexOf('.')); console.log('成功获取WBI Keys:', { img_key, sub_key }); return { img_key, sub_key }; } // 弹幕 protobuf 消息结构 const danmakuProto = { nested: { DmSegMobileReply: { fields: { elems: { rule: "repeated", type: "DanmakuElem", id: 1 } } }, DanmakuElem: { fields: { id: { type: "int64", id: 1 }, progress: { type: "int32", id: 2 }, mode: { type: "int32", id: 3 }, fontsize: { type: "int32", id: 4 }, color: { type: "uint32", id: 5 }, midHash: { type: "string", id: 6 }, content: { type: "string", id: 7 }, ctime: { type: "int64", id: 8 }, weight: { type: "int32", id: 9 }, idStr: { type: "string", id: 10 }, attr: { type: "int32", id: 11 }, action: { type: "string", id: 12 } } } } }; /** * 获取视频信息 */ async function getVideoInfo() { console.log('开始获取视频信息...'); // 1. 从URL获取bvid const bvid = window.location.pathname.split('/')[2]; if (!bvid) throw new Error('无法获取视频BV号'); console.log('获取到BV号:', bvid); // 2. 从页面全局变量获取信息 const initialState = window.__INITIAL_STATE__; if (initialState?.aid && initialState?.cid) { console.log('从页面变量获取视频信息:', { aid: initialState.aid, cid: initialState.cid, duration: initialState.duration }); return { aid: initialState.aid, cid: initialState.cid, duration: initialState.duration || 0 }; } // 3. 如果页面变量获取失败,则通过API获取 console.log('从API获取视频信息...'); const response = await fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${bvid}`); const data = await response.json(); if (data.code !== 0) throw new Error(`获取视频信息失败: ${data.message}`); if (!data.data?.aid || !data.data?.cid) throw new Error('无法获取视频信息'); const result = { aid: data.data.aid, cid: data.data.cid, duration: data.data.duration }; console.log('成功获取视频信息:', result); return result; } /** * 获取单个分段的弹幕 */ async function getSegmentDanmaku(cid, aid, segmentIndex, wbiKeys) { console.log(`获取第 ${segmentIndex} 段弹幕...`); // 准备参数 const params = { type: 1, oid: cid, segment_index: segmentIndex, pid: aid, web_location: 1315873, wts: Math.round(Date.now() / 1000) }; // WBI签名 const query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); const url = `https://api.bilibili.com/x/v2/dm/wbi/web/seg.so?${query}`; console.log('请求URL:', url); const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const buffer = await response.arrayBuffer(); // 使用protobuf解析 const root = protobuf.Root.fromJSON(danmakuProto); const DmSegMobileReply = root.lookupType("DmSegMobileReply"); try { const message = DmSegMobileReply.decode(new Uint8Array(buffer)); const danmakus = DmSegMobileReply.toObject(message, { longs: String, enums: String, bytes: String, }).elems || []; console.log(`第 ${segmentIndex} 段弹幕获取成功, 数量:`, danmakus.length); return danmakus; } catch (error) { console.error('Protobuf解析错误:', error); console.log('原始数据:', new Uint8Array(buffer)); throw new Error('弹幕数据解析失败'); } } /** * 下载弹幕 */ async function downloadDanmaku() { try { // 1. 获取WBI Keys const wbiKeys = await getWbiKeys(); // 2. 获取视频信息 const { cid, duration, aid } = await getVideoInfo(); console.log('视频信息:', { cid, duration, aid }); // 3. 计算分段数(每段6分钟) const segmentCount = Math.ceil(duration / 360); console.log(`需要获取 ${segmentCount} 个分段的弹幕`); // 4. 获取所有分段的弹幕 const allDanmakus = new Set(); for(let i = 1; i <= segmentCount; i++) { const danmakus = await getSegmentDanmaku(cid, aid, i, wbiKeys); danmakus.forEach(d => { const time = (d.progress/1000).toFixed(2); allDanmakus.add(`[${time}s] ${d.content}`); }); if(i < segmentCount) { await new Promise(resolve => setTimeout(resolve, 300)); } } // 5. 按时间排序并下载 const content = Array.from(allDanmakus) .sort((a, b) => { const timeA = parseFloat(a.match(/\[(.*?)s\]/)[1]); const timeB = parseFloat(b.match(/\[(.*?)s\]/)[1]); return timeA - timeB; }) .join('\n'); const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `danmaku_${cid}_full.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); console.log(`成功下载 ${allDanmakus.size} 条弹幕`); } catch (error) { console.error('获取弹幕失败:', error); alert('获取弹幕失败: ' + error.message); } } // 添加下载按钮 const button = document.createElement('button'); button.textContent = '下载完整弹幕(WBI)'; button.style.cssText = 'position: fixed; right: 20px; top: 100px; z-index: 9999; padding: 8px 16px;'; button.onclick = downloadDanmaku; document.body.appendChild(button); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址