您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
调整媒体音量与滤波器,增强倍数最高 20 倍,设置可记住并自动应用。部分网站可能无效、无声音或无法播放,可选择禁用。
// ==UserScript== // @name Media Volume Booster // @name:zh-TW 媒體音量增強器 // @name:zh-CN 媒体音量增强器 // @name:en Media Volume Booster // @version 2025.08.08-Beta // @author Canaan HS // @description 調整媒體音量與濾波器,增強倍數最高 20 倍,設置可記住並自動應用。部分網站可能無效、無聲音或無法播放,可選擇禁用。 // @description:zh-TW 調整媒體音量與濾波器,增強倍數最高 20 倍,設置可記住並自動應用。部分網站可能無效、無聲音或無法播放,可選擇禁用。 // @description:zh-CN 调整媒体音量与滤波器,增强倍数最高 20 倍,设置可记住并自动应用。部分网站可能无效、无声音或无法播放,可选择禁用。 // @description:en Adjust media volume and filters with enhancement factor up to 20x. Settings are saved and auto-applied. May not work on some sites (causing no sound or playback issues). Can be disabled if needed. // @noframes // @match *://*/* // @icon https://cdn-icons-png.flaticon.com/512/16108/16108408.png // @license MPL-2.0 // @namespace https://greasyfork.org/users/989635 // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_getResourceURL // @grant GM_registerMenuCommand // @grant GM_addValueChangeListener // @resource Img https://cdn-icons-png.flaticon.com/512/11243/11243783.png // @require https://update.greasyfork.org/scripts/487608/1637584/SyntaxLite_min.js // @run-at document-body // ==/UserScript== (function () { const Share = { Parame: null, SetControl: null }; const Default = { Gain: 1, LowFilterGain: 1.2, LowFilterFreq: 200, MidFilterQ: 1, MidFilterGain: 1.6, MidFilterFreq: 2e3, HighFilterGain: 1.8, HighFilterFreq: 1e4, CompressorRatio: 3, CompressorKnee: 4, CompressorThreshold: -8, CompressorAttack: .03, CompressorRelease: .2 }; function CreateMenu(Lib2, Share2, Img, Transl2) { return async () => { const shadowID = "Booster_Menu"; if (Lib2.$q(`#${shadowID}`)) return; const shadow = Lib2.createElement(Lib2.body, "div", { id: shadowID }); const shadowRoot = shadow.attachShadow({ mode: "open" }); const style = ` <style> :host { --primary-color: #3a7bfd; --secondary-color: #00d4ff; --text-color: #ffffff; --slider-track: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); --background-dark: #1a1f2c; --background-panel: #252b3a; --highlight-color: #00e5ff; --border-radius: 12px; --hover-bg: rgba(0, 229, 255, 0.06); --hover-border: rgba(0, 229, 255, 0.15); } ${shadowID} { top: 0; left: 0; width: 100%; height: 100%; display: flex; z-index: 999999; overflow: auto; position: fixed; align-items: center; justify-content: center; backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); transition: opacity 0.4s ease; background-color: rgba(0, 0, 0, 0.4); } ${shadowID}.close { animation: fadeOut 0.4s ease forwards; } .Booster-Modal-Content { min-width: 420px; max-width: 460px; width: 100%; padding: 20px; padding-inline-end: 10px; overflow-y: auto; scrollbar-gutter: stable; text-align: center; border-radius: var(--border-radius); background-color: var(--background-dark); border: 1px solid rgba(78, 164, 255, 0.3); box-shadow: inset -6px 0 10px -8px rgba(0, 0, 0, 0.5), 0 10px 30px rgba(0, 0, 0, 0.5), 0 0 15px rgba(0, 212, 255, 0.2); color: var(--text-color); max-height: 85vh; transition: all 0.5s ease; } .Booster-Modal-Content.close { animation: shrinkFadeOut 0.8s ease forwards; } .Booster-Modal-Content::-webkit-scrollbar { width: 8px; } .Booster-Modal-Content::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 8px; } .Booster-Modal-Content::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.1); } .Booster-Title { margin-top: 0; color: var(--secondary-color); font-size: 22px; font-weight: 600; letter-spacing: 0.5px; margin-bottom: 20px; text-shadow: 0 0 10px rgba(0, 212, 255, 0.4); transform: translateY(-10px); } .Booster-Multiplier { margin: 1.5rem 0; font-size: 22px; font-weight: 500; } .Booster-Multiplier img { width: 24px; margin-right: 8px; vertical-align: middle; } .Booster-Multiplier span { display: flex; align-items: center; justify-content: center; } #Booster-CurrentValue { color: var(--highlight-color); font-weight: 700; margin: 0 5px; font-size: 26px; } .Booster-Slider { -webkit-appearance: none; appearance: none; width: 90%; height: 6px; cursor: pointer; margin: 2rem 0 3.5rem 0; background: var(--slider-track); border-radius: 3px; outline: none; } .Booster-Slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 16px; height: 16px; border-radius: 50%; background: var(--secondary-color); cursor: pointer; box-shadow: 0 0 8px rgba(0, 212, 255, 0.6); } .Booster-Slider::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: var(--secondary-color); cursor: pointer; border: none; box-shadow: 0 0 8px rgba(0, 212, 255, 0.6); } .Booster-Slider::-moz-range-progress { background: var(--slider-track); border-radius: 3px; height: 6px; } .Booster-Buttons { display: flex; justify-content: flex-end; margin-top: 20px; gap: 10px; } .Booster-Modal-Button { color: var(--text-color); cursor: pointer; font-size: 15px; font-weight: 500; padding: 8px 16px; border-radius: 6px; background-color: rgba(58, 123, 253, 0.2); border: 1px solid rgba(78, 164, 255, 0.3); transition: all 0.2s ease; outline: none; } .Booster-Modal-Button:hover { background-color: rgba(58, 123, 253, 0.4); box-shadow: 0 0 10px rgba(0, 212, 255, 0.4); transform: translateY(-2px); } #Booster-Sound-Save { background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); border: none; position: relative; overflow: hidden; } #Booster-Sound-Save:hover { box-shadow: 0 0 15px rgba(0, 212, 255, 0.6); } #Booster-Sound-Save:after { content: ""; position: absolute; top: -50%; left: -60%; width: 20%; height: 200%; transform: rotate(30deg); background: rgba(255, 255, 255, 0.13); background: linear-gradient( to right, rgba(255, 255, 255, 0.13) 0%, rgba(255, 255, 255, 0.13) 77%, rgba(255, 255, 255, 0.5) 92%, rgba(255, 255, 255, 0.0) 100% ); } #Booster-Sound-Save:hover:after { opacity: 1; left: 130%; transition: left 0.7s ease, opacity 0.5s ease; } .Booster-Accordion { background-color: var(--background-panel); color: var(--text-color); cursor: pointer; padding: 12px 15px; width: 100%; text-align: left; border: none; outline: none; transition: 0.3s; border-radius: 8px; margin-bottom: 8px; font-weight: 500; display: flex; justify-content: space-between; align-items: center; transform: translateY(10px); } .Booster-Accordion:after { content: '+'; color: var(--secondary-color); font-weight: bold; float: right; margin-left: 5px; } .Booster-Accordion.active { border-bottom-left-radius: 0; border-bottom-right-radius: 0; margin-bottom: 0; } .Booster-Accordion.active:after { content: "-"; } .Booster-Panel { max-height: 0; overflow: hidden; padding: 0 15px; margin-top: 0; margin-bottom: 8px; transition: max-height 0.3s ease-out; background-color: var(--background-panel); border-radius: 0 0 8px 8px; } .Booster-Panel.active { margin-bottom: 15px; padding: 10px 15px 15px; } .Booster-Control-Group { margin-bottom: 15px; } .Booster-Control-Label { display: flex; justify-content: space-between; margin-bottom: 5px; font-size: 14px; color: rgba(255, 255, 255, 0.8); } .Booster-Mini-Slider { -webkit-appearance: none; appearance: none; width: 100%; height: 4px; background: var(--slider-track); border-radius: 2px; outline: none; } .Booster-Mini-Slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 12px; height: 12px; border-radius: 50%; background: var(--secondary-color); cursor: pointer; } .Booster-Mini-Slider::-moz-range-thumb { width: 12px; height: 12px; border-radius: 50%; background: var(--secondary-color); cursor: pointer; border: none; } .Booster-Mini-Slider::-moz-range-progress { background: var(--slider-track); border-radius: 2px; height: 4px; } .Booster-Label { padding: 0.1rem 0.2rem; font-size: larger; font-weight: bolder; cursor: pointer; border-radius: 6px; min-width: 50px; text-align: center; color: var(--highlight-color); transition: background-color 0.2s; } .Booster-Label:hover { background-color: var(--hover-bg); border-color: var(--hover-border); box-shadow: 0 0 0.5rem rgba(0, 229, 255, 0.12); transform: translateY(-1px); } .Booster-Label-Input { width: 60px; padding: 2px 5px; font-size: 18px; font-weight: bolder; color: var(--text-color); background-color: var(--background-panel); border: 1px solid var(--primary-color); border-radius: 4px; text-align: center; outline: none; } @keyframes fadeOut { from {opacity: 1;} to {opacity: 0; pointer-events: none;} } @keyframes shrinkFadeOut { from {transform: scale(1); opacity: 1;} to {transform: scale(0.5); opacity: 0;} } </style> `; const generateOtherTemplate = (label, groups) => ` <button class="Booster-Accordion">${Transl2(label)}</button> <div class="Booster-Panel"> ${groups.map(group => ` <div class="Booster-Control-Group"> <div class="Booster-Control-Label"> <span>${Transl2(group.label)}</span> <span id="${group.id}-Label" class="Booster-Label">${Share2.Parame[group.id]}</span> </div> <input type="range" id="${group.id}" class="Booster-Mini-Slider" min="${group.min}" max="${group.max}" value="${Share2.Parame[group.id]}" step="${group.step}"> </div> `).join("")} </div> `; shadowRoot.$iHtml(` ${style} <${shadowID} id="Booster-Modal-Menu"> <div class="Booster-Modal-Content"> <h2 class="Booster-Title">${Transl2("音量增強器")}</h2> <div class="Booster-Multiplier"> <span> <img src="${Img}">${Transl2("增強倍數 ")} <span id="Gain-Label" class="Booster-Label">${Share2.Parame.Gain}</span>${Transl2(" 倍")} </span> <input type="range" id="Gain" class="Booster-Slider" min="0" max="20.0" value="${Share2.Parame.Gain}" step="0.1"> </div> ${generateOtherTemplate("低頻設定", [{ label: "增益", id: "LowFilterGain", min: "-12", max: "12", step: "0.1" }, { label: "頻率", id: "LowFilterFreq", min: "20", max: "1000", step: "20" }])} ${generateOtherTemplate("中頻設定", [{ label: "增益", id: "MidFilterGain", min: "-12", max: "12", step: "0.1" }, { label: "頻率", id: "MidFilterFreq", min: "200", max: "8000", step: "100" }, { label: "Q值", id: "MidFilterQ", min: "0.5", max: "5", step: "0.1" }])} ${generateOtherTemplate("高頻設定", [{ label: "增益", id: "HighFilterGain", min: "-12", max: "12", step: "0.1" }, { label: "頻率", id: "HighFilterFreq", min: "2000", max: "22000", step: "500" }])} ${generateOtherTemplate("動態壓縮", [{ label: "壓縮率", id: "CompressorRatio", min: "1", max: "30", step: "0.1" }, { label: "過渡反應", id: "CompressorKnee", min: "0", max: "40", step: "1" }, { label: "閾值", id: "CompressorThreshold", min: "-60", max: "0", step: "1" }, { label: "起音速度", id: "CompressorAttack", min: "0.001", max: "0.5", step: "0.001" }, { label: "釋放速度", id: "CompressorRelease", min: "0.01", max: "2", step: "0.01" }])} <div class="Booster-Buttons"> <button class="Booster-Modal-Button" id="Booster-Menu-Close">${Transl2("關閉")}</button> <button class="Booster-Modal-Button" id="Booster-Sound-Save">${Transl2("保存")}</button> </div> </div> </${shadowID}> `); const shadowGate = shadow.shadowRoot; const modal = shadowGate.querySelector(shadowID); const content = shadowGate.querySelector(".Booster-Modal-Content"); function deleteMenu() { modal.classList.add("close"); content.classList.add("close"); setTimeout(() => { shadow.remove(); }, 800); } const displayMap = { ...Object.fromEntries([...shadowGate.querySelectorAll(".Booster-Label")].map(el => [el.id, el])) }; function updateControl(id, value) { displayMap[`${id}-Label`].textContent = value; shadowGate.querySelector(`#${id}`).value = value; Share2.SetControl(id, value); } content.addEventListener("input", event => { const target = event.target; if (target.type !== "range") return; const id = target.id; const value = parseFloat(target.value); updateControl(id, value); }); content.addEventListener("click", event => { const target = event.target; if (!target.classList.contains("Booster-Label") || target.isEditing) return; target.isEditing = true; const originalValue = target.textContent.trim(); const controlId = target.id.replace("-Label", ""); const slider = shadowGate.querySelector(`#${controlId}`); const input = Lib2.createElement("input", { class: "Booster-Label-Input", value: originalValue, on: [{ type: "blur", listener: () => { let newValue = parseFloat(input.value); const min = parseFloat(slider.min); const max = parseFloat(slider.max); if (isNaN(newValue)) { newValue = parseFloat(originalValue); } else if (newValue < min) { newValue = min; } else if (newValue > max) { newValue = max; } target.isEditing = false; updateControl(controlId, newValue); target.textContent = newValue; }, add: { once: true } }, { type: "keydown", listener: e => { if (e.key === "Enter") e.target.blur(); if (e.key === "Escape") { e.target.value = originalValue; e.target.blur(); } } }] }); target.textContent = ""; target.appendChild(input); input.focus(); }); modal.addEventListener("click", click => { const target = click.target; click.stopPropagation(); if (target.classList.contains("Booster-Accordion")) { target.classList.toggle("active"); const panel = target.nextElementSibling; if (panel.style.maxHeight) { panel.style.maxHeight = null; panel.classList.remove("active"); } else { panel.style.maxHeight = panel.scrollHeight + "px"; panel.classList.add("active"); } } else if (target.id === "Booster-Sound-Save") { Lib2.setV(Lib2.domain, Share2.Parame); deleteMenu(); } else if (target.id === "Booster-Menu-Close" || target.id === "Booster-Modal-Menu") { deleteMenu(); } }); }; } const Words = { Traditional: {}, Simplified: { "🛠️ 調整菜單": "🛠️ 调整菜单", "✂️ 斷開增幅": "✂️ 断开增幅", "🔗 恢復增幅": "🔗 恢复增幅", "❌ 禁用網域": "❌ 禁用网域", "✅ 啟用網域": "✅ 启用网域", "增強錯誤": "增强错误", "音量增強器": "音量增强器", "增強倍數 ": "增强倍数 ", " 倍": " 倍", "增益": "增益", "頻率": "频率", "Q值": "Q值", "低頻設定": "低频设置", "中頻設定": "中频设置", "高頻設定": "高频设置", "動態壓縮": "动态压缩", "壓縮率": "压缩率", "過渡反應": "过渡反应", "閾值": "阈值", "起音速度": "起音速度", "釋放速度": "释放速度", "關閉": "关闭", "保存": "保存", "不支援的媒體跳過": "不支持的媒体跳过", "不支援音頻增強節點": "不支持音频增强节点", "添加增強節點成功": "添加增强节点成功", "添加增強節點失敗": "添加增强节点失败", "當前沒有被增幅的媒體": "当前没有被增幅的媒体", "快捷組合 : (Alt + B)": "快捷组合 : (Alt + B)" }, English: { "🛠️ 調整菜單": "🛠️ Settings Menu", "✂️ 斷開增幅": "✂️ Disconnect Amplification", "🔗 恢復增幅": "🔗 Restore Amplification", "❌ 禁用網域": "❌ Disable Domain", "✅ 啟用網域": "✅ Enable Domain", "增強錯誤": "Enhancement Error", "音量增強器": "Volume Booster", "增強倍數 ": "Enhancement Multiplier ", " 倍": "x", "增益": "Gain", "頻率": "Frequency", "Q值": "Q Factor", "低頻設定": "Low Frequency Settings", "中頻設定": "Mid Frequency Settings", "高頻設定": "High Frequency Settings", "動態壓縮": "Dynamic Compressor", "壓縮率": "Compression Ratio", "過渡反應": "Knee", "閾值": "Threshold", "起音速度": "Attack", "釋放速度": "Release", "關閉": "Close", "保存": "Save", "不支援的媒體跳過": "Unsupported Media Skipped", "不支援音頻增強節點": "Audio Enhancement Node Not Supported", "添加增強節點成功": "Enhancement Node Added Successfully", "添加增強節點失敗": "Failed to Add Enhancement Node", "當前沒有被增幅的媒體": "No media is currently being amplified", "快捷組合 : (Alt + B)": "Shortcut: (Alt + B)" } }; const bannedDomains = (() => { let banned = new Set(Lib.getV("Banned", [])); let excludeStatus = banned.has(Lib.$domain); return { isEnabled: callback => callback(!excludeStatus), addBanned: async () => { excludeStatus ? banned.delete(Lib.$domain) : banned.add(Lib.$domain); Lib.setV("Banned", [...banned]); location.reload(); } }; })(); const { Transl } = (() => { const matcher = Lib.translMatcher(Words); return { Transl: Str => matcher[Str] ?? Str }; })(); (async () => { let menu = null; let updated = false; let processing = false; let initialized = false; const enhancedNodes = []; const processedElements = new Map(); let mediaAudioContent = null; const audioContext = window.AudioContext || window.webkitAudioContext; const updateParame = () => { let Config = Lib.getV(Lib.$domain, {}); if (typeof Config === "number") { Config = { Gain: Config }; } Share.Parame = Object.assign(Default, Config); }; function boosterCore(mediaObject) { try { if (!audioContext) throw new Error(Transl("不支援音頻增強節點")); if (!mediaAudioContent) mediaAudioContent = new audioContext(); if (mediaAudioContent.state === "suspended") mediaAudioContent.resume(); const successNode = []; for (const media of mediaObject) { processedElements.set(media, true); if (media.mediaKeys || media.encrypted || window.MediaSource && media.srcObject instanceof MediaSource) { Lib.log(Transl("不支援的媒體跳過"), media, { collapsed: false }); continue; } try { if (!media.crossOrigin) media.crossOrigin = "anonymous"; const SourceNode = mediaAudioContent.createMediaElementSource(media); const GainNode = mediaAudioContent.createGain(); const LowFilterNode = mediaAudioContent.createBiquadFilter(); const MidFilterNode = mediaAudioContent.createBiquadFilter(); const HighFilterNode = mediaAudioContent.createBiquadFilter(); const CompressorNode = mediaAudioContent.createDynamicsCompressor(); GainNode.gain.value = Share.Parame.Gain; LowFilterNode.type = "lowshelf"; LowFilterNode.gain.value = Share.Parame.LowFilterGain; LowFilterNode.frequency.value = Share.Parame.LowFilterFreq; MidFilterNode.type = "peaking"; MidFilterNode.Q.value = Share.Parame.MidFilterQ; MidFilterNode.gain.value = Share.Parame.MidFilterGain; MidFilterNode.frequency.value = Share.Parame.MidFilterFreq; HighFilterNode.type = "highshelf"; HighFilterNode.gain.value = Share.Parame.HighFilterGain; HighFilterNode.frequency.value = Share.Parame.HighFilterFreq; CompressorNode.ratio.value = Share.Parame.CompressorRatio; CompressorNode.knee.value = Share.Parame.CompressorKnee; CompressorNode.threshold.value = Share.Parame.CompressorThreshold; CompressorNode.attack.value = Share.Parame.CompressorAttack; CompressorNode.release.value = Share.Parame.CompressorRelease; SourceNode.connect(GainNode).connect(LowFilterNode).connect(MidFilterNode).connect(HighFilterNode).connect(CompressorNode).connect(mediaAudioContent.destination); enhancedNodes.push({ Connected: true, Destination: mediaAudioContent.destination, SourceNode: SourceNode, GainNode: GainNode, LowFilterNode: LowFilterNode, MidFilterNode: MidFilterNode, HighFilterNode: HighFilterNode, CompressorNode: CompressorNode, Gain: GainNode.gain, LowFilterGain: LowFilterNode.gain, LowFilterFreq: LowFilterNode.frequency, MidFilterQ: MidFilterNode.Q, MidFilterGain: MidFilterNode.gain, MidFilterFreq: MidFilterNode.frequency, HighFilterGain: HighFilterNode.gain, HighFilterFreq: HighFilterNode.frequency, CompressorRatio: CompressorNode.ratio, CompressorKnee: CompressorNode.knee, CompressorThreshold: CompressorNode.threshold, CompressorAttack: CompressorNode.attack, CompressorRelease: CompressorNode.release }); successNode.push(media); } catch (e) { Lib.log(Transl("添加增強節點失敗"), media, { collapsed: false }); } } if (successNode.length > 0) { processing = false; Lib.log(Transl("添加增強節點成功"), successNode, { collapsed: false }); if (!initialized) { let regChange2 = function () { Lib.regMenu({ [Transl(disconnected ? "🔗 恢復增幅" : "✂️ 斷開增幅")]: () => { if (enhancedNodes.length === 0) { alert(Transl("當前沒有被增幅的媒體")); return; } enhancedNodes.forEach(items => { const { Connected, SourceNode, GainNode, LowFilterNode, MidFilterNode, HighFilterNode, CompressorNode, Destination } = items; if (disconnected && !Connected) { SourceNode.connect(GainNode).connect(LowFilterNode).connect(MidFilterNode).connect(HighFilterNode).connect(CompressorNode).connect(Destination); items.Connected = true; } else if (!disconnected && Connected) { SourceNode.disconnect(); GainNode.disconnect(); LowFilterNode.disconnect(); MidFilterNode.disconnect(); HighFilterNode.disconnect(); CompressorNode.disconnect(); SourceNode.connect(Destination); items.Connected = false; } }); disconnected = !disconnected; regChange2(); }, [Transl("🛠️ 調整菜單")]: { desc: Transl("快捷組合 : (Alt + B)"), func: () => { menu(); } } }, { index: 2 }); }; var regChange = regChange2; initialized = true; let disconnected = false; regChange2(); Lib.onEvent(document, "keydown", event => { if (event.altKey && event.key.toUpperCase() == "B") menu(); }, { passive: true, capture: true, mark: "Media-Booster-Hotkey" }); Lib.storeListen([Lib.$domain], call => { if (call.far && call.key === Lib.$domain) { Object.entries(call.nv).forEach(([type, value]) => { Share.SetControl(type, value); }); } }); } } } catch (error) { Lib.log(Transl("增強錯誤"), error, { type: "error", collapsed: false }); } } function trigger(media) { try { if (!updated) { updated = true; updateParame(); } boosterCore(media); } catch (error) { Lib.log("Trigger Error : ", error, { type: "error", collapsed: false }); } } bannedDomains.isEnabled(Status => { const regMenu = async name => { Lib.regMenu({ [name]: () => bannedDomains.addBanned() }); }; if (Status) { Share.SetControl = (type, value) => { Share.Parame[type] = value; enhancedNodes.forEach(items => { items[type].value = value; }); }; menu = CreateMenu(Lib, Share, GM_getResourceURL("Img"), Transl); const findMedia = Lib.$debounce(func => { const media = []; const tree = document.createTreeWalker(Lib.body, NodeFilter.SHOW_ELEMENT, { acceptNode: node => { const tag = node.tagName; if (tag === "VIDEO" || tag === "AUDIO") { if (!processedElements.has(node)) return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_SKIP; } }); while (tree.nextNode()) { media.push(tree.currentNode); } media.length > 0 && func(media); }, 50); Lib.$observer(Lib.body, () => { if (processing) return; findMedia(media => { processing = true; trigger(media); }); }, { mark: "Media-Booster", attributes: false, throttle: 200 }, () => { regMenu(Transl("❌ 禁用網域")); }); Lib.onUrlChange(() => { processedElements.clear(); }); } else regMenu(Transl("✅ 啟用網域")); }); })(); })();