Greasy Fork is available in English.
从官方CDN加载视频
当前为
// ==UserScript==
// @name 【哔哩哔哩】屏蔽视频PCDN地址
// @namespace https://github.com/AkagiYui/UserScript
// @supportURL https://github.com/AkagiYui/UserScript/issues
// @homepage https://github.com/AkagiYui
// @version 0.2.0
// @description 从官方CDN加载视频
// @author AkagiYui
// @license MIT
// @icon https://static.hdslb.com/images/favicon.ico
// @match https://www.bilibili.com/video/*
// @match https://www.bilibili.com/list/*
// @match https://www.bilibili.com/bangumi/play/*
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-start
// ==/UserScript==
/******/ (() => { // webpackBootstrap
/******/ "use strict";
var __webpack_exports__ = {};
const log = console.log.bind(console, "AkagiYui");
// 获取配置
function getConfig(key) {
return GM_getValue(key, booleanConfigs[key].defaultValue);
}
// 配置注册
let menuIds = [];
function registerMenuCommand() {
menuIds.forEach((id) => {
GM_unregisterMenuCommand(id);
});
menuIds = [];
Object.entries(booleanConfigs).forEach(([key, config]) => {
let commandName = getConfig(key) ? "✅" : "❌";
commandName += ` ${config.title}`;
let id = GM_registerMenuCommand(commandName, () => {
let newValue = !getConfig(key);
let valueToSet = config.callback ? config.callback(newValue) : newValue;
GM_setValue(key, valueToSet);
registerMenuCommand();
});
menuIds.push(id);
});
}
// 配置项
const booleanConfigs = {
blockPlayError: {
title: "屏蔽“播放遇到问题?”提示",
defaultValue: false,
},
};
registerMenuCommand();
const pageWindow = unsafeWindow;
// 过滤掉pcdn的url
const filterBackupUrls = (mediaArray) => {
mediaArray.forEach((media) => {
const filteredUrls = media.backupUrl.filter((url) => !url.includes("mcdn.bilivideo.cn"));
if (!media.baseUrl.includes("mcdn.bilivideo.cn")) {
filteredUrls.unshift(media.baseUrl);
}
if (filteredUrls.length > 0) {
const baseUrl = filteredUrls[0];
const backupUrl = filteredUrls.slice(1);
media.baseUrl = baseUrl;
media.base_url = baseUrl;
media.backupUrl = backupUrl;
media.backup_url = backupUrl;
}
else {
log("没有可用的非pcdn url");
}
});
};
const replaceUrl = (data) => {
let dash = data;
log("处理前", JSON.parse(JSON.stringify(dash)));
const video = dash.video;
const audio = dash.audio;
filterBackupUrls(video);
filterBackupUrls(audio);
log("处理后", JSON.parse(JSON.stringify(dash)));
return dash;
};
// 播放器初始化参数
let __playinfo__ = pageWindow.__playinfo__;
Object.defineProperty(pageWindow, "__playinfo__", {
get: () => __playinfo__,
set: (value) => {
log("播放器初始化参数", value);
if (value.data) {
log("非番剧");
value.data.dash = replaceUrl(value.data.dash);
}
if (value.result) {
log("番剧");
value.result.video_info.dash = replaceUrl(value.result.video_info.dash);
}
__playinfo__ = value;
},
});
// 播放列表请求处理
const originalXHR = pageWindow.XMLHttpRequest;
const xhrOpen = originalXHR.prototype.open;
originalXHR.prototype.open = function (_, url) {
var _a, _b;
const xhr = this;
if (url.includes("api.bilibili.com/x/player/wbi/playurl")) {
// 包括单个视频的多个(画质数量*编码数量)的url
const avid = (_a = url.match(/avid=(\d+)/)) === null || _a === void 0 ? void 0 : _a[1]; // 提取出url中的avid参数
log("请求视频列表", `av${avid}`);
const getter = Object.getOwnPropertyDescriptor(originalXHR.prototype, "responseText").get;
Object.defineProperty(xhr, "responseText", {
get: () => {
let response = getter.call(xhr);
let responseJson = JSON.parse(response);
responseJson.data.dash = replaceUrl(responseJson.data.dash);
let modifiedResponse = JSON.stringify(responseJson);
return modifiedResponse;
},
});
}
if (url.includes("api.bilibili.com/pgc/player/web/v2/playurl")) {
const season_id = (_b = url.match(/season_id=(\d+)/)) === null || _b === void 0 ? void 0 : _b[1]; // 提取出url中的season_id参数
const ep_id = url.match(/ep_id=(\d+)/); // 提取出url中的ep_id参数
log("请求番剧列表", `ss${season_id}`, ep_id ? `ep${ep_id[1]}` : "ep_id not found");
const getter = Object.getOwnPropertyDescriptor(originalXHR.prototype, "responseText").get;
Object.defineProperty(xhr, "responseText", {
get: () => {
let response = getter.call(xhr);
let responseJson = JSON.parse(response);
responseJson.result.video_info.dash = replaceUrl(responseJson.result.video_info.dash);
let modifiedResponse = JSON.stringify(responseJson);
return modifiedResponse;
},
});
}
return xhrOpen.apply(xhr, arguments);
};
// 屏蔽“播放遇到问题?”提示
if (getConfig("blockPlayError")) {
const originalDefineProperty = pageWindow.Object.defineProperty;
pageWindow.Object.defineProperty = function (target, propertyKey, descriptor) {
if (propertyKey === "videoHasBuffered") {
originalDefineProperty(target, "showLoadTimeoutFeedback", {
get: () => () => { },
set: () => {
pageWindow.Object.defineProperty = originalDefineProperty;
},
});
}
return originalDefineProperty(target, propertyKey, descriptor);
};
}
/******/ })()
;