// ==UserScript== // @name B站直播间弹幕过滤 (带可视化设置) // @namespace http://tampermonkey.net/ // @version 2.0 // @description 悬浮按钮+可视化界面,自动屏蔽没有粉丝牌、或粉丝牌低于设定等级的弹幕 // @author YourName // @match https://live.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // ================= 默认配置与存储 ================= const CONFIG_KEY = 'BiliDanmakuFilterConfig'; let config = { minLevel: 5, blockNoMedal: true, onlyCurrentRoom: false }; // 读取本地保存的配置 try { const saved = localStorage.getItem(CONFIG_KEY); if (saved) { Object.assign(config, JSON.parse(saved)); } } catch (e) { console.error("读取配置失败", e); } function saveConfig() { localStorage.setItem(CONFIG_KEY, JSON.stringify(config)); } // ================= 弹幕过滤逻辑 ================= function processDanmaku(node) { if (!node.classList || (!node.classList.contains('chat-item') && !node.classList.contains('danmaku-item'))) { return; } const medalEl = node.querySelector('.fans-medal-item'); // 1. 无粉丝牌 if (!medalEl) { if (config.blockNoMedal) hideNode(node); return; } // 2. 有粉丝牌,检查等级 const medalText = medalEl.innerText || ""; const levelMatch = medalText.match(/\d+/); if (levelMatch) { const level = parseInt(levelMatch[0], 10); if (level < config.minLevel) { hideNode(node); return; } } // 3. 检查是否为当前直播间的牌子 (灰牌检测) if (config.onlyCurrentRoom) { if (medalEl.classList.contains('is-grey') || medalEl.innerHTML.includes('gray')) { hideNode(node); return; } } } function hideNode(node) { node.style.display = 'none'; } function startObserver() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) processDanmaku(node); }); }); }); const timer = setInterval(() => { const chatList = document.querySelector('#chat-history-list'); if (chatList) { clearInterval(timer); observer.observe(chatList, { childList: true, subtree: true }); console.log("[B站弹幕过滤] 监控已启动。"); } }, 1000); } // ================= 注入 CSS 样式 ================= GM_addStyle(` /* 悬浮按钮 */ #bili-filter-fab { position: fixed; right: 20px; bottom: 150px; width: 44px; height: 44px; background-color: #fb7299; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: bold; box-shadow: 0 4px 12px rgba(251, 114, 153, 0.4); cursor: grab; z-index: 999999; user-select: none; transition: transform 0.2s; } #bili-filter-fab:active { cursor: grabbing; transform: scale(0.95); } /* 设置面板 */ #bili-filter-panel { position: fixed; right: 80px; bottom: 150px; width: 240px; background-color: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); padding: 16px; z-index: 999999; display: none; /* 默认隐藏 */ font-family: "Microsoft YaHei", sans-serif; color: #333; border: 1px solid #eee; } #bili-filter-panel h3 { margin: 0 0 12px 0; font-size: 16px; color: #fb7299; display: flex; justify-content: space-between; align-items: center; } #bili-filter-panel .close-btn { cursor: pointer; color: #999; font-weight: normal; } #bili-filter-panel .close-btn:hover { color: #333; } #bili-filter-panel .setting-item { margin-bottom: 12px; font-size: 13px; display: flex; align-items: center; } #bili-filter-panel input[type="number"] { width: 50px; margin-left: 8px; padding: 2px 4px; border: 1px solid #ccc; border-radius: 4px; outline: none; } #bili-filter-panel input[type="checkbox"] { margin-right: 6px; cursor: pointer; } /* 黑夜模式兼容 */ html.dark #bili-filter-panel { background-color: #222; color: #ddd; border-color: #444; } html.dark #bili-filter-panel .close-btn { color: #aaa; } html.dark #bili-filter-panel input[type="number"] { background: #333; color: #fff; border-color: #555;} `); // ================= 构建 UI ================= function initUI() { // 1. 创建悬浮按钮 const fab = document.createElement('div'); fab.id = 'bili-filter-fab'; fab.innerText = '过滤'; document.body.appendChild(fab); // 2. 创建面板 const panel = document.createElement('div'); panel.id = 'bili-filter-panel'; panel.innerHTML = `

弹幕过滤设置

`; document.body.appendChild(panel); // ================= UI 交互事件 ================= const minLevelInput = document.getElementById('bf-min-level'); const blockNoInput = document.getElementById('bf-block-no'); const onlyRoomInput = document.getElementById('bf-only-room'); const closeBtn = document.getElementById('bf-close'); // 设置项修改事件 (修改后自动保存并生效) minLevelInput.addEventListener('input', (e) => { config.minLevel = parseInt(e.target.value) || 1; saveConfig(); }); blockNoInput.addEventListener('change', (e) => { config.blockNoMedal = e.target.checked; saveConfig(); }); onlyRoomInput.addEventListener('change', (e) => { config.onlyCurrentRoom = e.target.checked; saveConfig(); }); // 关闭按钮 closeBtn.addEventListener('click', () => { panel.style.display = 'none'; }); // ================= 悬浮按钮拖拽逻辑 ================= let isDragging = false; let isMoved = false; // 用于区分是“点击”还是“拖拽” let startX, startY, initialFabX, initialFabY; fab.addEventListener('mousedown', (e) => { isDragging = true; isMoved = false; startX = e.clientX; startY = e.clientY; const rect = fab.getBoundingClientRect(); initialFabX = rect.left; initialFabY = rect.top; fab.style.transition = 'none'; // 拖拽时取消动画,防止卡顿 }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; // 移动距离超过 3 像素才认为是拖拽,防止手抖误判 if (Math.abs(dx) > 3 || Math.abs(dy) > 3) { isMoved = true; } // 计算新位置并限制在屏幕范围内 let newX = initialFabX + dx; let newY = initialFabY + dy; const maxX = window.innerWidth - fab.offsetWidth; const maxY = window.innerHeight - fab.offsetHeight; newX = Math.max(0, Math.min(newX, maxX)); newY = Math.max(0, Math.min(newY, maxY)); // 修改定位方式为 left/top 以配合拖拽 fab.style.right = 'auto'; fab.style.bottom = 'auto'; fab.style.left = newX + 'px'; fab.style.top = newY + 'px'; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; fab.style.transition = 'transform 0.2s'; // 恢复点击动画 } }); // ================= 面板展开/收起 ================= fab.addEventListener('click', () => { // 如果刚刚进行了拖拽,则不要触发点击展开事件 if (isMoved) return; // 切换面板显示状态 if (panel.style.display === 'none' || panel.style.display === '') { // 将面板展示在按钮附近 const fabRect = fab.getBoundingClientRect(); // 判断面板应该放在按钮左边还是右边 (防止溢出屏幕) if (fabRect.left > 260) { panel.style.left = (fabRect.left - 250) + 'px'; } else { panel.style.left = (fabRect.right + 10) + 'px'; } // 上下位置微调 panel.style.top = Math.min(fabRect.top, window.innerHeight - 150) + 'px'; panel.style.right = 'auto'; panel.style.bottom = 'auto'; panel.style.display = 'block'; } else { panel.style.display = 'none'; } }); } // ================= 启动 ================= window.addEventListener('load', () => { initUI(); startObserver(); }); })();