Greasy Fork

Greasy Fork is available in English.

【哔哩哔哩】屏蔽视频PCDN地址

从官方CDN加载视频

当前为 2024-08-15 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         【哔哩哔哩】屏蔽视频PCDN地址
// @version      0.2.3
// @description  从官方CDN加载视频
// @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
// @namespace    https://github.com/AkagiYui/UserScript
// @supportURL   https://github.com/AkagiYui/UserScript/issues
// @homepage     https://github.com/AkagiYui
// @author       AkagiYui
// @license      MIT
// ==/UserScript==

/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ 636:
/***/ ((__unused_webpack_module, exports) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.useBooleanMenu = exports.useLogger = void 0;
/**
 * 生成 Logger
 * @param name 前缀
 * @returns console.log
 */
const useLogger = (name) => {
    if (name) {
        return console.log.bind(console, "AkagiYui", `[${name}]`);
    }
    return console.log.bind(console, "AkagiYui");
};
exports.useLogger = useLogger;
/**
 * 布尔菜单配置
 * @param configs 配置项
 * @returns 配置获取函数
 */
const useBooleanMenu = (configs) => {
    // 缓存
    const cache = {};
    // 获取配置
    const getConfig = (key) => {
        if (cache[key] !== undefined) {
            return cache[key];
        }
        let value = GM_getValue(key, configs[key].defaultValue);
        cache[key] = value;
        return value;
    };
    // 配置注册
    let menuIds = [];
    const registerMenuCommand = () => {
        menuIds.forEach((id) => {
            GM_unregisterMenuCommand(id);
        });
        menuIds = [];
        Object.entries(configs).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);
                cache[key] = valueToSet;
                registerMenuCommand();
            });
            menuIds.push(id);
        });
    };
    registerMenuCommand();
    return { getConfig };
};
exports.useBooleanMenu = useBooleanMenu;


/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
var exports = __webpack_exports__;
var __webpack_unused_export__;

__webpack_unused_export__ = ({ value: true });
const utils_1 = __webpack_require__(636);
const log = (0, utils_1.useLogger)("bilibili-ban-pcdn");
const { getConfig } = (0, utils_1.useBooleanMenu)({
    blockPlayError: {
        title: "屏蔽“播放遇到问题?”提示",
        defaultValue: false,
    },
    blockBCacheCDN: {
        title: "屏蔽地区CDN",
        defaultValue: false,
    },
});
// 真实页面
const pageWindow = unsafeWindow;
// 挑出有用的链接
const removeSomeUrls = (allUrls) => {
    const whichToRemove = [/mcdn.bilivideo.(com|cn)/];
    if (getConfig("blockBCacheCDN")) {
        whichToRemove.push(/(cn-.*\.bilivideo\.(com|cn))/);
    }
    const urls = allUrls.filter((url) => !whichToRemove.some((re) => re.test(url)));
    const baseUrl = urls[0];
    const backupUrls = urls.slice(1);
    return { baseUrl, backupUrls };
};
// 处理资源数据
const cleanPlayInfo = (playInfo) => {
    log("处理前", JSON.parse(JSON.stringify(playInfo)));
    if (playInfo.data) {
        log("非番剧视频");
        cleanNonBangumiVideo(playInfo.data);
    }
    else if (playInfo.result) {
        log("番剧视频");
        cleanBangumiVideo(playInfo.result);
    }
    log("处理后", JSON.parse(JSON.stringify(playInfo)));
};
const cleanNonBangumiVideo = (data) => {
    if (data.dash) {
        cleanDash(data.dash);
    }
    if (data.durl) {
        log("试看视频");
        cleanDurl(data.durl);
    }
};
const cleanBangumiVideo = (result) => {
    if (!result.video_info) {
        log("番剧播放列表不存在,可能是没有大会员或未承包");
        return;
    }
    const videoInfo = result.video_info;
    if (videoInfo.dash) {
        cleanDash(videoInfo.dash);
    }
    else if (videoInfo.durl || videoInfo.durls) {
        log("试看番剧");
        if (videoInfo.durl) {
            cleanDurl(videoInfo.durl);
        }
        if (videoInfo.durls) {
            videoInfo.durls.forEach((durlGroup) => cleanDurl(durlGroup.durl));
        }
    }
    else {
        log("番剧播放列表不存在,可能是没有大会员或未承包");
    }
};
const cleanDash = (dash) => {
    const cleanMedia = (media) => {
        const { baseUrl, backupUrls } = removeSomeUrls([media.baseUrl, ...media.backupUrl]);
        media.baseUrl = media.base_url = baseUrl;
        media.backupUrl = media.backup_url = backupUrls;
    };
    dash.video.forEach(cleanMedia);
    dash.audio?.forEach(cleanMedia);
};
const cleanDurl = (durls) => {
    durls.forEach((durl) => {
        const { baseUrl, backupUrls } = removeSomeUrls([durl.url, ...durl.backup_url]);
        durl.url = baseUrl;
        durl.backup_url = backupUrls;
    });
};
// 播放器初始化参数
let __playinfo__ = pageWindow.__playinfo__;
Object.defineProperty(pageWindow, "__playinfo__", {
    get: () => __playinfo__,
    set: (value) => {
        log("初始化参数", value);
        cleanPlayInfo(value);
        __playinfo__ = value;
    },
});
// 播放列表请求处理
const originalXHR = pageWindow.XMLHttpRequest;
const xhrOpen = originalXHR.prototype.open;
originalXHR.prototype.open = function (_, url) {
    if (url.includes("api.bilibili.com/x/player/wbi/playurl")) {
        // 包括单个视频的多个(画质数量*编码数量)的url
        const avid = url.match(/avid=(\d+)/)?.[1]; // 提取出url中的avid参数
        log("请求视频列表", `av${avid}`);
        const getter = Object.getOwnPropertyDescriptor(originalXHR.prototype, "responseText").get;
        Object.defineProperty(this, "responseText", {
            get: () => {
                const response = getter.call(this);
                const responseJson = JSON.parse(response);
                cleanPlayInfo(responseJson);
                return JSON.stringify(responseJson);
            },
        });
    }
    if (url.includes("api.bilibili.com/pgc/player/web/v2/playurl")) {
        const season_id = url.match(/season_id=(\d+)/)?.[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(this, "responseText", {
            get: () => {
                const response = getter.call(this);
                const responseJson = JSON.parse(response);
                cleanPlayInfo(responseJson);
                return JSON.stringify(responseJson);
            },
        });
    }
    return xhrOpen.apply(this, 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);
    };
}

})();

/******/ })()
;