// ==UserScript== // @name 网页音量增强 // @namespace https://github.com/Stareven233 // @version 1.6.2 // @description 增强网页视频音量,支持正则表达式匹配网址,仅对匹配页面生效(初始不对任何网页起作用),支持每个网页单独设置video选择器、音量,支持 iframe 跨域视频 // @description Boost web video volume. Supports regex URL matching, custom video selectors, individual volume settings, and cross-origin iframes. // @author Noetu // @license CC BY-NC-SA-4.0 // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_notification // @run-at document-end // @downloadURL https://update.greasyfork.icu/scripts/567482/%E7%BD%91%E9%A1%B5%E9%9F%B3%E9%87%8F%E5%A2%9E%E5%BC%BA.user.js // @updateURL https://update.greasyfork.icu/scripts/567482/%E7%BD%91%E9%A1%B5%E9%9F%B3%E9%87%8F%E5%A2%9E%E5%BC%BA.meta.js // ==/UserScript== (function () { 'use strict'; if(window._n_volumAmpInitialized) { return; } window._n_volumAmpInitialized = true; const TM_CONFIG_KEY = 'n_volumAmpTMConfig'; const SETTINGS_STORAGE_KEY = 'n_volumAmpSiteSettings'; const AMP_MAX_FACTOR = 6.66 const AMP_STEP = 0.01 // ============ Utility Functions ============ function escapeHTML(str) { return str.replace(/[&<>"']/g, m => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m])); } // ============ Configuration Management ============ function getTMConfig() { return GM_getValue(TM_CONFIG_KEY, { rules: [], globalSwitch: true }); } function saveTMConfig(config) { GM_setValue(TM_CONFIG_KEY, config); } function getSiteSettings() { return GM_getValue(SETTINGS_STORAGE_KEY, {}); } function saveSiteSettings(settings) { GM_setValue(SETTINGS_STORAGE_KEY, settings); } // ============ Rule Matching ============ function checkMatch() { const config = getTMConfig(); if(!config.globalSwitch || !config.rules?.length) { return { matched: false }; } const currentUrl = window.location.href; for(const pattern of config.rules) { try { if(new RegExp(pattern).test(currentUrl)) { return { matched: true, rule: pattern }; } } catch(e) { if(currentUrl.includes(pattern)) { return { matched: true, rule: pattern }; } } } return { matched: false }; } function parseRules(ruleText) { let rules = ruleText.split('\n').map(s => s.trim()).filter(s => s !== ''); // Limit regular expression length rules = rules.filter(s => s.length > 0 && s.length < 1000) // Validate if it's a valid regular expression rules = rules.filter(s => { try { new RegExp(s); return true; } catch { return false; } }) return Array.from(new Set(rules)); } // ============ Custom Management Modal ============ function openRuleManager() { const config = getTMConfig(); const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 23333; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4px); font-family: sans-serif; `; const modal = document.createElement('div'); modal.style.cssText = ` background: #1e1e2e; color: #cdd6f4; width: 90%; max-width: 500px; padding: 20px; border-radius: 12px; border: 1px solid #45475a; box-shadow: 0 10px 40px rgba(0,0,0,0.8); `; const locationPrefix = window.location.origin + window.location.pathname.replace(/\/[^\/]*\/?$/, ''); modal.innerHTML = `

⚙️ 匹配规则管理

每行输入一个正则表达式。匹配成功则启用网页音量增强。
当前 URL: ${escapeHTML(window.location.href)}
匹配当前 URL 前缀示例: ${escapeHTML(locationPrefix)}/?.*

`; overlay.appendChild(modal); document.body.appendChild(overlay); modal.querySelector('#_btnCancel').onclick = () => overlay.remove(); modal.querySelector('#_btnSave').onclick = () => { config.rules = parseRules(modal.querySelector('#_rulesArea').value); saveTMConfig(config); location.reload(); }; } // ============ Menu Registration ============ function registerMenu() { const { matched } = checkMatch(); const currentUrl = window.location.href; // 1. Quick toggle switch const statusLabel = matched ? '✅ 本页已启用 (点击移除)' : '⚪ 本页未启用 (点击添加)'; GM_registerMenuCommand(statusLabel, () => { const currentConfig = getTMConfig(); if(matched) { currentConfig.rules = currentConfig.rules.filter(r => { try { return !(new RegExp(r).test(currentUrl)); } catch(e) { return r !== currentUrl; } }); } else { const safeUrl = currentUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); currentConfig.rules.push(`^${safeUrl}$`); } saveTMConfig(currentConfig); location.reload(); }); // 2. Add regular expression rule GM_registerMenuCommand('🧪 管理匹配规则', openRuleManager); } // ============ Core Functionality ============ let audioCtx, gainNode; function initAmplifier(video, volume) { try { if(video._n_volumAmpBound) { return true; } const AudioContext = window.AudioContext || window.webkitAudioContext; audioCtx = new AudioContext(); const source = audioCtx.createMediaElementSource(video); gainNode = audioCtx.createGain(); gainNode.gain.value = volume; source.connect(gainNode).connect(audioCtx.destination); video._n_volumAmpBound = true; const resume = () => { if(audioCtx.state === 'suspended') audioCtx.resume(); }; window.addEventListener('mousedown', resume, { once: true }); window.addEventListener('keydown', resume, { once: true }); return true; } catch(e) { console.warn('音量增强初始化失败:', e); return false; } } function showUI(settings, rule) { if(document.getElementById('_n_volumAmpPanel')) { return; } const panel = document.createElement('div'); panel.id = '_n_volumAmpPanel'; panel.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 23333; background: rgba(30, 30, 46, 0.95); color: #cdd6f4; padding: 15px; border-radius: 12px; border: 1px solid #45475a; width: 200px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); font-family: sans-serif; `; panel.innerHTML = `

未找到视频 ×

${Math.round(settings.volume * 100)}%
`; document.body.appendChild(panel); const slider = panel.querySelector('#ampSlider'); const ampValText = panel.querySelector('#ampVal'); const updateVolume = (v) => { ampValText.innerText = Math.round(v * 100); if(gainNode) gainNode.gain.value = v; // Broadcast to iframe for(const frame of document.querySelectorAll('iframe')) { try { frame.contentWindow.postMessage({ type: '_n_volumAmpVolume', volume: v }, '*'); } catch(_) { } } }; slider.oninput = (e) => updateVolume(parseFloat(e.target.value)); panel.querySelector('#ampSave').onclick = () => { const selector = panel.querySelector('#ampSelector').value.trim() || 'video'; const volume = parseFloat(slider.value); const allSettings = getSiteSettings(); allSettings[rule] = { selector, volume }; saveSiteSettings(allSettings); GM_notification({ text: "配置已保存,刷新页面生效", timeout: 2000 }); }; panel.querySelector('#ampClose').onclick = () => panel.remove(); } // ============ Startup Logic ============ const isTop = window.self === window.top; if(isTop) registerMenu(); const { matched, rule } = checkMatch(); if(!matched && isTop) return; const allSettings = getSiteSettings(); const settings = (rule && allSettings[rule]) || { selector: 'video', volume: 1.0 }; if(isTop) { showUI(settings, rule); const uiTitle = document.getElementById('ampStatusTitle'); // Listen for iframe reporting window.addEventListener('message', (e) => { if(e.data?.type === '_n_volumAmpReady' && uiTitle) { uiTitle.innerText = '网页音量增强'; } }); const findVideo = () => { const video = document.querySelector(settings.selector); if(video) { const success = initAmplifier(video, settings.volume) if(uiTitle) { console.log('findVideo success', success); uiTitle.innerText = success ? '网页音量增强' : '音量增强初始化失败,检查是否有其他类似脚本占用