// ==UserScript==
// @name BiliBiliEX
// @name:zh-CN 哔哩哔哩扩展
// @namespace FyrGlow.BiliBili
// @version 1.0
// @description BiliBili Enhance
// @description:zh-CN 对哔哩哔哩添加一些扩展功能
// @author FyrGlow
// @match https://www.bilibili.com/video/*
// @require https://gitee.com/ASev/chrome/raw/master/SharedArrayBuffer.js
// @require https://update.greasyfork.org/scripts/4274/13748/FileSaverjs.js
// @require https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js
// @icon https://static.hdslb.com/images/favicon.ico
// @homepage https://space.bilibili.com/266986139
// @grant none
// @license CC BY-NC-ND
// ==/UserScript==
(function () {
'use strict';
function log(string) {
console.log(string);
}
function getVideoBV(url) {
const pattern = /BV[0-9A-Za-z]+(?![\w\s])/;
const result = url.match(pattern);
return result ? result[0] : null;
}
// 正则表达式匹配BV号 函数
async function getBvJson(Burl) {
const url = Burl
try {
const response = await fetch(url, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`HTTP错误! 状态: ${response.status}`);
}
const json = await response.json();
return json;
} catch (error) {
// 处理错误
console.error('获取JSON出错:', error);
}
}
// 获取视频信息 函数
function getElementsXY(elements) {
var elementsXY = document.getElementsByClassName(elements);
for (var i = 0; i < elementsXY.length; i++) {
var elementXY = elementsXY[i];
var rectXY = elementXY.getBoundingClientRect();
}
return rectXY;
}
// 取元素矩形信息
function SynElement(OElement, IElement) {
var sourceIcon = document.querySelector(OElement);
var styles = window.getComputedStyle(sourceIcon);
var targetIcon = document.querySelector(IElement);
Object.keys(styles).forEach(function (key) {
var property = styles[key];
if (property && property !== 'undefined' && targetIcon.style.setProperty) {
targetIcon.style.setProperty(key, property, styles.getPropertyPriority(key));
}
});
}
// 同步元素样式
function createNewBDB(xz, EBYID, id) {
var newDivBDB = document.createElement('div');
if (xz == 1) {
newDivBDB.innerHTML = `
`;
} else if (xz == 2) {
newDivBDB.innerHTML = ``;
}
var parentDiv = document.getElementById(EBYID);
if (parentDiv) {
parentDiv.insertAdjacentElement('afterend', newDivBDB);
} else {
console.error('找不到ID为' + EBYID + '的元素');
}
}
// 选择插入位置
function removeDiv(ID) {
var elements = document.querySelectorAll('#' + ID);
elements.forEach(function (element) {
element.remove();
});
}
// 删除元素
function toggleElements(height, width, translateX) {
var bpia = document.getElementById('bpia');
bpia.style.width = width + 'px';
bpia.style.height = height + 'px';
var bpmtra = document.getElementById('bpmtra');
bpmtra.style.transform = 'translateX(' + translateX + 'px)';
var bdcbpw = document.getElementById('bdcbpw');
bdcbpw.style.height = height + 'px';
var bpiac = document.getElementById('bpiac');
bpiac.style.height = height + 'px';
}
// 切换页面大小函数
function toggleWrap(show) {
var etw = document.querySelectorAll('.bpx-player-top-wrap');
var ecw = document.querySelectorAll('.bpx-player-control-wrap');
etw.forEach(function (element) {
element.style.display = show ? 'block' : 'none';
});
ecw.forEach(function (element) {
element.style.display = show ? 'block' : 'none';
});
}
// 切换warp的显示状态
function addInputEvent(name) {
var radioButtons = document.querySelectorAll('input[type="radio"][name="' + name + '"]');
radioButtons.forEach(function (radio) {
radio.addEventListener('change', function (event) {
if (this.checked) {
removeDiv('bpdsrfc2')
addFormat(this.id);
}
});
});
}
// 添加选择事件
function addFormat(ID) {
const BvSF = BvJson.data.support_formats;
for (var i = BvSF.length - 1; i >= 0; i--) {
if (BvSF[i].new_description == ID) {
const BvCodecs = BvSF[i].codecs;
var BCCount = BvCodecs.length;
BDSW2height = 70 + (BCCount * 38);
for (var j = BCCount - 1; j >= 0; j--) {
createNewBDB(2, 'bdwd', BvCodecs[j]);
if (j === 0) {
var av01 = document.getElementById(BvCodecs[j]);
av01.checked = true;
}
}
setFormatSettingText('选择 [ ' + ID + ' ] 的视频格式')
}
}
}
// 为指定质量视频添加格式选择
function setFormatSettingText(newText) {
const formatSettingElement = document.querySelector('#bpdslm .bpx-player-dm-setting-left-more-text');
formatSettingElement.textContent = newText;
}
// 设置格式选择的导航提醒
function checkJsonPathValue(jsonObj, path, content) {
function getValueFromPath(obj, keys) {
if (!keys.length) {
return [obj];
}
if (Array.isArray(obj)) {
let result = [];
for (let item of obj) {
result = result.concat(getValueFromPath(item, keys));
}
return result;
} else if (obj !== null && typeof obj === 'object' && keys[0] in obj) {
return getValueFromPath(obj[keys[0]], keys.slice(1));
} else {
return [];
}
}
let keys = path.split('.');
let values = getValueFromPath(jsonObj, keys);
return values.includes(content);
}
// 接受三个函数:json、路径、值 | 如果存在返回true
function StartDownload() {
var spzlID = SeltctedID('xzzlxz');
var spgsID = SeltctedID('xzgsxz');
var spzlQID = findQualityByDescription(BvJson, spzlID);
var spLink = findBaseUrlByIdAndCodecs(BvJson, spzlQID, spgsID);
var ypLink = BvJson.data.dash.audio[0].baseUrl;
var BiliURL = window.location.href;
var BVString = getVideoBV(BiliURL);
var BVtitle = document.title.match(/^[^_]+/);
if (yspxz.checked) {
startDownloadBVM(spLink, ypLink, `${BVtitle[0]}_${BVString}_${spzlID}.mp4`);
} else {
if (spxz.checked) {
startDownloadBV(spLink, `video_${BVtitle[0]}_${BVString}_${spzlID}.mp4`);
}
if (ypxz.checked) {
startDownloadBV(ypLink, `audio_${BVtitle[0]}_${BVString}.mp3`);
}
}
}
// 预备开始下载
const fetchFile = async (url, merge) => {
const text = document.querySelector('.video-title.special-text-indent');
const oldtext = text.textContent;
const res = await fetch(url);
const reader = res.body.getReader();
const contentLength = +res.headers.get("Content-Length");
if (!contentLength) {
const data = await res.arrayBuffer();
return new Uint8Array(data);
}
let receivedLength = 0;
let chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
chunks.push(value);
receivedLength += value.length;
console.log(
`文件总大小: ${contentLength} %c 已下载: ${receivedLength}`,
"background: #222; color: #bada55"
);
let percentage = (receivedLength / contentLength) * 100;
text.textContent = '正在下载,进度:' + percentage.toFixed(2) + '%';
}
text.textContent = oldtext;
if (merge === false) {
return new Blob(chunks);
} else {
let chunksAll = new Uint8Array(receivedLength);
let position = 0;
for (let chunk of chunks) {
chunksAll.set(chunk, position);
position += chunk.length;
}
return chunksAll;
}
};
// 下载文件
const Download = async (data, fileName = "download.mp4") => {
saveAs(data, fileName);
};
const startDownloadBV = async (Url, fileName) => {
const downFile = await fetchFile(Url, false);
Download(downFile, fileName);
console.log(
`%c 下载完成!`,
"background: #222; color: #bada55"
);
};
// 下载单文件
const startDownloadBVM = async (videoUrl, audioUrl, fileName) => {
const video = await fetchFile(videoUrl, true);
const audio = await fetchFile(audioUrl, true);
const downFile = await FFMerge(video, audio);
Download(downFile, fileName);
console.log(
`%c 下载合并完成!`,
"background: #222; color: #bada55"
);
};
// 下载合并文件
function SeltctedID(name) {
var selectedRadio = document.querySelector('input[name=' + name + ']:checked');
if (selectedRadio) {
var selectedId = selectedRadio.id;
return selectedId;
} else {
console.log('错误:没有ID为' + name + '的选项');
}
}
// 获取单选框所选中ID
function findQualityByDescription(Json, spzlID) {
for (var i = 0; i < Json.data.support_formats.length; i++) {
if (Json.data.support_formats[i].new_description === spzlID) {
return Json.data.support_formats[i].quality;
}
}
return 80;
}
// 获取视频质量ID
function findBaseUrlByIdAndCodecs(Json, spzlQID, spgsID) {
for (var i = 0; i < Json.data.dash.video.length; i++) {
if (Json.data.dash.video[i].id === spzlQID && Json.data.dash.video[i].codecs === spgsID) {
return Json.data.dash.video[i].baseUrl;
}
}
return null;
}
// 获取视频下载链接
const initFFmpeg = () => {
const { createFFmpeg } = FFmpeg;
let ffmpeg = null;
if (ffmpeg === null) {
ffmpeg = createFFmpeg({ log: false });
}
if (!ffmpeg.isLoaded()) {
ffmpeg.load();
}
return ffmpeg;
};
// 初始化FFmpeg
const FFMerge = async (video, audio) => {
if (!ffmpeg.isLoaded()) {
await ffmpeg.load();
}
ffmpeg.FS("writeFile", "video.mp4", video);
ffmpeg.FS("writeFile", "audio.mp3", audio);
await ffmpeg.run(
...`-i video.mp4 -i audio.mp3 -c copy -map 0:v:0 -map 1:a:0 output.mp4`.split(
" "
)
);
const data = ffmpeg.FS("readFile", "output.mp4");
return new Blob([data.buffer], { type: "video/mp4" });
};
// 合并音视频
// --------On函数群--------
var BDSWheight = 175;
var BDSW2height = 76;
var BvJson;
var FullScreen = false;
// --------On全局变量--------
function AllStart() {
var elements = document.querySelectorAll('.bd-down-dl-open');
elements.forEach(function (element) {
element.remove();
});
toggleWrap(true);
// 删除遗留元素
var originalDiv = document.querySelector('.bpx-player-dm-setting');
var newDiv = document.createElement('div');
newDiv.className = 'bd-down-dl-open';
newDiv.innerHTML = '';
originalDiv.parentNode.insertBefore(newDiv, originalDiv.nextSibling);
// SynElement('.bpx-player-dm-setting-wrap','.bd-down-dl-setting-wrap');
var bdOpen = document.querySelector('.bd-down-svg-icon');
var bdSetting = document.getElementById('bd-setting-warp');
var bdOpenBool = false;
if (bdOpen) {
bdOpen.addEventListener('click', function () {
if (bdOpenBool == false) {
bdSetting.style.display = '';
if (!FullScreen) {
toggleWrap(false)
} else {
toggleWrap(true)
}
bdOpenBool = true;
} else {
bdSetting.style.display = 'none';
toggleWrap(true)
bdOpenBool = false;
}
});
}
// 插入下载按钮元素
SynElement('.bpx-player-dm-switch', '.bd-down-dl-open');
// --brand_blue
var style = document.createElement('style');
style.innerHTML = `.bd-down-svg-icon:hover .icon {fill: var(--brand_blue);}`;
document.head.appendChild(style);
var classMode = document.getElementById('bilibili-player');
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
if (classMode.className === 'mode-webscreen') {
var svgElements = document.querySelectorAll('.bd-down-svg-icon');
svgElements.forEach(function (svgElement) {
svgElement.style.fill = 'rgba(255, 255, 255, 0.9)';
FullScreen = true;
});
} else {
var svgElements = document.querySelectorAll('.bd-down-svg-icon');
svgElements.forEach(function (svgElement) {
svgElement.style.fill = '#757575';
FullScreen = false;
});
}
}
});
}).observe(classMode, {
attributes: true
});
// 同步元素样式
var yspxz = document.getElementById('yspxz');
var spxz = document.getElementById('spxz');
var ypxz = document.getElementById('ypxz');
var yspxzc = localStorage.getItem('yspxz');
var spxzc = localStorage.getItem('spxz');
var ypxzc = localStorage.getItem('ypxz');
if (yspxzc == null || yspxzc == 1 || (spxzc == 0 && ypxzc == 0)) {
yspxz.checked = true;
}
if (spxzc == 1) {
spxz.checked = true;
}
if (ypxzc == 1) {
ypxz.checked = true;
}
yspxz.addEventListener('change', function () {
if (this.checked) {
localStorage.setItem('yspxz', 1)
localStorage.setItem('spxz', 0)
localStorage.setItem('ypxz', 0)
spxz.checked = false;
ypxz.checked = false;
} else {
if (spxz.checked == false && ypxz.checked == false) {
spxz.checked = true;
localStorage.setItem('spxz', 1)
ypxz.checked = true;
localStorage.setItem('ypxz', 1)
localStorage.setItem('yspxz', 0)
}
}
});
spxz.addEventListener('change', function () {
if (this.checked) {
localStorage.setItem('yspxz', 0)
localStorage.setItem('spxz', 1)
yspxz.checked = false;
} else {
localStorage.setItem('spxz', 0)
if (spxz.checked == false && ypxz.checked == false) {
yspxz.checked = true;
localStorage.setItem('yspxz', 1)
}
}
});
ypxz.addEventListener('change', function () {
if (this.checked) {
localStorage.setItem('yspxz', 0)
localStorage.setItem('ypxz', 1)
yspxz.checked = false;
} else {
localStorage.setItem('ypxz', 0)
if (spxz.checked == false && ypxz.checked == false) {
yspxz.checked = true;
localStorage.setItem('yspxz', 1)
}
}
});
var startBD = document.getElementById('ksxz');
var cancelBD = document.getElementById('qxxz');
startBD.addEventListener('click', function () {
console.log('开始下载!');
StartDownload()
// 在这里添加开始下载的逻辑
});
cancelBD.addEventListener('click', function () {
console.log('我给你取消个蛋!');
// 在这里添加取消下载的逻辑
});
// 设置选择与按钮
const BiliBiliURL = window.location.href;
const BV = getVideoBV(BiliBiliURL);
const BvUrl = `https://api.bilibili.com/x/web-interface/view?bvid=${BV}`;
getBvJson(BvUrl)
.then(BVInfo => {
const Cids = BVInfo.data.pages.map(page => page.cid);
const CidUrl = `https://api.bilibili.com/x/player/wbi/playurl?bvid=${BV}&cid=${Cids[0]}&fnval=4048`;
// log('CidUrl = ' + CidUrl);
getBvJson(CidUrl)
.then(BVInfo => {
BvJson = BVInfo;
const BvFormats = BVInfo.data.support_formats;
var sfcount = BvFormats.length;
var BDSWheightVar = 0;
for (var i = sfcount - 1; i >= 0; i--) {
const BvDescription = BvFormats[i].new_description;
const BvQuality = BvFormats[i].quality;
const DlQuality = "data.dash.video.id";
if (checkJsonPathValue(BVInfo, DlQuality, BvQuality)) {
createNewBDB(1, 'bdws', BvDescription);
var lastBDB = BvDescription;
BDSWheightVar++;
};
}
BDSWheight = 170 + (BDSWheightVar * 38);
toggleElements(BDSWheight, 250, 0);
var topQuality = document.getElementById(lastBDB);
topQuality.checked = true;
addFormat(lastBDB);
addInputEvent('xzzlxz');
})
})
.catch(error => {
console.error('获取视频CID出错:', error);
});
// 获取视频下载地址 添加下载选项
document.getElementById('bpdslm').addEventListener('click', function () {
toggleElements(BDSW2height, 320, -320);
});
document.getElementById('bpdsrm').addEventListener('click', function () {
toggleElements(BDSWheight, 250, 0);
});
// 设置切换页面
}
// 注入功能面板
var checkInterval = setInterval(function () {
var element = document.querySelector('.bpx-player-dm-setting');
if (element) {
AllStart()
clearInterval(checkInterval);
console.log('%c ____ __ _______ __ _____ __ __ __\n / __ )/ / / ____/ |/ / / ___// /_____ ______/ /_/ /\n / __ / / / __/ | / \\__ \\/ __/ __ `/ ___/ __/ / \n / /_/ / /___/ /___ / |_ ___/ / /_/ /_/ / / / /_/_/ \n/_____/_____/_____//_/|_( )____/\\__/\\__,_/_/ \\__(_) \n |/ By BiliBili FyrGlow', 'color: #00a1d6; font-size: 16px;');
}
}, 100); // 持续检测元素 存在则注入面板
const ffmpeg = initFFmpeg();
log(ffmpeg);
// 初始化FFMPEG
function reloadScript() {
AllStart();
console.log('%c ____ __ _______ __ _____ __ __ __\n / __ )/ / / ____/ |/ / / ___// /_____ ______/ /_/ /\n / __ / / / __/ | / \\__ \\/ __/ __ `/ ___/ __/ / \n / /_/ / /___/ /___ / |_ ___/ / /_/ /_/ / / / /_/_/ \n/_____/_____/_____//_/|_( )____/\\__/\\__,_/_/ \\__(_) \n |/ By BiliBili FyrGlow', 'color: #00a1d6; font-size: 16px;');
}
let ourlBV = getVideoBV(window.location.href);
const observer = new MutationObserver(() => {
if (ourlBV !== getVideoBV(window.location.href)) {
ourlBV = getVideoBV(window.location.href);
reloadScript();
}
});
const config = { subtree: true, childList: true };
observer.observe(document, config);
window.addEventListener('popstate', reloadScript);
// 检测URL改变,重载脚本
})();