把B站评论区挪到右边,边看视频边看评论

将B站评论区移动到右侧容器

// ==UserScript==
// @name         把B站评论区挪到右边,边看视频边看评论
// @namespace    http://tampermonkey.net/
// @version      2025.08.01
// @description  将B站评论区移动到右侧容器
// @author       FruitJellies
// @match        https://www.bilibili.com/video/*
// @match        https://www.bilibili.com/list/*
// @icon         https://www.bilibili.com/favicon.ico
// @grant        GM_addStyle
// @license      MIT
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand

// ==/UserScript==

(function() {
    'use strict';

    const path = window.location.pathname;

    // 配置对象
    const CONFIG = {
        containerSelector: "#mirror-vdcon > div.right-container",
        playlistContainerSelector: "#mirror-vdcon > div.playlist-container--right",
        commentAppSelector: "#commentapp",
        playlistTargetPositionSelector:"#mirror-vdcon > div.playlist-container--right > div.recommend-list-container",
        targetPositionSelector: ".recommend-list-v1",
        observerConfig: {
            childList: true,
            subtree: true,
            attributes: false
        },
        timeoutDuration: 9999999999, // 999……秒超时
        styleSettings: {
            containerWidth: "23%",
            containerHeight: "110vh",
            hideScrollbar: true // 默认隐藏滚动条
        }
    };

    // 全局变量
    let observer = null;
    let timeoutId = null;
    let isCommentsMoved = false;
    let originalPosition = null;
    let cachedElements = {};

    // 缓存DOM元素
    function getCachedElement(selector) {
        if (!cachedElements[selector]) {
            cachedElements[selector] = document.querySelector(selector);
        }
        return cachedElements[selector];
    }

    // 安全DOM操作
    function safeDOMOperation(operation) {
        try {
            return operation();
        } catch (error) {
            console.error('[BCTR] DOM操作失败:', error);
            return null;
        }
    }

    // 初始化容器样式
    function initContainerStyles() {
            // 从存储中读取用户配置,若无则使用默认值
    const hideScrollbar = GM_getValue('hideScrollbar', CONFIG.styleSettings.hideScrollbar);
        const commonStyles = `
            width: ${CONFIG.styleSettings.containerWidth};
            height: ${CONFIG.styleSettings.containerHeight};
            overflow-y: scroll !important;
            ${hideScrollbar ? 'scrollbar-width: none;' : ''}
            pointer-events: auto !important;
        `;
    // 动态生成滚动条隐藏样式
    const scrollbarStyle = hideScrollbar ? `
        ${CONFIG.playlistContainerSelector}::-webkit-scrollbar,
        ${CONFIG.containerSelector}::-webkit-scrollbar {
            display: none;
        }
    ` : '';
        GM_addStyle(`
            ${CONFIG.playlistContainerSelector}, ${CONFIG.containerSelector} {
                ${commonStyles}
            }
       ${scrollbarStyle}
            body {
                overflow: auto !important;
            }
        `);
    }

    // 处理评论区移动
    function handleCommentsMove() {
        return safeDOMOperation(() => {
            const commentApp = getCachedElement(CONFIG.commentAppSelector);
            const shadowContent = commentApp?.querySelector('bili-comments')?.shadowRoot?.querySelector('#contents');

            const targetPositionSelector = path.startsWith('/list/')
            ? CONFIG.playlistTargetPositionSelector
            : CONFIG.targetPositionSelector;

            const targetPosition = getCachedElement(targetPositionSelector);

            if (!commentApp || !targetPosition || !shadowContent) {
                return false;
            }

            // 保存原始位置
            if (!originalPosition) {
                originalPosition = commentApp.parentNode;
            }

            targetPosition.parentNode.insertBefore(commentApp, targetPosition);
            console.log('[BCTR] 评论区移动成功');
            isCommentsMoved = true;
            return true;
        });
    }

    // 恢复评论区到原始位置
    function restoreCommentsPosition() {
        return safeDOMOperation(() => {
            const commentApp = getCachedElement(CONFIG.commentAppSelector);

            if (!commentApp || !originalPosition) {
                return false;
            }

            originalPosition.appendChild(commentApp);
            console.log('[BCTR] 评论区恢复成功');
            isCommentsMoved = false;
            return true;
        });
    }

    // 切换评论区位置
    function toggleCommentsPosition() {
        isCommentsMoved ? restoreCommentsPosition() : handleCommentsMove();
    }

    // 创建切换按钮
    function createToggleButton() {
        const titleElement = path.startsWith('/list/')
        ?getCachedElement("#playlistToolbar > div.video-toolbar-right > div.video-note.video-toolbar-right-item.toolbar-right-note")
        :getCachedElement("#arc_toolbar_report > div.video-toolbar-right > div.video-note.video-toolbar-right-item.toolbar-right-note");
        if (!titleElement) return;
        const toggleBtn = document.createElement('button');
        toggleBtn.className = 'comment-toggle-btn';
        toggleBtn.textContent = '⇄';
        toggleBtn.title = '切换评论区位置';
        toggleBtn.addEventListener('click', toggleCommentsPosition);

        titleElement.appendChild(toggleBtn);
        const style = document.createElement('style');
        style.textContent = `
    .comment-toggle-btn {
        border-radius: 6px !important;
            display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    margin-left: 12px;
    background-color: #fb7299;
    color: white;
    border: none;
    cursor: pointer;
    vertical-align: middle;
    font-size: 14px;
    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
    z-index: 1;
    }

@media (prefers-color-scheme: dark) {
    .comment-toggle-btn {
        background-color: #ff85ad;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
`;
        document.head.appendChild(style);
    }

    // 清理资源
    function cleanupResources() {
        if (observer) {
            observer.disconnect();
            observer = null;
        }
        if (timeoutId) {
            clearTimeout(timeoutId);
            timeoutId = null;
        }
        cachedElements = {};
        console.log('[BCTR] 已释放观察器和定时器');
    }

    // MutationObserver回调
    function mutationCallback(mutationsList) {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && handleCommentsMove()) {
                cleanupResources();
                createToggleButton();
                break;
            }
        }
    }

    //禁用标题超链接
    function disableTitleLink() {
        const titleLink = document.querySelector("#mirror-vdcon > div.playlist-container--left > div.video-info-container.mac > div.video-info-title > div > h1 > a");
        if (titleLink) {
            titleLink.removeAttribute('href');
            titleLink.style.cursor = 'default';
            titleLink.style.textDecoration = 'none';
            titleLink.style.color = 'inherit';
            console.log('[BCTR] 已禁用标题超链接');
        }
    }

    //脚本设置
    function registerSettingsMenu() {
    GM_registerMenuCommand("⚙️ 脚本设置", function() {
        const currentValue = GM_getValue('hideScrollbar', CONFIG.styleSettings.hideScrollbar);
        const newValue = confirm(`当前滚动条设置:${currentValue ? '隐藏' : '显示'}\n\n是否切换?`);

        if (newValue !== null) {
            GM_setValue('hideScrollbar', !currentValue);
            alert('设置已保存!刷新页面后生效');
        }
    });
}

    // 初始化函数
    function init() {

        if (path.startsWith('/list/')) disableTitleLink();
        // 更精确地选择观察目标
        const observeTarget = document.body || document.documentElement;
        observer = new MutationObserver(mutationCallback);
        observer.observe(observeTarget, CONFIG.observerConfig);

        timeoutId = setTimeout(() => {
            console.warn('[BCTR] 操作超时,终止检测');
            cleanupResources();
        }, CONFIG.timeoutDuration);
    registerSettingsMenu();
    initContainerStyles();
        console.log('[BCTR] 脚本初始化完成');
    }

    // 启动脚本
    try {
        init();
    } catch (error) {
        console.error('[BCTR] 脚本初始化失败:', error);
        cleanupResources();
    }
})();