// ==UserScript== // @name Bilibili-Live-Danmaku-Assistant (B站直播间弹幕增强脚本) // @namespace https://github.com/SakikoTogawa0214/Bilibili-Live-Danmaku-Assistant/tree/main // @version 1.1 // @description 一个轻量、高效、完美融合原生的 B站 直播间弹幕增强 Tampermonkey (油猴) 脚本。 // @author SakikoTogawa0214 // @match https://live.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @grant GM_addStyle // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/575233/Bilibili-Live-Danmaku-Assistant%20%28B%E7%AB%99%E7%9B%B4%E6%92%AD%E9%97%B4%E5%BC%B9%E5%B9%95%E5%A2%9E%E5%BC%BA%E8%84%9A%E6%9C%AC%29.user.js // @updateURL https://update.greasyfork.icu/scripts/575233/Bilibili-Live-Danmaku-Assistant%20%28B%E7%AB%99%E7%9B%B4%E6%92%AD%E9%97%B4%E5%BC%B9%E5%B9%95%E5%A2%9E%E5%BC%BA%E8%84%9A%E6%9C%AC%29.meta.js // ==/UserScript== (function() { 'use strict'; // ================= 1. 样式注入 ================= GM_addStyle(` /* 聊天栏 +1 和 复制 按钮组样式 */ .chat-item.danmaku-item { position: relative; } .bili-action-group { display: none; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); flex-direction: column; gap: 4px; z-index: 100; } .chat-item.danmaku-item:hover .bili-action-group { display: flex; } .bili-action-btn { background-color: #00D1F1; color: #fff; border: none; border-radius: 4px; padding: 2px 6px; font-size: 12px; cursor: pointer; line-height: 1.2; box-shadow: 0 2px 4px rgba(0,0,0,0.2); transition: 0.2s; user-select: none; } .bili-action-btn:hover { background-color: #00b5d1; } .btn-copy { background-color: #ff6699; } .btn-copy:hover { background-color: #ff4785; } /* 独轮车入口按钮样式 */ .unicycle-entry { display: inline-block; vertical-align: middle; cursor: pointer; margin-right: 12px; font-size: 14px; color: #9499A0; user-select: none; transition: color 0.2s; position: relative; } .unicycle-entry:hover { color: #00D1F1; } /* 独轮车面板样式 */ .unicycle-panel { display: none; position: fixed; width: 280px; background: #fff; border: 1px solid #e3e5e7; border-radius: 8px; padding: 12px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); z-index: 999999; cursor: default; } .unicycle-panel.show { display: block; } /* 设置区样式 */ .unicycle-settings { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px dashed #e3e5e7; } .uni-row { display: flex; align-items: center; justify-content: space-between; font-size: 12px; color: #18191C; } .uni-interval input { width: 40px; padding: 2px; border: 1px solid #e3e5e7; border-radius: 4px; text-align: center; outline: none; } .uni-interval input:focus { border-color: #00D1F1; } .uni-mode-group label { cursor: pointer; margin-left: 8px; } .uni-toggle-label { font-weight: bold; color: #ff6699; cursor: pointer; } .uni-antispam-label { font-weight: bold; color: #00D1F1; cursor: pointer; } /* 顶部输入区 */ .unicycle-input-box { display: flex; gap: 6px; margin-bottom: 10px; } .unicycle-input { flex: 1; border: 1px solid #e3e5e7; border-radius: 4px; padding: 4px 8px; font-size: 12px; outline: none; color: #18191C; } .unicycle-input:focus { border-color: #00D1F1; } .unicycle-add-btn { background: #00D1F1; color: #fff; border: none; border-radius: 4px; padding: 0 10px; font-size: 12px; cursor: pointer; transition: 0.2s; } .unicycle-add-btn:hover { background: #00b5d1; } /* 梗列表区 */ .unicycle-list { max-height: 150px; overflow-y: auto; padding-right: 4px; } .unicycle-list::-webkit-scrollbar { width: 4px; } .unicycle-list::-webkit-scrollbar-thumb { background: #c0c4cc; border-radius: 2px; } .unicycle-item { display: flex; align-items: center; justify-content: space-between; padding: 6px 4px; border-bottom: 1px solid #f1f2f3; transition: background-color 0.2s; } .unicycle-item.running-active { background-color: #ffe6ed; border-left: 3px solid #ff6699; padding-left: 5px; } .unicycle-text { flex: 1; font-size: 12px; color: #18191C; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 8px; } .unicycle-action { display: flex; gap: 4px; } .unicycle-action button { cursor: pointer; border: none; background: #f1f2f3; color: #61666D; border-radius: 4px; padding: 3px 8px; font-size: 12px; transition: 0.2s; } .unicycle-action button:hover { background: #e3e5e7; } .unicycle-action .send-btn.running { background: #ff6699; color: #fff; } .unicycle-action .send-btn.running:hover { background: #ff4785; } .unicycle-action .del-btn:hover { color: #F56C6C; } `); // ================= 2. 状态管理 ================= let config = JSON.parse(localStorage.getItem('bili_unicycle_config')) || { isAutoOn: false, isAntiSpamOn: false, mode: 'single', interval: 6 }; let runTimer = null; let runState = null; function saveConfig() { localStorage.setItem('bili_unicycle_config', JSON.stringify(config)); } function getMemes() { try { return JSON.parse(localStorage.getItem('bili_unicycle_memes')) || []; } catch (e) { return []; } } function saveMemes(memes) { localStorage.setItem('bili_unicycle_memes', JSON.stringify(memes)); } // ================= 3. 核心发送功能 ================= function sendDanmaku(text) { const textarea = document.querySelector('textarea.chat-input') || document.querySelector('.chat-input'); const sendBtn = document.querySelector('.bottom-actions button'); if (textarea && sendBtn) { textarea.value = text; textarea.dispatchEvent(new Event('input', { bubbles: true })); setTimeout(() => { sendBtn.click(); }, 50); } } // ================= 4. 独轮车循环控制 ================= function updateVisualIndicator() { const items = document.querySelectorAll('.unicycle-item'); items.forEach((item, idx) => { const btn = item.querySelector('.send-btn'); if (!runState) { item.classList.remove('running-active'); btn.classList.remove('running'); btn.innerText = config.isAutoOn ? '启动' : '发送'; } else { if (idx === runState.currentIndex) { item.classList.add('running-active'); btn.classList.add('running'); } else { item.classList.remove('running-active'); btn.classList.remove('running'); } btn.innerText = '停止'; } }); } function stopUnicycle() { if (runTimer) clearInterval(runTimer); runTimer = null; runState = null; updateVisualIndicator(); } function startUnicycle(index) { const memes = getMemes(); if (!memes.length || index >= memes.length) return; stopUnicycle(); runState = { type: config.mode, currentIndex: index }; sendDanmaku(memes[runState.currentIndex]); updateVisualIndicator(); const intervalMs = Math.max(1, config.interval) * 1000; runTimer = setInterval(() => { const currentMemes = getMemes(); if (!currentMemes.length) { stopUnicycle(); return; } if (runState.type === 'list') { runState.currentIndex = (runState.currentIndex + 1) % currentMemes.length; } sendDanmaku(currentMemes[runState.currentIndex]); updateVisualIndicator(); }, intervalMs); } // ================= 5. 防刷屏模块 ================= const recentDanmakuCache = new Map(); const SPAM_WINDOW_MS = 10000; function initAntiSpam() { const chatItemsContainer = document.querySelector('#chat-history-list .chat-items'); if (!chatItemsContainer) { setTimeout(initAntiSpam, 1000); return; } const observer = new MutationObserver((mutations) => { if (!config.isAntiSpamOn) return; mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && node.classList.contains('danmaku-item')) { const text = node.getAttribute('data-danmaku'); if (text) { const now = Date.now(); const lastSeenTime = recentDanmakuCache.get(text); if (lastSeenTime && (now - lastSeenTime < SPAM_WINDOW_MS)) { node.style.display = 'none'; } else { recentDanmakuCache.set(text, now); } } } }); }); }); observer.observe(chatItemsContainer, { childList: true }); setInterval(() => { const now = Date.now(); for (let [text, time] of recentDanmakuCache.entries()) { if (now - time > SPAM_WINDOW_MS) recentDanmakuCache.delete(text); } }, SPAM_WINDOW_MS); } // ================= 6. 独轮车面板 ================= function renderUnicycleList(listContainer) { const memes = getMemes(); listContainer.innerHTML = ''; if(memes.length === 0) { listContainer.innerHTML = '