Greasy Fork

Greasy Fork is available in English.

哔哩哔哩 CDN 优选和画质固定

哔哩哔哩 CDN 优选,逻辑参考了 https://github.com/guozhigq/pilipala,大部分代码由 ai 完成。固定 cookie 中存储的默认画质,不允许哔哩哔哩修改。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         哔哩哔哩 CDN 优选和画质固定
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  哔哩哔哩 CDN 优选,逻辑参考了 https://github.com/guozhigq/pilipala,大部分代码由 ai 完成。固定 cookie 中存储的默认画质,不允许哔哩哔哩修改。
// @author       Moranjianghe
// @match        *://*.bilibili.com/*
// @icon         https://www.bilibili.com/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// @connect      proxy-tf-all-ws.bilivideo.com
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    /**
     * B站视频CDN优化与MCDN代理类
     */
    class BilibiliCDNOptimizer {
        constructor() {
            // CDN 节点列表,按优先级排序
            this.cdnList = {
                'ali': 'upos-sz-mirrorali.bilivideo.com',      // 阿里云 (推荐)
                'cos': 'upos-sz-mirrorcos.bilivideo.com',      // 腾讯云
                'hw': 'upos-sz-mirrorhw.bilivideo.com',        // 华为云
                'ws': 'upos-sz-mirrorws.bilivideo.com',        // 网宿
                'bda2': 'upos-sz-mirrorbda2.bilivideo.com',    // 百度云
            };

            // 视频画质选项
            this.qualityOptions = {
                '6': '240P 极速',
                '16': '360P 流畅',
                '32': '480P 清晰',
                '64': '720P 高清',
                '74': '720P60 高帧率',
                '80': '1080P 高清',
                '100': '智能修复',
                '112': '1080P+ 高码率',
                '116': '1080P60 高帧率',
                '120': '4K 超清',
                '125': 'HDR 真彩色',
                '126': '杜比视界',
                '127': '8K 超高清',
            };

            // 使用与cdn.js相同的MCDN检测方法
            this.upgcxcodeRegex = /(https?:\/\/)(.*?)(\/upgcxcode\/)/;

            // 初始化设置
            this.enableCDNOptimize = GM_getValue('enableCDNOptimize', true);
            this.enableMCDNProxy = GM_getValue('enableMCDNProxy', true);
            this.preferredCDN = GM_getValue('preferredCDN', 'ali');
            this.debugMode = GM_getValue('debugMode', false);

            // 画质固定功能
            this.enableQualityFix = GM_getValue('enableQualityFix', false);
            this.fixedQuality = GM_getValue('fixedQuality', '127'); // 默认8K超高清

            // 备用URL存储
            this.backupUrls = new Map();

            // 初始化后台拦截
            this.initInterceptors();

            // 注册菜单命令
            this.registerMenuCommands();

            // 设置画质cookie
            if (this.enableQualityFix) {
                this.setQualityCookie();
            }

            this.log("B站视频CDN优化与MCDN代理已初始化");
        }

        /**
         * 日志记录函数
         * @param {string} message - 日志消息
         */
        log(message) {
            if (this.debugMode) {
                console.log(`[CDN] ${message}`);  // 与cdn.js保持一致的日志前缀
            }
        }

        /**
         * 注册用户脚本菜单命令
         */
        registerMenuCommands() {
            // CDN优化开关
            GM_registerMenuCommand(`${this.enableCDNOptimize ? '✅' : '❌'} CDN优化`, () => {
                this.enableCDNOptimize = !this.enableCDNOptimize;
                GM_setValue('enableCDNOptimize', this.enableCDNOptimize);
                this.log(`CDN优化已${this.enableCDNOptimize ? '启用' : '禁用'}`);
                location.reload();
            });

            // MCDN代理开关
            GM_registerMenuCommand(`${this.enableMCDNProxy ? '✅' : '❌'} MCDN代理`, () => {
                this.enableMCDNProxy = !this.enableMCDNProxy;
                GM_setValue('enableMCDNProxy', this.enableMCDNProxy);
                this.log(`MCDN代理已${this.enableMCDNProxy ? '启用' : '禁用'}`);
                location.reload();
            });

            // CDN选择菜单
            GM_registerMenuCommand(`🔄 当前CDN: ${this.preferredCDN}`, () => {
                const cdnKeys = Object.keys(this.cdnList);
                const currentIndex = cdnKeys.indexOf(this.preferredCDN);
                const nextIndex = (currentIndex + 1) % cdnKeys.length;
                this.preferredCDN = cdnKeys[nextIndex];
                GM_setValue('preferredCDN', this.preferredCDN);
                this.log(`已切换CDN为: ${this.preferredCDN}`);
                alert(`已切换CDN为: ${this.preferredCDN} (${this.cdnList[this.preferredCDN]})`);
            });

            // 画质固定开关
            GM_registerMenuCommand(`${this.enableQualityFix ? '✅' : '❌'} 画质固定`, () => {
                this.enableQualityFix = !this.enableQualityFix;
                GM_setValue('enableQualityFix', this.enableQualityFix);
                this.log(`画质固定已${this.enableQualityFix ? '启用' : '禁用'}`);
                if (this.enableQualityFix) {
                    this.setQualityCookie();
                }
                location.reload();
            });

            // 画质选择菜单
            GM_registerMenuCommand(`🎞️ 固定画质: ${this.qualityOptions[this.fixedQuality]}`, () => {
                this.selectQuality();
            });

            // 调试模式开关
            GM_registerMenuCommand(`${this.debugMode ? '✅' : '❌'} 调试模式`, () => {
                this.debugMode = !this.debugMode;
                GM_setValue('debugMode', this.debugMode);
                this.log(`调试模式已${this.debugMode ? '启用' : '禁用'}`);
            });
        }

        /**
         * 初始化网络请求拦截器
         */
        initInterceptors() {
            // 拦截XMLHttpRequest
            this.interceptXHR();

            // 拦截Fetch
            this.interceptFetch();

            // 监听DOM变化处理视频元素
            this.observeDOM();

            // 拦截媒体源扩展(MSE)
            this.interceptMediaSource();
        }

        /**
         * 拦截XMLHttpRequest请求
         */
        interceptXHR() {
            const self = this;
            const originalXHROpen = XMLHttpRequest.prototype.open;
            const originalXHRSend = XMLHttpRequest.prototype.send;

            XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
                // 保存原始URL以便在send中使用
                this._originalUrl = url;

                const optimizedUrl = self.optimizeUrl(url);
                if (optimizedUrl && optimizedUrl !== url) {
                    self.log(`XHR请求已优化: ${url} -> ${optimizedUrl}`);
                    originalXHROpen.call(this, method, optimizedUrl, async !== false, user, password);
                } else {
                    originalXHROpen.call(this, method, url, async !== false, user, password);
                }
            };

            // 拦截响应以提取备用URL
            XMLHttpRequest.prototype.send = function(body) {
                const xhr = this;
                const originalUrl = xhr._originalUrl;

                // 检查是否是视频信息API
                if (originalUrl && (
                    originalUrl.includes('/x/player/playurl') ||
                    originalUrl.includes('/x/player/wbi/playurl') ||
                    originalUrl.includes('/pgc/player/web/playurl')
                )) {
                    // 添加响应监听器
                    xhr.addEventListener('load', function() {
                        try {
                            if (xhr.responseType === 'json' || xhr.responseType === '') {
                                const response = xhr.responseType === 'json' ? xhr.response : JSON.parse(xhr.responseText);
                                self.extractBackupUrls(response);
                            }
                        } catch (e) {
                            self.log(`提取备用URL错误: ${e.message}`);
                        }
                    });
                }

                originalXHRSend.call(this, body);
            };
        }

        /**
         * 拦截Fetch请求
         */
        interceptFetch() {
            const self = this;
            const originalFetch = window.fetch;

            window.fetch = function(resource, init) {
                let url = '';
                let originalRequest = null;

                if (typeof resource === 'string') {
                    url = resource;
                    const optimizedUrl = self.optimizeUrl(url);
                    if (optimizedUrl && optimizedUrl !== url) {
                        self.log(`Fetch请求已优化: ${url} -> ${optimizedUrl}`);
                        resource = optimizedUrl;
                    }
                } else if (resource instanceof Request) {
                    url = resource.url;
                    originalRequest = resource.clone();
                    const optimizedUrl = self.optimizeUrl(url);
                    if (optimizedUrl && optimizedUrl !== url) {
                        self.log(`Fetch Request已优化: ${url} -> ${optimizedUrl}`);
                        resource = new Request(optimizedUrl, resource);
                    }
                }

                // 判断是否是视频信息API
                const isVideoApi = url && (
                    url.includes('/x/player/playurl') ||
                    url.includes('/x/player/wbi/playurl') ||
                    url.includes('/pgc/player/web/playurl')
                );

                // 执行原始fetch请求
                return originalFetch.call(window, resource, init).then(response => {
                    // 如果是视频API,提取备用URL
                    if (isVideoApi) {
                        response.clone().json().then(data => {
                            self.extractBackupUrls(data);
                        }).catch(err => {
                            self.log(`提取备用URL错误: ${err.message}`);
                        });
                    }
                    return response;
                });
            };
        }

        /**
         * 拦截MediaSource
         */
        interceptMediaSource() {
            if (window.MediaSource) {
                const self = this;
                const originalAddSourceBuffer = MediaSource.prototype.addSourceBuffer;

                MediaSource.prototype.addSourceBuffer = function(mimeType) {
                    self.log(`MediaSource添加缓冲区: ${mimeType}`);
                    const sourceBuffer = originalAddSourceBuffer.call(this, mimeType);
                    return sourceBuffer;
                };
            }
        }

        /**
         * 从API响应中提取备用URL
         * @param {Object} data - API响应数据
         */
        extractBackupUrls(data) {
            try {
                // 处理不同API格式的响应
                if (data && data.data) {
                    const responseData = data.data;

                    // 处理备用URL
                    if (responseData.durl && Array.isArray(responseData.durl)) {
                        responseData.durl.forEach((item, index) => {
                            if (item.url) {
                                // 保存原始URL和备用URL的映射关系
                                if (item.backup_url && Array.isArray(item.backup_url)) {
                                    item.backup_url.forEach(backupUrl => {
                                        if (backupUrl && backupUrl.includes('http')) {
                                            this.backupUrls.set(item.url, backupUrl);
                                            this.log(`提取到备用URL: ${backupUrl} (对应 ${item.url})`);
                                        }
                                    });
                                }
                            }
                        });
                    }

                    // 处理DASH格式
                    if (responseData.dash) {
                        // 处理视频流
                        if (responseData.dash.video && Array.isArray(responseData.dash.video)) {
                            responseData.dash.video.forEach(video => {
                                if (video.base_url && video.backup_url && Array.isArray(video.backup_url)) {
                                    video.backup_url.forEach(backupUrl => {
                                        if (backupUrl && backupUrl.includes('http')) {
                                            this.backupUrls.set(video.base_url, backupUrl);
                                            this.log(`提取到视频备用URL: ${backupUrl} (对应 ${video.base_url})`);
                                        }
                                    });
                                }
                            });
                        }

                        // 处理音频流
                        if (responseData.dash.audio && Array.isArray(responseData.dash.audio)) {
                            responseData.dash.audio.forEach(audio => {
                                if (audio.base_url && audio.backup_url && Array.isArray(audio.backup_url)) {
                                    audio.backup_url.forEach(backupUrl => {
                                        if (backupUrl && backupUrl.includes('http')) {
                                            this.backupUrls.set(audio.base_url, backupUrl);
                                            this.log(`提取到音频备用URL: ${backupUrl} (对应 ${audio.base_url})`);
                                        }
                                    });
                                }
                            });
                        }
                    }
                }
            } catch (e) {
                this.log(`解析备用URL错误: ${e.message}`);
            }
        }

        /**
         * 观察DOM变化以处理视频元素
         */
        observeDOM() {
            const self = this;

            // 页面加载完成后开始观察DOM变化
            window.addEventListener('DOMContentLoaded', () => {
                self.processVideoElements();

                const observer = new MutationObserver(() => {
                    self.processVideoElements();
                });

                observer.observe(document.body, { childList: true, subtree: true });
                self.log('DOM观察器已激活');
            });
        }

        /**
         * 处理页面中的视频元素
         */
        processVideoElements() {
            const self = this;
            const videoElements = document.querySelectorAll('video');

            videoElements.forEach(video => {
                // 处理video.src
                if (video.src) {
                    const backupUrl = self.getBackupUrl(video.src);
                    const targetUrl = backupUrl || video.src;
                    const optimizedUrl = self.optimizeVideoUrl(targetUrl, backupUrl);

                    if (optimizedUrl && optimizedUrl !== video.src) {
                        self.log(`视频元素src已优化: ${video.src} -> ${optimizedUrl}`);
                        video.src = optimizedUrl;
                    }
                }

                // 处理source元素
                const sourceElements = video.querySelectorAll('source');
                sourceElements.forEach(source => {
                    if (source.src) {
                        const backupUrl = self.getBackupUrl(source.src);
                        const targetUrl = backupUrl || source.src;
                        const optimizedUrl = self.optimizeVideoUrl(targetUrl, backupUrl);

                        if (optimizedUrl && optimizedUrl !== source.src) {
                            self.log(`视频source元素已优化: ${source.src} -> ${optimizedUrl}`);
                            source.src = optimizedUrl;
                        }
                    }
                });
            });
        }

        /**
         * 获取URL对应的备用URL
         * @param {string} url - 原始URL
         * @returns {string|null} 备用URL或null
         */
        getBackupUrl(url) {
            // 先检查完全匹配
            if (this.backupUrls.has(url)) {
                return this.backupUrls.get(url);
            }

            // 检查部分匹配 (处理URL参数可能不同的情况)
            for (const [originalUrl, backupUrl] of this.backupUrls.entries()) {
                // 提取URL的基本部分(不含参数)
                const baseOriginalUrl = originalUrl.split('?')[0];
                const baseInputUrl = url.split('?')[0];

                if (baseInputUrl === baseOriginalUrl) {
                    this.log(`找到部分匹配的备用URL: ${backupUrl} (对应 ${url})`);
                    return backupUrl;
                }
            }

            return null;
        }

        /**
         * 检测是否是MCDN URL(使用与cdn.js相同的检测逻辑)
         * @param {string} url - 要检查的URL
         * @returns {boolean} 是否是MCDN URL
         */
        isMCDNUrl(url) {
            return url && (
                url.includes('.mcdn.bilivideo') ||
                url.includes('.mcdn.bilivideo.cn') ||
                url.includes('.mcdn.bilivideo.com')
            );
        }

        /**
         * 优化视频URL,综合处理备用URL、MCDN代理和CDN优选
         * @param {string} originalUrl - 原始URL
         * @param {string} backupUrl - 备用URL
         * @returns {string} 优化后的URL
         */
        optimizeVideoUrl(originalUrl, backupUrl = '') {
            this.log(`原始URL: ${originalUrl}`);
            this.log(`备用URL: ${backupUrl}`);

            // 检查CDN优化是否启用
            const enableCdn = this.enableCDNOptimize;
            if (!enableCdn && !this.enableMCDNProxy) {
                this.log('CDN优化和MCDN代理都已禁用,使用原始URL');
                return originalUrl;
            }

            // 优先使用backupUrl,通常是upgcxcode地址,播放更稳定
            let videoUrl = '';
            if (backupUrl && backupUrl.includes('http')) {
                videoUrl = backupUrl;
                this.log('使用备用URL');
            } else {
                videoUrl = originalUrl;
                this.log('使用原始URL');
            }

            // 处理mcdn域名的特殊情况 - 使用与cdn.js相同的检测逻辑
            if (this.enableMCDNProxy && this.isMCDNUrl(videoUrl)) {
                this.log(`检测到mcdn域名: ${videoUrl}`);
                const proxyUrl = this.proxyMCDN(videoUrl);
                this.log(`使用代理: ${proxyUrl}`);
                return proxyUrl;
            }

            // 处理upgcxcode路径,替换为优选CDN
            if (enableCdn && this.upgcxcodeRegex.test(videoUrl)) {
                this.log(`检测到upgcxcode路径,替换CDN`);

                // 从GM_getValue获取用户选择的CDN(与原始CDN.js行为保持一致)
                const preferredCdn = this.preferredCDN;
                // 获取对应的CDN主机名
                const cdn = this.cdnList[preferredCdn] || this.cdnList['ali'];

                // 使用正则表达式替换域名部分
                const replacedUrl = videoUrl.replace(this.upgcxcodeRegex, `https://${cdn}/upgcxcode/`);

                this.log(`替换CDN: ${preferredCdn} -> ${cdn}`);
                return replacedUrl;
            }

            this.log('无需优化,返回原始URL');
            return videoUrl;
        }

        /**
         * 优化普通URL,用于XHR/Fetch拦截
         * @param {string} url - 原始URL
         * @returns {string} 优化后的URL
         */
        optimizeUrl(url) {
            if (!url) return url;

            try {
                // 获取可能的备用URL
                const backupUrl = this.getBackupUrl(url);

                // 使用完整的优化逻辑
                return this.optimizeVideoUrl(url, backupUrl);
            } catch (error) {
                this.log(`URL优化错误: ${error.message}`);
                return url;
            }
        }

        /**
         * 使用代理服务器代理MCDN请求
         * @param {string} url - MCDN URL
         * @returns {string} 代理后的URL
         */
        proxyMCDN(url) {
            const proxyUrl = `https://proxy-tf-all-ws.bilivideo.com/?url=${encodeURIComponent(url)}`;
            this.log(`MCDN代理URL: ${proxyUrl}`);
            return proxyUrl;
        }

        /**
         * 替换CDN为优选节点
         * @param {string} url - 包含upgcxcode的URL
         * @returns {string} 替换CDN后的URL
         */
        replaceCDN(url) {
            const cdn = this.cdnList[this.preferredCDN] || this.cdnList['ali'];
            const replacedUrl = url.replace(this.upgcxcodeRegex, `https://${cdn}/upgcxcode/`);
            this.log(`替换CDN: ${this.preferredCDN} (${cdn})`);
            return replacedUrl;
        }

        /**
         * 弹出画质选择对话框
         */
        selectQuality() {
            const qualityEntries = Object.entries(this.qualityOptions);
            let optionsText = "请选择要固定的视频画质:\n(可以输入索引或质量值)\n(如果设置为自己的会员等级无法观看的画质或者不支持的画质,会自动向下兼容)\n\n";

            qualityEntries.forEach(([value, label], index) => {
                optionsText += `${index + 1}. ${label} (${value})\n`;
            });

            const userInput = prompt(optionsText, this.fixedQuality);

            if (userInput !== null) {
                // 检查用户输入是否是数字索引或质量值
                const numInput = parseInt(userInput);

                if (!isNaN(numInput)) {
                    // 检查是否是索引值
                    if (numInput >= 1 && numInput <= qualityEntries.length) {
                        // 用户输入了选项的序号
                        this.fixedQuality = qualityEntries[numInput - 1][0];
                    } else if (Object.keys(this.qualityOptions).includes(userInput)) {
                        // 用户直接输入了质量值
                        this.fixedQuality = userInput;
                    } else {
                        alert("无效的画质选择,请重新选择");
                        return;
                    }

                    GM_setValue('fixedQuality', this.fixedQuality);
                    this.log(`已设置固定画质为: ${this.qualityOptions[this.fixedQuality]}`);
                    alert(`已设置固定画质为: ${this.qualityOptions[this.fixedQuality]}`);

                    if (this.enableQualityFix) {
                        this.setQualityCookie();
                        location.reload();
                    }
                } else {
                    alert("请输入有效的数字");
                }
            }
        }

        /**
         * 设置画质cookie
         */
        setQualityCookie() {
            const cookieName = "CURRENT_QUALITY";
            const cookieValue = this.fixedQuality;
            const expiryDate = new Date();
            expiryDate.setFullYear(expiryDate.getFullYear() + 1); // 设置一年有效期

            document.cookie = `${cookieName}=${cookieValue}; domain=.bilibili.com; path=/; expires=${expiryDate.toUTCString()}`;
            this.log(`已设置画质Cookie: ${cookieName}=${cookieValue}`);
        }

        /**
         * 获取当前画质设置信息
         * @returns {Object} 画质设置信息
         */
        getQualityFixInfo() {
            return {
                enabled: this.enableQualityFix,
                quality: this.fixedQuality,
                qualityName: this.qualityOptions[this.fixedQuality] || '未知画质'
            };
        }

        /**
         * 获取当前CDN信息
         * @returns {Object} CDN信息
         */
        getCDNInfo() {
            return {
                key: this.preferredCDN,
                host: this.cdnList[this.preferredCDN],
                enabled: this.enableCDNOptimize
            };
        }

        /**
         * 获取MCDN代理信息
         * @returns {Object} MCDN代理信息
         */
        getMCDNProxyInfo() {
            return {
                enabled: this.enableMCDNProxy,
                proxy: 'proxy-tf-all-ws.bilivideo.com'
            };
        }

        /**
         * 获取备用URL统计信息
         * @returns {Object} 备用URL统计
         */
        getBackupUrlStats() {
            return {
                count: this.backupUrls.size,
                urls: Array.from(this.backupUrls.entries()).slice(0, 5) // 仅返回前5个示例
            };
        }
    }

    // 创建并初始化B站CDN优化器
    const bilibiliCDNOptimizer = new BilibiliCDNOptimizer();

    // 将优化器实例暴露到全局,方便调试
    window.bilibiliCDNOptimizer = bilibiliCDNOptimizer;
})();