Greasy Fork

Greasy Fork is available in English.

豆包播客音频获取

捕获豆包网页版中的音频数据,支持直接下载、合并下载多个音频 此为修改版 原脚本作者:cenglin123 http://greasyfork.icu/zh-CN/scripts/533430-%E8%B1%86%E5%8C%85%E7%BD%91%E9%A1%B5%E7%89%88%E9%9F%B3%E9%A2%91%E6%8D%95%E8%8E%B7%E4%B8%8E%E5%90%88%E5%B9%B6

// ==UserScript==
// @name         豆包播客音频获取
// @namespace    http://tampermonkey.net/
// @version      0.0.1
// @description  捕获豆包网页版中的音频数据,支持直接下载、合并下载多个音频 此为修改版 原脚本作者:cenglin123 http://greasyfork.icu/zh-CN/scripts/533430-%E8%B1%86%E5%8C%85%E7%BD%91%E9%A1%B5%E7%89%88%E9%9F%B3%E9%A2%91%E6%8D%95%E8%8E%B7%E4%B8%8E%E5%90%88%E5%B9%B6
// @author       木子不是木子狸
// @match        https://www.doubao.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=doubao.com
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        unsafeWindow
// @require      https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.0/lame.min.js
// @license MIT
// ==/UserScript==

(function() {
    'use strict';
    
    // 存储捕获的音频数据
    let capturedAudio = [];
    let isMonitoring = false;
    let originalXHR = unsafeWindow.XMLHttpRequest;
    let originalFetch = unsafeWindow.fetch;
    let observer = null;
    let isDarkMode = false;
    
    // 自动合并下载相关变量
    let autoMergeTimer = null;
    let lastCaptureCount = 0;
    let isAutoMergeEnabled = true; // 默认启用自动合并功能
    
    // 检测暗黑模式
    function detectDarkMode() {
        // 检查系统偏好
        if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
            return true;
        }
        
        // 检查页面背景色
        const bodyBg = window.getComputedStyle(document.body).backgroundColor;
        if (bodyBg) {
            // 转换为RGB值并判断亮度
            const rgb = bodyBg.match(/\d+/g);
            if (rgb && rgb.length >= 3) {
                // 计算亮度 (简化版)
                const brightness = (parseInt(rgb[0]) * 299 + parseInt(rgb[1]) * 587 + parseInt(rgb[2]) * 114) / 1000;
                return brightness < 128;
            }
        }
        
        // 尝试查找豆包网站特定的暗色模式元素
        return document.documentElement.classList.contains('dark') || 
               document.body.classList.contains('dark-theme') ||
               document.querySelector('[data-theme="dark"]') !== null;
    }
    
    // 创建UI
    function createMainInterface() {
        // 检查是否已存在UI
        if (document.getElementById('audio-capture-panel')) {
            const panel = document.getElementById('audio-capture-panel');
            panel.style.display = 'block';
            
            // 如果之前最小化了,还需要检查并恢复正常状态
            if (panel.classList.contains('minimized')) {
                // 确保迷你按钮容器可见
                const miniButtons = panel.querySelector('.mini-buttons-container');
                if (miniButtons) {
                    miniButtons.classList.add('active');
                    miniButtons.style.display = '';
                }
            } else {
                // 显示所有子元素
                Array.from(panel.children).forEach(child => {
                    if (child.id !== 'panel-drag-handle') {
                        child.style.display = '';
                    }
                });
                panel.style.height = 'auto';
            }
            
            return;
        }
        
        // 检测暗黑模式
        isDarkMode = detectDarkMode();
        
        // 添加全局样式
        const globalStyle = document.createElement('style');
        globalStyle.textContent = `
            #audio-capture-panel * {
                box-sizing: border-box;
                font-family: 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif;
            }
            #audio-capture-panel {
                transition: all 0.3s ease;
                user-select: none;
            }
            #panel-drag-handle {
                cursor: move;
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 15px;
                padding-bottom: 10px;
                border-bottom: 1px solid ${isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.05)'};
            }
            #panel-drag-handle h3 {
                margin: 0;
                color: ${isDarkMode ? '#7AB4FF' : '#4285f4'};
                font-size: 16px;
                font-weight: 600;
            }
            .panel-actions {
                display: flex;
                gap: 10px;
                align-items: center;
            }
            .action-buttons-container {
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 10px;
                margin-bottom: 15px;
            }
            .action-button {
                width: 100%;
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                gap: 5px;
                padding: 12px 5px;
                border-radius: 10px;
                transition: all 0.2s;
            }
            .action-button svg {
                width: 22px;
                height: 22px;
                margin-bottom: 5px;
            }
            .action-button-text {
                font-size: 12px;
                font-weight: 500;
            }
            /* 最小化模式样式 */
            #audio-capture-panel.minimized {
                width: auto !important;
                height: auto !important;
                padding: 10px;
                border-radius: 10px;
            }
            .mini-buttons-container {
                display: none;
                flex-direction: row;
                align-items: center;
                gap: 8px;
            }
            .mini-buttons-container.active {
                display: flex !important; /* 使用!important确保优先级 */
            }
            #audio-capture-panel.minimized .mini-buttons-container {
                display: flex;
            }
            .mini-button {
                width: 36px;
                height: 36px;
                border-radius: 8px;
                display: flex;
                align-items: center;
                justify-content: center;
                padding: 0;
                border: none;
                background: ${isDarkMode ? 'rgba(60, 60, 70, 0.7)' : 'rgba(248, 248, 248, 0.9)'};
                color: ${isDarkMode ? '#eee' : '#333'};
                cursor: pointer;
                transition: all 0.2s;
                position: relative;
            }
            .mini-button svg {
                width: 18px;
                height: 18px;
            }
            .mini-button:hover {
                transform: translateY(-2px);
                box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            }
            .mini-button.active {
                background: ${isDarkMode ? '#2D5A9F' : '#4285f4'};
                color: white;
            }
            .mini-button.red {
                background: ${isDarkMode ? '#A1352B' : '#db4437'};
                color: white;
            }
            .mini-button.green {
                background: ${isDarkMode ? '#096D3B' : '#0f9d58'};
                color: white;
            }
            .mini-count {
                position: absolute;
                top: -5px;
                right: -5px;
                background: ${isDarkMode ? '#2D5A9F' : '#4285f4'};
                color: white;
                font-size: 10px;
                font-weight: bold;
                width: 16px;
                height: 16px;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            #audio-capture-panel button {
                transition: all 0.2s ease;
                border: none;
                border-radius: 8px;
                padding: 10px 15px;
                cursor: pointer;
                font-weight: 500;
                font-size: 14px;
                outline: none;
            }
            #audio-capture-panel button:hover {
                filter: brightness(1.1);
                transform: translateY(-2px);
                box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            }
            #audio-capture-panel button:active {
                transform: translateY(0);
                filter: brightness(0.95);
            }
            .audio-capture-modal-backdrop {
                backdrop-filter: blur(8px);
            }
            @keyframes pulse {
                0% { box-shadow: 0 0 0 0 rgba(66, 133, 244, 0.4); }
                70% { box-shadow: 0 0 0 10px rgba(66, 133, 244, 0); }
                100% { box-shadow: 0 0 0 0 rgba(66, 133, 244, 0); }
            }
            .pulse-animation {
                animation: pulse 1.5s infinite;
            }
            #global-play-button {
                position: relative;
                display: flex;
                align-items: center;
                justify-content: center;
                gap: 8px;
                background: linear-gradient(135deg, ${isDarkMode ? '#3a6bc9, #2D5A9F' : '#5294ff, #4285f4'});
                color: white;
                font-weight: 600;
                overflow: hidden;
                width: 100%;
                margin-bottom: 15px;
            }
            #global-play-button::before {
                content: '';
                position: absolute;
                top: 0;
                left: -100%;
                width: 100%;
                height: 100%;
                background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
                transition: all 0.5s;
            }
            #global-play-button:hover::before {
                left: 100%;
            }
            #status-area {
                margin-top: 10px;
                padding: 10px;
                font-size: 12px;
                border-radius: 8px;
                transition: all 0.3s;
                min-height: 20px;
                text-align: center;
            }
        `;
        document.head.appendChild(globalStyle);
        
        const panel = document.createElement('div');
        panel.id = 'audio-capture-panel';
        
        // 根据暗黑模式设置样式
        updatePanelTheme(panel, isDarkMode);
        
        // 添加动画样式
        const animStyle = document.createElement('style');
        animStyle.textContent = `
            @keyframes slideIn {
                from { opacity: 0; transform: translateX(-20px); }
                to { opacity: 1; transform: translateX(0); }
            }
        `;
        document.head.appendChild(animStyle);
        
        panel.innerHTML = `
            <div id="panel-drag-handle">
                <h3>豆包播客音频获取</h3>
                <div class="panel-actions">
                    <button id="minimize-tool" style="background: none; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; border-radius: 50%; padding: 0; color: ${isDarkMode ? '#aaa' : '#666'}; margin-right: 5px;">_</button>
                    <button id="close-tool" style="background: none; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; border-radius: 50%; padding: 0; color: ${isDarkMode ? '#aaa' : '#666'};">✕</button>
                </div>
            </div>
            
            <!-- 最小化模式的按钮组 -->
            <div class="mini-buttons-container">
                <button class="mini-button" id="mini-play" title="播放音频">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M5.536 21.886a1.004 1.004 0 0 0 1.033-.064l13-9a1 1 0 0 0 0-1.644l-13-9A1 1 0 0 0 5 3v18a1 1 0 0 0 .536.886z"></path>
                    </svg>
                </button>
                <button class="mini-button" id="mini-monitor" title="开始/停止监控">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z"/>
                        <path d="M13 7h-2v6h6v-2h-4z"/>
                    </svg>
                </button>
                <button class="mini-button" id="mini-view" title="查看已捕获的音频">
                    <span id="mini-audio-count" class="mini-count">0</span>
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M19 15V5c0-1.103-.897-2-2-2H7c-1.103 0-2 .897-2 2v10c0 1.103.897 2 2 2h10c1.103 0 2-.897 2-2zM7 5h10l.001 10H7V5z"/>
                        <path d="M5 21h12c1.103 0 2-.897 2-2v-2h-2v2H5V7H3v12c0 1.103.897 2 2 2z"/>
                    </svg>
                </button>
                <button class="mini-button green" id="mini-download" title="合并下载">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M12 16l4-5h-3V4h-2v7H8z"/>
                        <path d="M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z"/>
                    </svg>
                </button>
                <button class="mini-button" id="mini-expand" title="展开面板">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M21 11H3c-.6 0-1 .4-1 1s.4 1 1 1h18c.6 0 1-.4 1-1s-.4-1-1-1zm0-4H3c-.6 0-1 .4-1 1s.4 1 1 1h18c.6 0 1-.4 1-1s-.4-1-1-1zm0-4H3c-.6 0-1 .4-1 1s.4 1 1 1h18c.6 0 1-.4 1-1s-.4-1-1-1zm-18 16h18c.6 0 1-.4 1-1s-.4-1-1-1H3c-.6 0-1 .4-1 1s.4 1 1 1z"/>
                    </svg>
                </button>
            </div>
            
            <button id="global-play-button">
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M5.714 8.857h.8l.596-.532 4.366-3.899v15.148L7.11 15.675l-.596-.532H3.095V8.857zm0-2.095 5.295-4.728c1.027-.834 2.562-.103 2.562 1.22v17.492c0 1.324-1.535 2.054-2.562 1.22l-5.295-4.728H3.095A2.095 2.095 0 0 1 1 15.143V8.857c0-1.157.938-2.095 2.095-2.095zM18.03 4.274a1.05 1.05 0 0 1 1.48-.082A10.45 10.45 0 0 1 23 12c0 3.103-1.35 5.892-3.492 7.809a1.048 1.048 0 0 1-1.397-1.562A8.36 8.36 0 0 0 20.905 12a8.36 8.36 0 0 0-2.794-6.247 1.05 1.05 0 0 1-.082-1.48m-.5 3.924a1.048 1.048 0 0 0-1.63 1.318c.606.748.932 1.518.932 2.484 0 .967-.326 1.736-.931 2.484a1.048 1.048 0 0 0 1.629 1.318c.85-1.052 1.397-2.274 1.397-3.802s-.546-2.75-1.397-3.802" clip-rule="evenodd"></path></svg>
                播放音频
            </button>
            
            <div style="margin-bottom: 15px; background: ${isDarkMode ? 'rgba(40, 40, 45, 0.5)' : 'rgba(248, 248, 248, 0.8)'}; padding: 12px; border-radius: 10px;">
                <label for="file-name-prefix" style="display: block; margin-bottom: 8px; font-size: 13px; color: ${isDarkMode ? '#ccc' : '#555'};">文件名前缀:</label>
                <input type="text" id="file-name-prefix" value="doubao_audio" style="width: 100%; padding: 8px; border: 1px solid ${isDarkMode ? '#444' : '#e0e0e0'}; border-radius: 8px; background: ${isDarkMode ? 'rgba(30, 30, 35, 0.7)' : 'rgba(255, 255, 255, 0.9)'}; color: ${isDarkMode ? '#eee' : '#333'}; font-size: 13px; transition: all 0.2s;">
            </div>
            
            <div style="display: flex; align-items: center; margin-bottom: 10px;">
                <input type="checkbox" id="auto-merge-toggle" checked style="margin-right: 8px; width: 16px; height: 16px; accent-color: ${isDarkMode ? '#2D5A9F' : '#4285f4'};">
                <label for="auto-merge-toggle" style="font-size: 13px; color: ${isDarkMode ? '#ccc' : '#555'};">5秒无新音频时自动合并下载</label>
            </div>
            
            <div class="action-buttons-container">
                <button class="action-button" id="monitor-toggle" style="background: ${isMonitoring ? (isDarkMode ? '#A1352B' : '#db4437') : (isDarkMode ? 'rgba(60, 60, 70, 0.5)' : 'rgba(248, 248, 248, 0.8)')}; color: ${isMonitoring ? 'white' : (isDarkMode ? '#eee' : '#333')};">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z"/>
                        <path d="M13 7h-2v6h6v-2h-4z"/>
                    </svg>
                    <span class="action-button-text">
                        ${isMonitoring ? '停止监控' : '开始监控'}
                    </span>
                </button>
                
                <button class="action-button" id="view-captured" style="background: ${isDarkMode ? 'rgba(60, 60, 70, 0.5)' : 'rgba(248, 248, 248, 0.8)'}; color: ${isDarkMode ? '#eee' : '#333'}; position: relative;">
                    <span id="audio-count" style="position: absolute; top: 5px; right: 5px; background: ${isDarkMode ? '#2D5A9F' : '#4285f4'}; color: white; padding: 1px 6px; border-radius: 10px; font-size: 11px; font-weight: bold;">0</span>
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M19 15V5c0-1.103-.897-2-2-2H7c-1.103 0-2 .897-2 2v10c0 1.103.897 2 2 2h10c1.103 0 2-.897 2-2zM7 5h10l.001 10H7V5z"/>
                        <path d="M5 21h12c1.103 0 2-.897 2-2v-2h-2v2H5V7H3v12c0 1.103.897 2 2 2z"/>
                    </svg>
                    <span class="action-button-text">
                        已捕获音频
                    </span>
                </button>
                
                <button class="action-button" id="merge-download" style="background: ${isDarkMode ? '#096D3B' : '#0f9d58'}; color: white;">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M12 16l4-5h-3V4h-2v7H8z"/>
                        <path d="M20 18H4v-7H2v7c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2v-7h-2v7z"/>
                    </svg>
                    <span class="action-button-text">
                        合并下载
                    </span>
                </button>
                
                <button class="action-button" id="clear-all" style="background: ${isDarkMode ? 'rgba(60, 60, 70, 0.5)' : 'rgba(248, 248, 248, 0.8)'}; color: ${isDarkMode ? '#eee' : '#333'};">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M6 7H5v13a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7H6zm10.618-3L15 2H9L7.382 4H3v2h18V4z"/>
                    </svg>
                    <span class="action-button-text">
                        清空列表
                    </span>
                </button>
            </div>
            
            <div style="display: flex; gap: 8px; margin-top: 5px;">
                <button id="direct-download" style="flex: 1; padding: 8px 5px; background: ${isDarkMode ? 'rgba(60, 60, 70, 0.5)' : 'rgba(248, 248, 248, 0.8)'}; color: ${isDarkMode ? '#eee' : '#333'}; border: 1px solid ${isDarkMode ? '#444' : '#e0e0e0'}; font-size: 12px;">解析URL</button>
                <button id="process-base64" style="flex: 1; padding: 8px 5px; background: ${isDarkMode ? 'rgba(60, 60, 70, 0.5)' : 'rgba(248, 248, 248, 0.8)'}; color: ${isDarkMode ? '#eee' : '#333'}; border: 1px solid ${isDarkMode ? '#444' : '#e0e0e0'}; font-size: 12px;">处理Base64</button>
            </div>
            
            <div id="status-area" style="color: ${isDarkMode ? '#aaa' : '#666'}; background: ${isDarkMode ? 'rgba(40, 40, 45, 0.5)' : 'rgba(248, 248, 248, 0.8)'};">工具已准备就绪</div>
        `;
        
        document.body.appendChild(panel);
        
        // 添加面板拖动功能
        makePanelDraggable(panel);
        
        // 更新音频计数
        updateAudioCount();
        
        // 添加事件监听
        document.getElementById('close-tool').addEventListener('click', () => {
            panel.style.display = 'none';
        });

        // 添加最小化按钮功能
        document.getElementById('minimize-tool').addEventListener('click', () => {
            toggleMinimizeMode(panel);
        });
        
        // 添加迷你模式中的展开按钮功能
        document.getElementById('mini-expand').addEventListener('click', () => {
            toggleMinimizeMode(panel);
        });
        
        // 添加迷你模式的其他按钮事件
        document.getElementById('mini-play').addEventListener('click', triggerPageAudioPlay);
        document.getElementById('mini-monitor').addEventListener('click', toggleMonitoring);
        document.getElementById('mini-view').addEventListener('click', showCapturedAudioList);
        document.getElementById('mini-download').addEventListener('click', function() {
            showMergeOptions(true); // 自动全选
        });
        
        // 全局播放按钮
        document.getElementById('global-play-button').addEventListener('click', triggerPageAudioPlay);
        
        document.getElementById('direct-download').addEventListener('click', downloadFromDataUrl);
        document.getElementById('process-base64').addEventListener('click', handleBase64FromRequest);
        document.getElementById('view-captured').addEventListener('click', showCapturedAudioList);
        document.getElementById('merge-download').addEventListener('click', function() {
            // 调用合并选项但自动全选
            showMergeOptions(true);
        });
        
        // 清空列表按钮
        document.getElementById('clear-all').addEventListener('click', function() {
            if (confirm('确定要清空所有已捕获的音频吗?')) {
                capturedAudio = [];
                updateAudioCount();
                saveAudioData();
                updateStatus('已清空音频列表');
                
                // 清除自动合并计时器
                if (autoMergeTimer) {
                    clearTimeout(autoMergeTimer);
                    autoMergeTimer = null;
                }
            }
        });
        
        // 监控网络请求按钮
        document.getElementById('monitor-toggle').addEventListener('click', toggleMonitoring);
        
        // 美化输入框焦点效果
        const fileNameInput = document.getElementById('file-name-prefix');
        fileNameInput.addEventListener('focus', function() {
            this.style.border = `1px solid ${isDarkMode ? '#7AB4FF' : '#4285f4'}`;
            this.style.boxShadow = `0 0 0 2px ${isDarkMode ? 'rgba(122, 180, 255, 0.2)' : 'rgba(66, 133, 244, 0.2)'}`;
        });
        fileNameInput.addEventListener('blur', function() {
            this.style.border = `1px solid ${isDarkMode ? '#444' : '#e0e0e0'}`;
            this.style.boxShadow = 'none';
        });
        
        // 监听暗黑模式变化
        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
            isDarkMode = e.matches || detectDarkMode();
            updatePanelTheme(panel, isDarkMode);
        });
        
        // 每分钟检查一次暗黑模式变化(为了捕捉网站主题切换)
        setInterval(() => {
            const newDarkMode = detectDarkMode();
            if (newDarkMode !== isDarkMode) {
                isDarkMode = newDarkMode;
                updatePanelTheme(panel, isDarkMode);
            }
        }, 60000);
        
        // 自动合并下载复选框
        document.getElementById('auto-merge-toggle').addEventListener('change', function() {
            isAutoMergeEnabled = this.checked;
            updateStatus(`自动合并下载已${isAutoMergeEnabled ? '启用' : '禁用'}`);
        });
    }
    
    // 更新面板主题
    function updatePanelTheme(panel, isDark) {
        panel.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 20px;
            background: ${isDark ? 'rgba(30, 30, 35, 0.85)' : 'rgba(255, 255, 255, 0.85)'};
            border: ${isDark ? '1px solid #444' : 'none'};
            border-radius: 12px;
            padding: 15px;
            box-shadow: ${isDark ? '0 5px 25px rgba(0,0,0,0.25)' : '0 5px 25px rgba(0,0,0,0.15)'};
            z-index: 9999;
            font-family: 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif;
            max-width: 300px;
            width: 300px;
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            color: ${isDark ? '#eee' : '#333'};
            height: auto;
        `;
        
        // 更新其他元素
        if (document.getElementById('close-tool')) {
            document.getElementById('close-tool').style.color = isDark ? '#aaa' : '#666';
        }
        
        if (document.getElementById('monitor-toggle')) {
            const monitorBtn = document.getElementById('monitor-toggle');
            monitorBtn.style.background = isMonitoring ? 
                (isDark ? '#A1352B' : '#db4437') : 
                (isDark ? '#2D5A9F' : '#4285f4');
            monitorBtn.style.boxShadow = `0 2px 5px ${isDark ? 'rgba(0, 0, 0, 0.4)' : isMonitoring ? 'rgba(219, 68, 55, 0.3)' : 'rgba(66, 133, 244, 0.3)'}`;
        }
        
        // 文件名输入框
        if (document.getElementById('file-name-prefix')) {
            const input = document.getElementById('file-name-prefix');
            input.style.background = isDark ? 'rgba(30, 30, 35, 0.7)' : 'rgba(255, 255, 255, 0.9)';
            input.style.color = isDark ? '#eee' : '#333';
            input.style.border = `1px solid ${isDark ? '#444' : '#e0e0e0'}`;
        }
        
        // 查看已捕获的音频按钮
        if (document.getElementById('view-captured')) {
            const btn = document.getElementById('view-captured');
            btn.style.background = isDark ? 'rgba(60, 60, 70, 0.5)' : 'rgba(248, 248, 248, 0.8)';
            btn.style.color = isDark ? '#eee' : '#333';
            btn.style.border = `1px solid ${isDark ? '#444' : '#e0e0e0'}`;
        }
        
        // 音频计数
        if (document.getElementById('audio-count')) {
            document.getElementById('audio-count').style.background = isDark ? '#2D5A9F' : '#4285f4';
        }
        
        // 合并下载按钮
        if (document.getElementById('merge-download')) {
            const btn = document.getElementById('merge-download');
            btn.style.background = isDark ? '#096D3B' : '#0f9d58';
            btn.style.boxShadow = `0 2px 5px ${isDark ? 'rgba(0, 0, 0, 0.4)' : 'rgba(15, 157, 88, 0.3)'}`;
        }
        
        // 清空列表按钮
        if (document.getElementById('clear-all')) {
            const btn = document.getElementById('clear-all');
            btn.style.background = isDark ? '#A1352B' : '#db4437';
            btn.style.boxShadow = `0 2px 5px ${isDark ? 'rgba(0, 0, 0, 0.4)' : 'rgba(219, 68, 55, 0.3)'}`;
        }
        
        // 直接下载和处理Base64按钮
        const smallBtns = ['direct-download', 'process-base64'];
        smallBtns.forEach(id => {
            const btn = document.getElementById(id);
            if (btn) {
                btn.style.background = isDark ? 'rgba(60, 60, 70, 0.5)' : 'rgba(248, 248, 248, 0.8)';
                btn.style.color = isDark ? '#eee' : '#333';
                btn.style.border = `1px solid ${isDark ? '#444' : '#e0e0e0'}`;
            }
        });
        
        // 状态区域
        if (document.getElementById('status-area')) {
            const status = document.getElementById('status-area');
            status.style.background = isDark ? 'rgba(40, 40, 45, 0.5)' : 'rgba(248, 248, 248, 0.8)';
            status.style.color = isDark ? '#aaa' : '#666';
        }
        
        // 全局播放按钮
        if (document.getElementById('global-play-button')) {
            const btn = document.getElementById('global-play-button');
            btn.style.background = `linear-gradient(135deg, ${isDark ? '#3a6bc9, #2D5A9F' : '#5294ff, #4285f4'})`;
        }
    }
    
    // 更新状态区域
    function updateStatus(message) {
        const statusArea = document.getElementById('status-area');
        if (statusArea) {
            // 添加动画效果
            statusArea.style.background = isDarkMode ? 'rgba(45, 90, 159, 0.4)' : '#e8f0fe';
            statusArea.style.color = isDarkMode ? '#aae' : '#4285f4';
            statusArea.textContent = message;
            
            // 3秒后恢复原样
            setTimeout(() => {
                statusArea.style.background = isDarkMode ? 'rgba(40, 40, 45, 0.5)' : 'rgba(248, 248, 248, 0.8)';
                statusArea.style.color = isDarkMode ? '#aaa' : '#666';
            }, 3000);
        }
    }
    
    // 更新音频计数
    function updateAudioCount() {
        const countElement = document.getElementById('audio-count');
        const miniCountElement = document.getElementById('mini-audio-count');
        
        if (countElement) {
            countElement.textContent = capturedAudio.length;
            
            // 如果有新音频,添加脉冲动画效果
            if (capturedAudio.length > 0) {
                countElement.classList.add('pulse-animation');
                setTimeout(() => {
                    countElement.classList.remove('pulse-animation');
                }, 3000);
            }
        }
        
        // 更新迷你模式的计数
        if (miniCountElement) {
            miniCountElement.textContent = capturedAudio.length;
            if (capturedAudio.length > 0) {
                miniCountElement.classList.add('pulse-animation');
                setTimeout(() => {
                    miniCountElement.classList.remove('pulse-animation');
                }, 3000);
            }
        }
    }
    
    // 开始监控网络请求
    function startMonitoring() {
        if (isMonitoring) return;
        isMonitoring = true;
        
        // 拦截XHR请求
        unsafeWindow.XMLHttpRequest = function() {
            const xhr = new originalXHR();
            const originalOpen = xhr.open;
            const originalSend = xhr.send;
            
            xhr.open = function() {
                this.method = arguments[0];
                this.url = arguments[1];
                return originalOpen.apply(this, arguments);
            };
            
            xhr.send = function() {
                this.addEventListener('load', function() {
                    try {
                        // 检查是否是音频相关内容
                        const contentType = this.getResponseHeader('Content-Type') || '';
                        const isAudio = contentType.includes('audio') || 
                                       contentType.includes('octet-stream') ||
                                       this.url.match(/\.(mp3|wav|ogg|aac|flac|m4a)($|\?)/i);
                        
                        if (isAudio || contentType.includes('octet-stream')) {
                            captureAudioFromResponse(this.response, contentType, this.url);
                        }
                    } catch (e) {
                        console.error('处理XHR请求时出错:', e);
                    }
                });
                
                return originalSend.apply(this, arguments);
            };
            
            return xhr;
        };
        
        // 拦截Fetch请求
        unsafeWindow.fetch = function() {
            const url = arguments[0] instanceof Request ? arguments[0].url : arguments[0];
            const method = arguments[0] instanceof Request ? arguments[0].method : 'GET';
            
            return originalFetch.apply(this, arguments).then(response => {
                try {
                    const contentType = response.headers.get('Content-Type') || '';
                    const isAudio = contentType.includes('audio') || 
                                   contentType.includes('octet-stream') ||
                                   url.match(/\.(mp3|wav|ogg|aac|flac|m4a)($|\?)/i);
                    
                    if (isAudio || contentType.includes('octet-stream')) {
                        // 克隆响应以不影响原始处理
                        response.clone().arrayBuffer().then(buffer => {
                            captureAudioFromResponse(buffer, contentType, url);
                        });
                    }
                } catch (e) {
                    console.error('处理Fetch请求时出错:', e);
                }
                
                return response;
            });
        };
        
        // 监控DOM变化以捕获新添加的媒体元素
        observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO') {
                        node.addEventListener('play', () => {
                            if (node.src) {
                                captureAudioFromMediaElement(node);
                            }
                        });
                    }
                });
            });
        });
        
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
        
        // 监控现有的媒体元素
        document.querySelectorAll('audio, video').forEach(mediaElement => {
            mediaElement.addEventListener('play', () => {
                if (mediaElement.src) {
                    captureAudioFromMediaElement(mediaElement);
                }
            });
        });
        
        // 扫描页面中的data URLs
        scanPageForDataUrls();
    }
    
    // 停止监控
    function stopMonitoring() {
        if (!isMonitoring) return;
        isMonitoring = false;
        
        // 恢复原始的XHR和Fetch
        unsafeWindow.XMLHttpRequest = originalXHR;
        unsafeWindow.fetch = originalFetch;
        
        // 停止DOM观察
        if (observer) {
            observer.disconnect();
            observer = null;
        }
    }
    
    // 从响应捕获音频
    function captureAudioFromResponse(response, contentType, url) {
        // 检查是否已捕获
        if (capturedAudio.some(audio => audio.url === url)) {
            return;
        }
        
        const audioItem = {
            id: generateId(),
            source: 'network',
            url: url,
            contentType: contentType,
            timestamp: new Date().toISOString(),
            data: response,
            format: guessAudioFormat(contentType, url),
            size: response ? (response.byteLength || 0) : 0
        };
        
        capturedAudio.push(audioItem);
        updateAudioCount();
        saveAudioData();
        updateStatus(`捕获到新音频: ${getShortUrl(url)}`);
        
        // 重置自动合并计时器
        resetAutoMergeTimer();
    }
    
    // 从媒体元素捕获音频
    function captureAudioFromMediaElement(mediaElement) {
        if (capturedAudio.some(audio => audio.url === mediaElement.src)) {
            return;
        }
        
        const audioItem = {
            id: generateId(),
            source: 'media',
            url: mediaElement.src,
            contentType: 'audio/media',
            timestamp: new Date().toISOString(),
            mediaElement: mediaElement,
            format: 'mp3',
            size: 'unknown'
        };
        
        capturedAudio.push(audioItem);
        updateAudioCount();
        saveAudioData();
        updateStatus(`捕获到媒体元素音频: ${getShortUrl(mediaElement.src)}`);
        
        // 重置自动合并计时器
        resetAutoMergeTimer();
    }
    
    // 生成唯一ID
    function generateId() {
        return 'audio_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
    }
    
    // 获取简短URL
    function getShortUrl(url) {
        if (!url) return 'unknown';
        if (url.startsWith('data:')) return 'data:URL';
        try {
            const urlObj = new URL(url);
            const path = urlObj.pathname;
            if (path.length > 20) {
                return path.substr(0, 17) + '...';
            }
            return path;
        } catch (e) {
            return url.substr(0, 20) + '...';
        }
    }
    
    // 猜测音频格式
    function guessAudioFormat(contentType, url) {
        if (contentType.includes('mpeg') || contentType.includes('mp3')) {
            return 'mp3';
        } else if (contentType.includes('wav')) {
            return 'wav';
        } else if (contentType.includes('ogg')) {
            return 'ogg';
        } else if (contentType.includes('aac')) {
            return 'aac';
        } else if (contentType.includes('flac')) {
            return 'flac';
        } else if (url) {
            // 从URL猜测
            if (url.match(/\.mp3($|\?)/i)) return 'mp3';
            if (url.match(/\.wav($|\?)/i)) return 'wav';
            if (url.match(/\.ogg($|\?)/i)) return 'ogg';
            if (url.match(/\.aac($|\?)/i)) return 'aac';
            if (url.match(/\.flac($|\?)/i)) return 'flac';
        }
        return 'mp3'; // 默认值
    }
    
    // 保存音频数据到GM存储
    function saveAudioData() {
        try {
            // 只保存必要的信息,不保存二进制数据
            const serializedData = capturedAudio.map(audio => {
                const { id, source, url, contentType, timestamp, format, size } = audio;
                return { id, source, url, contentType, timestamp, format, size };
            });
            
            GM_setValue('capturedAudioMeta', JSON.stringify(serializedData));
        } catch (e) {
            console.error('保存音频元数据时出错:', e);
        }
    }
    
    // 加载音频元数据
    function loadAudioData() {
        try {
            const data = GM_getValue('capturedAudioMeta');
            if (data) {
                capturedAudio = JSON.parse(data);
                updateAudioCount();
            }
        } catch (e) {
            console.error('加载音频元数据时出错:', e);
        }
    }
    
    // 扫描页面中的data URLs
    function scanPageForDataUrls() {
        const pageContent = document.documentElement.innerHTML;
        const dataUrlRegex = /data:(application\/octet-stream|audio\/[^;]+);base64,([A-Za-z0-9+/=]{100,})/g;
        
        let match;
        while ((match = dataUrlRegex.exec(pageContent)) !== null) {
            const mimeType = match[1];
            const base64Data = match[2];
            const dataUrl = `data:${mimeType};base64,${base64Data}`;
            
            if (!capturedAudio.some(audio => audio.url === dataUrl)) {
                // 验证是否为有效音频
                validateAudioDataUrl(dataUrl, () => {
                    captureDataUrl(dataUrl, mimeType);
                });
            }
        }
    }
    
    // 验证数据URL是否为有效音频
    function validateAudioDataUrl(dataUrl, callback) {
        const audio = new Audio();
        
        audio.onloadedmetadata = function() {
            // 数据加载成功,是有效音频
            if (audio.duration > 0) {
                callback();
            }
        };
        
        audio.onerror = function() {
            // 尝试作为二进制数据处理
            try {
                fetch(dataUrl)
                    .then(response => response.arrayBuffer())
                    .then(buffer => {
                        // 检查二进制标记
                        const isAudioData = checkAudioSignature(buffer);
                        if (isAudioData) {
                            callback();
                        }
                    });
            } catch (e) {
                // 忽略错误
            }
        };
        
        audio.src = dataUrl;
    }
    
    // 从data URL捕获音频
    function captureDataUrl(dataUrl, mimeType) {
        const audioItem = {
            id: generateId(),
            source: 'dataUrl',
            url: dataUrl,
            contentType: mimeType,
            timestamp: new Date().toISOString(),
            format: guessAudioFormat(mimeType, null),
            size: 'embedded'
        };
        
        capturedAudio.push(audioItem);
        updateAudioCount();
        saveAudioData();
        updateStatus('捕获到data URL音频');
        
        // 重置自动合并计时器
        resetAutoMergeTimer();
    }
    
    // 检查二进制数据是否为音频
    function checkAudioSignature(buffer) {
        if (!buffer || buffer.byteLength < 8) return false;
        
        const view = new Uint8Array(buffer);
        const signatures = {
            // MP3 (ID3)
            'ID3': [0x49, 0x44, 0x33],
            // MP3 (no ID3)
            'MP3': [0xFF, 0xFB],
            // WAV
            'RIFF': [0x52, 0x49, 0x46, 0x46],
            // OGG
            'OGG': [0x4F, 0x67, 0x67, 0x53],
            // FLAC
            'FLAC': [0x66, 0x4C, 0x61, 0x43],
            // M4A/AAC
            'M4A': [0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70],
            // FLV
            'FLV': [0x46, 0x4C, 0x56, 0x01]
        };
        
        for (const [format, sig] of Object.entries(signatures)) {
            let match = true;
            for (let i = 0; i < sig.length; i++) {
                if (view[i] !== sig[i]) {
                    match = false;
                    break;
                }
            }
            if (match) return true;
        }
        
        // 检查字符串标记
        try {
            const textDecoder = new TextDecoder('utf-8');
            const text = textDecoder.decode(new Uint8Array(buffer.slice(0, 100)));
            return text.includes('Lavf') || text.includes('matroska') || text.includes('webm');
        } catch (e) {
            return false;
        }
    }
    
    // 从data URL直接下载
    function downloadFromDataUrl() {
        const audioDataUrl = prompt(
            "请粘贴data:application/octet-stream;base64,开头的URL:",
            ""
        );
        
        if (!audioDataUrl || !audioDataUrl.startsWith('data:')) {
            alert('请提供有效的data URL');
            return;
        }
        
        try {
            // 获取用户设置的文件名前缀
            const fileNamePrefix = document.getElementById('file-name-prefix').value || 'doubao_audio';
            const fileName = `${fileNamePrefix}_${Date.now()}.mp3`;
            
            // 创建下载链接
            const a = document.createElement('a');
            a.href = audioDataUrl;
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            
            updateStatus(`音频下载已启动: ${fileName}`);
            
            // 加入捕获列表
            const mimeType = audioDataUrl.split(';')[0].split(':')[1];
            captureDataUrl(audioDataUrl, mimeType);
        } catch (error) {
            console.error('下载失败:', error);
            alert('下载失败: ' + error.message);
        }
    }
    
    // 处理base64编码的音频数据
    function handleBase64FromRequest() {
        const modal = createModal('处理Base64数据');
        
        const content = document.createElement('div');
        content.innerHTML = `
            <textarea id="base64-input" placeholder="在此粘贴base64编码的音频数据" 
                      style="width: 100%; height: 150px; padding: 8px; margin-bottom: 10px; font-family: monospace; border: 1px solid #ddd; border-radius: 4px;"></textarea>
            <div style="margin-bottom: 10px;">
                <label for="format-select" style="display: block; margin-bottom: 5px;">保存格式:</label>
                <select id="format-select" style="padding: 8px; width: 100%; border: 1px solid #ddd; border-radius: 4px;">
                    <option value="mp3">MP3</option>
                    <option value="wav">WAV</option>
                    <option value="ogg">OGG</option>
                    <option value="flac">FLAC</option>
                </select>
            </div>
            <div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 15px;">
                <button id="cancel-base64" style="padding: 8px 15px; background: #f0f0f0; border: 1px solid #ccc; border-radius: 4px;">取消</button>
                <button id="process-base64-btn" style="padding: 8px 15px; background: #4285f4; color: white; border: none; border-radius: 4px;">处理并下载</button>
            </div>
        `;
        
        modal.appendChild(content);
        
        document.getElementById('cancel-base64').addEventListener('click', () => {
            closeModal(modal);
        });
        
        document.getElementById('process-base64-btn').addEventListener('click', () => {
            const base64Data = document.getElementById('base64-input').value.trim();
            if (!base64Data) {
                alert('请输入base64数据');
                return;
            }
            
            // 移除可能的前缀
            let cleanBase64 = base64Data;
            if (cleanBase64.includes('base64,')) {
                cleanBase64 = cleanBase64.split('base64,')[1];
            }
            
            try {
                // 检查是否为有效base64
                atob(cleanBase64.substring(0, 10));
                
                const format = document.getElementById('format-select').value;
                const mimeTypes = {
                    'mp3': 'audio/mpeg',
                    'wav': 'audio/wav',
                    'ogg': 'audio/ogg',
                    'flac': 'audio/flac'
                };
                
                // 获取用户设置的文件名前缀
                const fileNamePrefix = document.getElementById('file-name-prefix').value || 'doubao_audio';
                const fileName = `${fileNamePrefix}_${Date.now()}.${format}`;
                
                // 创建完整的data URL
                const dataUrl = `data:${mimeTypes[format] || 'application/octet-stream'};base64,${cleanBase64}`;
                
                // 下载文件
                const a = document.createElement('a');
                a.href = dataUrl;
                a.download = fileName;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                
                // 加入捕获列表
                captureDataUrl(dataUrl, mimeTypes[format]);
                
                closeModal(modal);
                updateStatus(`音频处理并下载成功: ${fileName}`);
            } catch (e) {
                alert('无效的base64数据: ' + e.message);
            }
        });
    }
    
    // 显示已捕获的音频列表
    function showCapturedAudioList() {
        if (capturedAudio.length === 0) {
            alert('尚未捕获任何音频');
            return;
        }
        
        const modal = createModal('已捕获的音频列表');
        
        const content = document.createElement('div');
        content.innerHTML = `
            <div style="margin-bottom: 10px;">
                <input type="text" id="search-audio" placeholder="搜索音频..." 
                    style="width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px;">
                <button id="close-audio-list" style="padding: 8px 15px; background: #f0f0f0; border: 1px solid #ccc; float: right; border-radius: 4px;">
                    关闭
                </button>
            </div>
            <div id="audio-list-container" style="max-height: 400px; overflow-y: auto; margin-top: 40px;"></div>
        `;
        
        modal.appendChild(content);
        
        // 添加关闭按钮事件
        document.getElementById('close-audio-list').addEventListener('click', () => {
            closeModal(modal);
        });
        
        // 显示音频列表
        renderAudioList();
        
        // 搜索功能
        document.getElementById('search-audio').addEventListener('input', function() {
            renderAudioList(this.value);
        });
        
        // 渲染音频列表
        function renderAudioList(searchTerm = '') {
            const container = document.getElementById('audio-list-container');
            container.innerHTML = '';
            
            const filteredAudio = searchTerm ? 
                capturedAudio.filter(audio => 
                    audio.url.toLowerCase().includes(searchTerm.toLowerCase()) || 
                    audio.format.toLowerCase().includes(searchTerm.toLowerCase())
                ) : 
                capturedAudio;
            
            if (filteredAudio.length === 0) {
                container.innerHTML = '<p>没有匹配的音频</p>';
                return;
            }
            
            filteredAudio.forEach((audio, index) => {
                const item = document.createElement('div');
                item.style.cssText = `
                    border-bottom: 1px solid #eee;
                    padding: 10px;
                    margin-bottom: 5px;
                    border-radius: 4px;
                    transition: background 0.2s;
                `;
                item.addEventListener('mouseover', () => {
                    item.style.background = '#f9f9f9';
                });
                item.addEventListener('mouseout', () => {
                    item.style.background = 'transparent';
                });
                
                const date = new Date(audio.timestamp).toLocaleString();
                const size = typeof audio.size === 'number' ? 
                    (audio.size / 1024).toFixed(2) + ' KB' : 
                    audio.size;
                
                item.innerHTML = `
                    <div style="display: flex; justify-content: space-between;">
                        <div>
                            <strong>#${index + 1}</strong> - ${audio.format.toUpperCase()}
                        </div>
                        <div style="font-size: 12px; color: #666;">
                            ${date}
                        </div>
                    </div>
                    <div title="${audio.url}" style="font-size: 12px; word-break: break-all; margin: 5px 0;">
                        ${getShortUrl(audio.url)}
                    </div>
                    <div style="font-size: 12px; color: #666;">
                        来源: ${audio.source} | 大小: ${size}
                    </div>
                    <div style="display: flex; gap: 5px; margin-top: 8px;">
                        <input type="checkbox" class="audio-select" data-id="${audio.id}" style="margin-right: 5px;">
                        <button class="download-btn" data-id="${audio.id}" style="padding: 5px 10px; background: #4285f4; color: white; border: none; border-radius: 3px; cursor: pointer;">下载</button>
                        <button class="play-btn" data-id="${audio.id}" style="padding: 5px 10px; background: #0f9d58; color: white; border: none; border-radius: 3px; cursor: pointer;">播放</button>
                        <button class="remove-btn" data-id="${audio.id}" style="padding: 5px 10px; background: #db4437; color: white; border: none; border-radius: 3px; cursor: pointer;">删除</button>
                    </div>
                `;
                
                container.appendChild(item);
            });
            
            // 绑定按钮事件
            document.querySelectorAll('.download-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    const id = this.getAttribute('data-id');
                    downloadAudio(id);
                });
            });
            
            document.querySelectorAll('.play-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    const id = this.getAttribute('data-id');
                    playAudio(id);
                });
            });
            
            document.querySelectorAll('.remove-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    const id = this.getAttribute('data-id');
                    removeAudio(id);
                    renderAudioList(searchTerm);
                });
            });
        }
        
        // 下载音频
        function downloadAudio(id) {
            const audio = capturedAudio.find(a => a.id === id);
            if (!audio) return;
            
            // 获取用户设置的文件名前缀
            const fileNamePrefix = document.getElementById('file-name-prefix').value || 'doubao_audio';
            const fileName = `${fileNamePrefix}_${Date.now()}.${audio.format}`;
            
            if (audio.source === 'dataUrl') {
                // 直接下载data URL
                const a = document.createElement('a');
                a.href = audio.url;
                a.download = fileName;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                
                // 下载后从列表中移除
                removeAudio(id);
                updateStatus(`已下载并移除: ${fileName}`);
            } else if (audio.url) {
                // 下载URL
                GM_download({
                    url: audio.url,
                    name: fileName,
                    onload: () => {
                        updateStatus(`下载完成: ${fileName}`);
                        // 下载后从列表中移除
                        removeAudio(id);
                    },
                    onerror: (e) => {
                        console.error('下载失败:', e);
                        updateStatus('下载失败');
                    }
                });
            }
        }
        
        // 播放音频
        function playAudio(id) {
            const audio = capturedAudio.find(a => a.id === id);
            if (!audio) return;
            
            if (audio.source === 'dataUrl') {
                const audioElement = new Audio(audio.url);
                audioElement.play();
            } else if (audio.mediaElement) {
                audio.mediaElement.play();
            } else if (audio.url) {
                const audioElement = new Audio(audio.url);
                audioElement.play();
            }
        }
        
        // 删除音频
        function removeAudio(id) {
            const index = capturedAudio.findIndex(a => a.id === id);
            if (index !== -1) {
                capturedAudio.splice(index, 1);
                updateAudioCount();
                saveAudioData();
                updateStatus('已删除音频');
            }
        }
    }
    
    // 显示合并选项
    function showMergeOptions(autoSelectAll = false) {
        if (capturedAudio.length === 0) {
            alert('尚未捕获任何音频');
            return;
        }
        
        const modal = createModal('合并下载音频');
        
        const content = document.createElement('div');
        content.innerHTML = `
            <p style="color: ${isDarkMode ? '#ccc' : '#333'};">当前有 ${capturedAudio.length} 个已捕获的音频,您可以选择要合并的音频范围:</p>
            <div style="margin: 15px 0;">
                <div style="display: flex; gap: 10px; align-items: center; margin-bottom: 10px;">
                    <label for="merge-range" style="min-width: 60px; color: ${isDarkMode ? '#ccc' : '#333'};">合并范围:</label>
                    <input type="text" id="merge-range" placeholder="例如: 1-5,7,9-12" style="flex: 1; padding: 8px; border: 1px solid ${isDarkMode ? '#444' : '#ddd'}; border-radius: 8px; background: ${isDarkMode ? 'rgba(40, 40, 45, 0.7)' : 'white'}; color: ${isDarkMode ? '#eee' : '#333'};">
                    <button id="select-all-btn" style="padding: 8px 12px; background: ${isDarkMode ? '#2D5A9F' : '#4285f4'}; color: white; border: none; border-radius: 8px;">全选</button>
                </div>
                <div style="margin-bottom: 10px;">
                    <label for="merge-format" style="color: ${isDarkMode ? '#ccc' : '#333'};">输出格式:</label>
                    <select id="merge-format" style="padding: 8px; margin-left: 10px; border: 1px solid ${isDarkMode ? '#444' : '#ddd'}; border-radius: 8px; background: ${isDarkMode ? 'rgba(40, 40, 45, 0.7)' : 'white'}; color: ${isDarkMode ? '#eee' : '#333'};">
                        <option value="mp3">MP3</option>
                        <option value="wav">WAV</option>
                    </select>
                </div>
                <div style="font-size: 12px; color: ${isDarkMode ? '#888' : '#666'}; margin-top: 5px;">
                    * 范围格式: 单个数字(如5)、范围(如1-5)或组合(如1-3,5,7-9)
                </div>
            </div>
            
            <div style="max-height: 300px; overflow-y: auto; margin: 15px 0; border: 1px solid ${isDarkMode ? '#444' : '#eee'}; padding: 10px; border-radius: 8px; background: ${isDarkMode ? 'rgba(35, 35, 40, 0.5)' : 'rgba(250, 250, 250, 0.9)'};">
                <h4 style="margin-top: 0; color: ${isDarkMode ? '#ccc' : '#333'};">可选择的音频列表:</h4>
                <div id="merge-audio-list"></div>
            </div>
            
            <div style="display: flex; justify-content: flex-end; gap: 10px;">
                <button id="cancel-merge" style="padding: 10px 15px; background: ${isDarkMode ? 'rgba(60, 60, 70, 0.7)' : '#f0f0f0'}; color: ${isDarkMode ? '#ccc' : '#333'}; border: ${isDarkMode ? '1px solid #444' : '1px solid #ccc'}; border-radius: 8px;">取消</button>
                <button id="start-merge" style="padding: 10px 15px; background: ${isDarkMode ? '#096D3B' : '#0f9d58'}; color: white; border: none; border-radius: 8px; font-weight: bold;">开始合并</button>
            </div>
        `;
        
        modal.appendChild(content);
        
        // 显示音频列表
        const audioListContainer = document.getElementById('merge-audio-list');
        capturedAudio.forEach((audio, index) => {
            const item = document.createElement('div');
            item.style.cssText = `
                display: flex;
                align-items: center;
                padding: 10px;
                margin-bottom: 6px;
                border-bottom: 1px solid ${isDarkMode ? '#333' : '#f0f0f0'};
                border-radius: 6px;
                transition: background 0.2s;
            `;
            
            item.addEventListener('mouseover', () => {
                item.style.background = isDarkMode ? 'rgba(55, 55, 60, 0.5)' : '#f9f9f9';
            });
            
            item.addEventListener('mouseout', () => {
                item.style.background = 'transparent';
            });
            
            item.innerHTML = `
                <input type="checkbox" class="merge-select" data-index="${index}" id="merge-item-${index}" style="margin-right: 12px; width: 18px; height: 18px; accent-color: ${isDarkMode ? '#2D5A9F' : '#4285f4'};">
                <label for="merge-item-${index}" style="flex: 1; cursor: pointer; color: ${isDarkMode ? '#ccc' : '#333'};">
                    <strong>#${index + 1}</strong> - ${audio.format.toUpperCase()} 
                    <span style="font-size: 12px; color: ${isDarkMode ? '#888' : '#666'};">(${getShortUrl(audio.url)})</span>
                </label>
            `;
            
            audioListContainer.appendChild(item);
        });
        
        // 取消按钮
        document.getElementById('cancel-merge').addEventListener('click', () => {
            closeModal(modal);
        });
        
        // 全选按钮
        document.getElementById('select-all-btn').addEventListener('click', () => {
            document.getElementById('merge-range').value = `1-${capturedAudio.length}`;
            document.querySelectorAll('.merge-select').forEach(checkbox => {
                checkbox.checked = true;
            });
        });
        
        // 如果设置了自动全选
        if (autoSelectAll) {
            setTimeout(() => {
                document.getElementById('select-all-btn').click();
            }, 100);
        }
        
        // 范围输入框事件
        document.getElementById('merge-range').addEventListener('input', function() {
            const range = this.value.trim();
            if (!range) {
                document.querySelectorAll('.merge-select').forEach(checkbox => {
                    checkbox.checked = false;
                });
                return;
            }
            
            // 解析范围
            const indices = parseRangeString(range, capturedAudio.length);
            
            // 更新复选框
            document.querySelectorAll('.merge-select').forEach(checkbox => {
                const index = parseInt(checkbox.getAttribute('data-index'));
                checkbox.checked = indices.includes(index);
            });
        });
        
        // 复选框变化时更新范围
        document.querySelectorAll('.merge-select').forEach(checkbox => {
            checkbox.addEventListener('change', () => {
                // 获取所有选中的索引
                const selectedIndices = [];
                document.querySelectorAll('.merge-select:checked').forEach(cb => {
                    selectedIndices.push(parseInt(cb.getAttribute('data-index')));
                });
                
                // 生成范围字符串
                document.getElementById('merge-range').value = generateRangeString(selectedIndices);
            });
        });
        
        // 开始合并按钮
        document.getElementById('start-merge').addEventListener('click', () => {
            const range = document.getElementById('merge-range').value.trim();
            if (!range) {
                alert('请选择要合并的音频范围');
                return;
            }
            
            const indices = parseRangeString(range, capturedAudio.length);
            if (indices.length === 0) {
                alert('未选择任何有效的音频');
                return;
            }
            
            const format = document.getElementById('merge-format').value;
            
            // 开始合并
            mergeAudio(indices, format);
            closeModal(modal);
            
            // 清除自动合并计时器,因为用户已手动触发合并
            if (autoMergeTimer) {
                clearTimeout(autoMergeTimer);
                autoMergeTimer = null;
            }
        });
    }
    
    // 解析范围字符串,例如 "1-3,5,7-9"
    function parseRangeString(rangeStr, maxValue) {
        const result = [];
        const parts = rangeStr.split(',');
        
        for (const part of parts) {
            const trimmed = part.trim();
            if (!trimmed) continue;
            
            if (trimmed.includes('-')) {
                // 范围
                const [start, end] = trimmed.split('-').map(n => parseInt(n.trim()));
                // 转换为0-based索引
                const startIndex = Math.max(0, start - 1);
                const endIndex = Math.min(maxValue - 1, end - 1);
                
                if (!isNaN(startIndex) && !isNaN(endIndex) && startIndex <= endIndex) {
                    for (let i = startIndex; i <= endIndex; i++) {
                        if (!result.includes(i)) result.push(i);
                    }
                }
            } else {
                // 单个数字
                const index = parseInt(trimmed) - 1; // 转换为0-based索引
                if (!isNaN(index) && index >= 0 && index < maxValue && !result.includes(index)) {
                    result.push(index);
                }
            }
        }
        
        return result.sort((a, b) => a - b);
    }
    
    // 生成范围字符串
    function generateRangeString(indices) {
        if (indices.length === 0) return '';
        
        // 排序
        indices.sort((a, b) => a - b);
        
        const ranges = [];
        let start = indices[0];
        let end = start;
        
        for (let i = 1; i < indices.length; i++) {
            if (indices[i] === end + 1) {
                end = indices[i];
            } else {
                // 结束当前范围
                if (start === end) {
                    ranges.push((start + 1).toString()); // 转换为1-based显示
                } else {
                    ranges.push(`${start + 1}-${end + 1}`); // 转换为1-based显示
                }
                
                // 开始新范围
                start = end = indices[i];
            }
        }
        
        // 添加最后一个范围
        if (start === end) {
            ranges.push((start + 1).toString()); // 转换为1-based显示
        } else {
            ranges.push(`${start + 1}-${end + 1}`); // 转换为1-based显示
        }
        
        return ranges.join(',');
    }
    
    // 合并音频
    function mergeAudio(indices, format) {
        // 检查索引
        if (indices.length === 0) {
            alert('未选择任何音频');
            return;
        }
        
        // 创建进度模态框
        const modal = createModal('音频合并进度');
        
        const content = document.createElement('div');
        content.innerHTML = `
            <div style="text-align: center; margin: 20px 0;">
                <div id="merge-progress-text">准备合并 ${indices.length} 个音频文件...</div>
                <div style="margin: 15px 0; background: #f0f0f0; border-radius: 4px; overflow: hidden;">
                    <div id="merge-progress-bar" style="width: 0%; height: 20px; background: #0f9d58; transition: width 0.3s;"></div>
                </div>
                <div id="merge-status">正在初始化...</div>
            </div>
        `;
        
        modal.appendChild(content);
        
        // 开始合并流程
        setTimeout(() => {
            startMergeProcess(indices, format, modal);
        }, 500);
    }
    
    // 开始合并流程
    async function startMergeProcess(indices, format, modal) {
        try {
            updateMergeProgress(5, '开始下载音频数据...');
            
            // 准备音频数据
            const audioBuffers = [];
            let currentIndex = 0;
            
            for (const index of indices) {
                currentIndex++;
                const progress = 5 + Math.floor((currentIndex / indices.length) * 50);
                updateMergeProgress(progress, `正在处理第 ${currentIndex}/${indices.length} 个音频...`);
                
                const audio = capturedAudio[index];
                if (!audio) continue;
                
                try {
                    const buffer = await getAudioBuffer(audio);
                    if (buffer) {
                        // 如果是MP3格式且需要合并为MP3,直接添加
                        if (format === 'mp3' && (audio.format === 'mp3' || isValidMp3(buffer))) {
                            audioBuffers.push(buffer);
                        } else {
                            // 如果需要转换格式,仍需添加
                            audioBuffers.push(buffer);
                        }
                    }
                } catch (e) {
                    console.error(`处理第 ${index + 1} 个音频时出错:`, e);
                    updateMergeStatus(`处理第 ${index + 1} 个音频时出错: ${e.message}`);
                }
            }
            
            if (audioBuffers.length === 0) {
                updateMergeStatus('没有有效的音频数据可合并');
                setTimeout(() => closeModal(modal), 3000);
                return;
            }
            
            updateMergeProgress(60, `已加载 ${audioBuffers.length} 个音频,开始合并...`);
            
            // 合并音频
            const mergedAudio = await mergeAudioBuffers(audioBuffers, format);
            updateMergeProgress(90, '合并完成,准备下载...');
            
            // 获取用户设置的文件名前缀
            const fileNamePrefix = document.getElementById('file-name-prefix').value || 'doubao_merged';
            const fileName = `${fileNamePrefix}_${Date.now()}.${format}`;
            
            // 下载合并后的文件
            const blob = new Blob([mergedAudio], { type: format === 'mp3' ? 'audio/mpeg' : 'audio/wav' });
            const url = URL.createObjectURL(blob);
            
            const a = document.createElement('a');
            a.href = url;
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            
            updateMergeProgress(100, `合并完成,已开始下载 ${fileName}!`);
            updateStatus(`已成功合并 ${audioBuffers.length} 个音频文件并下载`);
            
            // 3秒后关闭窗口
            setTimeout(() => {
                closeModal(modal);
                
                // 询问用户是否要清空列表
                if (confirm('下载完成,是否清空音频列表?')) {
                    capturedAudio = [];
                    updateAudioCount();
                    saveAudioData();
                    updateStatus('已清空音频列表');
                }
            }, 3000);
        } catch (error) {
            console.error('合并音频过程中出错:', error);
            updateMergeStatus(`合并失败: ${error.message}`);
        }
    }
    
    // 获取音频的ArrayBuffer数据
    // 处理base64编码的音频数据
    async function getAudioBuffer(audio) {
        return new Promise(async (resolve, reject) => {
            try {
                if (audio.data instanceof ArrayBuffer) {
                    // 已经有ArrayBuffer数据
                    resolve(audio.data);
                } else if (audio.source === 'dataUrl') {
                    // 如果是data URL,先检查是否为base64编码的MP3
                    if (audio.url.startsWith('data:application/octet-stream;base64,') || 
                        audio.url.startsWith('data:audio/mpeg;base64,')) {
                        // 直接从data URL获取二进制数据
                        const base64Data = audio.url.split('base64,')[1];
                        const binaryString = atob(base64Data);
                        const bytes = new Uint8Array(binaryString.length);
                        for (let i = 0; i < binaryString.length; i++) {
                            bytes[i] = binaryString.charCodeAt(i);
                        }
                        resolve(bytes.buffer);
                    } else {
                        // 其他类型的data URL
                        fetch(audio.url)
                            .then(response => response.arrayBuffer())
                            .then(buffer => resolve(buffer))
                            .catch(reject);
                    }
                } else if (audio.url) {
                    // 从URL获取
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: audio.url,
                        responseType: 'arraybuffer',
                        onload: function(response) {
                            resolve(response.response);
                        },
                        onerror: function(error) {
                            reject(new Error('无法下载音频: ' + error));
                        }
                    });
                } else {
                    reject(new Error('无法获取音频数据'));
                }
            } catch (e) {
                reject(e);
            }
        });
    }
    
    // 合并音频缓冲区
    // 直接合并MP3文件
    async function mergeAudioBuffers(audioBuffers, format) {
        return new Promise(async (resolve, reject) => {
            try {
                // 如果选择的格式不是MP3,仍使用旧方法
                if (format !== 'mp3') {
                    updateMergeStatus('非MP3格式仍需要完整处理,可能需要较长时间...');
                    // 使用之前的方法处理
                    // ...原来的mergeAudioBuffers代码...
                    return;
                }
                
                updateMergeStatus('正在直接合并MP3文件...');
                
                // 检查每个文件的MP3头,确保是有效的MP3
                const validMp3Buffers = [];
                for (let i = 0; i < audioBuffers.length; i++) {
                    const buffer = audioBuffers[i];
                    // 简单检查是否为MP3文件
                    if (isValidMp3(buffer)) {
                        validMp3Buffers.push(buffer);
                    } else {
                        console.warn(`跳过第${i+1}个非MP3格式文件`);
                    }
                    
                    updateMergeProgress(60 + Math.floor((i / audioBuffers.length) * 30), 
                        `正在处理第 ${i + 1}/${audioBuffers.length} 个文件...`);
                }
                
                if (validMp3Buffers.length === 0) {
                    reject(new Error('没有有效的MP3文件可以合并'));
                    return;
                }
                
                updateMergeStatus(`正在合并 ${validMp3Buffers.length} 个MP3文件...`);
                
                // 直接拼接MP3文件内容
                const totalLength = validMp3Buffers.reduce((total, buffer) => total + buffer.byteLength, 0);
                const mergedMp3 = new Uint8Array(totalLength);
                
                let offset = 0;
                for (const buffer of validMp3Buffers) {
                    const data = new Uint8Array(buffer);
                    mergedMp3.set(data, offset);
                    offset += buffer.byteLength;
                    
                    updateMergeProgress(90, `已合并 ${offset} / ${totalLength} 字节...`);
                }
                
                updateMergeProgress(95, '合并完成,准备下载...');
                resolve(mergedMp3.buffer);
                
            } catch (e) {
                reject(e);
            }
        });
    }

    // 简单检查是否为有效的MP3文件
    function isValidMp3(buffer) {
        if (!buffer || buffer.byteLength < 3) return false;
        
        const view = new Uint8Array(buffer);
        
        // 检查ID3v2标记
        if (view[0] === 0x49 && view[1] === 0x44 && view[2] === 0x33) {
            return true;
        }
        
        // 检查MP3帧头标记 (通常以0xFF开头)
        for (let i = 0; i < Math.min(100, view.length); i++) {
            if (view[i] === 0xFF && (view[i+1] & 0xE0) === 0xE0) {
                return true;
            }
        }
        
        return false;
    }
    
    // 编码为MP3
    // 优化的MP3编码
    function encodeOptimizedMp3(audioBuffer, sampleRate, callback) {
        const channels = audioBuffer.numberOfChannels;
        const mp3encoder = new lamejs.Mp3Encoder(channels, sampleRate, 128);
        
        // 获取音频样本,一次性处理以减少循环次数
        const samples = getInterleavedSamples(audioBuffer);
        const mp3Data = [];
        
        // 使用更大的块大小,减少处理次数
        const chunkSize = 1152 * 10; // 增加批量处理量
        
        // 创建工作器函数用于批处理
        const processChunks = (startIndex) => {
            let endTime = Date.now() + 50; // 每50ms让出主线程
            let currentIndex = startIndex;
            
            while (currentIndex < samples.length && Date.now() < endTime) {
                const end = Math.min(currentIndex + chunkSize, samples.length);
                const chunk = samples.subarray(currentIndex, end);
                const mp3buf = mp3encoder.encodeBuffer(chunk);
                
                if (mp3buf.length > 0) {
                    mp3Data.push(mp3buf);
                }
                
                currentIndex = end;
                
                // 仅在处理一定量数据后更新进度,减少DOM操作
                if (currentIndex % (chunkSize * 10) === 0) {
                    const progress = 80 + Math.floor((currentIndex / samples.length) * 10);
                    updateMergeProgress(progress, `正在编码MP3: ${Math.floor(currentIndex / samples.length * 100)}%...`);
                }
            }
            
            // 所有数据处理完毕或时间片用完
            if (currentIndex < samples.length) {
                // 还有数据要处理,安排下一个时间片
                setTimeout(() => processChunks(currentIndex), 0);
            } else {
                // 所有数据处理完毕,结束编码
                finishEncoding();
            }
        };
        
        // 完成编码
        const finishEncoding = () => {
            const end = mp3encoder.flush();
            if (end.length > 0) {
                mp3Data.push(end);
            }
            
            // 合并所有数据
            const totalLength = mp3Data.reduce((acc, buf) => acc + buf.length, 0);
            const mp3Array = new Uint8Array(totalLength);
            let offset = 0;
            
            for (const buf of mp3Data) {
                mp3Array.set(buf, offset);
                offset += buf.length;
            }
            
            callback(mp3Array.buffer);
        };
        
        // 开始处理
        processChunks(0);
    }

    // 优化的交错样本获取(用于MP3编码)
    function getInterleavedSamples(audioBuffer) {
        const channels = audioBuffer.numberOfChannels;
        const length = audioBuffer.length * channels;
        const result = new Int16Array(length);
        
        // 预先获取所有通道数据,避免反复调用getChannelData
        const channelsData = [];
        for (let channel = 0; channel < channels; channel++) {
            channelsData.push(audioBuffer.getChannelData(channel));
        }
        
        // 块处理,减少循环次数
        const blockSize = 8192;
        for (let blockStart = 0; blockStart < audioBuffer.length; blockStart += blockSize) {
            const blockEnd = Math.min(blockStart + blockSize, audioBuffer.length);
            
            for (let i = blockStart; i < blockEnd; i++) {
                for (let channel = 0; channel < channels; channel++) {
                    const sample = Math.max(-1, Math.min(1, channelsData[channel][i]));
                    result[i * channels + channel] = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
                }
            }
        }
        
        return result;
    }

    // 编码为WAV
    // 优化的WAV编码
    function encodeOptimizedWAV(audioBuffer) {
        const numChannels = audioBuffer.numberOfChannels;
        const sampleRate = audioBuffer.sampleRate;
        const format = 1; // PCM格式
        const bitsPerSample = 16;
        const bytesPerSample = bitsPerSample / 8;
        const blockAlign = numChannels * bytesPerSample;
        const bytesPerSecond = sampleRate * blockAlign;
        
        // 一次性获取所有样本数据
        const samplesData = getWavSamples(audioBuffer);
        
        const buffer = new ArrayBuffer(44 + samplesData.length);
        const view = new DataView(buffer);
        
        // WAV头
        writeString(view, 0, 'RIFF');
        view.setUint32(4, 36 + samplesData.length, true);
        writeString(view, 8, 'WAVE');
        writeString(view, 12, 'fmt ');
        view.setUint32(16, 16, true);
        view.setUint16(20, format, true);
        view.setUint16(22, numChannels, true);
        view.setUint32(24, sampleRate, true);
        view.setUint32(28, bytesPerSecond, true);
        view.setUint16(32, blockAlign, true);
        view.setUint16(34, bitsPerSample, true);
        writeString(view, 36, 'data');
        view.setUint32(40, samplesData.length, true);
        
        // 写入样本数据
        const uint8 = new Uint8Array(buffer);
        uint8.set(samplesData, 44);
        
        return buffer;
    }

    // 优化的WAV样本获取
    function getWavSamples(audioBuffer) {
        const numChannels = audioBuffer.numberOfChannels;
        const length = audioBuffer.length;
        const samples = new Uint8Array(length * numChannels * 2);
        let offset = 0;
        
        // 预先获取所有通道数据
        const channelsData = [];
        for (let channel = 0; channel < numChannels; channel++) {
            channelsData.push(audioBuffer.getChannelData(channel));
        }
        
        // 批量处理样本
        const processBlock = 10000;
        
        for (let blockStart = 0; blockStart < length; blockStart += processBlock) {
            const blockEnd = Math.min(blockStart + processBlock, length);
            
            for (let i = blockStart; i < blockEnd; i++) {
                for (let channel = 0; channel < numChannels; channel++) {
                    const sample = Math.max(-1, Math.min(1, channelsData[channel][i]));
                    const value = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
                    samples[offset++] = value & 0xFF;
                    samples[offset++] = (value >> 8) & 0xFF;
                }
            }
            
            // 只在每个块完成后更新进度
            const progress = 80 + Math.floor((blockEnd / length) * 10);
            updateMergeProgress(progress, `正在编码WAV: ${Math.floor(blockEnd / length * 100)}%...`);
        }
        
        return samples;
    }
    
    // 辅助函数:写入字符串到DataView
    function writeString(view, offset, string) {
        for (let i = 0; i < string.length; i++) {
            view.setUint8(offset + i, string.charCodeAt(i));
        }
    }
    
    // 更新合并进度
    function updateMergeProgress(percent, message) {
        const progressBar = document.getElementById('merge-progress-bar');
        const progressText = document.getElementById('merge-progress-text');
        
        if (progressBar && progressText) {
            progressBar.style.width = `${percent}%`;
            progressText.textContent = message || `进度: ${percent}%`;
        }
    }
    
    // 更新合并状态
    function updateMergeStatus(message) {
        const statusElement = document.getElementById('merge-status');
        if (statusElement) {
            statusElement.textContent = message;
        }
    }
    
    // 辅助函数:创建模态框
    function createModal(title) {
        // 检查是否已存在模态框
        const existingModal = document.querySelector('.audio-capture-modal-backdrop');
        if (existingModal && document.body.contains(existingModal)) {
            document.body.removeChild(existingModal);
        }
        
        // 检测暗黑模式
        const currentDarkMode = detectDarkMode();
        
        // 创建背景
        const modalBackdrop = document.createElement('div');
        modalBackdrop.className = 'audio-capture-modal-backdrop';
        modalBackdrop.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: ${currentDarkMode ? 'rgba(0, 0, 0, 0.7)' : 'rgba(0, 0, 0, 0.5)'};
            z-index: 10000;
            display: flex;
            justify-content: center;
            align-items: center;
            backdrop-filter: blur(8px);
            -webkit-backdrop-filter: blur(8px);
            transition: opacity 0.3s;
        `;
        
        // 创建模态框
        const modal = document.createElement('div');
        modal.className = 'audio-capture-modal';
        modal.style.cssText = `
            background: ${currentDarkMode ? 'rgba(35, 35, 40, 0.9)' : 'rgba(255, 255, 255, 0.9)'};
            border-radius: 12px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, ${currentDarkMode ? '0.4' : '0.2'});
            width: 90%;
            max-width: 600px;
            max-height: 85vh;
            overflow-y: auto;
            z-index: 10001;
            padding: 25px;
            animation: modalFadeIn 0.3s ease-out;
            border: ${currentDarkMode ? '1px solid #444' : 'none'};
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            color: ${currentDarkMode ? '#eee' : '#333'};
        `;
        
        // 添加动画样式
        const style = document.createElement('style');
        style.textContent = `
            @keyframes modalFadeIn {
                from { opacity: 0; transform: translateY(-10px) scale(0.98); }
                to { opacity: 1; transform: translateY(0) scale(1); }
            }
            .audio-capture-modal::-webkit-scrollbar {
                width: 8px;
            }
            .audio-capture-modal::-webkit-scrollbar-track {
                background: ${currentDarkMode ? '#333' : '#f1f1f1'};
                border-radius: 4px;
            }
            .audio-capture-modal::-webkit-scrollbar-thumb {
                background: ${currentDarkMode ? '#666' : '#c1c1c1'};
                border-radius: 4px;
            }
            .audio-capture-modal::-webkit-scrollbar-thumb:hover {
                background: ${currentDarkMode ? '#888' : '#a1a1a1'};
            }
        `;
        document.head.appendChild(style);
        
        // 标题
        const titleElement = document.createElement('h3');
        titleElement.textContent = title;
        titleElement.style.cssText = `
            margin-top: 0;
            margin-bottom: 20px;
            padding-bottom: 12px;
            border-bottom: 1px solid ${currentDarkMode ? '#444' : '#eee'};
            color: ${currentDarkMode ? '#7AB4FF' : '#4285f4'};
            font-size: 18px;
            font-weight: 600;
        `;
        
        // 添加到DOM
        modal.appendChild(titleElement);
        modalBackdrop.appendChild(modal);
        document.body.appendChild(modalBackdrop);
        
        return modal;
    }
    
    // 关闭模态框
    // 修改 closeModal 函数
    function closeModal(modal) {
        try {
            const backdrop = modal.parentElement;
            if (backdrop && document.body.contains(backdrop)) {
                document.body.removeChild(backdrop);
            }
        } catch (e) {
            console.error('关闭模态框时出错:', e);
            // 备用方案:查找并移除所有模态框背景
            document.querySelectorAll('.audio-capture-modal-backdrop').forEach(element => {
                if (document.body.contains(element)) {
                    document.body.removeChild(element);
                }
            });
        }
    }
    
    // 注册GM菜单
    GM_registerMenuCommand('打开豆包音频捕获工具', createMainInterface);
    GM_registerMenuCommand('开始/停止监控', function() {
        if (isMonitoring) {
            stopMonitoring();
            updateStatus('已停止监控网络请求');
        } else {
            startMonitoring();
            updateStatus('正在监控网络请求...');
        }
    });
    GM_registerMenuCommand('查看已捕获的音频', showCapturedAudioList);
    GM_registerMenuCommand('合并下载音频', showMergeOptions);
    
    // 加载保存的音频元数据
    loadAudioData();
    
    // 自动创建UI
    window.addEventListener('load', function() {
        setTimeout(createMainInterface, 1000);
    });
    
    // 触发页面上的音频播放按钮
    function triggerPageAudioPlay() {
        try {
            // 寻找页面上的播放按钮
            const playButton = document.querySelector('button[data-testid="audio_play_button"]');
            
            if (playButton) {
                // 模拟点击
                playButton.click();
                updateStatus('已触发页面音频播放');
                
                // 添加视觉反馈
                const globalPlayBtn = document.getElementById('global-play-button');
                if (globalPlayBtn) {
                    globalPlayBtn.classList.add('pulse-animation');
                    setTimeout(() => {
                        globalPlayBtn.classList.remove('pulse-animation');
                    }, 2000);
                }
            } else {
                updateStatus('未找到页面上的音频播放按钮');
            }
        } catch (e) {
            console.error('触发音频播放失败:', e);
            updateStatus('触发音频播放失败');
        }
    }
    
    // 使面板可拖动
    function makePanelDraggable(panel) {
        const dragHandle = document.getElementById('panel-drag-handle');
        let isDragging = false;
        let offsetX, offsetY;
        
        // 添加恢复位置功能
        restorePosition(panel);
        
        dragHandle.addEventListener('mousedown', startDrag);
        dragHandle.addEventListener('touchstart', startDrag, { passive: false });
        
        function startDrag(e) {
            // 阻止事件默认行为,避免文本选择等
            e.preventDefault();
            
            // 获取鼠标或触摸事件坐标
            const clientX = e.clientX || (e.touches && e.touches[0].clientX);
            const clientY = e.clientY || (e.touches && e.touches[0].clientY);
            
            if (clientX === undefined || clientY === undefined) return;
            
            // 计算偏移量
            const rect = panel.getBoundingClientRect();
            offsetX = clientX - rect.left;
            offsetY = clientY - rect.top;
            
            isDragging = true;
            
            // 记录当前高度并设置为固定值,防止拖动时变形
            if (!panel.classList.contains('minimized')) {
                panel.dataset.originalHeight = panel.offsetHeight + 'px';
                panel.style.height = panel.offsetHeight + 'px';
            }
            
            // 添加拖动和结束拖动事件监听
            document.addEventListener('mousemove', doDrag);
            document.addEventListener('touchmove', doDrag, { passive: false });
            document.addEventListener('mouseup', stopDrag);
            document.addEventListener('touchend', stopDrag);
            
            // 添加拖动样式
            panel.style.transition = 'none';
            dragHandle.style.cursor = 'grabbing';
        }
        
        function doDrag(e) {
            if (!isDragging) return;
            
            e.preventDefault();
            
            // 获取鼠标或触摸事件坐标
            const clientX = e.clientX || (e.touches && e.touches[0].clientX);
            const clientY = e.clientY || (e.touches && e.touches[0].clientY);
            
            if (clientX === undefined || clientY === undefined) return;
            
            // 计算新位置
            const newLeft = clientX - offsetX;
            const newTop = clientY - offsetY;
            
            // 边界处理,防止面板拖出视口
            const maxX = window.innerWidth - panel.offsetWidth;
            const maxY = window.innerHeight - panel.offsetHeight;
            
            panel.style.left = `${Math.max(0, Math.min(newLeft, maxX))}px`;
            panel.style.top = `${Math.max(0, Math.min(newTop, maxY))}px`;
            
            // 清除底部定位,确保面板完全跟随鼠标移动
            panel.style.bottom = 'auto';
            
            // 如果到达顶部区域,添加贴附效果
            if (newTop < 20) {
                panel.style.top = '0px';
            }
        }
        
        function stopDrag() {
            if (!isDragging) return;
            
            isDragging = false;
            
            // 移除事件监听
            document.removeEventListener('mousemove', doDrag);
            document.removeEventListener('touchmove', doDrag);
            document.removeEventListener('mouseup', stopDrag);
            document.removeEventListener('touchend', stopDrag);
            
            // 恢复样式
            panel.style.transition = 'box-shadow 0.3s ease';
            dragHandle.style.cursor = 'move';
            
            // 如果不是最小化状态,恢复自动高度
            if (!panel.classList.contains('minimized') && panel.dataset.originalHeight) {
                panel.style.height = 'auto';
            }
            
            // 保存面板位置
            savePosition(panel.style.left, panel.style.top);
        }
    }
    
    // 保存面板位置
    function savePosition(left, top) {
        try {
            GM_setValue('panelPosition', JSON.stringify({ left, top }));
        } catch(e) {
            console.error('保存面板位置失败:', e);
        }
    }
    
    // 恢复面板位置
    function restorePosition(panel) {
        try {
            const position = GM_getValue('panelPosition');
            if (position) {
                const { left, top } = JSON.parse(position);
                if (left && top) {
                    panel.style.left = left;
                    panel.style.top = top;
                }
            }
        } catch(e) {
            console.error('恢复面板位置失败:', e);
        }
    }
    
    // 切换监控状态
    function toggleMonitoring() {
        if (isMonitoring) {
            stopMonitoring();
            if (document.getElementById('monitor-toggle')) {
                document.getElementById('monitor-toggle').textContent = '开始监控';
                document.getElementById('monitor-toggle').style.background = isDarkMode ? '#2D5A9F' : '#4285f4';
            }
            updateStatus('已停止监控网络请求');
            
            // 更新迷你模式按钮状态
            const miniMonitorBtn = document.getElementById('mini-monitor');
            if (miniMonitorBtn) {
                miniMonitorBtn.classList.remove('active');
            }
        } else {
            startMonitoring();
            if (document.getElementById('monitor-toggle')) {
                document.getElementById('monitor-toggle').textContent = '停止监控';
                document.getElementById('monitor-toggle').style.background = isDarkMode ? '#A1352B' : '#db4437';
            }
            updateStatus('正在监控网络请求...');
            
            // 更新迷你模式按钮状态
            const miniMonitorBtn = document.getElementById('mini-monitor');
            if (miniMonitorBtn) {
                miniMonitorBtn.classList.add('active');
            }
        }
    }
    
    // 切换最小化模式
    function toggleMinimizeMode(panel) {
        // 保存当前位置
        const currentLeft = panel.style.left;
        const currentTop = panel.style.top;
        
        // 获取迷你按钮容器
        const miniButtons = panel.querySelector('.mini-buttons-container');
        
        // 如果面板已经最小化,则恢复
        if (panel.classList.contains('minimized')) {
            panel.classList.remove('minimized');
            panel.style.width = '300px';
            panel.style.height = 'auto';
            
            // 不要设置display:none,而是通过CSS类控制显示
            if (miniButtons) {
                miniButtons.classList.remove('active');
            }
            
            // 显示所有常规内容
            Array.from(panel.children).forEach(child => {
                if (child.className !== 'mini-buttons-container' && child.id !== 'panel-drag-handle') {
                    child.style.display = '';
                }
            });
            
            document.getElementById('minimize-tool').textContent = '_';
        } else {
            // 最小化面板
            panel.classList.add('minimized');
            
            // 通过CSS类激活迷你按钮,而不是直接设置style
            if (miniButtons) {
                miniButtons.classList.add('active');
                // 确保没有内联样式干扰
                miniButtons.style.display = '';
            }
            
            // 隐藏所有常规内容,只显示迷你按钮和拖动手柄
            Array.from(panel.children).forEach(child => {
                if (child.className !== 'mini-buttons-container' && child.id !== 'panel-drag-handle') {
                    child.style.display = 'none';
                }
            });
            
            document.getElementById('minimize-tool').textContent = '□';
            
            // 更新迷你模式按钮状态
            if (isMonitoring) {
                document.getElementById('mini-monitor').classList.add('active');
            } else {
                document.getElementById('mini-monitor').classList.remove('active');
            }
        }
        
        // 恢复位置,防止位置重置
        panel.style.left = currentLeft;
        panel.style.top = currentTop;
        panel.style.bottom = 'auto';
    }
    
    // 监控网络请求按钮 
    document.getElementById('monitor-toggle').addEventListener('click', toggleMonitoring);
    
    // 重置自动合并计时器
    function resetAutoMergeTimer() {
        // 清除现有计时器
        if (autoMergeTimer) {
            clearTimeout(autoMergeTimer);
        }
        
        // 记录当前捕获的音频数量
        lastCaptureCount = capturedAudio.length;
        
        // 如果自动合并功能启用且捕获了音频,设置新计时器
        if (isAutoMergeEnabled && capturedAudio.length > 0) {
            autoMergeTimer = setTimeout(() => {
                // 检查5秒内音频数量是否有变化
                if (capturedAudio.length === lastCaptureCount && capturedAudio.length > 0) {
                    // 音频数量未变化,触发自动合并下载
                    updateStatus('检测到5秒内无新音频,开始自动合并下载...');
                    showMergeOptions(true); // 自动全选并开始合并下载
                }
            }, 5000); // 5秒检测间隔
        }
    }
})();