// ==UserScript==
// @name 🔥🔥🔥B站视频高清下载🔥🔥🔥
// @namespace http://tampermonkey.net/
// @version 0.1.21
// @description 下载B站视频
// @author 抖音兔不迟到
// @run-at document-start
// @license MIT License
// @grant GM_download
// @include *://*.bilibili.com/*
// @inject-into page
// @require https://greasyfork.org/scripts/440006-mono/code/mono.js?version=1021983
// @require https://unpkg.com/protobufjs@6.10.1/dist/protobuf.min.js
// ==/UserScript==
var API_HOST = 'https://api.bilibili.com/x';
var PB_CONF = '{"nested":{"bilibili":{"nested":{"DmWebViewReply":{"fields":{"state":{"type":"int32","id":1},"text":{"type":"string","id":2},"textSide":{"type":"string","id":3},"dmSge":{"type":"DmSegConfig","id":4},"flag":{"type":"DanmakuFlagConfig","id":5},"specialDms":{"rule":"repeated","type":"string","id":6},"checkBox":{"type":"bool","id":7},"count":{"type":"int64","id":8},"commandDms":{"rule":"repeated","type":"CommandDm","id":9},"dmSetting":{"type":"DanmuWebPlayerConfig","id":10}}},"CommandDm":{"fields":{"id":{"type":"int64","id":1},"oid":{"type":"int64","id":2},"mid":{"type":"int64","id":3},"command":{"type":"string","id":4},"content":{"type":"string","id":5},"progress":{"type":"int32","id":6},"ctime":{"type":"string","id":7},"mtime":{"type":"string","id":8},"extra":{"type":"string","id":9},"idStr":{"type":"string","id":10}}},"DmSegConfig":{"fields":{"pageSize":{"type":"int64","id":1},"total":{"type":"int64","id":2}}},"DanmakuFlagConfig":{"fields":{"recFlag":{"type":"int32","id":1},"recText":{"type":"string","id":2},"recSwitch":{"type":"int32","id":3}}},"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},"action":{"type":"string","id":10},"pool":{"type":"int32","id":11},"idStr":{"type":"string","id":12}}},"DanmuWebPlayerConfig":{"fields":{"dmSwitch":{"type":"bool","id":1},"aiSwitch":{"type":"bool","id":2},"aiLevel":{"type":"int32","id":3},"blocktop":{"type":"bool","id":4},"blockscroll":{"type":"bool","id":5},"blockbottom":{"type":"bool","id":6},"blockcolor":{"type":"bool","id":7},"blockspecial":{"type":"bool","id":8},"preventshade":{"type":"bool","id":9},"dmask":{"type":"bool","id":10},"opacity":{"type":"float","id":11},"dmarea":{"type":"int32","id":12},"speedplus":{"type":"float","id":13},"fontsize":{"type":"float","id":14},"screensync":{"type":"bool","id":15},"speedsync":{"type":"bool","id":16},"fontfamily":{"type":"string","id":17},"bold":{"type":"bool","id":18},"fontborder":{"type":"int32","id":19},"drawType":{"type":"string","id":20}}}}}}}';
(function () {
var mono = window['mono-descargar'];
var useDefaultErr = mono.FAIL_TO_DEFAULT;
var $ = mono.jQuery;
var md5 = mono.md5;
var onRequest = mono.onRequest;
var videoInfo;
var bvid;
var danmuCache = {};
var detailCache = {};
onRequest(({url, resp}) => {
if (!resp) return;
if (url.startsWith('//')) url = `https:${url}`;
var urlObj;
try {
urlObj = new URL(url);
} catch (err) {
return;
}
var bid = urlObj.searchParams.get('bvid');
var aid = urlObj.searchParams.get('aid');
var cid = urlObj.searchParams.get('cid');
if (aid) window.aid = aid;
if (cid) window.cid = cid;
if (bid) bvid = bid;
if (url.includes("playurl?")) {
videoInfo = JSON.parse(resp);
} else if (url.includes("view/detail?")) {
var json = JSON.parse(resp);
if (json?.data?.View) {
detailCache[bid] = json.data.View;
}
}
});
var filename = (title) => {
var name = title.replace(' ', '').replace(/[/\\?%*:|"<>]/g, '-');
return `${name}.mp4`;
}
var root = protobuf.Root.fromJSON(JSON.parse(PB_CONF));
var protoSeg = root.lookupType('bilibili.DmSegMobileReply');
var protoView = root.lookupType('bilibili.DmWebViewReply');
var getBangumiInfo = async function (epid) {
var api = `https://api.bilibili.com/pgc/view/web/season?ep_id=${epid.replace('ep', '')}`;
return new Promise(function (resolve) {
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", () => {
var json = JSON.parse(xhr.responseText);
if (json?.result?.episodes) {
var ep_data = json.result.episodes.filter(ep => {
return ep.link.includes(epid);
});
if (ep_data.length > 0) {
detailCache[epid] = ep_data[0];
return resolve();
}
if (json?.result?.section && json?.result?.section.length > 0) {
for (var sec of json.result.section) {
var ep_data = sec.episodes.filter(ep => {
return ep.link.includes(epid);
});
if (ep_data.length > 0) {
detailCache[epid] = ep_data[0];
break;
}
}
}
}
resolve()
});
xhr.open("get", api);
xhr.responseType = "text";
xhr.send();
});
}
var getDetailInfo = async function (bvid) {
var aid = window.aid;
var api = `${API_HOST}/web-interface/view/detail?bvid=${bvid}&aid=${aid}&need_operation_card=1&web_rm_repeat=&need_elec=1&out_referer=${encodeURIComponent(window.location.href)}`;
console.log('bvid, aid', bvid, aid)
return new Promise(function (resolve) {
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", () => {
var json = JSON.parse(xhr.responseText);
console.log('getDetailInfo json :', json)
if (json?.data?.View) detailCache[bvid] = json.data.View;
resolve()
});
xhr.open("get", api);
xhr.responseType = "text";
xhr.send();
});
}
var parsePlayInfo = async function(rs) {
var data = rs.result || rs.data;
var infos = [];
var sortBw = function(a, b) {
return b.id != a.id ? b.id - a.id : b.bandwidth - a.bandwidth;
}
var { baseUrl: audioUrl } = data.dash.audio.sort(sortBw)[0];
var defns = [];
data.dash.video.sort(sortBw).forEach(video => {
if (defns.includes(video.id)) return;
defns.push(video.id);
var { width, height, baseUrl: videoUrl } = video;
infos.push({ audio: audioUrl, video: videoUrl });
});
// 分辨率从高到低排序
return infos[0];
}
var getDanmuConfig = async function (oid, pid) {
return new Promise(function (resolve) {
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function () {
var res = protoView.decode(new Uint8Array(xhr.response));
resolve(res);
});
xhr.open("get", `${API_HOST}/v2/dm/web/view?type=1&oid=${oid}&pid=${pid}`);
xhr.responseType = "arraybuffer";
xhr.send();
});
}
var parseDanmu = async function(meta) {
var oid = meta.cid || window.cid;
var pid = meta.aid || window.aid;
if (!oid || !pid) return;
var cacheKey = `${oid}.${pid}`;
if (danmuCache[cacheKey]) return danmuCache[cacheKey];
var config = await getDanmuConfig(oid, pid);
var total = config.dmSge.total;
console.log(config);
var allrequset = [];
var protoSegments = [];
// todo: 会不会太快的被封IP?
for (var index = 1; index <= total; index++) {
allrequset.push(new Promise(function (resolve) {
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function () {
protoSegments[index] = xhr.response;
resolve();
});
xhr.open("get", `${API_HOST}/v2/dm/web/seg.so?type=1&oid=${oid}&pid=${pid}&segment_index=${index}`);
xhr.responseType = "arraybuffer";
xhr.send();
}));
}
//完成所有的网络请求大概要300ms
await Promise.all(allrequset);
var segments = [];
protoSegments.forEach(function (seg) {
segments = segments.concat(protoSeg.decode(new Uint8Array(seg)).elems);
});
// console.log('got danmu', segments);
danmuCache[cacheKey] = segments;
return segments;
}
var parser = async function () {
var href = new URL(window.location.href);
var paths = href.pathname.split('/');
if (paths[1] === 'video' || paths[1] === 'bangumi') {
if (!videoInfo) {
// 首次加载
var infoKey = '__playinfo__';
var scripts = $('script').filter((i, e) => e.innerText.includes(infoKey));
if (scripts.length > 0) {
eval(scripts[0].innerText);
videoInfo = window[infoKey];
// console.log('首次加载 videoInfo', videoInfo)
}
}
if (!videoInfo) return [];
var url;
var meta = await parsePlayInfo(videoInfo);
if (meta && meta.video && meta.audio) {
url = meta.video;
}
// console.log({url, meta})
if (!url) return [];
var video_id = md5(url + meta.audio);
var id = `bilib-${md5(video_id)}`
if ($(`[mono-dsg-id=${id}]`).length > 0) return [];
// 获取 meta 信息
var detailInfo;
if (paths[1] === 'video' && paths[2].startsWith('BV')) {
var cacheKey = paths[2];
if (!detailCache[cacheKey]) await getDetailInfo(cacheKey);
detailInfo = detailCache[cacheKey];
detailInfo.cover = detailInfo?.pic;
} else if (paths[1] === 'bangumi' && paths[3].startsWith('ep')) {
var cacheKey = paths[3];
if (!detailCache[cacheKey]) await getBangumiInfo(cacheKey);
detailInfo = detailCache[cacheKey];
detailInfo.title = detailInfo.share_copy;
}
if (detailInfo) {
Object.assign(meta, detailInfo);
console.log('has detail', meta);
}
// 获取弹幕,必须在detail之后,不然可能没有oid/cid
// try {
// meta.danmu = await parseDanmu(meta);
// console.log('danmu load', meta.danmu?.length)
// } catch (err) {
// console.log('danmu err', err);
// }
var container = $('.bilibili-player-video-wrap')[0];
if (!container) container = $('.bpx-player-primary-area')[0];
meta.name = filename(meta.title || document?.title);
item = { id, url, container, meta }
return [item];
} else {
return [];
}
}
if (mono?.init) mono.init({
parser,
interval: 100,
});
})()