// ==UserScript== // @name B站直播间弹幕自动发送 // @namespace http://tampermonkey.net/ // @author Xubai0224 // @version 1.3.0 // @description B站直播间自动发送弹幕,支持随机间隔、数量限制和去重随机选择 // @grant GM_addStyle // @grant GM_addElement // @include https://live.bilibili.com/* // @match *://live.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @icon64 https://www.bilibili.com/favicon.ico // @tag BiliBili // @license MIT // ==/UserScript== (function() { 'use strict'; // 配置项 const MIN_INTERVAL_SEC = 3; // 最小发送间隔(秒) const MAX_INTERVAL_SEC = 7; // 最大发送间隔(秒) const MAX_DAILY_MESSAGES = 3000; // 单直播间每日最大弹幕数 const CONTROL_PANEL_STYLE = { position: 'fixed', top: '80px', left: '24px', width: '200px', height: '150px', zIndex: '9999999', borderRadius: '10px', boxShadow: '0 0 10px rgba(173, 216, 230, 0.8)', margin: '0', padding: '0', backgroundColor: '#ffffff' }; // 状态变量 let controlPanel = null; let titleBar = null; let countDisplay = null; let messageInput = null; let controlButton = null; let isDragging = false; let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; let isSending = false; let chatInput = null; let sendButton = null; let messageTimeout = null; let currentRoomId = null; let messageStats = {}; let lastMessageIndex = -1; // 记录上一条发送的弹幕索引 /** * 初始化弹幕统计数据 */ function initMessageStats() { const urlMatch = window.location.href.match(/live\.bilibili\.com\/(\d+)/); if (urlMatch && urlMatch[1]) { currentRoomId = urlMatch[1]; } else { console.log('无法获取直播间ID'); return; } const savedStats = localStorage.getItem('bilibiliAutoMessageStats'); if (savedStats) { try { messageStats = JSON.parse(savedStats); } catch (e) { console.error('解析弹幕统计数据失败', e); messageStats = {}; } } const today = new Date().toISOString().split('T')[0]; if (!messageStats[currentRoomId] || messageStats[currentRoomId].date !== today) { messageStats[currentRoomId] = { date: today, count: 0 }; } saveMessageStats(); } /** * 保存弹幕统计数据到localStorage */ function saveMessageStats() { try { localStorage.setItem('bilibiliAutoMessageStats', JSON.stringify(messageStats)); } catch (e) { console.error('保存弹幕统计数据失败', e); } } /** * 增加弹幕计数并检查是否超过限制 * @returns {boolean} 是否超过每日限制 */ function incrementMessageCount() { if (!currentRoomId) return false; messageStats[currentRoomId].count++; saveMessageStats(); countDisplay.innerText = `已发送: ${messageStats[currentRoomId].count} 次`; return messageStats[currentRoomId].count >= MAX_DAILY_MESSAGES; } /** * 检查是否已达到每日弹幕限制 * @returns {boolean} 是否超过限制 */ function isOverDailyLimit() { if (!currentRoomId) return false; return messageStats[currentRoomId].count >= MAX_DAILY_MESSAGES; } /** * 生成随机间隔时间(毫秒) * @returns {number} 随机间隔时间 */ function getRandomInterval() { return Math.random() * (MAX_INTERVAL_SEC - MIN_INTERVAL_SEC) * 1000 + MIN_INTERVAL_SEC * 1000; } /** * 随机选择一条弹幕,与上一条不重复 * @param {Array} messageList - 弹幕列表 * @returns {number} 选中的索引 */ function getRandomIndex(messageList) { // 如果只有一条弹幕,直接返回0 if (messageList.length <= 1) { return 0; } let randomIndex; // 确保随机索引与上一条不同 do { randomIndex = Math.floor(Math.random() * messageList.length); } while (randomIndex === lastMessageIndex); // 更新上一条索引 lastMessageIndex = randomIndex; return randomIndex; } /** * 初始化控制面板 */ function initControlPanel() { controlPanel = document.createElement('div'); Object.assign(controlPanel.style, CONTROL_PANEL_STYLE); document.body.appendChild(controlPanel); createTitleBar(); createCountDisplay(); createMessageInput(); createControlButton(); } /** * 创建标题栏(可拖动) */ function createTitleBar() { titleBar = document.createElement('p'); titleBar.innerText = '弹幕发送'; Object.assign(titleBar.style, { backgroundColor: '#dfdfdf', color: '#000000', fontWeight: '600', alignContent: 'center', width: 'calc(100% - 8px)', height: '30px', margin: '0', padding: '0', paddingLeft: '8px', borderRadius: '6px 6px 0 0', boxShadow: '0 0 10px rgba(173, 216, 230, 0.8)', cursor: 'grab' }); titleBar.addEventListener('mousedown', startDrag); controlPanel.appendChild(titleBar); } /** * 创建计数显示区域 */ function createCountDisplay() { countDisplay = document.createElement('p'); const currentCount = currentRoomId ? messageStats[currentRoomId].count : 0; countDisplay.innerText = `已发送: ${currentCount} 次`; Object.assign(countDisplay.style, { color: 'black', alignContent: 'center', width: 'calc(100% - 8px)', height: '30px', margin: '0', padding: '0', paddingLeft: '8px', fontSize: '12px' }); controlPanel.appendChild(countDisplay); } /** * 创建消息输入框 */ function createMessageInput() { messageInput = document.createElement('input'); messageInput.placeholder = '请输入要发送内容,多条用 ; 隔开'; messageInput.value = '666;主播加油鸭;加油加油;这个操作太绝了;支持主播;太精彩了;笑死我了;哈哈哈哈;主播666;爱了爱了;打卡打卡~;原来这么精彩;关注了关注了;这个必须赞;这个主播关注了;学到了学到了l;前方高能(不是);太秀了吧~;主播辛苦了!!!;继续继续!'; Object.assign(messageInput.style, { width: 'calc(100% - 24px)', height: '24px', padding: '0', paddingLeft: '6px', margin: '8px', backgroundColor: 'white', color: 'black', border: '1px #dcdfe6 solid', outline: 'none' }); messageInput.addEventListener('focus', () => { messageInput.style.border = '2px #409EFF solid'; messageInput.style.width = 'calc(100% - 26px)'; }); messageInput.addEventListener('blur', () => { messageInput.style.border = '1px #dcdfe6 solid'; messageInput.style.width = 'calc(100% - 24px)'; }); controlPanel.appendChild(messageInput); } /** * 创建控制按钮 */ function createControlButton() { controlButton = document.createElement('button'); controlButton.innerText = '发送'; Object.assign(controlButton.style, { margin: '4px 0 0 8px', backgroundColor: '#409EFF', borderRadius: '5px', outline: 'none', border: 'none', alignContent: 'center', color: 'white', fontWeight: '600', padding: '4px 8px', cursor: 'pointer' }); controlButton.addEventListener('click', toggleSend); controlPanel.appendChild(controlButton); } /** * 开始拖动面板 * @param {MouseEvent} e - 鼠标事件 */ function startDrag(e) { titleBar.style.cursor = 'grabbing'; isDragging = true; startX = e.clientX; startY = e.clientY; startLeft = parseInt(window.getComputedStyle(controlPanel).left, 10); startTop = parseInt(window.getComputedStyle(controlPanel).top, 10); document.addEventListener('mousemove', handleDrag); document.addEventListener('mouseup', stopDrag); } /** * 处理拖动逻辑 * @param {MouseEvent} e - 鼠标事件 */ function handleDrag(e) { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; controlPanel.style.left = `${startLeft + dx}px`; controlPanel.style.top = `${startTop + dy}px`; } /** * 停止拖动 */ function stopDrag() { titleBar.style.cursor = 'grab'; isDragging = false; document.removeEventListener('mousemove', handleDrag); document.removeEventListener('mouseup', stopDrag); } /** * 切换发送状态(开始/停止) */ function toggleSend() { if (!isSending && isOverDailyLimit()) { alert(`本直播间今日弹幕已达上限(${MAX_DAILY_MESSAGES}条)`); return; } if (!isSending) { const messageText = messageInput.value.trim(); if (messageText) { // 重置上一条索引 lastMessageIndex = -1; startSending(messageText); } else { shakeInput(); } } else { stopSending(); } } /** * 开始发送弹幕(使用随机间隔和去重随机选择) * @param {string} messageText - 输入的消息文本 */ function startSending(messageText) { console.log('开始发送消息'); isSending = true; controlButton.innerText = '停止'; const messageList = messageText.split(';'); // 发送函数(递归调用实现随机间隔) function sendNextMessage() { if (isOverDailyLimit()) { alert(`本直播间今日弹幕已达上限(${MAX_DAILY_MESSAGES}条)`); stopSending(); return; } // 随机选择一条与上一条不同的弹幕 const currentIndex = getRandomIndex(messageList); // 聚焦并填充输入框 chatInput.focus(); chatInput.value = messageList[currentIndex]; chatInput.dispatchEvent(new Event('input', { bubbles: true })); console.log(`文本已输入:${messageList[currentIndex]}`); // 模拟发送按钮点击 simulateClick(sendButton); console.log('已触发发送按钮点击事件!'); // 增加计数并检查是否超过限制 const isOverLimit = incrementMessageCount(); if (isOverLimit) { alert(`本直播间今日弹幕已达上限(${MAX_DAILY_MESSAGES}条)`); stopSending(); return; } // 计算下一次随机间隔并预约发送 const nextInterval = getRandomInterval(); messageTimeout = setTimeout(sendNextMessage, nextInterval); console.log(`下次发送间隔:${(nextInterval / 1000).toFixed(1)}秒`); } // 立即发送第一条 sendNextMessage(); } /** * 停止发送弹幕 */ function stopSending() { console.log('取消发送消息'); if (messageTimeout) { clearTimeout(messageTimeout); } isSending = false; controlButton.innerText = '发送'; } /** * 模拟点击事件 * @param {HTMLElement} element - 目标元素 */ function simulateClick(element) { const events = ['mousedown', 'mouseup', 'click']; events.forEach(eventType => { const event = new MouseEvent(eventType, { bubbles: true, cancelable: true, view: document.defaultView }); element.dispatchEvent(event); }); } /** * 输入框为空时抖动提示 */ function shakeInput() { messageInput.style.border = '2px solid red'; let position = 0; let shakeCount = 0; const shakeInterval = setInterval(() => { position = position === 0 ? 5 : 0; messageInput.style.transform = `translateX(${position}px)`; shakeCount++; if (shakeCount > 10) { clearInterval(shakeInterval); messageInput.style.transform = 'translateX(0)'; messageInput.style.border = '1px #dcdfe6 solid'; } }, 50); } /** * 初始化:检查并获取弹幕输入框和发送按钮 */ function init() { initMessageStats(); const checkInterval = setInterval(() => { chatInput = document.querySelector('textarea.chat-input.border-box'); sendButton = document.querySelector( '.bl-button.live-skin-highlight-button-bg.live-skin-button-text.bl-button--primary.bl-button--small' ); if (chatInput && sendButton) { console.log('✔️ 找到输入框和发送按钮!'); clearInterval(checkInterval); initControlPanel(); } else { console.log('❌ 未找到输入框和按钮...'); } }, 10000); } // 启动脚本 console.log('B站直播间弹幕发送: 脚本启动'); init(); })();