// ==UserScript== // @name bili直播助手 // @namespace https://github.com/wakefun // @author 醒悦哥哥 // @description 浏览器去除直播页面遮挡块。绕过b站强制直播姬开播限制,修复OBS无法获取推流码的问题。自动点赞功能。 // @version 3.0.3 // @match https://link.bilibili.com/p/center/index* // @match https://live.bilibili.com/* // @icon https://i1.hdslb.com/bfs/face/ec3c19295a9ce487bdf670174fd7eaa1da690923.jpg@256w_256h.jpg // @run-at document-idle // @grant none // ==/UserScript== (function () { 'use strict'; const MASK_ID = 'web-player-module-area-mask-panel'; const START_LIVE_URL = 'room/v1/Room/startLive'; // --- 去除直播播放器遮挡 --- function observeMask() { const player = document.getElementById('live-player'); if (!player) return; // 先清除已存在的遮挡 document.getElementById(MASK_ID)?.remove(); new MutationObserver((mutations) => { for (const { addedNodes } of mutations) { for (const node of addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; const mask = node.id === MASK_ID ? node : node.querySelector?.(`#${MASK_ID}`); if (mask) { mask.remove(); console.log('[bili-live] 已去除遮挡'); } } } }).observe(player, { childList: true, subtree: true }); } // DOM 可能尚未完全就绪,延迟重试 if (document.getElementById('live-player')) { observeMask(); } else { const retry = setInterval(() => { if (document.getElementById('live-player')) { clearInterval(retry); observeMask(); } }, 500); // 30s 后放弃 setTimeout(() => clearInterval(retry), 30000); } // --- 自动点赞 --- const AUTO_LIKE_MAX = 1000; const AUTO_LIKE_INTERVAL = 666; const LIKE_WRAPPER_ID = '__auto-like-wrapper__'; const LIKE_BTN_HOST_SELECTOR = '#chat-control-panel-vm div.superChat div.icon-left-part'; const LIKE_WATCH_INTERVAL = 3000; let _likeTimer = null; let _likeCount = 0; let _likeObserver = null; let _likeObserveRoot = null; let _likeWatchTimer = null; function simulateLikeKey() { const opts = { key: 'k', code: 'KeyK', bubbles: true }; document.dispatchEvent(new KeyboardEvent('keydown', opts)); document.dispatchEvent(new KeyboardEvent('keyup', opts)); } function stopLike() { clearInterval(_likeTimer); _likeTimer = null; const wrapper = document.getElementById(LIKE_WRAPPER_ID); if (wrapper) { const btn = wrapper.querySelector('button'); const tip = wrapper.querySelector('span'); if (btn) { btn.textContent = '自动点赞'; btn.style.background = '#fff'; btn.style.color = '#999'; const ants = btn.querySelector('div'); if (ants) { ants.style.opacity = '0'; ants.style.animation = ''; } } if (tip) tip.textContent = ''; } console.log(`[bili-live] 自动点赞停止,共点赞 ${_likeCount} 次`); _likeCount = 0; } function startLike() { _likeCount = 0; const wrapper = document.getElementById(LIKE_WRAPPER_ID); if (wrapper) { const btn = wrapper.querySelector('button'); const tip = wrapper.querySelector('span'); if (btn) { btn.textContent = '停止点赞'; btn.style.background = '#00a1d6'; btn.style.color = '#fff'; const ants = btn.querySelector('div'); if (ants) { ants.style.opacity = '0'; ants.style.animation = ''; } } if (tip) tip.textContent = '发弹幕时点赞会暂停,点击其他区域可恢复'; } _likeTimer = setInterval(() => { if (_likeCount >= AUTO_LIKE_MAX) return stopLike(); simulateLikeKey(); _likeCount++; }, AUTO_LIKE_INTERVAL); } function injectLikeBtn() { if (document.getElementById(LIKE_WRAPPER_ID)) return; const host = document.querySelector(LIKE_BTN_HOST_SELECTOR); if (!host) return; if (!document.getElementById('__auto-like-style__')) { const style = document.createElement('style'); style.id = '__auto-like-style__'; style.textContent = `@keyframes __like-btn-march { to { background-position: 8px 0, 100% 8px, -8px 100%, 0 -8px; } }`; document.head.appendChild(style); } const wrapper = document.createElement('div'); wrapper.id = LIKE_WRAPPER_ID; Object.assign(wrapper.style, { display: 'flex', alignItems: 'center', }); const btn = document.createElement('button'); btn.textContent = _likeTimer ? '停止点赞' : '自动点赞'; Object.assign(btn.style, { padding: '8px 10px', cursor: 'pointer', marginLeft: '2px', border: 'none', borderRadius: '4px', background: _likeTimer ? '#00a1d6' : '#fff', color: _likeTimer ? '#fff' : '#999', fontSize: '12px', transition: 'background 0.25s ease, color 0.25s ease', position: 'relative', }); btn.addEventListener('mouseenter', () => { if (_likeTimer) return; btn.style.background = '#00a1d6'; btn.style.color = '#fff'; }); btn.addEventListener('mouseleave', () => { if (_likeTimer) return; btn.style.background = '#fff'; btn.style.color = '#999'; }); // 跑马灯虚线边框(CSS 渐变顺时针流动) const ants = document.createElement('div'); Object.assign(ants.style, { position: 'absolute', top: '0', left: '0', width: '100%', height: '100%', pointerEvents: 'none', borderRadius: '4px', opacity: '0', transition: 'opacity 0.25s ease', backgroundImage: [ 'repeating-linear-gradient(90deg, #fff 0 4px, transparent 4px 8px)', 'repeating-linear-gradient(180deg, #fff 0 4px, transparent 4px 8px)', 'repeating-linear-gradient(90deg, #fff 0 4px, transparent 4px 8px)', 'repeating-linear-gradient(180deg, #fff 0 4px, transparent 4px 8px)', ].join(','), backgroundSize: '100% 1px, 1px 100%, 100% 1px, 1px 100%', backgroundPosition: '0 0, 100% 0, 0 100%, 0 0', backgroundRepeat: 'no-repeat', }); btn.append(ants); btn.addEventListener('mouseenter', () => { if (_likeTimer) return; ants.style.opacity = '1'; ants.style.animation = '__like-btn-march 1s linear infinite'; }); btn.addEventListener('mouseleave', () => { if (_likeTimer) return; ants.style.opacity = '0'; ants.style.animation = ''; }); const tip = document.createElement('span'); Object.assign(tip.style, { marginLeft: '6px', fontSize: '11px', color: '#999', }); if (_likeTimer) tip.textContent = '发弹幕时点赞会暂停,点击其他区域可恢复'; btn.addEventListener('click', () => _likeTimer ? stopLike() : startLike()); wrapper.append(btn, tip); host.appendChild(wrapper); } function disconnectLikeObserver() { if (_likeObserver) { _likeObserver.disconnect(); _likeObserver = null; } _likeObserveRoot = null; } function bindLikeObserver() { const host = document.querySelector(LIKE_BTN_HOST_SELECTOR); const root = host?.parentElement; if (!root) return; if (_likeObserver && _likeObserveRoot === root) return; disconnectLikeObserver(); _likeObserveRoot = root; _likeObserver = new MutationObserver(() => injectLikeBtn()); _likeObserver.observe(root, { childList: true, subtree: true }); } function ensureLikeFeatureMounted() { injectLikeBtn(); bindLikeObserver(); } ensureLikeFeatureMounted(); _likeWatchTimer = setInterval(ensureLikeFeatureMounted, LIKE_WATCH_INTERVAL); window.addEventListener('beforeunload', () => { clearInterval(_likeWatchTimer); disconnectLikeObserver(); }); // --- 绕过强制直播姬开播限制(伪装 platform) --- function patchStartLiveBody(body) { const params = new URLSearchParams(body); params.set('platform', 'android_link'); return params.toString(); } // 拦截 XHR const origXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url, ...rest) { if (method === 'POST' && url.includes(START_LIVE_URL)) { const origSend = this.send; this.send = function (body) { origSend.call(this, patchStartLiveBody(body)); }; } origXHROpen.call(this, method, url, ...rest); }; // 拦截 fetch const origFetch = window.fetch; window.fetch = function (input, init) { const url = typeof input === 'string' ? input : input?.url; if (url?.includes(START_LIVE_URL) && init?.method?.toUpperCase() === 'POST' && init.body) { init = { ...init, body: patchStartLiveBody(init.body) }; } return origFetch.call(this, input, init); }; })();