Greasy Fork

Greasy Fork is available in English.

Bilibili评论展开助手

智能展开Bilibili评论回复,一键查看所有子评论,支持按热度和时间排序,提供流畅的评论浏览体验

// ==UserScript==
// @name         Bilibili评论展开助手
// @namespace    http://tampermonkey.net/
// @version      2.3.1
// @description  智能展开Bilibili评论回复,一键查看所有子评论,支持按热度和时间排序,提供流畅的评论浏览体验
// @author       Rygtx
// @icon         https://www.bilibili.com/favicon.ico
// @match        https://www.bilibili.com/video/*
// @grant        none
// @license      CC-BY-NC-4.0
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    // 配置常量
    const CONFIG = {
        API_BASE: 'https://api.bilibili.com',
        COMMENT_TYPE: 1, // 视频评论类型
        MAX_RETRIES: 3,
        RETRY_DELAY: 1000,
        REQUEST_TIMEOUT: 10000
    };

    // 工具函数
    const Utils = {
        // 调试日志输出功能
        log(level, message, ...args) {
            const timestamp = new Date().toISOString();
            const prefix = `[Bilibili评论展开助手 ${timestamp}]`;

            switch (level) {
                case 'error':
                    console.error(prefix, message, ...args);
                    break;
                case 'warn':
                    console.warn(prefix, message, ...args);
                    break;
                case 'info':
                    console.info(prefix, message, ...args);
                    break;
                default:
                    console.log(prefix, message, ...args);
            }
        },

        formatTime(timestamp) {
            if (!timestamp) return '未知时间';

            const date = new Date(timestamp * 1000);
            const now = new Date();
            const diff = now - date;

            const minute = 60 * 1000;
            const hour = 60 * minute;
            const day = 24 * hour;
            const month = 30 * day;
            const year = 365 * day;

            if (diff < minute) {
                return '刚刚';
            } else if (diff < hour) {
                return `${Math.floor(diff / minute)}分钟前`;
            } else if (diff < day) {
                return `${Math.floor(diff / hour)}小时前`;
            } else if (diff < month) {
                return `${Math.floor(diff / day)}天前`;
            } else if (diff < year) {
                return `${Math.floor(diff / month)}个月前`;
            } else {
                return `${Math.floor(diff / year)}年前`;
            }
        },

        formatDetailedTime(timestamp) {
            if (!timestamp) return '未知时间';

            const date = new Date(timestamp * 1000);
            const year = date.getFullYear();
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');
            const hours = String(date.getHours()).padStart(2, '0');
            const minutes = String(date.getMinutes()).padStart(2, '0');
            const seconds = String(date.getSeconds()).padStart(2, '0');

            return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
        },

        // HTML转义工具函数
        escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        },

        // 获取视频标题
        async getVideoTitle(videoId, isAv = false) {
            try {
                let apiUrl;
                if (isAv) {
                    apiUrl = `https://api.bilibili.com/x/web-interface/view?aid=${videoId}`;
                } else {
                    apiUrl = `https://api.bilibili.com/x/web-interface/view?bvid=${videoId}`;
                }

                const response = await fetch(apiUrl, {
                    method: 'GET',
                    headers: {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
                    }
                });

                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}`);
                }

                const data = await response.json();
                if (data.code === 0 && data.data && data.data.title) {
                    return data.data.title;
                }

                throw new Error('API返回错误');
            } catch (error) {
                return null;
            }
        },

        // 处理评论内容中的视频链接 - 增强版
        async processCommentContentEnhanced(content) {
            if (!content) return '内容为空';

            const escapedContent = this.escapeHtml(content);

            // 识别av号和BV号的正则表达式
            const avPattern = /\b(av)(\d+)\b/gi;

            let processedContent = escapedContent;
            const videoPromises = [];

            // 收集所有视频链接
            const videoMatches = [];

            // 处理av号
            let match;
            while ((match = avPattern.exec(escapedContent)) !== null) {
                videoMatches.push({
                    match: match[0],
                    type: 'av',
                    id: match[2],
                    fullMatch: match[0]
                });
            }

            // 处理BV号
            const bvRegex = /\b(BV[a-zA-Z0-9]+)\b/gi;
            while ((match = bvRegex.exec(escapedContent)) !== null) {
                videoMatches.push({
                    match: match[0],
                    type: 'bv',
                    id: match[0],
                    fullMatch: match[0]
                });
            }

            // 为每个视频获取标题
            for (const video of videoMatches) {
                const titlePromise = this.getVideoTitle(video.id, video.type === 'av')
                    .then(title => ({ ...video, title }));
                videoPromises.push(titlePromise);
            }

            // 等待所有标题获取完成
            const videoData = await Promise.all(videoPromises);

            // 替换视频链接
            for (const video of videoData) {
                const url = video.type === 'av'
                    ? `https://www.bilibili.com/video/av${video.id}/`
                    : `https://www.bilibili.com/video/${video.id}/`;

                const title = video.title || video.fullMatch;
                const linkHtml = this.createVideoLinkHtml(url, title, video.fullMatch);

                processedContent = processedContent.replace(video.fullMatch, linkHtml);
            }

            return processedContent;
        },

        // 创建B站风格的视频链接HTML - 暗色主题优化
        createVideoLinkHtml(url, title) {
            // 限制标题长度,避免过长
            const displayTitle = title.length > 30 ? title.substring(0, 30) + '...' : title;

            return `<a href="${url}" target="_blank" class="bili-video-link-enhanced" style="--icon-width:1.2em;--icon-height:1.2em;color:#00a1d6;text-decoration:none;display:inline-flex;align-items:center;gap:6px;transition:all 0.2s ease;border-radius:6px;padding:4px 8px;background:rgba(0,161,214,0.08);border:1px solid rgba(0,161,214,0.2);margin:2px 0;max-width:300px;" data-type="link" title="${title}">
                <img src="https://i0.hdslb.com/bfs/activity-plat/static/20201110/4c8b2dbaded282e67c9a31daa4297c3c/AeQJlYP7e.png" loading="lazy" style="width:var(--icon-width);height:var(--icon-height);vertical-align:middle;flex-shrink:0;filter:brightness(1.1);" alt="播放">
                <span style="color:#00a1d6;font-weight:500;font-size:13px;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${displayTitle}</span>
            </a>`;
        },

        // 为增强版视频链接添加悬停效果 - 暗色主题优化
        addEnhancedVideoLinkHoverEffects(container) {
            const videoLinks = container.querySelectorAll('.bili-video-link-enhanced');
            videoLinks.forEach(link => {
                link.addEventListener('mouseover', () => {
                    link.style.background = 'rgba(0,161,214,0.15)';
                    link.style.borderColor = 'rgba(0,161,214,0.4)';
                    link.style.transform = 'translateY(-1px)';
                    link.style.boxShadow = '0 2px 8px rgba(0,161,214,0.2)';
                    const span = link.querySelector('span');
                    if (span) span.style.color = '#40a9ff';
                    const img = link.querySelector('img');
                    if (img) img.style.filter = 'brightness(1.3)';
                });

                link.addEventListener('mouseout', () => {
                    link.style.background = 'rgba(0,161,214,0.08)';
                    link.style.borderColor = 'rgba(0,161,214,0.2)';
                    link.style.transform = 'translateY(0)';
                    link.style.boxShadow = 'none';
                    const span = link.querySelector('span');
                    if (span) span.style.color = '#00a1d6';
                    const img = link.querySelector('img');
                    if (img) img.style.filter = 'brightness(1.1)';
                });

                // 点击效果
                link.addEventListener('mousedown', () => {
                    link.style.transform = 'translateY(0) scale(0.98)';
                });

                link.addEventListener('mouseup', () => {
                    link.style.transform = 'translateY(-1px) scale(1)';
                });
            });
        },

        // 为简化版视频链接添加悬停效果
        addVideoLinkHoverEffects(container) {
            const videoLinks = container.querySelectorAll('.bili-video-link-simple');
            videoLinks.forEach(link => {
                link.addEventListener('mouseover', () => {
                    link.style.borderBottomColor = '#00a1d6';
                    link.style.color = '#40a9ff';
                    link.style.background = 'rgba(0,161,214,0.1)';
                });

                link.addEventListener('mouseout', () => {
                    link.style.borderBottomColor = 'transparent';
                    link.style.color = '#00a1d6';
                    link.style.background = 'rgba(0,161,214,0.05)';
                });
            });
        },

        formatNumber(num) {
            if (num >= 10000) {
                return `${(num / 10000).toFixed(1)}万`;
            }
            return num.toString();
        },

        async sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        },

        async fetchWithRetry(url, options = {}, retries = CONFIG.MAX_RETRIES) {
            for (let i = 0; i < retries; i++) {
                try {
                    const controller = new AbortController();
                    const timeoutId = setTimeout(() => controller.abort(), CONFIG.REQUEST_TIMEOUT);

                    const response = await fetch(url, {
                        ...options,
                        signal: controller.signal,
                        headers: {
                            'User-Agent': navigator.userAgent,
                            'Referer': window.location.href,
                            ...options.headers
                        }
                    });

                    clearTimeout(timeoutId);

                    if (!response.ok) {
                        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                    }

                    return response;
                } catch (error) {
                    if (i === retries - 1) {
                        throw error;
                    }

                    await Utils.sleep(CONFIG.RETRY_DELAY * (i + 1));
                }
            }
        }
    };

    // B站评论API组件
    class BilibiliCommentAPI {
        constructor() {
            this.cache = new Map();
            this.cacheExpiry = 5 * 60 * 1000; // 5分钟缓存
        }

        getCacheKey(type, oid, rpid = null, page = 1) {
            return `${type}_${oid}_${rpid || 'root'}_${page}`;
        }

        isValidCache(cacheItem) {
            return cacheItem && (Date.now() - cacheItem.timestamp) < this.cacheExpiry;
        }

        async getCommentReplies(oid, rootRpid, page = 1, pageSize = 20) {
            const cacheKey = this.getCacheKey('replies', oid, rootRpid, page);
            const cached = this.cache.get(cacheKey);

            if (this.isValidCache(cached)) {
                Utils.log('info', `使用缓存数据: ${cacheKey}`);
                return cached.data;
            }

            try {
                const url = new URL(`${CONFIG.API_BASE}/x/v2/reply/reply`);
                url.searchParams.set('type', CONFIG.COMMENT_TYPE);
                url.searchParams.set('oid', oid);
                url.searchParams.set('root', rootRpid);
                url.searchParams.set('ps', pageSize);
                url.searchParams.set('pn', page);

                Utils.log('info', `请求评论回复: ${url.toString()}`);
                const response = await Utils.fetchWithRetry(url.toString());
                const data = await response.json();

                if (data.code !== 0) {
                    throw new Error(`API错误: ${data.message || '未知错误'} (code: ${data.code})`);
                }

                // 缓存结果
                this.cache.set(cacheKey, {
                    data: data.data,
                    timestamp: Date.now()
                });

                Utils.log('info', `成功获取评论回复: ${data.data?.replies?.length || 0} 条`);
                return data.data;

            } catch (error) {
                Utils.log('error', '获取评论回复失败:', error);
                throw error;
            }
        }

        async getAllReplies(oid, rootRpid, maxPages = 10) {
            const allReplies = [];
            let page = 1;
            let hasMore = true;

            while (hasMore && page <= maxPages) {
                try {
                    const data = await this.getCommentReplies(oid, rootRpid, page);

                    if (data?.replies && data.replies.length > 0) {
                        allReplies.push(...data.replies);

                        // 检查是否还有更多页
                        const pageInfo = data.page;
                        hasMore = pageInfo && page < Math.ceil(pageInfo.count / pageInfo.size);
                        page++;

                        // 避免请求过快
                        if (hasMore) {
                            await Utils.sleep(500);
                        }
                    } else {
                        hasMore = false;
                    }
                } catch (error) {
                    Utils.log('error', `获取第${page}页回复失败:`, error);
                    hasMore = false;
                }
            }

            Utils.log('info', `总共获取到 ${allReplies.length} 条回复`);
            return allReplies;
        }

        async getCommentInfo(oid, rpid) {
            const cacheKey = this.getCacheKey('info', oid, rpid);
            const cached = this.cache.get(cacheKey);

            if (this.isValidCache(cached)) {
                return cached.data;
            }

            try {
                const url = new URL(`${CONFIG.API_BASE}/x/v2/reply/info`);
                url.searchParams.set('type', CONFIG.COMMENT_TYPE);
                url.searchParams.set('oid', oid);
                url.searchParams.set('rpid', rpid);

                const response = await Utils.fetchWithRetry(url.toString());
                const data = await response.json();

                if (data.code !== 0) {
                    throw new Error(`API错误: ${data.message || '未知错误'} (code: ${data.code})`);
                }

                this.cache.set(cacheKey, {
                    data: data.data,
                    timestamp: Date.now()
                });

                return data.data;

            } catch (error) {
                throw error;
            }
        }

        clearCache() {
            this.cache.clear();
            Utils.log('info', 'API缓存已清空');
        }
    }

    // DOM监听器组件
    class DOMWatcher {
        constructor() {
            this.observer = null;
            this.isObserving = false;
            this.viewMoreButtons = new Set();
            this.onViewMoreClick = null;
            this.intervalId = null;
            this.scanInterval = 1000; // 增加扫描频率到1秒
        }

        observeCommentSection() {
            if (this.isObserving) {
                Utils.log('warn', 'DOM监听器已在运行');
                return;
            }

            this.observer = new MutationObserver((mutations) => {
                let shouldRescan = false;

                mutations.forEach((mutation) => {
                    // 检查是否有节点被添加或移除
                    if (mutation.type === 'childList') {
                        // 检查是否涉及评论相关的DOM变化
                        const hasCommentChanges = Array.from(mutation.addedNodes).some(node =>
                            node.nodeType === Node.ELEMENT_NODE &&
                            (node.tagName === 'BILI-COMMENT-THREAD-RENDERER' ||
                             node.querySelector && node.querySelector('bili-comment-thread-renderer'))
                        ) || Array.from(mutation.removedNodes).some(node =>
                            node.nodeType === Node.ELEMENT_NODE &&
                            (node.tagName === 'BILI-COMMENT-THREAD-RENDERER' ||
                             node.querySelector && node.querySelector('bili-comment-thread-renderer'))
                        );

                        if (hasCommentChanges) {
                            shouldRescan = true;
                        }
                    }
                });

                if (shouldRescan) {
                    // 延迟一点时间让DOM完全更新
                    setTimeout(() => {
                        this.scanForViewMoreButtons();
                        this.reattachMissingButtons();
                    }, 100);
                }
            });

            const targetNode = document.body;
            const config = {
                childList: true,
                subtree: true,
                attributes: false
            };

            this.observer.observe(targetNode, config);
            this.isObserving = true;

            this.startPeriodicScan();
            this.scanForViewMoreButtons();
            Utils.log('info', 'DOM监听器已启动 - 增强版');
        }

        startPeriodicScan() {
            if (this.intervalId) {
                clearInterval(this.intervalId);
            }

            this.intervalId = setInterval(() => {
                this.scanForViewMoreButtons();
                this.reattachMissingButtons();
            }, this.scanInterval);

            Utils.log('info', `定时检测已启动,每 ${this.scanInterval}ms 检测一次`);
        }

        // 重新附加丢失的瀑布流按钮
        reattachMissingButtons() {
            try {
                const commentApp = document.querySelector("#commentapp > bili-comments");
                if (!commentApp || !commentApp.shadowRoot) return;

                const threadRenderers = commentApp.shadowRoot.querySelectorAll("#feed > bili-comment-thread-renderer");
                let reattachedCount = 0;

                threadRenderers.forEach((threadRenderer) => {
                    if (!threadRenderer.shadowRoot) return;

                    const repliesRenderer = threadRenderer.shadowRoot.querySelector("#replies > bili-comment-replies-renderer");
                    if (!repliesRenderer || !repliesRenderer.shadowRoot) return;

                    const viewMoreButton = repliesRenderer.shadowRoot.querySelector("#view-more > bili-text-button");
                    if (!viewMoreButton || !viewMoreButton.shadowRoot) return;

                    // 检查是否已有评论展开按钮
                    const existingExpandBtn = this.findExpandButton(threadRenderer);
                    if (!existingExpandBtn) {
                        // 如果没有评论展开按钮,重新添加
                        const button = viewMoreButton.shadowRoot.querySelector("button");
                        if (button && this.viewMoreButtons.has(button)) {
                            // 这个按钮之前已经处理过,但评论展开按钮丢失了
                            const commentInfo = this.extractCommentInfo(viewMoreButton, threadRenderer);
                            if (commentInfo) {
                                this.addExpandButtonToStableLocation(threadRenderer, commentInfo);
                                reattachedCount++;
                            }
                        }
                    }
                });

                if (reattachedCount > 0) {
                    Utils.log('info', `重新附加了 ${reattachedCount} 个评论展开按钮`);
                }

            } catch (error) {
                Utils.log('error', '重新附加按钮时出错:', error);
            }
        }

        // 查找评论展开按钮(优化版)
        findExpandButton(threadRenderer) {
            try {
                // 优先在主要位置查找(基于实际使用情况优化)
                const primaryLocations = [
                    threadRenderer.shadowRoot?.querySelector("#replies"),
                    threadRenderer
                ];

                for (const location of primaryLocations) {
                    if (location) {
                        // 直接查找按钮
                        const expandBtn = location.querySelector?.('.bili-comment-expand-btn');
                        if (expandBtn) {
                            return expandBtn;
                        }
                        // 检查shadowRoot
                        if (location.shadowRoot) {
                            const expandBtn = location.shadowRoot.querySelector('.bili-comment-expand-btn');
                            if (expandBtn) {
                                return expandBtn;
                            }
                        }
                    }
                }
                return null;
            } catch (error) {
                return null;
            }
        }

        scanForViewMoreButtons() {
            try {
                const commentApp = document.querySelector("#commentapp > bili-comments");
                if (!commentApp || !commentApp.shadowRoot) {
                    Utils.log('warn', '未找到评论区或shadowRoot');
                    return;
                }

                const threadRenderers = commentApp.shadowRoot.querySelectorAll("#feed > bili-comment-thread-renderer");
                Utils.log('info', `扫描评论区: 找到 ${threadRenderers.length} 个评论线程`);
                let newButtonsFound = 0;

                threadRenderers.forEach((threadRenderer) => {
                    if (!threadRenderer.shadowRoot) return;

                    const repliesRenderer = threadRenderer.shadowRoot.querySelector("#replies > bili-comment-replies-renderer");
                    if (!repliesRenderer || !repliesRenderer.shadowRoot) return;

                    const viewMoreButton = repliesRenderer.shadowRoot.querySelector("#view-more > bili-text-button");
                    if (!viewMoreButton || !viewMoreButton.shadowRoot) return;

                    const button = viewMoreButton.shadowRoot.querySelector("button");
                    if (!button) return;

                    if (!this.viewMoreButtons.has(button)) {
                        this.processViewMoreButton(viewMoreButton, button, threadRenderer);
                        newButtonsFound++;
                    }
                });

                // 只在有新按钮时输出日志
                if (newButtonsFound > 0) {
                    Utils.log('info', `新处理了 ${newButtonsFound} 个"点击查看"按钮,总计: ${this.viewMoreButtons.size}`);
                }

            } catch (error) {
                Utils.log('error', '扫描按钮时出错:', error);
            }
        }

        processViewMoreButton(container, button, threadRenderer) {
            this.viewMoreButtons.add(button);
            button.setAttribute('data-waterfall-processed', 'true');

            // 先尝试提取评论信息
            const commentInfo = this.extractCommentInfo(container, threadRenderer);

            if (!commentInfo) {
                // 如果无法提取评论信息,创建一个基本的信息对象
                const basicInfo = {
                    rootId: 'unknown',
                    oid: this.extractVideoId() || 'unknown',
                    replyCount: 0,
                    container,
                    commentElement: threadRenderer
                };
                this.addExpandButtonToStableLocation(commentInfo.commentElement, basicInfo);
                Utils.log('info', '已添加评论展开按钮(无法提取完整信息)');
                return;
            }

            this.addExpandButtonToStableLocation(commentInfo.commentElement, commentInfo);
            Utils.log('info', `已添加评论展开按钮,评论ID: ${commentInfo.rootId}, 回复数: ${commentInfo.replyCount}`);
        }

        // 将评论展开按钮添加到稳定的位置(优化版)
        addExpandButtonToStableLocation(threadRenderer, commentInfo) {
            try {
                // 检查是否已经存在评论展开按钮
                if (this.findExpandButton(threadRenderer)) {
                    Utils.log('info', '评论展开按钮已存在,跳过添加');
                    return;
                }

                // 直接使用已验证有效的容器选择器
                let targetContainer = threadRenderer.shadowRoot?.querySelector("#replies");

                if (targetContainer) {
                    Utils.log('info', '使用主要容器: #replies');
                } else {
                    // 降级方案:使用threadRenderer本身
                    targetContainer = threadRenderer;
                    Utils.log('warn', '未找到#replies容器,使用threadRenderer作为降级方案');
                }

                // 创建评论展开按钮
                const expandBtn = this.createExpandButton(commentInfo);

                // 创建一个包装容器,使按钮更稳定并居中
                const buttonWrapper = document.createElement('div');
                buttonWrapper.className = 'bili-comment-expand-wrapper';
                buttonWrapper.style.cssText = `
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    margin: 8px 0;
                    position: relative;
                    z-index: 1000;
                    width: 100%;
                `;
                buttonWrapper.appendChild(expandBtn);

                // 将按钮添加到稳定位置
                if (targetContainer.shadowRoot) {
                    // 如果目标容器有shadowRoot,添加到shadowRoot中
                    targetContainer.shadowRoot.appendChild(buttonWrapper);
                    Utils.log('info', `评论展开按钮已添加到稳定位置(shadowRoot): ${targetContainer.tagName || 'unknown'}`);
                } else {
                    // 否则直接添加到容器中
                    targetContainer.appendChild(buttonWrapper);
                    Utils.log('info', `评论展开按钮已添加到稳定位置: ${targetContainer.tagName || 'unknown'}`);
                }

            } catch (error) {
                Utils.log('error', '添加评论展开按钮到稳定位置失败:', error);
                Utils.log('info', '尝试使用降级方案...');
                // 降级到原始方法
                this.addExpandButton(commentInfo.container || threadRenderer, commentInfo);
            }
        }

        // 创建评论展开按钮(提取为独立方法)
        createExpandButton(commentInfo) {
            const expandBtn = document.createElement('button');
            expandBtn.className = 'bili-comment-expand-btn';
            expandBtn.style.cssText = `
                padding: 8px 16px;
                background: linear-gradient(135deg, #00a1d6, #0084b4);
                color: #ffffff;
                border: 1px solid rgba(0, 161, 214, 0.3);
                border-radius: 6px;
                font-size: 12px;
                font-weight: 500;
                cursor: pointer;
                transition: all 0.3s ease;
                box-shadow: 0 2px 6px rgba(0, 161, 214, 0.2);
                display: flex;
                align-items: center;
                justify-content: center;
                gap: 6px;
                position: relative;
                overflow: hidden;
                min-width: 120px;
            `;

            // 添加展开图标和文字 - 使用展开箭头符号
            expandBtn.innerHTML = `
                <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style="filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));">
                    <path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"/>
                </svg>
                <span style="text-shadow: 0 1px 2px rgba(0,0,0,0.3);">展开回复</span>
            `;

            // 悬停效果 - 更丰富的动画
            expandBtn.onmouseover = () => {
                expandBtn.style.background = 'linear-gradient(135deg, #40a9ff, #1890ff)';
                expandBtn.style.transform = 'translateY(-2px) scale(1.05)';
                expandBtn.style.boxShadow = '0 6px 12px rgba(0, 161, 214, 0.4)';
                expandBtn.style.borderColor = 'rgba(64, 169, 255, 0.6)';
            };

            expandBtn.onmouseout = () => {
                expandBtn.style.background = 'linear-gradient(135deg, #00a1d6, #0084b4)';
                expandBtn.style.transform = 'translateY(0) scale(1)';
                expandBtn.style.boxShadow = '0 2px 6px rgba(0, 161, 214, 0.2)';
                expandBtn.style.borderColor = 'rgba(0, 161, 214, 0.3)';
            };

            // 点击效果
            expandBtn.onmousedown = () => {
                expandBtn.style.transform = 'translateY(0) scale(0.95)';
            };

            expandBtn.onmouseup = () => {
                expandBtn.style.transform = 'translateY(-2px) scale(1.05)';
            };

            // 绑定点击事件
            expandBtn.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                this.handleExpandClick(commentInfo);
            };

            return expandBtn;
        }

        // 原始的添加评论展开按钮方法(作为降级选项)
        addExpandButton(container, commentInfo) {
            // 检查是否已经添加过评论展开按钮
            if (container.querySelector('.bili-comment-expand-btn')) {
                return;
            }

            const expandBtn = this.createExpandButton(commentInfo);
            container.appendChild(expandBtn);
        }

        handleExpandClick(commentInfo) {
            // 调用主控制器的处理函数
            if (this.onViewMoreClick) {
                this.onViewMoreClick(commentInfo);
            }
        }

        extractCommentInfo(container, threadRenderer) {
            try {
                // 从容器文本中提取回复数量
                const containerText = container.textContent || '';
                const replyCountMatch = containerText.match(/(\d+)\s*条回复/) || containerText.match(/共\s*(\d+)\s*条/) || containerText.match(/(\d+)\s*回复/);
                const replyCount = replyCountMatch ? parseInt(replyCountMatch[1], 10) : 0;

                // 更强力的评论ID提取
                const rootId = this.extractCommentId(threadRenderer);
                const oid = this.extractVideoId();

                Utils.log('info', `提取到的信息: rootId=${rootId}, oid=${oid}, replyCount=${replyCount}`);

                if (!rootId) {
                    Utils.log('warn', '无法提取评论ID');
                    return null;
                }

                if (!oid) {
                    Utils.log('warn', '无法提取视频ID');
                    return null;
                }

                return {
                    rootId,
                    oid,
                    replyCount,
                    container,
                    commentElement: threadRenderer
                };
            } catch (error) {
                Utils.log('error', '提取评论信息失败', error);
                return null;
            }
        }

        extractCommentId(threadRenderer) {
            // 方法1: 从__data对象获取(最新的B站结构)
            if (threadRenderer.__data && threadRenderer.__data.rpid) {
                const rpid = threadRenderer.__data.rpid.toString();
                Utils.log('info', `方法1获取到rpid: ${rpid}`);
                return rpid;
            }

            // 方法2: 从data属性获取
            if (threadRenderer.data && threadRenderer.data.rpid) {
                const rpid = threadRenderer.data.rpid.toString();
                Utils.log('info', `方法2获取到rpid: ${rpid}`);
                return rpid;
            }

            // 方法3: 从Shadow DOM中的commentRenderer获取
            if (threadRenderer.shadowRoot) {
                const commentRenderer = threadRenderer.shadowRoot.querySelector('bili-comment-renderer');
                if (commentRenderer) {
                    // 从commentRenderer的__data获取
                    if (commentRenderer.__data && commentRenderer.__data.rpid) {
                        const rpid = commentRenderer.__data.rpid.toString();
                        Utils.log('info', `方法3获取到rpid: ${rpid}`);
                        return rpid;
                    }

                    // 从commentRenderer的data属性获取
                    if (commentRenderer.data && commentRenderer.data.rpid) {
                        const rpid = commentRenderer.data.rpid.toString();
                        Utils.log('info', `方法3.1获取到rpid: ${rpid}`);
                        return rpid;
                    }

                    // 从属性获取
                    const rpidAttr = commentRenderer.getAttribute('data-rpid') ||
                                   commentRenderer.getAttribute('rpid');
                    if (rpidAttr) {
                        Utils.log('info', `方法3.2获取到rpid: ${rpidAttr}`);
                        return rpidAttr;
                    }
                }
            }

            // 方法4: 传统方法 - 从属性获取
            let rpid = threadRenderer.getAttribute('data-rpid') ||
                      threadRenderer.getAttribute('rpid') ||
                      threadRenderer.getAttribute('data-id');

            if (rpid) {
                Utils.log('info', `方法4获取到rpid: ${rpid}`);
                return rpid;
            }

            // 方法5: 从dataset获取
            const dataRpid = threadRenderer.dataset?.rpid;
            if (dataRpid) {
                Utils.log('info', `方法5获取到rpid: ${dataRpid}`);
                return dataRpid;
            }

            Utils.log('warn', '所有方法都无法获取到rpid');
            return null;
        }



        extractIdFromComponent(component) {
            // 尝试从组件本身的属性获取
            const possibleAttributes = ['data-rpid', 'rpid', 'data-id', 'comment-id'];

            for (const attr of possibleAttributes) {
                const value = component.getAttribute(attr);
                if (value) {
                    return value;
                }
            }

            // 尝试从Shadow DOM内部查找
            if (component.shadowRoot) {
                const shadowElements = component.shadowRoot.querySelectorAll('[data-rpid], [rpid], [data-id]');
                for (const element of shadowElements) {
                    for (const attr of possibleAttributes) {
                        const value = element.getAttribute(attr);
                        if (value) {
                            return value;
                        }
                    }
                }
            }

            // 尝试从子元素查找
            const childElements = component.querySelectorAll('[data-rpid], [rpid], [data-id]');
            for (const element of childElements) {
                for (const attr of possibleAttributes) {
                    const value = element.getAttribute(attr);
                    if (value) {
                        return value;
                    }
                }
            }

            // 如果还是找不到,尝试从URL或其他地方提取
            try {
                // 检查组件内是否有包含ID的链接
                const links = component.querySelectorAll('a[href]');
                for (const link of links) {
                    const href = link.getAttribute('href') || '';
                    const idMatch = href.match(/\/(\d+)/);
                    if (idMatch) {
                        return idMatch[1];
                    }
                }
            } catch (error) {
                // 静默处理错误
            }

            return null;
        }

        extractVideoId() {
            // 方法1: 从URL提取
            const url = window.location.href;
            const match = url.match(/\/video\/(?:av(\d+)|BV([a-zA-Z0-9]+))/);

            if (match) {
                if (match[1]) {
                    // av号直接返回
                    return match[1];
                } else if (match[2]) {
                    // BV号需要转换,但先尝试从页面数据获取对应的aid
                    const aid = this.getAidFromPageData();
                    if (aid) {
                        return aid;
                    }
                    // 如果无法获取aid,返回BV号(某些API可能支持)
                    return match[2];
                }
            }

            // 方法2: 从页面数据获取
            const aid = this.getAidFromPageData();
            if (aid) {
                return aid;
            }

            // 方法3: 从meta标签获取
            const metaAid = document.querySelector('meta[property="og:url"]');
            if (metaAid) {
                const metaMatch = metaAid.content.match(/\/video\/av(\d+)/);
                if (metaMatch) {
                    return metaMatch[1];
                }
            }

            return null;
        }

        getAidFromPageData() {
            try {
                // 尝试多种可能的全局变量
                const sources = [
                    () => window.__INITIAL_STATE__?.videoData?.aid,
                    () => window.__initialState__?.videoData?.aid,
                    () => window.__INITIAL_STATE__?.aid,
                    () => window.__initialState__?.aid,
                    () => window.aid,
                    () => {
                        // 从页面中的script标签查找
                        const scripts = document.querySelectorAll('script');
                        for (const script of scripts) {
                            const content = script.textContent || '';
                            const aidMatch = content.match(/"aid":(\d+)/);
                            if (aidMatch) {
                                return aidMatch[1];
                            }
                        }
                        return null;
                    }
                ];

                for (const source of sources) {
                    const aid = source();
                    if (aid) {
                        return aid.toString();
                    }
                }
            } catch (error) {
                // 静默处理错误
            }

            return null;
        }



        setViewMoreClickHandler(handler) {
            this.onViewMoreClick = handler;
        }

        getProcessedButtonCount() {
            return this.viewMoreButtons.size;
        }

        destroy() {
            if (this.observer) {
                this.observer.disconnect();
                this.observer = null;
            }

            if (this.intervalId) {
                clearInterval(this.intervalId);
                this.intervalId = null;
            }

            this.isObserving = false;
            this.viewMoreButtons.clear();
            this.onViewMoreClick = null;
        }
    }

    // 主控制器
    class BilibiliCommentExpandController {
        constructor() {
            this.domWatcher = new DOMWatcher();
            this.commentAPI = new BilibiliCommentAPI();
            this.isInitialized = false;
        }

        async initialize() {
            if (this.isInitialized) {
                Utils.log('warn', '脚本已初始化');
                return;
            }

            try {
                Utils.log('info', '开始初始化Bilibili评论展开助手脚本');
                this.setupEventHandlers();
                this.domWatcher.observeCommentSection();

                this.isInitialized = true;
                Utils.log('info', 'Bilibili评论展开助手脚本初始化完成');

            } catch (error) {
                Utils.log('error', '脚本初始化失败', error);
                throw error;
            }
        }

        setupEventHandlers() {
            this.domWatcher.setViewMoreClickHandler((commentInfo) => {
                this.handleViewMoreClick(commentInfo);
            });
            Utils.log('info', '事件处理函数已设置');
        }

        async handleViewMoreClick(commentInfo) {
            try {
                Utils.log('info', '处理评论展开按钮点击', commentInfo);

                // 显示加载提示
                this.showLoadingIndicator();

                // 尝试从按钮文本和周围元素中提取回复数量
                const buttonText = commentInfo.container.textContent || '';

                // 尝试多种模式匹配回复数量
                let replyCount = 0;
                const patterns = [
                    /(\d+)\s*条回复/,
                    /共\s*(\d+)\s*条/,
                    /(\d+)\s*回复/,
                    /(\d+)\s*replies?/i
                ];

                for (const pattern of patterns) {
                    const match = buttonText.match(pattern);
                    if (match) {
                        replyCount = parseInt(match[1], 10);
                        break;
                    }
                }

                // 如果按钮文本中没有找到,尝试从父元素中查找
                if (replyCount === 0) {
                    const parentText = commentInfo.commentElement?.textContent || '';
                    for (const pattern of patterns) {
                        const match = parentText.match(pattern);
                        if (match) {
                            replyCount = parseInt(match[1], 10);
                            break;
                        }
                    }
                }

                // 尝试从__data中获取回复数量
                if (replyCount === 0 && commentInfo.commentElement?.__data?.rcount) {
                    replyCount = commentInfo.commentElement.__data.rcount;
                    Utils.log('info', `从__data获取到回复数量: ${replyCount}`);
                }

                // 获取真实的评论回复数据
                let realReplies = [];
                let apiError = null;

                // 只要有评论ID和视频ID就尝试调用API,不依赖回复数量检测
                if (commentInfo.rootId && commentInfo.oid && commentInfo.rootId !== 'unknown') {
                    try {
                        Utils.log('info', `开始获取回复数据: oid=${commentInfo.oid}, rootId=${commentInfo.rootId}, 预期回复数=${replyCount}`);
                        realReplies = await this.commentAPI.getAllReplies(commentInfo.oid, commentInfo.rootId);
                        Utils.log('info', `成功获取 ${realReplies.length} 条真实回复数据`);

                        // 如果API返回了数据,更新回复数量
                        if (realReplies.length > 0 && replyCount === 0) {
                            replyCount = realReplies.length;
                            Utils.log('info', `根据API结果更新回复数量: ${replyCount}`);
                        }
                    } catch (error) {
                        Utils.log('error', '获取真实回复数据失败:', error);
                        apiError = error;
                    }
                } else {
                    Utils.log('warn', `跳过API调用: rootId=${commentInfo?.rootId}, oid=${commentInfo?.oid}, replyCount=${replyCount}`);
                }

                // 创建评论展开弹出框,传入真实数据
                Utils.log('info', `创建弹出框: replyCount=${replyCount}, realReplies.length=${realReplies.length}, hasError=${!!apiError}`);
                this.createExpandModal(replyCount, buttonText, commentInfo, realReplies, apiError);

                // 隐藏加载提示
                this.hideLoadingIndicator();

            } catch (error) {
                Utils.log('error', '处理"点击查看"按钮失败', error);
                this.hideLoadingIndicator();
            }
        }

        showLoadingIndicator() {
            const loading = document.createElement('div');
            loading.id = 'bili-comment-expand-loading';
            loading.style.cssText = `
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: rgba(0, 0, 0, 0.8);
                color: #fff;
                padding: 12px 20px;
                border-radius: 4px;
                z-index: 10001;
                font-size: 14px;
            `;
            loading.textContent = '正在加载评论...';
            document.body.appendChild(loading);
        }

        hideLoadingIndicator() {
            const loading = document.getElementById('bili-comment-expand-loading');
            if (loading) {
                loading.remove();
            }
        }

        createExpandModal(replyCount, buttonText, commentInfo, realReplies = [], apiError = null) {
            // 创建遮罩层
            const overlay = document.createElement('div');
            overlay.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.8);
                z-index: 10000;
                display: flex;
                align-items: center;
                justify-content: center;
                backdrop-filter: blur(4px);
            `;

            // 创建弹出框 - 暗色主题,模仿Bilibili原生设计
            const modal = document.createElement('div');
            modal.style.cssText = `
                background: #1f1f1f;
                border: 1px solid #3a3a3a;
                border-radius: 8px;
                width: 85%;
                max-width: 900px;
                max-height: 85%;
                display: flex;
                flex-direction: column;
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
                color: #e1e2e3;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            `;

            // 创建头部 - 暗色主题
            const header = document.createElement('div');
            header.style.cssText = `
                padding: 16px 20px;
                border-bottom: 1px solid #3a3a3a;
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: #2a2a2a;
                border-radius: 8px 8px 0 0;
            `;

            const title = document.createElement('h3');
            title.style.cssText = `
                margin: 0;
                font-size: 16px;
                font-weight: 500;
                color: #e1e2e3;
            `;
            title.textContent = `评论回复 (${replyCount}条)`;

            const closeButton = document.createElement('button');
            closeButton.style.cssText = `
                background: none;
                border: none;
                font-size: 20px;
                cursor: pointer;
                color: #9499a0;
                padding: 4px;
                width: 28px;
                height: 28px;
                display: flex;
                align-items: center;
                justify-content: center;
                border-radius: 4px;
                transition: all 0.2s ease;
            `;
            closeButton.textContent = '×';
            closeButton.onclick = () => overlay.remove();

            // 关闭按钮悬停效果
            closeButton.onmouseover = () => {
                closeButton.style.background = '#3a3a3a';
                closeButton.style.color = '#ffffff';
            };
            closeButton.onmouseout = () => {
                closeButton.style.background = 'none';
                closeButton.style.color = '#9499a0';
            };

            header.appendChild(title);
            header.appendChild(closeButton);

            // 创建内容区域 - 暗色主题,左对齐
            const body = document.createElement('div');
            body.style.cssText = `
                flex: 1;
                overflow: auto;
                background: #1f1f1f;
                color: #9499a0;
                text-align: left;
            `;

            // 根据是否有真实数据来显示不同内容
            if (realReplies && realReplies.length > 0) {
                // 显示真实的回复数据
                this.renderRepliesContent(body, realReplies, replyCount);
            } else if (replyCount > 0) {
                // 显示加载失败或无数据的提示 - 暗色主题
                const errorMsg = apiError ? apiError.message : '未知错误';
                body.innerHTML = `
                    <div style="padding: 40px 20px; color: #e1e2e3;">
                        <div style="text-align: center; margin-bottom: 30px;">
                            <p style="color: #ff6b6b; margin-bottom: 16px; font-size: 16px;">⚠️ 无法获取回复数据</p>
                            <p style="color: #9499a0; margin-bottom: 8px;">检测到 ${replyCount} 条回复,但API请求失败</p>
                            <p style="color: #9499a0; margin-bottom: 20px;">按钮文本: "${buttonText}"</p>
                        </div>
                        <details style="margin: 20px 0; max-width: 600px; margin-left: auto; margin-right: auto;">
                            <summary style="cursor: pointer; color: #ff6b6b; padding: 8px; border-radius: 4px; background: #2a2a2a; text-align: center;">错误详情 (点击展开)</summary>
                            <div style="margin-top: 12px; font-size: 12px; background: #2a2a2a; padding: 16px; border-radius: 6px; border: 1px solid #3a3a3a; color: #e1e2e3; text-align: left;">
                                <p style="margin-bottom: 8px;"><strong style="color: #ff6b6b;">错误信息:</strong> ${errorMsg}</p>
                                <p style="margin-bottom: 8px;"><strong style="color: #00a1d6;">评论ID:</strong> ${commentInfo?.rootId || '未获取到'}</p>
                                <p style="margin-bottom: 8px;"><strong style="color: #00a1d6;">视频ID:</strong> ${commentInfo?.oid || '未获取到'}</p>
                                <p style="margin-bottom: 0;"><strong style="color: #00a1d6;">API URL:</strong> <code style="background: #1f1f1f; padding: 2px 4px; border-radius: 3px; font-size: 11px;">${CONFIG.API_BASE}/x/v2/reply/reply?type=${CONFIG.COMMENT_TYPE}&oid=${commentInfo?.oid}&root=${commentInfo?.rootId}</code></p>
                            </div>
                        </details>
                        <div style="text-align: center;">
                            <p style="color: #9499a0; font-size: 12px; margin-top: 20px; line-height: 1.5;">
                                可能原因:网络问题、API限制、需要登录或评论ID提取失败
                            </p>
                            <p style="color: #00a1d6; margin-top: 16px; font-weight: 500;">✅ 基础架构已完成</p>
                        </div>
                    </div>
                `;
            } else {
                // 显示调试信息 - 暗色主题
                const parentText = commentInfo.commentElement?.textContent || '';
                const containerHTML = commentInfo.container?.outerHTML?.substring(0, 200) || '';

                body.innerHTML = `
                    <div style="padding: 40px 20px; color: #e1e2e3;">
                        <div style="text-align: center; margin-bottom: 30px;">
                            <p style="font-size: 18px; margin-bottom: 16px; color: #9499a0;">暂无回复数据</p>
                            <p style="color: #9499a0; margin-bottom: 20px;">按钮文本: "${buttonText}"</p>
                        </div>
                        <details style="margin: 20px 0; max-width: 600px; margin-left: auto; margin-right: auto;">
                            <summary style="cursor: pointer; color: #00a1d6; padding: 8px; border-radius: 4px; background: #2a2a2a; text-align: center;">调试信息 (点击展开)</summary>
                            <div style="margin-top: 12px; font-size: 12px; background: #2a2a2a; padding: 16px; border-radius: 6px; border: 1px solid #3a3a3a; color: #e1e2e3; text-align: left;">
                                <p style="margin-bottom: 12px;"><strong style="color: #00a1d6;">父元素文本:</strong></p>
                                <p style="word-break: break-all; max-height: 100px; overflow-y: auto; background: #1f1f1f; padding: 8px; border-radius: 4px; margin-bottom: 12px; font-family: monospace; font-size: 11px;">${parentText.substring(0, 300)}...</p>
                                <p style="margin-bottom: 12px;"><strong style="color: #00a1d6;">容器HTML:</strong></p>
                                <p style="word-break: break-all; max-height: 100px; overflow-y: auto; background: #1f1f1f; padding: 8px; border-radius: 4px; font-family: monospace; font-size: 11px;">${containerHTML}...</p>
                            </div>
                        </details>
                        <div style="text-align: center;">
                            <p style="color: #00a1d6; margin-bottom: 12px; font-weight: 500;">脚本已成功工作!</p>
                            <p style="color: #52c41a; margin-bottom: 8px;">✅ 成功找到并处理"点击查看"按钮</p>
                            <p style="color: #52c41a; margin-bottom: 8px;">✅ 弹出框功能完全正常</p>
                            <p style="color: #52c41a; margin-bottom: 8px;">✅ 基础架构已完成</p>
                            <p style="color: #52c41a; margin-bottom: 8px;">✅ 用户名点击跳转功能</p>
                            <p style="color: #52c41a; margin-bottom: 8px;">✅ 视频链接识别功能</p>
                            <p style="color: #52c41a; margin-bottom: 8px;">✅ 时间排序正序/倒序切换</p>
                            <p style="color: #52c41a; margin-bottom: 8px;">✅ 独立瀑布流按钮</p>
                            <p style="color: #00a1d6; margin-top: 16px; font-size: 14px;">Bilibili评论瀑布流 - 排序功能已优化</p>
                        </div>
                    </div>
                `;
            }

            modal.appendChild(header);
            modal.appendChild(body);
            overlay.appendChild(modal);

            // 点击遮罩层关闭
            overlay.onclick = (e) => {
                if (e.target === overlay) {
                    overlay.remove();
                }
            };

            // ESC键关闭
            const escHandler = (e) => {
                if (e.key === 'Escape') {
                    overlay.remove();
                    document.removeEventListener('keydown', escHandler);
                }
            };
            document.addEventListener('keydown', escHandler);

            document.body.appendChild(overlay);
        }

        renderRepliesContent(container, replies, totalCount) {
            // 创建排序控制 - 暗色主题
            const sortControls = document.createElement('div');
            sortControls.style.cssText = `
                padding: 12px 20px;
                border-bottom: 1px solid #3a3a3a;
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: #2a2a2a;
            `;

            const sortInfo = document.createElement('span');
            sortInfo.style.cssText = 'font-size: 13px; color: #9499a0;';
            sortInfo.textContent = `共 ${totalCount} 条回复,显示 ${replies.length} 条`;

            const sortButtons = document.createElement('div');
            sortButtons.style.cssText = 'display: flex; gap: 8px;';

            const hotSortBtn = this.createSortButton('按热度', true);
            const timeSortBtn = this.createSortButton('按时间↓', false);

            sortButtons.appendChild(hotSortBtn);
            sortButtons.appendChild(timeSortBtn);

            sortControls.appendChild(sortInfo);
            sortControls.appendChild(sortButtons);

            // 创建回复列表容器 - 暗色主题
            const repliesContainer = document.createElement('div');
            repliesContainer.style.cssText = `
                flex: 1;
                overflow-y: auto;
                padding: 0;
                background: #1f1f1f;
            `;

            // 初始化排序状态
            let currentSort = 'hot';
            let timeOrder = 'desc'; // 'desc' 为倒序(最新在前),'asc' 为正序(最旧在前)

            // 渲染回复列表
            this.renderRepliesList(repliesContainer, replies, currentSort, timeOrder);

            // 绑定排序事件
            hotSortBtn.onclick = () => {
                currentSort = 'hot';
                this.updateSortButtons(hotSortBtn, timeSortBtn);
                this.renderRepliesList(repliesContainer, replies, currentSort, timeOrder);
            };

            timeSortBtn.onclick = () => {
                if (currentSort === 'time') {
                    // 如果已经是时间排序,则切换正序/倒序
                    timeOrder = timeOrder === 'desc' ? 'asc' : 'desc';
                } else {
                    // 如果不是时间排序,则切换到时间排序(默认倒序)
                    currentSort = 'time';
                    timeOrder = 'desc';
                }

                // 更新按钮文本
                timeSortBtn.textContent = `按时间${timeOrder === 'desc' ? '↓' : '↑'}`;

                this.updateSortButtons(timeSortBtn, hotSortBtn);
                this.renderRepliesList(repliesContainer, replies, currentSort, timeOrder);
            };

            container.appendChild(sortControls);
            container.appendChild(repliesContainer);
        }

        createSortButton(text, active) {
            const button = document.createElement('button');
            button.style.cssText = `
                padding: 6px 14px;
                border: 1px solid ${active ? '#00a1d6' : '#4a4a4a'};
                background: ${active ? '#00a1d6' : '#3a3a3a'};
                color: ${active ? '#ffffff' : '#e1e2e3'};
                border-radius: 6px;
                cursor: pointer;
                font-size: 12px;
                font-weight: 500;
                transition: all 0.2s ease;
            `;
            button.textContent = text;

            button.onmouseover = () => {
                if (!button.classList.contains('active')) {
                    button.style.borderColor = '#00a1d6';
                    button.style.background = '#4a4a4a';
                    button.style.color = '#00a1d6';
                }
            };

            button.onmouseout = () => {
                if (!button.classList.contains('active')) {
                    button.style.borderColor = '#4a4a4a';
                    button.style.background = '#3a3a3a';
                    button.style.color = '#e1e2e3';
                }
            };

            if (active) {
                button.classList.add('active');
            }

            return button;
        }

        updateSortButtons(activeBtn, inactiveBtn) {
            // 更新活跃按钮 - 暗色主题
            activeBtn.style.background = '#00a1d6';
            activeBtn.style.color = '#ffffff';
            activeBtn.style.borderColor = '#00a1d6';
            activeBtn.classList.add('active');

            // 更新非活跃按钮 - 暗色主题
            inactiveBtn.style.background = '#3a3a3a';
            inactiveBtn.style.color = '#e1e2e3';
            inactiveBtn.style.borderColor = '#4a4a4a';
            inactiveBtn.classList.remove('active');
        }

        renderRepliesList(container, replies, sortType, timeOrder = 'desc') {
            // 排序回复
            const sortedReplies = [...replies].sort((a, b) => {
                if (sortType === 'hot') {
                    return (b.like || 0) - (a.like || 0);
                } else if (sortType === 'time') {
                    const timeA = a.ctime || 0;
                    const timeB = b.ctime || 0;
                    return timeOrder === 'desc' ? timeB - timeA : timeA - timeB;
                }
                return 0;
            });

            // 清空容器
            container.innerHTML = '';

            // 渲染每条回复
            sortedReplies.forEach((reply) => {
                const replyElement = this.createReplyElement(reply);
                container.appendChild(replyElement);
            });
        }

        createReplyElement(reply) {
            const replyDiv = document.createElement('div');
            replyDiv.style.cssText = `
                padding: 16px 20px;
                border-bottom: 1px solid #3a3a3a;
                display: flex;
                gap: 12px;
                transition: background-color 0.2s ease;
                background: #1f1f1f;
            `;

            replyDiv.onmouseover = () => {
                replyDiv.style.backgroundColor = '#2a2a2a';
            };

            replyDiv.onmouseout = () => {
                replyDiv.style.backgroundColor = '#1f1f1f';
            };

            // 用户头像 - 模仿Bilibili原生尺寸
            const avatar = document.createElement('img');
            avatar.style.cssText = `
                width: 40px;
                height: 40px;
                border-radius: 50%;
                flex-shrink: 0;
                object-fit: cover;
                border: 2px solid #3a3a3a;
            `;
            avatar.src = reply.member?.avatar || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMjAiIGN5PSIyMCIgcj0iMjAiIGZpbGw9IiM0YTRhNGEiLz4KPHRleHQgeD0iMjAiIHk9IjI2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjE0IiBmaWxsPSIjOTQ5OWEwIj7nlKg8L3RleHQ+Cjwvc3ZnPgo=';
            avatar.alt = reply.member?.uname || '用户';

            // 内容区域
            const content = document.createElement('div');
            content.style.cssText = 'flex: 1; min-width: 0;';

            // 用户信息行 - 模仿Bilibili原生布局
            const userInfo = document.createElement('div');
            userInfo.style.cssText = `
                display: flex;
                align-items: center;
                gap: 8px;
                margin-bottom: 10px;
                font-size: 13px;
            `;

            const username = document.createElement('span');
            const vipColor = reply.member?.vip?.nickname_color;
            const userId = reply.member?.mid || reply.mid;

            username.style.cssText = `
                font-weight: 500;
                color: ${vipColor || '#e1e2e3'};
                max-width: 150px;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                cursor: pointer;
                transition: all 0.2s ease;
                border-bottom: 1px solid transparent;
            `;
            username.textContent = reply.member?.uname || '匿名用户';

            // 添加用户名点击跳转功能
            if (userId) {
                username.onclick = (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    const userUrl = `https://space.bilibili.com/${userId}`;
                    window.open(userUrl, '_blank');
                };

                // 悬停效果
                username.onmouseover = () => {
                    username.style.borderBottomColor = vipColor || '#e1e2e3';
                    username.style.color = vipColor || '#ffffff';
                };

                username.onmouseout = () => {
                    username.style.borderBottomColor = 'transparent';
                    username.style.color = vipColor || '#e1e2e3';
                };
            }

            // 时间显示 - 添加tooltip显示详细时间
            const timeSpan = document.createElement('span');
            timeSpan.style.cssText = `
                color: #9499a0;
                font-size: 12px;
                cursor: help;
            `;
            timeSpan.textContent = Utils.formatTime(reply.ctime);
            timeSpan.title = Utils.formatDetailedTime(reply.ctime);

            userInfo.appendChild(username);
            userInfo.appendChild(timeSpan);

            // 评论内容 - 暗色主题,支持视频链接识别
            const messageDiv = document.createElement('div');
            messageDiv.style.cssText = `
                color: #e1e2e3;
                line-height: 1.6;
                margin-bottom: 12px;
                word-break: break-word;
                font-size: 14px;
                text-align: left;
            `;

            // 处理评论内容中的视频链接 - 使用增强版
            // 先显示原始内容,然后异步加载视频标题
            const originalContent = reply.content?.message || '';
            messageDiv.textContent = originalContent;

            // 异步处理视频链接
            Utils.processCommentContentEnhanced(originalContent).then(processedContent => {
                messageDiv.innerHTML = processedContent;
                // 为增强版视频链接添加悬停效果
                Utils.addEnhancedVideoLinkHoverEffects(messageDiv);
            }).catch(() => {
                // 如果失败,使用简单版本
                const simpleProcessed = Utils.processCommentContent(originalContent);
                messageDiv.innerHTML = simpleProcessed;
                Utils.addVideoLinkHoverEffects(messageDiv);
            });

            // 互动信息 - 模仿Bilibili原生样式
            const actions = document.createElement('div');
            actions.style.cssText = `
                display: flex;
                align-items: center;
                gap: 20px;
                font-size: 12px;
                color: #9499a0;
            `;

            const likeSpan = document.createElement('span');
            likeSpan.style.cssText = `
                display: flex;
                align-items: center;
                gap: 4px;
                cursor: pointer;
                padding: 4px 8px;
                border-radius: 4px;
                transition: all 0.2s ease;
            `;
            likeSpan.innerHTML = `👍 ${Utils.formatNumber(reply.like || 0)}`;

            // 点赞按钮悬停效果
            likeSpan.onmouseover = () => {
                likeSpan.style.background = '#3a3a3a';
                likeSpan.style.color = '#00a1d6';
            };
            likeSpan.onmouseout = () => {
                likeSpan.style.background = 'transparent';
                likeSpan.style.color = '#9499a0';
            };

            actions.appendChild(likeSpan);

            content.appendChild(userInfo);
            content.appendChild(messageDiv);
            content.appendChild(actions);

            replyDiv.appendChild(avatar);
            replyDiv.appendChild(content);

            return replyDiv;
        }

        destroy() {
            try {
                this.domWatcher.destroy();
                this.commentAPI.clearCache();
                this.isInitialized = false;
                Utils.log('info', 'Bilibili评论展开助手脚本已销毁');
            } catch (error) {
                Utils.log('error', '脚本销毁失败', error);
            }
        }

        getStatus() {
            return {
                initialized: this.isInitialized,
                processedButtons: this.domWatcher.getProcessedButtonCount()
            };
        }
    }

    // 全局实例
    let commentExpandController = null;

    // 脚本入口点
    function initializeScript() {
        try {
            Utils.log('info', '=== Bilibili评论展开助手脚本启动 ===');
            Utils.log('info', `当前页面: ${window.location.href}`);

            if (!window.location.href.includes('bilibili.com/video/')) {
                Utils.log('warn', '当前页面不是bilibili视频页面,脚本将不会运行');
                return;
            }

            commentExpandController = new BilibiliCommentExpandController();

            commentExpandController.initialize().then(() => {
                Utils.log('info', '脚本初始化成功');

                window.bilibiliCommentExpand = {
                    controller: commentExpandController,
                    getStatus: () => commentExpandController.getStatus(),
                    destroy: () => commentExpandController.destroy()
                };

                Utils.log('info', '全局调试接口已添加: window.bilibiliCommentExpand');

            }).catch((error) => {
                Utils.log('error', '脚本初始化失败', error);
            });

        } catch (error) {
            Utils.log('error', '脚本启动失败', error);
        }
    }

    // 页面卸载时清理资源
    window.addEventListener('beforeunload', () => {
        if (commentExpandController) {
            commentExpandController.destroy();
        }
    });

    // 启动脚本
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        setTimeout(initializeScript, 1000);
    }

    Utils.log('info', 'Bilibili评论展开助手脚本 v2.3.0 已加载 - 优化按钮附加和用户体验');

})();