你的雨姐-B站免登录畅享助手-Bilibili Login-Free Enjoyment Helper

哎呀妈呀,来了老铁!免登录看b评论、不登录也能看1080P,无登陆弹窗。支持收藏夹/列表无限连播。

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        你的雨姐-B站免登录畅享助手-Bilibili Login-Free Enjoyment Helper
// @namespace    https://github.com/
// @version      49.4.9.49
// @author       东北雨姐
// @license      MIT
// @description  哎呀妈呀,来了老铁!免登录看b评论、不登录也能看1080P,无登陆弹窗。支持收藏夹/列表无限连播。
// @match        *://*.bilibili.com/*
// @icon         https://www.bilibili.com/favicon.ico
// @run-at       document-start
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';
    const win = window.unsafeWindow || window;

    // 【修复核心】检测当前是否为个人空间页面
    const isSpacePage = win.location.hostname === 'space.bilibili.com';
    console.log('%c B站助手 ', 'background: #00A1D6; color: #fff; padding: 4px; border-radius: 4px;', isSpacePage ? '(个人空间模式-已暂停伪造以防闪屏)' : '(沉浸模式-列表连播加强版)');

    // 1. 安全地注入CSS,保证在任何页面都不显示登录弹窗
    const css = `
        .bili-mini-mask,
        .login-panel-popover,
        .vip-login-tip,
        .bpx-player-toast-login,
        .ad-report,
        .video-unlogin-popover,
        #mini-login-prompt,
        .unlogin-popover
        { display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; }
        body { overflow: auto !important; }
    `;
    const style = document.createElement('style');
    style.textContent = css;

    // 【修复】安全的样式注入,处理document-start时DOM可能不存在的情况
    const appendStyle = () => {
        const target = document.head || document.documentElement;
        if (target) {
            target.appendChild(style);
        } else {
            // DOM还未准备好,等待下一帧
            requestAnimationFrame(appendStyle);
        }
    };
    appendStyle();

    // 伪造的用户数据
    const mockUserInfo = {
        code: 0, message: "0", ttl: 1,
        data: {
            isLogin: true, email_verified: 1, face: "https://img1.baidu.com/it/u=3908630427,395681705&fm=253&fmt=auto&app=138&f=JPEG?w=847&h=800",
            level_info: { current_level: 6 }, mid: 12345678, money: 49, moral: 70, uname: "东北雨姐",
            vipStatus: 1, vipType: 2, vip: { type: 2, status: 1, due_date: 1999999999999, label: { text: "雨姐大会员" } },
            is_senior_member: 1
        }
    };
    const mockUserInfoStr = JSON.stringify(mockUserInfo);
    const originalFetch = win.fetch;
    const originalXHR = win.XMLHttpRequest;

    // 2. 拦截 Fetch
    win.fetch = function(input, init) {
        const url = (typeof input === 'string' ? input : input.url) || '';
        // 如果是请求用户信息,且不在个人空间页面,才进行伪造
        if (url.includes('/x/web-interface/nav')) {
            if (isSpacePage) {
                // 如果在个人主页,直接放行真实请求,防止死循环
                return originalFetch.apply(this, arguments);
            }
            return Promise.resolve(new Response(mockUserInfoStr, {
                status: 200, statusText: "OK", headers: { 'Content-Type': 'application/json' }
            }));
        }
        if (url.includes('/x/v2/reply')) {
            if (init) init.credentials = 'omit';
        }
        return originalFetch.apply(this, arguments);
    };

    // 3. 【修复】改进的 XHR 拦截
    class ProxyXHR extends originalXHR {
        constructor() {
            super();
            this._url = '';
            this._isMocked = false;
        }
        open(method, url, ...args) {
            this._url = url.toString();
            super.open(method, url, ...args);
        }
        send(body) {
            // 同样,如果是个人空间页面,不进行拦截
            if (this._url.includes('/x/web-interface/nav') && !isSpacePage) {
                this._isMocked = true;

                // 【修复】使用getter来正确模拟XHR属性
                const mockResponse = mockUserInfoStr;
                const mockHeaders = 'content-type: application/json';

                Object.defineProperties(this, {
                    readyState: { get: () => 4, configurable: true },
                    status: { get: () => 200, configurable: true },
                    statusText: { get: () => 'OK', configurable: true },
                    responseText: { get: () => mockResponse, configurable: true },
                    response: { get: () => mockResponse, configurable: true },
                    responseURL: { get: () => this._url, configurable: true }
                });

                // 【修复】覆盖header相关方法
                this.getAllResponseHeaders = () => mockHeaders;
                this.getResponseHeader = (name) => {
                    if (name.toLowerCase() === 'content-type') return 'application/json';
                    return null;
                };

                setTimeout(() => {
                    // 触发事件
                    this.dispatchEvent(new Event('readystatechange'));

                    // 【修复】同时调用回调函数(如果存在)
                    if (typeof this.onreadystatechange === 'function') {
                        try { this.onreadystatechange(new Event('readystatechange')); } catch(e) {}
                    }

                    this.dispatchEvent(new Event('load'));

                    if (typeof this.onload === 'function') {
                        try { this.onload(new ProgressEvent('load')); } catch(e) {}
                    }

                    this.dispatchEvent(new Event('loadend'));

                    if (typeof this.onloadend === 'function') {
                        try { this.onloadend(new ProgressEvent('loadend')); } catch(e) {}
                    }
                }, 10);
                return;
            }
            super.send(body);
        }
    }
    win.XMLHttpRequest = ProxyXHR;

    const forceLocalStorage = () => {
        try {
            localStorage.setItem('bilibili_player_codec_prefer_type', '0');
            localStorage.setItem('recommend_auto_play', '0');
        } catch(e) {}
    };
    forceLocalStorage();

    // 【修复】更安全的Object.defineProperty覆盖,使用WeakSet记录已处理的对象
    const originDefineProperty = Object.defineProperty;
    const targetProps = new Set(['isViewToday', 'isVideoAble']);

    Object.defineProperty = function(obj, prop, descriptor) {
        if (targetProps.has(prop)) {
            descriptor = {
                get: () => true,
                enumerable: false,
                configurable: true
            };
        }
        try {
            return originDefineProperty.call(this, obj, prop, descriptor);
        } catch(e) {
            // 如果定义失败(例如属性已存在且不可配置),静默失败
            console.warn('B站助手: defineProperty failed for', prop);
            return obj;
        }
    };

    const originSetTimeout = win.setTimeout;
    win.setTimeout = function(func, delay) {
        if (delay && delay > 20000 && delay < 70000) {
            // console.log(`B站助手: 拦截到疑似试用结束定时器 (${delay}ms),已推迟。`);
            delay = 300000000;
        }
        return originSetTimeout.call(this, func, delay);
    };

    // ==========================================
    // 【加强版轮询逻辑】已移除所有超时限制
    // 专治收藏夹、列表连播、合集视频切换后画质失效问题
    // ==========================================
    const intervalId = setInterval(() => {
        try {
            // 1. 自动处理试用/登录提示(部分1080P需要确认)
            const trialBtn = document.querySelector('.bpx-player-toast-confirm-login');
            if (trialBtn) {
                // console.log('B站助手: 发现试用按钮,自动点击...');
                trialBtn.click();
            }

            // 2. 核心画质守护
            // 每次轮询都检查当前画质,如果切集了导致画质重置,这里会立即纠正
            if (win.player && typeof win.player.getQuality === 'function' && typeof win.player.requestQuality === 'function') {
                try {
                    const currentQuality = win.player.getQuality(); // 获取当前画质对象
                    const supportedQualities = win.player.getSupportedQualityList ? win.player.getSupportedQualityList() : null;
                    const targetQuality = 80; // 80 对应 1080P 高清

                    // 如果当前画质存在,且低于1080P,且当前视频支持1080P,则切换
                    if (currentQuality && currentQuality.nowQ < targetQuality) {
                        if (supportedQualities && supportedQualities.includes(targetQuality)) {
                            // console.log('B站助手: 监测到画质不足,正在切换至 1080P...');
                            win.player.requestQuality(targetQuality);
                        }
                    }
                    // 这里不需要 else if 标记完成,因为是无限轮询,状态由播放器实时决定
                } catch(e) {
                    // 播放器内部状态未就绪,跳过本次检查
                }
            }
        } catch(e) {
            // 静默处理 DOM 错误,防止脚本中断
        }
    }, 2000); // 2秒一次的心跳检查,资源消耗极低,请放心使用

    // 【修复核心】不要在个人空间页面注入Cookie,否则会触发服务端校验失败导致的刷新
    // 【修复】延长Cookie有效期至1年
    if (!isSpacePage) {
        try {
            document.cookie = "DedeUserID=12345678; path=/; domain=.bilibili.com; max-age=31536000";
        } catch(e) {}
    }

    // 【新增】页面卸载时清理定时器(虽然页面都要关了,但好习惯要有)
    win.addEventListener('beforeunload', () => {
        if (intervalId) {
            clearInterval(intervalId);
        }
    });
})();