B站展开所有评论(Ctrl + Alt + E)

按 Ctrl+Alt+E 展开所有B站评论(运行中持续提示)

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站展开所有评论(Ctrl + Alt + E)
// @namespace    http://tampermonkey.net/
// @version      2025-07-31
// @description  按 Ctrl+Alt+E 展开所有B站评论(运行中持续提示)
// @author       You
// @match        https://www.bilibili.com/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const sleep = ms => new Promise(res => setTimeout(res, ms));

    //持久 Toast(返回DOM元素)
    function showToastPersistent(message) {
        const toast = document.createElement("div");
        toast.textContent = message;
        toast.style.position = "fixed";
        toast.style.top = "0px";
        toast.style.left = "50%";
        toast.style.transform = "translateX(-50%)";
        toast.style.background = "rgba(0, 0, 0, 0.75)";
        toast.style.color = "#fff";
        toast.style.padding = "10px 16px";
        toast.style.borderRadius = "8px";
        toast.style.fontSize = "18px";
        toast.style.zIndex = 9999;
        toast.style.boxShadow = "0 2px 6px rgba(0,0,0,0.3)";
        toast.style.opacity = "0";
        toast.style.transition = "opacity 0.3s ease";

        document.body.appendChild(toast);
        requestAnimationFrame(() => toast.style.opacity = "1");

        return toast;
    }

    // ✅ 替换内容 & 自动消失
    function replaceToast(toastEl, newMsg, delay = 3000) {
        toastEl.textContent = newMsg;
        setTimeout(() => {
            toastEl.style.opacity = "0";
            setTimeout(() => toastEl.remove(), 300);
        }, delay);
    }

    function getLoadedCommentCount() {
        try {
            const shadowHost = document.querySelector("#commentapp > bili-comments");
            if (!shadowHost || !shadowHost.shadowRoot) return 0;
            const shadowRoot = shadowHost.shadowRoot;
            const items = shadowRoot.querySelectorAll("#feed > bili-comment-thread-renderer");
            return items.length;
        } catch (e) {
            console.warn('获取评论数失败', e);
            return 0;
        }
    }

    async function scrollAndWaitForMoreComments(lastCount) {
        window.scrollTo(0, document.documentElement.scrollHeight);
        await sleep(1200);
        const newCount = getLoadedCommentCount();
        console.log(`滚动后评论数:${newCount},上次:${lastCount}`);
        return newCount > lastCount ? newCount : lastCount;
    }

    async function loadAllComments(maxScrolls = 30) {
        for (let i = 0; i < 20; i++) {
            if (document.querySelector("#commentapp > bili-comments")) break;
            await sleep(500);
        }

        let lastCount = 0;
        for (let i = 0; i < maxScrolls; i++) {
            const newCount = await scrollAndWaitForMoreComments(lastCount);
            if (newCount === lastCount) {
                console.log('评论数未增加,可能加载完毕,停止滚动');
                break;
            }
            lastCount = newCount;
        }
        console.log(`最终加载评论数: ${lastCount}`);
    }

    async function expandAllClickToView() {
        const mainHost = document.querySelector("#commentapp > bili-comments");
        if (!mainHost || !mainHost.shadowRoot) {
            console.warn('找不到评论组件');
            return;
        }
        const mainRoot = mainHost.shadowRoot;
        const threads = mainRoot.querySelectorAll("#feed > bili-comment-thread-renderer");

        let count = 0;

        for (const thread of threads) {
            const threadRoot = thread.shadowRoot;
            if (!threadRoot) continue;

            const replies = threadRoot.querySelector("#replies > bili-comment-replies-renderer");
            if (!replies || !replies.shadowRoot) continue;

            const viewMore = replies.shadowRoot.querySelector("#view-more > bili-text-button");
            if (viewMore) {
                try {
                    viewMore.click();
                    count++;
                    await sleep(300);
                } catch (e) {
                    console.warn("点击失败", e);
                }
            }
        }

        console.log(`展开“点击查看”按钮数量:${count}`);
    }

    async function main() {
        const toast = showToastPersistent("正在展开评论...");
        await loadAllComments();
        await expandAllClickToView();
        replaceToast(toast, "所有评论已展开", 3000);
    }

    // 快捷键监听:Ctrl + Alt + E
    document.addEventListener('keydown', function (e) {
        if (e.ctrlKey && e.altKey && e.key.toLowerCase() === 'e') {
            main();
        }
    });
})();