// ==UserScript== // @name 抖音直播间自动弹幕 (纯净事件兼容版) // @namespace douyin-auto-danmaku // @version 2.1.0 // @description 攻克 Failed to construct 'MouseEvent' 报错,采用无视窗口状态的老牌底层事件派发技术 // @author 小德捡银子 // @match https://live.douyin.com/* // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // @license MIT // @tag 抖音 // @tag 弹幕助手 // @tag 抖音直播 // ==/UserScript== (function () { 'use strict'; // ========== 配置 ========== const MAX_LOG_COUNT = 200; // ========== 状态 ========== let timer = null; let currentIndex = 0; let stats = { total: 0, success: 0, fail: 0 }; let danmakuList = GM_getValue('danmakuList', ['大家好', '主播加油', '666']); let interval = GM_getValue('interval', 10); // ========== UI 构建 ========== const panel = document.createElement('div'); panel.id = 'dy-danmaku-panel'; panel.innerHTML = `
弹幕助手 (纯净兼容版) +
`; document.body.appendChild(panel); const $body = document.getElementById('dp-body'), $toggle = document.getElementById('dp-toggle'), $list = document.getElementById('dp-list'), $newText = document.getElementById('dp-new-text'), $addBtn = document.getElementById('dp-add-btn'), $intervalInput = document.getElementById('dp-interval'), $startBtn = document.getElementById('dp-start-btn'), $sTotal = document.getElementById('dp-s-total'), $sOk = document.getElementById('dp-s-ok'), $sFail = document.getElementById('dp-s-fail'), $log = document.getElementById('dp-log'); function timeStr() { const d = new Date(); return [d.getHours(), d.getMinutes(), d.getSeconds()].map(v => String(v).padStart(2, '0')).join(':'); } function addLog(msg, type = 'ok') { const line = document.createElement('div'); line.innerHTML = `[${timeStr()}] ${msg}`; $log.appendChild(line); while ($log.children.length > MAX_LOG_COUNT) { $log.removeChild($log.firstChild); } $log.scrollTop = $log.scrollHeight; } function updateStats() { $sTotal.textContent = stats.total; $sOk.textContent = stats.success; $sFail.textContent = stats.fail; } function saveDanmakuList() { GM_setValue('danmakuList', danmakuList); } function saveInterval() { GM_setValue('interval', interval); } function renderList() { $list.innerHTML = ''; if (danmakuList.length === 0) { $list.innerHTML = '
暂无弹幕
'; return; } danmakuList.forEach((text, i) => { const item = document.createElement('div'); item.className = 'dp-item'; item.innerHTML = `${i + 1}. ${text}×`; $list.appendChild(item); }); } // ========== 发送核心逻辑 ========== function sendDanmaku(text) { try { const input = document.querySelector('#chatInput [contenteditable="true"]') || document.querySelector('[data-slate-editor="true"]'); if (!input) { addLog('错误: 未找到输入框', 'err'); stats.total++; stats.fail++; updateStats(); return; } // 1. 点亮高亮输入 const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(input); selection.removeAllRanges(); selection.addRange(range); document.execCommand('delete', false, null); document.execCommand('insertText', false, text); input.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: text })); // 2. 延迟 150ms 后触发不依赖 window/view 状态的老牌事件派发 setTimeout(() => { try { const targetSVG = document.querySelector('svg.webcast-chatroom___send-btn'); if (targetSVG) { // 使用老牌的、不受非活动窗口限制的 initEvent 机制创建纯净点击 const evt = document.createEvent('MouseEvents'); // 参数含义:事件类型(click), 是否冒泡(true), 是否可取消(true), 后面补齐不带 window 属性的默认数据 evt.initEvent('click', true, true); // 像红包抢夺脚本那样,全方位轰炸: // A. 点 SVG 本身 targetSVG.dispatchEvent(evt); // B. 轰炸内部的所有 path 图层(确保穿透到位) const paths = targetSVG.querySelectorAll('path'); paths.forEach(p => p.dispatchEvent(evt)); // C. 尝试寻找它的父级节点(通常是容纳 SVG 的包裹 div 或容器) if (targetSVG.parentElement) { targetSVG.parentElement.dispatchEvent(evt); } addLog(`成功: [纯净兼容发送] ${text}`, 'ok'); stats.success++; } else { addLog('错误: 后台未锁定亮起的飞机SVG图标', 'err'); stats.fail++; } } catch (innerE) { addLog(`点击异常: ${innerE.message}`, 'err'); stats.fail++; } stats.total++; updateStats(); }, 150); } catch (e) { addLog(`执行异常: ${e.message}`, 'err'); stats.total++; stats.fail++; updateStats(); } } // ========== 定时器 ========== function startSending() { if (danmakuList.length === 0) { addLog('列表为空,无法开始', 'err'); return; } currentIndex = 0; addLog(`挂机技术已就绪,请测试并查看最新表现...`, 'info'); sendDanmaku(danmakuList[currentIndex]); currentIndex = (currentIndex + 1) % danmakuList.length; timer = setInterval(() => { if (danmakuList.length === 0) { stopSending(); return; } sendDanmaku(danmakuList[currentIndex]); currentIndex = (currentIndex + 1) % danmakuList.length; }, interval * 1000); $startBtn.textContent = '停止发送'; $startBtn.className = 'dp-btn dp-btn-stop'; } function stopSending() { if (timer) { clearInterval(timer); timer = null; } addLog('系统提示: 已停止自动发送', 'info'); $startBtn.textContent = '开始发送'; $startBtn.className = 'dp-btn dp-btn-start'; } // ========== UI 事件绑定 ========== $toggle.addEventListener('click', () => { $body.classList.toggle('collapsed'); $toggle.textContent = $body.classList.contains('collapsed') ? '+' : '−'; }); function addDanmaku() { const text = $newText.value.trim(); if (!text) return; danmakuList.push(text); saveDanmakuList(); $newText.value = ''; renderList(); } $addBtn.addEventListener('click', addDanmaku); $newText.addEventListener('keydown', (e) => { if (e.key === 'Enter') addDanmaku(); }); $list.addEventListener('click', (e) => { if (e.target.classList.contains('dp-del')) { const idx = parseInt(e.target.dataset.index, 10); danmakuList.splice(idx, 1); saveDanmakuList(); renderList(); if (currentIndex >= danmakuList.length) currentIndex = 0; } }); $intervalInput.addEventListener('change', () => { let val = parseInt($intervalInput.value, 10); if (isNaN(val) || val < 3) val = 3; $intervalInput.value = val; interval = val; saveInterval(); if (timer) { stopSending(); startSending(); } }); $startBtn.addEventListener('click', () => { if (timer) { stopSending(); } else { startSending(); } }); // 拖拽面板 const header = panel.querySelector('.dp-header'); let isDragging = false, dragX, dragY; header.addEventListener('mousedown', (e) => { if (e.target.classList.contains('dp-toggle')) return; isDragging = true; dragX = e.clientX - panel.offsetLeft; dragY = e.clientY - panel.offsetTop; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; panel.style.left = (e.clientX - dragX) + 'px'; panel.style.top = (e.clientY - dragY) + 'px'; panel.style.right = 'auto'; }); document.addEventListener('mouseup', () => { isDragging = false; }); renderList(); })();