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

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

// ==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();
        }
    });
})();