B站直播间弹幕备注显示

支持可视化管理的Bilibili直播弹幕备注系统

// ==UserScript==
// @name         B站直播间弹幕备注显示
// @namespace    https://www.mcbaoge.com/
// @version      0.1
// @description  支持可视化管理的Bilibili直播弹幕备注系统
// @author       MC豹哥
// @match        https://live.bilibili.com/*
// @license      MIT
// ==/UserScript==

/*
MIT License

Copyright (c) [year] [copyright holders]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
(function() {
    'use strict';

    // 初始化备注数据
    let userRemarks = GM_getValue('userRemarks') || {};

    // 创建控制按钮
    function createControlButton() {
        const btn = document.createElement('button');
        btn.textContent = '弹幕备注管理';
        btn.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
            padding: 10px;
            background: #00a1d6;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        `;
        document.body.appendChild(btn);
        return btn;
    }

    // 创建管理界面
    function createManagementUI() {
        const ui = document.createElement('div');
        ui.id = 'remark-manager';
        ui.style.cssText = `
        display: none;
        position: fixed;
        bottom: 60px;
        right: 20px;
        width: 300px;
        height: 200px; /* 固定高度 */
        background: #fff;
        padding: 15px;
        border-radius: 5px;
        box-shadow: 0 0 10px rgba(0,0,0,0.2);
        z-index: 9998;
        color: #000;
        overflow: hidden; /* 隐藏外部滚动 */
        display: flex;
        flex-direction: column;
        `;

        ui.innerHTML = `
<div style="margin-bottom:15px;">
        <input type="text" id="remark-uid" placeholder="用户UID"
            style="width:100%; margin-bottom:5px; padding:5px; background-color: white;">
        <input type="text" id="remark-text" placeholder="备注内容"
            style="width:100%; margin-bottom:5px; padding:5px; background-color: white;">
        <button id="save-remark" style="width:100%">保存备注</button>
    </div>
    <div id="remark-list" style="max-height:300px; overflow-y:auto;"></div>
        `;

        document.body.appendChild(ui);
        return ui;
    }

    // 渲染备注列表(带操作按钮)
    function renderRemarkList() {
        const list = document.getElementById('remark-list');
        list.innerHTML = Object.entries(userRemarks).map(([uid, remark]) => `
            <div class="remark-item" data-uid="${uid}"
                style="padding:8px; border-bottom:1px solid #eee; display:flex; align-items:center;">
                <div style="flex:1">
                    <div style="font-weight:bold;">${uid}</div>
                    <div class="remark-content">${remark}</div>
                </div>
                <div style="display:flex; gap:5px; margin-left:10px;">
                    <button class="edit-btn" style="padding:3px 5px;">修改</button>
                    <button class="delete-btn" style="padding:3px 5px; background:#ff5555;">删除</button>
                </div>
            </div>
        `).join('');
    }

    // 进入编辑模式
    function enterEditMode(item) {
        const uid = item.dataset.uid;
        const contentDiv = item.querySelector('.remark-content');
        const originalText = contentDiv.textContent;

        // 替换为输入框
        contentDiv.innerHTML = `
            <input type="text" value="${originalText}"
                style="width:100%; padding:3px; margin:2px 0;">
        `;

        // 修改操作按钮
        item.querySelector('.edit-btn').textContent = '确认';
        item.querySelector('.delete-btn').textContent = '取消';
    }

    // 退出编辑模式
    function exitEditMode(item, success = false) {
        const uid = item.dataset.uid;
        const input = item.querySelector('input');

        if (success && input.value.trim()) {
            userRemarks[uid] = input.value.trim();
            GM_setValue('userRemarks', userRemarks);
        }

        // 恢复显示
        item.querySelector('.remark-content').textContent = userRemarks[uid];
        item.querySelector('.edit-btn').textContent = '修改';
        item.querySelector('.delete-btn').textContent = '删除';
    }

    // 事件委托处理
    function handleListClick(e) {
        const item = e.target.closest('.remark-item');
        if (!item) return;

        const isEditBtn = e.target.classList.contains('edit-btn');
        const isDeleteBtn = e.target.classList.contains('delete-btn');

        if (isEditBtn) {
            if (e.target.textContent === '修改') {
                enterEditMode(item);
            } else {
                exitEditMode(item, true);
                renderRemarkList();
            }
        }

        if (isDeleteBtn) {
            if (e.target.textContent === '删除') {
                delete userRemarks[item.dataset.uid];
                GM_setValue('userRemarks', userRemarks);
                renderRemarkList();
            } else {
                exitEditMode(item);
            }
        }
    }

    // 初始化管理界面
    const btn = createControlButton();
    const ui = createManagementUI();
    const remarkList = document.getElementById('remark-list');
    remarkList.addEventListener('click', handleListClick);

    // 切换界面显示
    btn.addEventListener('click', () => {
        ui.style.display = ui.style.display === 'none' ? 'block' : 'none';
        renderRemarkList();
    });

    // 保存新备注
    document.getElementById('save-remark').addEventListener('click', () => {
        const uid = document.getElementById('remark-uid').value.trim();
        const text = document.getElementById('remark-text').value.trim();

        if (uid && text) {
            userRemarks[uid] = text;
            GM_setValue('userRemarks', userRemarks);
            renderRemarkList();
            document.getElementById('remark-uid').value = '';
            document.getElementById('remark-text').value = '';
        }
    });

    // 弹幕点击处理
    function handleDanmakuClick(e) {
        const uid = e.currentTarget.getAttribute('data-uid');
        document.getElementById('remark-uid').value = uid;
    }

    // 绑定弹幕点击事件
    function initDanmakuClick() {
        document.querySelectorAll('.chat-item.danmaku-item').forEach(item => {
            if (!item.dataset.clickBound) {
                item.addEventListener('click', handleDanmakuClick);
                item.dataset.clickBound = true;
            }
        });
    }

    // 弹幕备注显示
    function addRemarkToDanmaku(item) {
        const uid = item.getAttribute('data-uid');
        const remark = userRemarks[uid];
        const userName = item.querySelector('.user-name');

        if (remark && !userName.querySelector('.remarkTag')) {
            const tag = document.createElement('span');
            tag.className = 'remarkTag';
            tag.style.cssText = 'color:yellow; margin-left:5px; font-size:0.8em;';
            tag.textContent = `(${remark})`;
            userName.appendChild(tag);
        }
    }

    // 定时检查
    setInterval(() => {
        document.querySelectorAll('.chat-item.danmaku-item').forEach(item => {
            addRemarkToDanmaku(item);
        });
        initDanmakuClick();
    }, 500);

    // 初始化
    renderRemarkList();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址