// ==UserScript== // @name 抖音直播净化(+自动抢福袋) // @namespace https://local/douyin-live-lite // @version 1.2.0 // @author 小德捡银子 // @description 直播净化 + 自动抢福袋(可独立开关) // @license GPL-3.0-only // @match *://*.douyin.com/* // @match *://*.iesdouyin.com/* // @exclude *://creator.douyin.com/* // @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant unsafeWindow // @run-at document-start // @tag 抖音 // @tag 自动抢免费福袋 // @tag 抖音直播 // ==/UserScript== (function () { "use strict"; /* ========================================================= * 基础工具与配置 * ========================================================= */ const NS = "dylive-lite"; const log = (...args) => console.log(`[${NS}]`, ...args); const _win = (() => (typeof unsafeWindow !== "undefined" ? unsafeWindow : window))(); const DEFAULTS = { // 总开关 enable_all: true, // 悬浮窗位置记忆默认值 btn_pos: { right: 16, bottom: 120 }, // 布局屏蔽-聊天室 block_message_broadcast: true, // 屏蔽信息播报 block_bottom_area: true, // 屏蔽底部遮挡区域 // 功能 quality_select: "", // 清晰度选择 enable_lucky_bag: true, // 抢福袋 // 布局屏蔽-视频区域内 block_gift_effects: true, // 屏蔽礼物特效 block_yellow_car: true, // 屏蔽小黄车 block_exhibition_tooltip: true, // 屏蔽提示 block_danmaku: true, // 屏蔽弹幕 block_gifts_container: true, // 屏蔽礼物栏容器 // 聊天室消息过滤器 filter_enable: true, // 过滤器总开关 filter_block_gift_msg: false, // 屏蔽送礼消息 filter_block_lucky_bag: false, // 屏蔽福袋口令 filter_block_emoji: false, // 屏蔽emoji消息 filter_block_room_message: true, // 屏蔽房间播报 filter_custom_rules: "", // 自定义正则 }; const GM = { get(key) { const v = GM_getValue(`${NS}:${key}`, undefined); return v === undefined ? DEFAULTS[key] : v; }, set(key, value) { GM_setValue(`${NS}:${key}`, value); }, }; function addStyle(id, css) { let $style = document.getElementById(id); if (!$style) { $style = document.createElement("style"); $style.id = id; (document.head || document.documentElement).appendChild($style); } $style.textContent = css || ""; return $style; } function removeStyle(id) { const $style = document.getElementById(id); if ($style) $style.remove(); } function debounce(fn, wait) { let timer = null; return function (...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), wait); }; } function onReady(cb) { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", cb, { once: true }); } else { cb(); } } /* ========================================================= * 直播页面判断 * ========================================================= */ function isLivePage() { try { const { hostname, pathname } = location; if (hostname === "live.douyin.com") return true; if ( (hostname === "www.douyin.com" || hostname === "douyin.com") && (pathname.startsWith("/follow/live/") || pathname.startsWith("/root/live/")) ) { return true; } } catch (e) {} return false; } function watchRouteChange(callback) { let lastHref = location.href; const check = () => { if (location.href !== lastHref) { lastHref = location.href; callback(); } }; ["pushState", "replaceState"].forEach((method) => { const origin = history[method]; history[method] = function (...args) { const ret = origin.apply(this, args); setTimeout(check, 0); return ret; }; }); window.addEventListener("popstate", () => setTimeout(check, 0)); setInterval(check, 1000); } /* ========================================================= * CSS 布局屏蔽 * ========================================================= */ const CssBlocks = { message_broadcast: ` #chatroom .webcast-chatroom___bottom-message, #chatroom > div > div > pace-island:has(div[style*="new_grade_enter"]), #chatroom > div > div > div:has(div[style*="new_grade_enter"]) { display: none !important; } `, bottom_area: ` .webcast-chatroom___list { clip-path: none !important; } `, gift_effects: ` #GiftTrayLayout, #GiftEffectLayout, #GiftMenuLayout, div[id^="gift_effect_bg_"] { display: none !important; } `, yellow_car: ` div[id^="living_room_player_container"] .basicPlayer > div:has(div[data-e2e="yellowCart-container"]), #EcmoCardLayout { display: none !important; } `, exhibition_tooltip: ` [data-e2e="exhibition-banner"] .dylive-tooltip { display: none !important; } `, danmaku: ` xg-danmu.xgplayer-danmu, #DanmakuLayout { display: none !important; } `, gifts_container: ` div[data-e2e="gifts-container"] { display: none !important; visibility: hidden !important; height: 0 !important; overflow: hidden !important; } `, }; function applyCssBlocks() { const enableAll = GM.get("enable_all"); const map = { "dylive-lite-style-message-broadcast": ["block_message_broadcast", CssBlocks.message_broadcast], "dylive-lite-style-bottom-area": ["block_bottom_area", CssBlocks.bottom_area], "dylive-lite-style-gift-effects": ["block_gift_effects", CssBlocks.gift_effects], "dylive-lite-style-yellow-car": ["block_yellow_car", CssBlocks.yellow_car], "dylive-lite-style-exhibition-tooltip": ["block_exhibition_tooltip", CssBlocks.exhibition_tooltip], "dylive-lite-style-danmaku": ["block_danmaku", CssBlocks.danmaku], "dylive-lite-style-gifts-container": ["block_gifts_container", CssBlocks.gifts_container], }; Object.entries(map).forEach(([styleId, [key, css]]) => { if (enableAll && GM.get(key)) addStyle(styleId, css); else removeStyle(styleId); }); } /* ========================================================= * 功能-清晰度选择 * ========================================================= */ const VideoQualityMap = { auto: { label: "自动", sign: 0 }, origin: { label: "原画", sign: 5 }, uhd: { label: "蓝光", sign: 4 }, hd: { label: "超清", sign: 3 }, sd: { label: "高清", sign: 2 }, ld: { label: "标清", sign: 1 }, }; function getReactProps(dom) { if (!dom) return null; const key = Object.keys(dom).find((k) => k.startsWith("__reactProps$")); return key ? dom[key] : null; } function getReactFiber(dom) { if (!dom) return null; const key = Object.keys(dom).find( (k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$") ); return key ? dom[key] : null; } function waitForElement(selector, timeoutMs = 8000, intervalMs = 200) { return new Promise((resolve) => { const start = Date.now(); const timer = setInterval(() => { let $el = null; try { $el = typeof selector === "function" ? selector() : document.querySelector(selector); } catch (e) {} if ($el || Date.now() - start > timeoutMs) { clearInterval(timer); resolve($el || null); } }, intervalMs); }); } function pickHighestAvailableQuality(list) { const sorted = list .slice() .filter((q) => VideoQualityMap[q]) .sort((a, b) => VideoQualityMap[a].sign - VideoQualityMap[b].sign); return sorted[sorted.length - 1]; } let qualityApplyToken = 0; async function applyQuality() { const quality = GM.get("quality_select"); if (!GM.get("enable_all") || !quality || !VideoQualityMap[quality]) return; if (!isLivePage()) return; const myToken = ++qualityApplyToken; try { localStorage.setItem("webcast_local_quality", quality); } catch (e) {} try { document.cookie = `webcast_local_quality=${quality}; domain=.douyin.com; path=/`; document.cookie = `live_local_quality=${quality}; domain=.douyin.com; path=/`; } catch (e) {} const $ctrl = await waitForElement( () => document.querySelector('xg-inner-controls xg-right-grid > div:has([data-e2e="quality-selector"])'), 8000 ); if (myToken !== qualityApplyToken || !isLivePage()) return; if ($ctrl) { const props = getReactProps($ctrl); const qualityHandler = props?.children?.props?.children?.props?.qualityHandler; if (qualityHandler && typeof qualityHandler.setCurrentQuality === "function") { let target = quality; const list = typeof qualityHandler.getCurrentQualityList === "function" ? qualityHandler.getCurrentQualityList() : []; if (Array.isArray(list) && list.length && !list.includes(quality)) { target = pickHighestAvailableQuality(list) || quality; log(`当前直播没有【${VideoQualityMap[quality].label}】画质,自动选择最高画质【${VideoQualityMap[target]?.label || target}】`); } qualityHandler.setCurrentQuality(target); log(`已设置画质为【${VideoQualityMap[target]?.label || target}】`); return; } } const $plugin = await waitForElement("#PlayerLayout .douyin-player-controls .QualitySwitchNewPlugin > div", 8000); if (myToken !== qualityApplyToken || !isLivePage()) return; if ($plugin) { const fiber = getReactFiber($plugin); const qualityHandler = fiber?.return?.memoizedProps?.qualityHandler; const qualityList = fiber?.return?.memoizedProps?.qualityList; if (qualityHandler && typeof qualityHandler.setCurrentQuality === "function") { let target = quality; if (Array.isArray(qualityList) && qualityList.length && !qualityList.includes(quality)) { target = pickHighestAvailableQuality(qualityList) || quality; log(`当前直播没有【${VideoQualityMap[quality].label}】画质,自动选择最高画质【${VideoQualityMap[target]?.label || target}】`); } qualityHandler.setCurrentQuality(target); log(`已设置画质为【${VideoQualityMap[target]?.label || target}】`); return; } } log("未找到画质控制器,本次设置画质失败(可能直播间尚未加载完成)"); } /* ========================================================= * 聊天室消息过滤器 * ========================================================= */ const MessageFilter = { rules: [], initRules() { this.rules = []; const text = GM.get("filter_custom_rules") || ""; text .split("\n") .map((line) => line.trim()) .filter((line) => line !== "") .forEach((line) => { try { this.rules.push(new RegExp(line)); } catch (e) { log("自定义规则解析失败,已跳过:", line, e); } }); }, check(messageInst, method) { if (!GM.get("enable_all") || !GM.get("filter_enable")) return false; const payload = messageInst?.payload || messageInst; const message = payload?.content || payload?.common?.describe; method = method ?? messageInst?.method ?? payload?.method; const chat_by = payload?.chat_by; const public_area_common = payload?.public_area_common || {}; let flag = false; if (method === "WebcastGiftMessage") { if (GM.get("filter_block_gift_msg")) flag = true; } else if (method === "WebcastChatMessage") { const isLuckyBag = chat_by === "9" || chat_by === "10" || Object.keys(public_area_common?.individual_strategy_result || {}).length !== 0; if (isLuckyBag && GM.get("filter_block_lucky_bag")) flag = true; } else if (method === "WebcastRoomMessage") { if (GM.get("filter_block_room_message")) flag = true; } else if (method === "WebcastEmojiChatMessage") { if (GM.get("filter_block_emoji")) flag = true; } if (!flag && typeof message === "string" && this.rules.length) { flag = this.rules.some((re) => { try { return re.test(message); } catch (e) { return false; } }); } return flag; }, }; function extractMessageFromFiber(fiber) { if (!fiber) return null; let node = fiber; for (let i = 0; i < 8 && node; i++) { const props = node.memoizedProps; if (props?.message) return props.message; if (props?.children?.props?.message) return props.children.props.message; if (props?.children?.props?.children?.props?.message) return props.children.props.children.props.message; node = node.return; } return null; } async function tryHookDecoder(timeoutMs = 8000) { const start = Date.now(); return new Promise((resolve) => { const timer = setInterval(() => { const decoder = _win["__MESSAGE_INSTANCE__"]?.decoder; const found = decoder && typeof decoder.decode === "function"; if (found || Date.now() - start > timeoutMs) { clearInterval(timer); if (!found) { resolve(false); return; } const originDecode = decoder.decode; decoder.decode = async function (...args) { const [, method] = args; const payload = await Reflect.apply(originDecode, this, args); try { if (MessageFilter.check({ payload }, method)) { return {}; } } catch (e) { log("消息过滤出错:", e); } return payload; }; log("已挂钩直播消息解码器(decoder hook 生效)"); resolve(true); } }, 200); }); } function startDomFallbackFilter() { log("未找到消息解码器,启用DOM兜底过滤方案"); addStyle( "dylive-lite-style-chatroom-fix", `.webcast-chatroom___list > div { height: 100% !important; }` ); const scan = () => { if (!isLivePage()) return; const nodes = document.querySelectorAll( '#chatroom .webcast-chatroom___item:not([data-dylive-filter-checked])' ); nodes.forEach(($item) => { $item.setAttribute("data-dylive-filter-checked", "true"); try { const fiber = getReactFiber($item); const message = extractMessageFromFiber(fiber); if (message) { if (MessageFilter.check(message)) { $item.remove(); } } } catch (e) {} }); }; const observer = new MutationObserver(debounce(scan, 80)); observer.observe(document.documentElement, { childList: true, subtree: true }); scan(); } async function initMessageFilter() { MessageFilter.initRules(); const hooked = await tryHookDecoder(); if (!hooked) startDomFallbackFilter(); } /* ========================================================= * 功能-抢福袋(集成自“抖音快速抢福袋”) * ========================================================= */ let luckyBagTimer = null; let luckyBagCt = 0; let luckyBagWt = 0; // 颜色输出(仅用于控制台) function cc(color, ...args) { const colorsMap = { reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m' }; console.log(colorsMap[color] + args.join('') + colorsMap.reset); } function timestampToDatetime(timestamp) { return new Date(timestamp).toLocaleString(); } function startLuckyBag() { if (luckyBagTimer) { clearInterval(luckyBagTimer); luckyBagTimer = null; } if (!GM.get("enable_all") || !GM.get("enable_lucky_bag") || !isLivePage()) { return; } cc('cyan', '========== 启动抢福袋 =========='); luckyBagTimer = setInterval(function() { var currentDate = new Date(); var currentTime = currentDate.toLocaleString(); var currentms = currentDate.getTime(); var lofind = 0; // ========== 福袋弹窗处理 ========== $('#lottery_close_cotainer').each(function() { var tt = $(this).text(); lofind = 1; // 1. 没抽中福袋,点击知道了 if (tt.indexOf('没抽中福袋') > -1) { cc('red', '❌ 没抽中福袋 ', currentTime); $(this).find('div[role="button"]').each(function() { if (tt.indexOf('知道了') > -1) { console.log('点击知道了', $(this)[0]); $(this).click(); luckyBagCt = currentms; } }); return; } // 时间间隔检查 if (currentms > luckyBagCt + 200 && currentms > luckyBagWt + 2000) { luckyBagWt = currentms; // 提取倒计时时间 var ix = tt.match(/[0-9]{1,2}:[0-9]{1,2}/g); if (ix) { ix = ix[0].split(':'); ix = ix[0] * 60 + parseInt(ix[1]); if (ix > 0) { console.log('福袋倒计时 ', ix, ' 秒 ', currentTime); luckyBagWt += ix * 1000 - 1000; console.log('福袋开始时间:', timestampToDatetime(luckyBagWt)); } } // 2. 已参与福袋,点击关闭弹窗 if (tt.indexOf('已参与') > -1) { $(this).find('div > div > div.YnybGvCL,div > div > div.hJ3SHYaQ').last().each(function() { if ($(this).text().length < 3) { $(this).click(); console.log('已参与福袋,点击关闭', $(this)[0]); luckyBagCt = currentms; } }); return; } // 3. 倒计时中、加载中或可以一键评论参与 if (tt.indexOf('倒计时') > -1 || tt.indexOf('加载中') > -1 || tt.indexOf('一键发评论参与福袋') > -1) { $(this).find('div[role="button"]').each(function() { if (tt.indexOf('一键发评论参与福袋') > -1) { console.log('点击一键发评论参与福袋', $(this)[0]); $(this).click(); luckyBagCt = currentms; } }); return; } // 4. 恭喜中奖 if (tt.indexOf('恭喜你') > -1) { cc('green', '🎉 ' + tt, currentTime); $(this).find('div > div > div.YnybGvCL,div > div > div.hJ3SHYaQ').last().each(function() { if ($(this).text().length < 3) { $(this).click(); console.log('已得奖,点击关闭', $(this)[0]); luckyBagCt = currentms; } }); } // 5. 条件不满足(如需要升级或其他条件,且不需要加粉丝团) else if (tt.indexOf('加入') < 0 && tt.indexOf('提升') < 0) { cc('yellow', '⚠️ ' + tt, currentTime); $(this).find('div > div > div.YnybGvCL,div > div > div.hJ3SHYaQ').last().each(function() { if ($(this).text().length < 3) { $(this).click(); console.log('条件不满足,点击关闭', $(this)[0]); luckyBagCt = currentms; } }); } else { console.log('⚠️ 福袋需要加入粉丝团或提升等级,跳过'); } } }); // ========== 点击挂件进入福袋界面 ========== if (lofind === 0) { $('#ShortTouchLayout > div > div > div > div > div').each(function() { var ttx = $(this).text(); if (ttx.indexOf(':') > -1 && ttx.indexOf('00:00') < 0) { if (currentms - luckyBagWt > 0 && currentms - luckyBagCt > 60000) { console.log('发现福袋挂件,点击打开', ttx, currentTime); $(this).click(); luckyBagCt = currentms; } } }); } }, 1000); cc('green', '✅ 纯净福袋自动抢夺已启动'); } function stopLuckyBag() { if (luckyBagTimer) { clearInterval(luckyBagTimer); luckyBagTimer = null; cc('yellow', '⏹ 抢福袋已停止'); } } // 页面离开或切换时停止,进入直播时启动(由init调度) /* ========================================================= * 设置面板 UI(还原经典界面样式 + 增加位置记忆移动) * ========================================================= */ const PANEL_ID = "dylive-lite-panel"; const BUTTON_ID = "dylive-lite-toggle-btn"; function injectPanelStyle() { addStyle( "dylive-lite-panel-style", ` #${BUTTON_ID} { position: fixed; width: 44px; height: 44px; border-radius: 50%; background: linear-gradient(135deg, #fe2c55, #ff6b81); box-shadow: 0 4px 12px rgba(0,0,0,0.25); display: flex; align-items: center; justify-content: center; cursor: move; z-index: 999999; color: #fff; font-size: 20px; user-select: none; transition: transform .2s ease; } #${BUTTON_ID}:hover { transform: scale(1.08); } #${PANEL_ID} { position: fixed; width: 320px; max-height: 70vh; overflow-y: auto; background: #1e1e22; color: #f2f2f2; border-radius: 14px; box-shadow: 0 10px 30px rgba(0,0,0,0.4); z-index: 999999; font-size: 13px; display: none; padding: 14px 14px 18px; box-sizing: border-box; } #${PANEL_ID}.show { display: block; } #${PANEL_ID} .dl-title { font-size: 15px; font-weight: 600; margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between; } #${PANEL_ID} .dl-title .dl-close { cursor: pointer; opacity: .6; font-size: 16px; } #${PANEL_ID} .dl-title .dl-close:hover { opacity: 1; } #${PANEL_ID} .dl-section { margin-bottom: 12px; border-top: 1px solid rgba(255,255,255,0.08); padding-top: 8px; } #${PANEL_ID} .dl-section:first-of-type { border-top: none; padding-top: 0; } #${PANEL_ID} .dl-section-title { font-size: 12px; color: #ff6b81; margin-bottom: 6px; font-weight: 600; } #${PANEL_ID} .dl-row { display: flex; align-items: center; justify-content: space-between; padding: 6px 0; } #${PANEL_ID} .dl-row-label { flex: 1; margin-right: 8px; line-height: 1.3; } #${PANEL_ID} .dl-row-desc { display: block; font-size: 11px; color: #9a9a9a; margin-top: 2px; } #${PANEL_ID} .dl-switch { position: relative; width: 36px; height: 20px; border-radius: 10px; background: #444; flex-shrink: 0; cursor: pointer; transition: background .2s ease; } #${PANEL_ID} .dl-switch.on { background: #fe2c55; } #${PANEL_ID} .dl-switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: left .2s ease; } #${PANEL_ID} .dl-switch.on::after { left: 18px; } #${PANEL_ID} .dl-select { flex-shrink: 0; background: #101012; color: #eee; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 3px 6px; font-size: 12px; max-width: 110px; } #${PANEL_ID} textarea { width: 100%; height: 120px; margin-top: 6px; box-sizing: border-box; background: #101012; color: #eee; border: 1px solid rgba(255,255,255,0.12); border-radius: 8px; padding: 8px; font-size: 12px; resize: vertical; } #${PANEL_ID} .dl-hint { font-size: 11px; color: #9a9a9a; margin-top: 4px; } ` ); } function buildSelectRow(label, key, options, desc, onChange) { const $row = document.createElement("div"); $row.className = "dl-row"; const current = GM.get(key); const optionsHtml = options .map((opt) => ``) .join(""); $row.innerHTML = `
${label}${desc ? `${desc}` : ""}
`; const $select = $row.querySelector(".dl-select"); $select.addEventListener("change", () => { GM.set(key, $select.value); if (onChange) onChange($select.value); }); return $row; } function buildSwitchRow(label, key, desc, onChange) { const $row = document.createElement("div"); $row.className = "dl-row"; $row.innerHTML = `
${label}${desc ? `${desc}` : ""}
`; const $switch = $row.querySelector(".dl-switch"); $switch.addEventListener("click", () => { const next = !GM.get(key); GM.set(key, next); $switch.classList.toggle("on", next); if (onChange) onChange(next); applyCssBlocks(); // 如果开关是抢福袋,动态启停 if (key === "enable_lucky_bag") { if (next && isLivePage()) { startLuckyBag(); } else { stopLuckyBag(); } } }); return $row; } // 悬浮球拖拽以及智能跟随面板的位置记忆逻辑 function makeElementDraggable($btn, $panel) { let isDragging = false; let startX, startY; let savedPos = GM.get("btn_pos"); // 初始化加载上次记忆好的位置 $btn.style.right = savedPos.right + "px"; $btn.style.bottom = savedPos.bottom + "px"; function updatePanelPosition() { const btnRect = $btn.getBoundingClientRect(); $panel.style.right = (window.innerWidth - btnRect.right) + "px"; $panel.style.bottom = (window.innerHeight - btnRect.top + 8) + "px"; } $btn.addEventListener("mousedown", (e) => { if (e.button !== 0) return; isDragging = false; startX = e.clientX; startY = e.clientY; const rect = $btn.getBoundingClientRect(); const currentRight = window.innerWidth - rect.right; const currentBottom = window.innerHeight - rect.bottom; function onMouseMove(ev) { isDragging = true; const deltaX = ev.clientX - startX; const deltaY = ev.clientY - startY; let newRight = currentRight - deltaX; let newBottom = currentBottom - deltaY; newRight = Math.max(0, Math.min(window.innerWidth - 44, newRight)); newBottom = Math.max(0, Math.min(window.innerHeight - 44, newBottom)); $btn.style.right = newRight + "px"; $btn.style.bottom = newBottom + "px"; if ($panel.classList.contains("show")) updatePanelPosition(); } function onMouseUp() { document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); if (isDragging) { const finalRect = $btn.getBoundingClientRect(); GM.set("btn_pos", { right: window.innerWidth - finalRect.right, bottom: window.innerHeight - finalRect.bottom }); } } document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); }); $btn.addEventListener("click", (e) => { if (isDragging) { e.preventDefault(); e.stopPropagation(); return; } $panel.classList.toggle("show"); if ($panel.classList.contains("show")) updatePanelPosition(); }); } function buildPanel() { injectPanelStyle(); const $btn = document.createElement("div"); $btn.id = BUTTON_ID; $btn.title = "抖音直播净化设置"; $btn.textContent = "净"; document.documentElement.appendChild($btn); const $panel = document.createElement("div"); $panel.id = PANEL_ID; document.documentElement.appendChild($panel); renderPanelContent($panel); makeElementDraggable($btn, $panel); } function renderPanelContent($panel) { $panel.innerHTML = ""; const $title = document.createElement("div"); $title.className = "dl-title"; $title.innerHTML = `直播净化设置`; $title.querySelector(".dl-close").addEventListener("click", () => { $panel.classList.remove("show"); }); $panel.appendChild($title); // 总开关 const $master = document.createElement("div"); $master.className = "dl-section"; $master.appendChild(buildSwitchRow("启用所有功能", "enable_all", "关闭后所有净化功能都会暂停")); $panel.appendChild($master); // 功能 const $funcSection = document.createElement("div"); $funcSection.className = "dl-section"; $funcSection.innerHTML = `
功能
`; $funcSection.appendChild( buildSelectRow( "清晰度", "quality_select", [ { value: "", text: "不设置" }, ...Object.keys(VideoQualityMap).map((key) => ({ value: key, text: VideoQualityMap[key].label })), ], "自行选择清晰度", () => applyQuality() ) ); $funcSection.appendChild( buildSwitchRow("抢福袋", "enable_lucky_bag", "自动参与福袋并领取结果") ); $panel.appendChild($funcSection); // 布局屏蔽-聊天室 const $chatSection = document.createElement("div"); $chatSection.className = "dl-section"; $chatSection.innerHTML = `
布局屏蔽 · 聊天室
`; $chatSection.appendChild( buildSwitchRow("屏蔽信息播报", "block_message_broadcast", "如“xxx进入/加入了直播间”等滚动播报") ); $chatSection.appendChild( buildSwitchRow("屏蔽底部遮挡区域", "block_bottom_area", "该区域会裁切部分聊天内容,导致显示不全") ); $panel.appendChild($chatSection); // 布局屏蔽-视频区域内 const $videoSection = document.createElement("div"); $videoSection.className = "dl-section"; $videoSection.innerHTML = `
布局屏蔽 · 视频区域内
`; $videoSection.appendChild(buildSwitchRow("屏蔽礼物特效", "block_gift_effects")); $videoSection.appendChild(buildSwitchRow("屏蔽小黄车", "block_yellow_car")); $videoSection.appendChild( buildSwitchRow("屏蔽点亮展馆帮主播集星", "block_exhibition_tooltip", "礼物展馆下方的悬浮提示") ); $videoSection.appendChild( buildSwitchRow("屏蔽礼物栏/礼物容器", "block_gifts_container", "直播间侧边/底部的礼物列表容器") ); $panel.appendChild($videoSection); // 布局屏蔽-视频区域内-弹幕 const $danmakuSection = document.createElement("div"); $danmakuSection.className = "dl-section"; $danmakuSection.innerHTML = `
布局屏蔽 · 视频区域内 · 弹幕
`; $danmakuSection.appendChild( buildSwitchRow("屏蔽弹幕", "block_danmaku", "视频画面上飘过的弹幕(区别于聊天室消息)") ); $panel.appendChild($danmakuSection); // 聊天室消息过滤器 const $filterSection = document.createElement("div"); $filterSection.className = "dl-section"; $filterSection.innerHTML = `
聊天室消息过滤器
`; $filterSection.appendChild(buildSwitchRow("启用过滤器", "filter_enable")); $filterSection.appendChild(buildSwitchRow("屏蔽送礼消息", "filter_block_gift_msg")); $filterSection.appendChild(buildSwitchRow("屏蔽福袋口令", "filter_block_lucky_bag")); $filterSection.appendChild(buildSwitchRow("屏蔽emoji/图片/表情包", "filter_block_emoji")); $filterSection.appendChild( buildSwitchRow("屏蔽房间播报", "filter_block_room_message", "如“xxx来了/给主播点赞了”等") ); const $ruleWrap = document.createElement("div"); $ruleWrap.innerHTML = `
自定义屏蔽规则(正则,每行一条)
命中任意一条规则的消息将被屏蔽,修改后自动保存并生效
`; const $textarea = $ruleWrap.querySelector("textarea"); $textarea.addEventListener( "input", debounce(() => { GM.set("filter_custom_rules", $textarea.value); MessageFilter.initRules(); }, 800) ); $filterSection.appendChild($ruleWrap); $panel.appendChild($filterSection); } function ensurePanelVisibility() { const $btn = document.getElementById(BUTTON_ID); const shouldShow = isLivePage(); if (shouldShow && !$btn) { buildPanel(); } else if (!shouldShow && $btn) { $btn.remove(); const $panel = document.getElementById(PANEL_ID); if ($panel) $panel.remove(); } } /* ========================================================= * 初始化调度 * ========================================================= */ let filterInitialized = false; function initForLivePage() { if (!isLivePage()) { // 离开直播页面时停止抢福袋 stopLuckyBag(); return; } applyCssBlocks(); ensurePanelVisibility(); applyQuality(); // 启动抢福袋(如果开关打开) if (GM.get("enable_all") && GM.get("enable_lucky_bag")) { startLuckyBag(); } else { stopLuckyBag(); } if (!filterInitialized) { filterInitialized = true; initMessageFilter(); } } function init() { onReady(() => { applyCssBlocks(); ensurePanelVisibility(); if (isLivePage()) { applyQuality(); if (GM.get("enable_all") && GM.get("enable_lucky_bag")) { startLuckyBag(); } initMessageFilter().then(() => (filterInitialized = true)); } watchRouteChange(() => { applyCssBlocks(); ensurePanelVisibility(); if (isLivePage()) { applyQuality(); if (GM.get("enable_all") && GM.get("enable_lucky_bag")) { startLuckyBag(); } else { stopLuckyBag(); } if (!filterInitialized) { filterInitialized = true; initMessageFilter(); } } else { stopLuckyBag(); } }); const boot = new MutationObserver( debounce(() => { if (isLivePage()) { applyCssBlocks(); ensurePanelVisibility(); } }, 300) ); boot.observe(document.documentElement, { childList: true, subtree: true }); }); try { GM_registerMenuCommand("⚙ 打开/关闭 直播净化设置面板", () => { const $panel = document.getElementById(PANEL_ID); if ($panel) $panel.classList.toggle("show"); else log("当前不在直播页面"); }); } catch (e) {} } init(); })();