Greasy Fork

Greasy Fork is available in English.

豆包音频下载助手

捕获豆包网页版中的音频数据,支持主动/被动捕获、自动合并、暗黑模式、可拖拽面板、音频排序管理

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         豆包音频下载助手
// @namespace    http://tampermonkey.net/
// @version      2.0.5
// @description  捕获豆包网页版中的音频数据,支持主动/被动捕获、自动合并、暗黑模式、可拖拽面板、音频排序管理
// @author       cenglin123
// @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; // 监控是否开启 (XHR/Fetch)
    let isCapturing = false;  // 是否处于"一键获取"的主动模式
    let originalXHR = unsafeWindow.XMLHttpRequest;
    let originalFetch = unsafeWindow.fetch;
    let observer = null;

    // 自动合并相关
    let autoMergeEnabled = GM_getValue('autoMergeEnabled', false);
    let autoMergeTimer = null;
    let lastAudioCaptureTime = null;
    const AUTO_MERGE_DELAY = 10000; // 10秒

    // 自动清空列表
    let autoClearList = GM_getValue('autoClearList');
    if (autoClearList === undefined) {
        autoClearList = true;
        GM_setValue('autoClearList', true);
    }

    // 暗黑模式检测
    let isDarkMode = false;

    // 面板拖拽相关
    let isDragging = false;
    let dragOffsetX = 0;
    let dragOffsetY = 0;
    let isMinimized = GM_getValue('isMinimized', false);

    // 验证并修正面板位置
    function validatePanelPosition(position) {
        const defaultPosition = { bottom: 20, right: 20 };

        // 如果没有位置信息,使用默认值
        if (!position || typeof position.bottom !== 'number' || typeof position.right !== 'number') {
            console.log('位置信息无效,使用默认位置');
            return defaultPosition;
        }

        // 获取窗口尺寸
        const windowWidth = window.innerWidth;
        const windowHeight = window.innerHeight;
        const panelWidth = 320; // 面板宽度
        const panelHeight = 650; // 面板大致高度

        // 验证位置是否在合理范围内
        // bottom 和 right 应该在 0 到窗口尺寸之间
        const isValidBottom = position.bottom >= 0 && position.bottom < windowHeight - 100;
        const isValidRight = position.right >= 0 && position.right < windowWidth - 100;

        if (!isValidBottom || !isValidRight) {
            console.log('保存的位置超出屏幕范围:', position, '窗口尺寸:', windowWidth, windowHeight);
            console.log('重置为默认位置');
            // 保存修正后的位置
            GM_setValue('panelPosition', defaultPosition);
            return defaultPosition;
        }

        console.log('位置验证通过:', position);
        return position;
    }

    let panelPosition = validatePanelPosition(GM_getValue('panelPosition', { bottom: 20, right: 20 }));

    // 文件名
    let fileNamePrefix = GM_getValue('fileNamePrefix', 'doubao_audio');

    // 静音定时器
    let muteInterval = null;
    let dataUrlScanInterval = null; // 用于定期扫描 data URL

    // SVG图标定义
    const icons = {
        speaker: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path><path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path></svg>',
        mic: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>',
        stop: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"></rect></svg>',
        eye: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>',
        download: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>',
        trash: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>',
        link: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>',
        clock: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>',
        check: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>',
        code: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>',
        minimize: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>',
        maximize: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>',
        close: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>',
        play: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>',
        music: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>',
        copy: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>',
        sort: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M7 12h10M11 18h6"></path></svg>',
        search: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>'
    };

    // 检测暗黑模式
    function detectDarkMode() {
        // 优先使用浏览器的暗色模式偏好
        const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;

        // 如果系统有明确的主题偏好,直接使用它
        if (window.matchMedia && window.matchMedia('(prefers-color-scheme)').media !== 'not all') {
            isDarkMode = prefersDark;
            console.log('暗色模式检测结果:', isDarkMode, '(使用系统偏好)');
            return isDarkMode;
        }

        // 如果系统没有主题偏好,才检查页面背景色
        let pageDark = false;
        try {
            const bodyBg = window.getComputedStyle(document.body).backgroundColor;
            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;
                pageDark = brightness < 128;
            }
        } catch (e) {
            console.log('检测页面背景色失败:', e);
        }

        isDarkMode = pageDark;
        console.log('暗色模式检测结果:', isDarkMode, '(使用页面背景)');
        return isDarkMode;
    }

    // 获取主题样式
    function getThemeStyles() {
        detectDarkMode();
        if (isDarkMode) {
            return {
                background: '#1e1e1e', color: '#e0e0e0', border: '#444',
                buttonBg: '#2d2d2d', buttonHover: '#3d3d3d',
                primaryBg: '#5B8DEF', primaryHover: '#4A7DD9',
                dangerBg: '#E57373', shadowColor: 'rgba(0,0,0,0.5)',
                disabledBg: '#374151', disabledColor: '#6b7280',
                successBg: '#66BB6A', successHover: '#57AB5A'
            };
        } else {
            return {
                background: '#ffffff', color: '#333', border: '#e5e7eb',  // 更浅的边框
                buttonBg: '#f8f9fa', buttonHover: '#e9ecef',                // 更浅的背景
                primaryBg: '#5B8DEF', primaryHover: '#4A7DD9',
                dangerBg: '#EF5350', shadowColor: 'rgba(0,0,0,0.08)',       // 更浅的阴影
                disabledBg: '#f3f4f6', disabledColor: '#9ca3af',
                successBg: '#66BB6A', successHover: '#57AB5A'
            };
        }
    }

    // 注入自定义滚动条样式
    function injectCustomScrollbarStyles() {
        const theme = getThemeStyles(); // 获取当前主题
        const styleId = 'audio-capturer-scrollbar-style';
        let styleElement = document.getElementById(styleId);

        if (!styleElement) {
            styleElement = document.createElement('style');
            styleElement.id = styleId;
            document.head.appendChild(styleElement);
        }

        // 为模态框内的滚动区域和面板本身(如果需要)添加样式
        styleElement.textContent = `
            /* Custom Scrollbar for Audio Capturer (WebKit) */
            .audio-capture-modal-backdrop ::-webkit-scrollbar,
            #audio-list-container::-webkit-scrollbar,
            div[style*="max-height: 300px"][style*="overflow-y: auto"]::-webkit-scrollbar,
            #audio-capture-panel ::-webkit-scrollbar {
                width: 8px;
                height: 8px;
            }
            .audio-capture-modal-backdrop ::-webkit-scrollbar-track,
            #audio-list-container::-webkit-scrollbar-track,
            div[style*="max-height: 300px"][style*="overflow-y: auto"]::-webkit-scrollbar-track,
            #audio-capture-panel ::-webkit-scrollbar-track {
                background: ${theme.buttonBg};
                border-radius: 4px;
            }
            .audio-capture-modal-backdrop ::-webkit-scrollbar-thumb,
            #audio-list-container::-webkit-scrollbar-thumb,
            div[style*="max-height: 300px"][style*="overflow-y: auto"]::-webkit-scrollbar-thumb,
            #audio-capture-panel ::-webkit-scrollbar-thumb {
                background: ${isDarkMode ? '#555' : '#aaa'};
                border-radius: 4px;
                border: 2px solid ${theme.buttonBg};
            }
            .audio-capture-modal-backdrop ::-webkit-scrollbar-thumb:hover,
            #audio-list-container::-webkit-scrollbar-thumb:hover,
            div[style*="max-height: 300px"][style*="overflow-y: auto"]::-webkit-scrollbar-thumb:hover,
            #audio-capture-panel ::-webkit-scrollbar-thumb:hover {
                background: ${isDarkMode ? '#777' : '#888'};
            }

            /* Custom Scrollbar (Firefox) */
            .audio-capture-modal-backdrop .audio-capture-modal,
            #audio-list-container,
            div[style*="max-height: 300px"][style*="overflow-y: auto"] {
                scrollbar-width: thin;
                scrollbar-color: ${isDarkMode ? '#555' : '#aaa'} ${theme.buttonBg};
            }

            /* 拖拽排序样式 */
            .audio-item.dragging {
                opacity: 0.85;
                transform: translate3d(6px, 6px, 0);
                box-shadow: 0 12px 24px ${isDarkMode ? 'rgba(0,0,0,0.45)' : 'rgba(15,23,42,0.18)'};
            }
            .audio-item.drag-over {
                border: 2px dashed ${theme.primaryBg};
                background: ${isDarkMode ? 'rgba(91, 141, 239, 0.1)' : 'rgba(91, 141, 239, 0.05)'};
            }
            .drag-handle {
                cursor: grab;
                opacity: 0.6;
                transition: opacity 0.2s;
            }
            .drag-handle:hover {
                opacity: 1;
            }
            .drag-handle:active {
                cursor: grabbing;
            }
        `;
    }

    // 自动点击页面播放/停止按钮
    function clickAudioToggleButton() {
        try {
            const stopBtn = document.querySelector('button[data-testid="audio_stop_button"]');
            if (stopBtn && !stopBtn.disabled) {
                stopBtn.click();
                updateStatus('✓ 已触发停止按钮');
                return true;
            }
            const playBtn = document.querySelector('button[data-testid="audio_play_button"]');
            if (playBtn && !playBtn.disabled) {
                playBtn.click();
                updateStatus('✓ 已触发播放按钮');
                return true;
            }
            const playBtnByClass = document.querySelector('button.semi-button-primary[aria-disabled="false"]');
            if (playBtnByClass && playBtnByClass.querySelector('svg')) {
                playBtnByClass.click();
                updateStatus('✓ 已触发播放/停止按钮(备用方法)');
                return true;
            }
            updateStatus('⚠ 未找到播放/停止按钮');
            return false;
        } catch (e) {
            console.error('点击播放/停止按钮失败:', e);
            updateStatus('⚠ 触发播放/停止失败');
            return false;
        }
    }

    // 创建UI函数
    function createMainInterface() {
        try {
            console.log('createMainInterface被调用');

            // 先清理可能存在的旧面板
            const oldPanel = document.getElementById('audio-capture-panel');
            if (oldPanel) {
                console.log('发现旧面板,正在移除...');
                oldPanel.remove();
            }

            const theme = getThemeStyles();
            const panel = document.createElement('div');
            panel.id = 'audio-capture-panel';
            const positionStyle = `bottom: ${panelPosition.bottom}px; right: ${panelPosition.right}px;`;

            panel.style.cssText = `
                position: fixed !important;
                ${positionStyle}
                background: ${theme.background}; color: ${theme.color};
                border: 1px solid ${theme.border}; border-radius: 12px;
                padding: ${isMinimized ? '12px' : '20px'};
                box-shadow: 0 4px 20px ${theme.shadowColor}; z-index: 999999 !important;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
                width: ${isMinimized ? 'auto' : '320px'};
                transition: all 0.3s ease;
                user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;
                display: block !important;
                visibility: visible !important;
                opacity: 1 !important;
            `;

            console.log('面板样式已设置,当前主题:', isDarkMode ? '暗色' : '亮色');

                // 最小化面版
                const headerHtml = `
                <div id="panel-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: ${isMinimized ? '0' : '16px'}; user-select: none;">
                    ${isMinimized ? `
                    <div style="display: flex; align-items: center;">
                        <div style="display: flex; align-items: center; gap: 4px;">
                            <button id="active-capture-btn" class="icon-btn minimized-btn" title="一键获取" style="padding: 8px; background: transparent !important; border: none; cursor: pointer; opacity: 0.7; transition: all 0.2s; display: flex; align-items: center; color: inherit;">${icons.speaker}</button>
                            <button id="passive-capture-btn" class="icon-btn minimized-btn" title="手动获取" style="padding: 8px; background: transparent !important; border: none; cursor: pointer; opacity: 0.7; transition: all 0.2s; display: flex; align-items: center; color: inherit;">${icons.clock}</button>
                            <div style="width: 1px; height: 20px; background: ${theme.border}; margin: 0 8px;"></div>
                            <button id="view-captured" class="icon-btn minimized-btn" title="已捕获音频管理" style="padding: 8px; background: transparent !important; border: none; cursor: pointer; opacity: 0.7; transition: all 0.2s; display: flex; align-items: center; position: relative; color: inherit;">
                            ${icons.eye}
                            <div class="audio-count-badge" style="position: absolute; top: 2px; right: 2px; background: ${theme.primaryBg}; color: white; border-radius: 8px; padding: 1px 4px; font-size: 10px; line-height: 1; min-width: 14px; text-align: center;">0</div>
                        </button>
                            <button id="merge-download" class="icon-btn minimized-btn" title="合并下载" style="padding: 8px; background: transparent !important; border: none; cursor: pointer; opacity: 0.7; transition: all 0.2s; display: flex; align-items: center;">${icons.download}</button>
                            <button id="clear-all-audio" class="icon-btn minimized-btn" title="清空列表" style="padding: 8px; background: transparent !important; border: none; cursor: pointer; opacity: 0.7; transition: all 0.2s; display: flex; align-items: center;">${icons.trash}</button>
                        </div>
                        <div style="width: 1px; height: 20px; background: ${theme.border}; margin: 0 8px;"></div>
                        <style>
                            .icon-btn:hover { opacity: 1 !important; transform: scale(1.1); }
                            .icon-btn:active { transform: scale(0.95); }
                            .icon-btn svg { width: 20px; height: 20px; }
                        </style>
                    </div>
                    ` : `
                    <h3 style="margin: 0; font-size: 16px; font-weight: 600; user-select: none; display: flex; align-items: center; gap: 8px;">
                        ${icons.music} <span>豆包音频下载助手</span>
                    </h3>
                    `}
                    <div style="display: flex; gap: 8px;">
                        <button id="minimize-toggle" style="background: none; border: none; cursor: pointer; opacity: 0.7; transition: opacity 0.2s; padding: 4px; display: flex; align-items: center;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.7'">
                            ${isMinimized ? icons.maximize : icons.minimize}
                        </button>
                        <button id="close-tool" style="background: none; border: none; cursor: pointer; opacity: 0.7; transition: opacity 0.2s; padding: 4px; display: flex; align-items: center;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.7'">
                            ${icons.close}
                        </button>
                    </div>
                </div>
            `;

            // 主内容区域
            const mainContent = isMinimized ? '' : `
                <div style="display: flex; flex-direction: column; gap: 12px;">

                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                        <button id="active-capture-btn" style="
                            padding: 14px; background: ${theme.successBg};
                            color: white; border: none; border-radius: 10px; cursor: pointer; font-size: 15px; font-weight: 500;
                            display: flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.2s;
                            box-shadow: 0 2px 6px ${isDarkMode ? 'rgba(102, 187, 106, 0.25)' : 'rgba(102, 187, 106, 0.15)'};
                        ">
                            <div style="pointer-events: none; display: flex; align-items: center; justify-content: center; gap: 8px;">
                                ${icons.speaker} <span>一键获取</span>
                            </div>
                        </button>
                        <button id="passive-capture-btn" style="
                            padding: 14px; background: ${theme.primaryBg};
                            color: white; border: none; border-radius: 10px; cursor: pointer; font-size: 15px; font-weight: 500;
                            display: flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.2s;
                            box-shadow: 0 2px 6px ${isDarkMode ? 'rgba(91, 141, 239, 0.25)' : 'rgba(91, 141, 239, 0.15)'};
                        ">
                            <div style="pointer-events: none; display: flex; align-items: center; justify-content: center; gap: 8px;">
                                ${icons.clock} <span>手动获取</span>
                            </div>
                        </button>
                    </div>

                    <div style="margin-bottom: 4px;">
                        <label style="display: block; font-size: 13px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'}; margin-bottom: 6px;">文件名前缀</label>
                        <input type="text" id="filename-prefix" value="${fileNamePrefix}" placeholder="doubao_audio"
                            style="width: 100%; padding: 10px 12px; background: ${isDarkMode ? '#374151' : '#f3f4f6'}; color: ${theme.color}; border: 1px solid ${isDarkMode ? '#4b5563' : '#e5e7eb'}; border-radius: 6px; font-size: 14px; box-sizing: border-box; transition: all 0.2s;"
                            onfocus="this.style.borderColor='#3b82f6'; this.style.background='${isDarkMode ? '#1f2937' : '#ffffff'}'"
                            onblur="this.style.borderColor='${isDarkMode ? '#4b5563' : '#e5e7eb'}'; this.style.background='${isDarkMode ? '#374151' : '#f3f4f6'}'">
                    </div>

                    <div style="margin: -4px 0;">
                        <div style="padding: 4px 10px; cursor: default;">
                            <label style="display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; padding: 4px 0;" onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='transparent'">
                                <input type="checkbox" id="auto-merge-toggle" ${autoMergeEnabled ? 'checked' : ''} style="cursor: pointer; width: 16px; height: 16px;">
                                <span style="font-size: 14px; flex: 1;">自动合并下载(10秒无新音频时)</span>
                            </label>
                            <label style="display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; padding: 4px 0;" onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='transparent'">
                                <input type="checkbox" id="auto-clear-toggle" ${autoClearList ? 'checked' : ''} style="cursor: pointer; width: 16px; height: 16px;">
                                <span style="font-size: 14px; flex: 1;">下载完成后自动清空列表</span>
                            </label>
                        </div>
                    </div>

                    <div style="display: flex; gap: 4px; margin: 4px 0;">
                        <button id="merge-download" style="
                            flex: 1; padding: 14px; background: ${isDarkMode ? '#374151' : '#f3f4f6'}; color: ${theme.color};
                            border: 1px solid ${isDarkMode ? '#4b5563' : '#e5e7eb'}; border-radius: 10px; cursor: pointer; font-size: 15px; font-weight: 500;
                            display: flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.2s;
                        " onmouseover="this.style.transform='scale(1.02)'; this.style.background='${isDarkMode ? '#4b5563' : '#e5e7eb'}'" onmouseout="this.style.transform='scale(1)'; this.style.background='${isDarkMode ? '#374151' : '#f3f4f6'}'">
                            <div style="pointer-events: none; display: flex; align-items: center; justify-content: center; gap: 8px;">
                                ${icons.download} <span>合并下载</span>
                            </div>
                        </button>
                    </div>

                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px;">
                        <button id="view-captured" style="
                            padding: 10px; background: ${theme.buttonBg}; color: ${theme.color};
                            border: 1px solid ${theme.border}; border-radius: 8px;
                            cursor: pointer; font-size: 14px; font-weight: 400;
                            display: flex; align-items: center; justify-content: center; gap: 8px;
                            transition: all 0.2s;
                        " onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='${theme.buttonBg}'">
                            ${icons.eye} <span>音频管理 <span id="audio-count">0</span></span>
                        </button>

                        <button id="clear-all-audio" style="
                            padding: 10px; background: ${theme.buttonBg}; color: ${theme.color};
                            border: 1px solid ${theme.border}; border-radius: 8px;
                            cursor: pointer; font-size: 14px; font-weight: 400;
                            display: flex; align-items: center; justify-content: center; gap: 8px;
                            transition: all 0.2s;
                        " onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='${theme.buttonBg}'">
                            ${icons.trash} <span>清空列表</span>
                        </button>
                    </div>

                <div id="status-area" style="
                    margin-top: 6px; padding: 10px 12px; font-size: 12px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'};
                    background: ${isDarkMode ? '#1f2937' : '#f9fafb'}; border-radius: 6px; text-align: center; user-select: text; min-height: 20px;
                ">工具已启动就绪</div>
            `;

            panel.innerHTML = headerHtml + mainContent;
            document.body.appendChild(panel);

            console.log('面板已添加到DOM');
            console.log('面板元素:', panel);
            console.log('面板父元素:', panel.parentElement);
            console.log('面板当前位置:', panel.getBoundingClientRect());

            updateAudioCount();
            setupDraggable(panel);

            // 最小化/关闭 按钮
            document.getElementById('minimize-toggle').addEventListener('click', (e) => {
                e.stopPropagation();
                isMinimized = !isMinimized;
                GM_setValue('isMinimized', isMinimized);

                // 获取当前面板的位置
                const rect = panel.getBoundingClientRect();
                const currentPosition = {
                    bottom: window.innerHeight - rect.bottom,
                    right: window.innerWidth - rect.right
                };
                panelPosition = currentPosition;
                createMainInterface();
            });

            document.getElementById('close-tool').addEventListener('click', (e) => {
                e.stopPropagation();
                panel.remove();
            });

            // 按钮事件监听 (不管是否最小化都需要)
            document.getElementById('active-capture-btn').addEventListener('click', handleActiveClick);
            document.getElementById('passive-capture-btn').addEventListener('click', handlePassiveClick);
            document.getElementById('view-captured').addEventListener('click', (e) => { e.stopPropagation(); showAudioManagementWindow(); });
            document.getElementById('merge-download').addEventListener('click', (e) => { e.stopPropagation(); showAudioManagementWindow({ autoSelectAll: true }); });
            document.getElementById('clear-all-audio').addEventListener('click', function(e) {
                e.stopPropagation();
                if (capturedAudio.length === 0) {
                    updateStatus('当前没有已捕获的音频');
                    return;
                }
                capturedAudio = [];
                updateAudioCount();
                saveAudioData();
                updateStatus('已清空所有音频');
            });

            if (!isMinimized) {
                // 文件名前缀保存
                document.getElementById('filename-prefix').addEventListener('change', function(e) {
                    e.stopPropagation();
                    fileNamePrefix = this.value.trim() || 'doubao_audio';
                    GM_setValue('fileNamePrefix', fileNamePrefix);
                    updateStatus('文件名前缀已保存: ' + fileNamePrefix);
                });

                // 其他按钮
                document.getElementById('view-captured').addEventListener('click', (e) => { e.stopPropagation(); showAudioManagementWindow(); });
                document.getElementById('merge-download').addEventListener('click', (e) => { e.stopPropagation(); showAudioManagementWindow({ autoSelectAll: true }); });

                // 清空音频按钮(无确认)
                document.getElementById('clear-all-audio').addEventListener('click', function(e) {
                    e.stopPropagation();
                    if (capturedAudio.length === 0) {
                        updateStatus('当前没有已捕获的音频');
                        return;
                    }
                    capturedAudio = [];
                    updateAudioCount();
                    saveAudioData();
                    updateStatus('已清空所有音频');
                });

                // 自动合并开关
                document.getElementById('auto-merge-toggle').addEventListener('change', function(e) {
                    e.stopPropagation();
                    autoMergeEnabled = this.checked;
                    GM_setValue('autoMergeEnabled', autoMergeEnabled);
                    updateStatus(autoMergeEnabled ? '自动合并已启用' : '自动合并已禁用');
                    if (autoMergeEnabled && capturedAudio.length > 0) {
                        resetAutoMergeTimer();
                    }
                });

                // 从存储中同步自动合并状态
                syncAutoMergeCheckbox();

                // 自动清空开关
                document.getElementById('auto-clear-toggle').addEventListener('change', function(e) {
                    e.stopPropagation();
                    autoClearList = this.checked;
                    GM_setValue('autoClearList', autoClearList);
                    updateStatus(autoClearList ? '自动清空已启用' : '自动清空已禁用');
                });

                // 从存储中同步自动清空状态
                syncAutoClearCheckbox();
            }

            // 监听主题变化
            if (window.matchMedia) {
                window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
                    createMainInterface();
                });
            }

            // 在面板创建完成后,立即更新按钮状态
            setTimeout(() => {
                updateCaptureUI();
            }, 100);

            console.log('音频捕获工具界面已创建');
        } catch (error) {
            console.error('创建界面时出错:', error);
            alert('创建工具界面失败,请刷新页面重试');
        }
    }

    // 处理"一键获取"点击
    function handleActiveClick() {
        if (isMonitoring && isCapturing) {
            // 正在主动捕获 -> 停止
            stopCaptureActions(true);
            // 同步UI状态
            updateCaptureUI();
        } else if (!isMonitoring) {
            // 已停止 -> 开始主动捕获
            isCapturing = true;
            startMonitoring();
            mutePageAudio();

            // 自动勾选"自动合并"
            const autoMergeCheckbox = document.getElementById('auto-merge-toggle');
            if (autoMergeCheckbox) {
                autoMergeCheckbox.checked = true;
            }
            autoMergeEnabled = true;
            GM_setValue('autoMergeEnabled', autoMergeEnabled);

            setTimeout(clickAudioToggleButton, 500);
            updateStatus('一键获取已启动,已静音');
            // 同步UI状态
            updateCaptureUI();
        }
    }

    // 在 handlePassiveClick 函数中,在更新状态后添加同步代码
    function handlePassiveClick() {
        if (isMonitoring && !isCapturing) {
            // 正在被动监控 -> 停止
            stopCaptureActions(false);
            // 同步UI状态
            updateCaptureUI();
        } else if (!isMonitoring) {
            // 已停止 -> 开始被动监控
            isCapturing = false;
            startMonitoring();
            updateStatus('手动监控已启动,请点击播放');
            // 同步UI状态
            updateCaptureUI();
        }
    }

    // 在 stopCaptureActions 函数中,确保调用 updateCaptureUI
    function stopCaptureActions(isActiveMode) {
        stopMonitoring();
        if (isActiveMode) {
            unmutePageAudio(true);
        } else {
            unmutePageAudio(false);
        }
        isCapturing = false;
        updateCaptureUI(); // 确保这里被调用
    }

    // 修改 updateCaptureUI 函数,确保它能正确处理两种界面状态
    function updateCaptureUI() {
        const activeBtn = document.getElementById('active-capture-btn');
        const passiveBtn = document.getElementById('passive-capture-btn');
        if (!activeBtn || !passiveBtn) return;

        const theme = getThemeStyles();

        // 默认样式
        const styles = {
            green: theme.successBg,
            blue: theme.primaryBg,
            red: '#EF5350',
            gray: theme.disabledBg,
            shadowGreen: `0 2px 6px ${isDarkMode ? 'rgba(102, 187, 106, 0.25)' : 'rgba(102, 187, 106, 0.15)'}`,
            shadowBlue: `0 2px 6px ${isDarkMode ? 'rgba(91, 141, 239, 0.25)' : 'rgba(91, 141, 239, 0.15)'}`,
            shadowRed: `0 2px 6px ${isDarkMode ? 'rgba(239, 83, 80, 0.25)' : 'rgba(239, 83, 80, 0.15)'}`,
            shadowGray: 'none'
        };

        // 检查当前面板状态
        const currentPanel = document.getElementById('audio-capture-panel');
        const isCurrentlyMinimized = currentPanel ? currentPanel.style.width === 'auto' || currentPanel.style.padding === '12px' : isMinimized;

        if (!isMonitoring) {
            // 状态: OFF
            if (isCurrentlyMinimized) {
                // 最小化模式
                activeBtn.innerHTML = icons.speaker;
                activeBtn.style.background = 'transparent';
                activeBtn.style.boxShadow = 'none';
                activeBtn.style.color = 'inherit';
                activeBtn.style.opacity = '0.7';
                activeBtn.disabled = false;

                passiveBtn.innerHTML = icons.clock;
                passiveBtn.style.background = 'transparent';
                passiveBtn.style.boxShadow = 'none';
                passiveBtn.style.color = 'inherit';
                passiveBtn.style.opacity = '0.7';
                passiveBtn.disabled = false;
            } else {
                // 展开模式
                activeBtn.innerHTML = `<div style="pointer-events: none; display: flex; align-items: center; justify-content: center; gap: 8px;">${icons.speaker} <span>一键获取</span></div>`;
                activeBtn.style.background = styles.green;
                activeBtn.style.boxShadow = styles.shadowGreen;
                activeBtn.style.color = 'white';
                activeBtn.disabled = false;

                passiveBtn.innerHTML = `<div style="pointer-events: none; display: flex; align-items: center; justify-content: center; gap: 8px;">${icons.clock} <span>手动获取</span></div>`;
                passiveBtn.style.background = styles.blue;
                passiveBtn.style.boxShadow = styles.shadowBlue;
                passiveBtn.style.color = 'white';
                passiveBtn.disabled = false;
            }

        } else if (isCapturing) {
            // 状态: ACTIVE (一键获取中)
            if (isCurrentlyMinimized) {
                // 最小化模式
                activeBtn.innerHTML = icons.stop;
                activeBtn.style.background = styles.red;
                activeBtn.style.boxShadow = styles.shadowRed;
                activeBtn.style.color = 'white';
                activeBtn.style.opacity = '1';
                activeBtn.disabled = false;

                passiveBtn.innerHTML = icons.clock;
                passiveBtn.style.background = 'transparent';
                passiveBtn.style.boxShadow = 'none';
                passiveBtn.style.color = 'inherit';
                passiveBtn.style.opacity = '0.3';
                passiveBtn.disabled = true;
            } else {
                // 展开模式
                activeBtn.innerHTML = `<div style="pointer-events: none; display: flex; align-items: center; justify-content: center; gap: 8px;">${icons.stop} <span>停止获取</span></div>`;
                activeBtn.style.background = styles.red;
                activeBtn.style.boxShadow = styles.shadowRed;
                activeBtn.style.color = 'white';
                activeBtn.disabled = false;

                passiveBtn.innerHTML = `<div style="pointer-events: none; display: flex; align-items: center; justify-content: center; gap: 8px;">${icons.clock} <span>手动获取</span></div>`;
                passiveBtn.style.background = styles.gray;
                passiveBtn.style.boxShadow = styles.shadowGray;
                passiveBtn.style.color = theme.disabledColor;
                passiveBtn.disabled = true;
            }

        } else {
            // 状态: PASSIVE (手动监控中)
            if (isCurrentlyMinimized) {
                // 最小化模式
                activeBtn.innerHTML = icons.speaker;
                activeBtn.style.background = 'transparent';
                activeBtn.style.boxShadow = 'none';
                activeBtn.style.color = 'inherit';
                activeBtn.style.opacity = '0.3';
                activeBtn.disabled = true;

                passiveBtn.innerHTML = icons.stop;
                passiveBtn.style.background = styles.red;
                passiveBtn.style.boxShadow = styles.shadowRed;
                passiveBtn.style.color = 'white';
                passiveBtn.style.opacity = '1';
                passiveBtn.disabled = false;
            } else {
                // 展开模式
                activeBtn.innerHTML = `<div style="pointer-events: none; display: flex; align-items: center; justify-content: center; gap: 8px;">${icons.speaker} <span>一键获取</span></div>`;
                activeBtn.style.background = styles.gray;
                activeBtn.style.boxShadow = styles.shadowGray;
                activeBtn.style.color = theme.disabledColor;
                activeBtn.disabled = true;

                passiveBtn.innerHTML = `<div style="pointer-events: none; display: flex; align-items: center; justify-content: center; gap: 8px;">${icons.stop} <span>停止监控</span></div>`;
                passiveBtn.style.background = styles.red;
                passiveBtn.style.boxShadow = styles.shadowRed;
                passiveBtn.style.color = 'white';
                passiveBtn.disabled = false;
            }
        }

        // 添加悬停效果(只在非禁用状态下)
        if (!activeBtn.disabled) {
            activeBtn.onmouseover = () => {
                if (!isCurrentlyMinimized) activeBtn.style.transform = 'translateY(-1px)';
                if (isMonitoring && isCapturing) activeBtn.style.background = styles.red;
            };
            activeBtn.onmouseout = () => {
                if (!isCurrentlyMinimized) activeBtn.style.transform = 'translateY(0)';
                if (isMonitoring && isCapturing) activeBtn.style.background = styles.red;
            };
        } else {
            activeBtn.onmouseover = null;
            activeBtn.onmouseout = null;
        }

        if (!passiveBtn.disabled) {
            passiveBtn.onmouseover = () => {
                if (!isCurrentlyMinimized) passiveBtn.style.transform = 'translateY(-1px)';
                if (isMonitoring && !isCapturing) passiveBtn.style.background = styles.red;
            };
            passiveBtn.onmouseout = () => {
                if (!isCurrentlyMinimized) passiveBtn.style.transform = 'translateY(0)';
                if (isMonitoring && !isCapturing) passiveBtn.style.background = styles.red;
            };
        } else {
            passiveBtn.onmouseover = null;
            passiveBtn.onmouseout = null;
        }
    }


    // 同步自动合并checkbox状态
    function syncAutoMergeCheckbox() {
        const checkbox = document.getElementById('auto-merge-toggle');
        if (checkbox) {
            checkbox.checked = autoMergeEnabled;
        }
    }

    // 同步自动清空checkbox状态
    function syncAutoClearCheckbox() {
        const checkbox = document.getElementById('auto-clear-toggle');
        if (checkbox) {
            checkbox.checked = autoClearList;
        }
    }

    // 设置可拖拽
    function setupDraggable(panel) {
        panel.addEventListener('mousedown', (e) => {
            const interactiveElements = ['BUTTON', 'INPUT', 'TEXTAREA', 'SELECT', 'A'];
            if (interactiveElements.includes(e.target.tagName) || e.target.closest('button') || e.target.closest('input') || e.target.closest('textarea')) {
                return;
            }
            e.preventDefault();
            e.stopPropagation();
            isDragging = true;
            const rect = panel.getBoundingClientRect();
            dragOffsetX = e.clientX - rect.left;
            dragOffsetY = e.clientY - rect.top;
            document.body.style.cursor = 'grabbing';
            panel.style.cursor = 'grabbing';
            document.body.style.userSelect = 'none';
            document.body.style.webkitUserSelect = 'none';
        });
        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            e.preventDefault();
            const x = e.clientX - dragOffsetX;
            const y = e.clientY - dragOffsetY;
            const maxX = window.innerWidth - panel.offsetWidth;
            const maxY = window.innerHeight - panel.offsetHeight;
            const finalX = Math.max(0, Math.min(x, maxX));
            const finalY = Math.max(0, Math.min(y, maxY));
            panel.style.left = finalX + 'px';
            panel.style.top = finalY + 'px';
            panel.style.right = 'auto';
            panel.style.bottom = 'auto';
            panel.style.transition = 'none';
        });
        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                document.body.style.cursor = '';
                panel.style.cursor = '';
                document.body.style.userSelect = '';
                document.body.style.webkitUserSelect = '';
                panel.style.transition = 'all 0.3s ease';
                const rect = panel.getBoundingClientRect();
                const newPosition = {
                    bottom: window.innerHeight - rect.bottom,
                    right: window.innerWidth - rect.right
                };

                // 验证并保存位置
                const validatedPosition = validatePanelPosition(newPosition);
                panelPosition = validatedPosition;
                GM_setValue('panelPosition', validatedPosition);
                console.log('保存面板位置:', validatedPosition);
            }
        });
        panel.addEventListener('selectstart', (e) => {
            if (isDragging) e.preventDefault();
        });
    }

    // 页面静音 (不太好用,后续继续开发)
    function mutePageAudio() {
        if (muteInterval) clearInterval(muteInterval);

        const muteAllElements = () => {
            const audioElements = document.querySelectorAll('audio, video');
            audioElements.forEach(element => {
                if (!element.dataset.originalVolume) {
                    element.dataset.originalVolume = element.volume;
                }
                element.volume = 0;
                element.muted = true;
            });
        };

        muteAllElements();
        muteInterval = setInterval(muteAllElements, 500);

        if (window.AudioContext || window.webkitAudioContext) {
            try {
                const audioContext = new (window.AudioContext || window.webkitAudioContext)();
                if (audioContext.state === 'running') {
                    audioContext.suspend();
                }
            } catch (e) {
                console.log('无法静音AudioContext:', e);
            }
        }
    }

    // 解除静音并可选择性停止播放
    function unmutePageAudio(shouldClickButton = true) {
        if (muteInterval) clearInterval(muteInterval);
        muteInterval = null;

        const audioElements = document.querySelectorAll('audio, video');
        audioElements.forEach(element => {
            if (element.dataset.originalVolume) {
                element.volume = parseFloat(element.dataset.originalVolume);
                delete element.dataset.originalVolume;
            }
            element.muted = false;
            element.pause();
        });

        if (window.AudioContext || window.webkitAudioContext) {
            try {
                const audioContext = new (window.AudioContext || window.webkitAudioContext)();
                if (audioContext.state === 'suspended') {
                    audioContext.resume();
                }
            } catch (e) {
                console.log('无法恢复AudioContext:', e);
            }
        }

        // 只有在主动模式下才点击停止按钮
        if (shouldClickButton) {
            clickAudioToggleButton();
            updateStatus('已恢复页面音频并暂停播放');
        } else {
            updateStatus('已停止监控');
        }
    }

    // 重置自动合并计时器
    function resetAutoMergeTimer() {
        if (autoMergeTimer) clearTimeout(autoMergeTimer);
        if (autoMergeEnabled && capturedAudio.length > 0) {
            autoMergeTimer = setTimeout(() => {
                const now = Date.now();
                const timeSinceLastCapture = lastAudioCaptureTime ? now - lastAudioCaptureTime : 0;
                if (timeSinceLastCapture >= AUTO_MERGE_DELAY && capturedAudio.length > 0) {
                    updateStatus('🤖 自动合并中...');
                    autoMergeAndDownload();
                }
            }, AUTO_MERGE_DELAY);
        }
    }

    // 自动合并并下载
    function autoMergeAndDownload() {
        if (capturedAudio.length === 0) return;
        const indices = capturedAudio.map((_, index) => index);
        const modal = createModal('自动合并进度');
        const content = document.createElement('div');
        content.innerHTML = `
            <div style="text-align: center; margin: 20px 0;">
                <div id="merge-progress-text">🤖 自动合并 ${capturedAudio.length} 个音频文件...</div>
                <div style="margin: 15px 0; background: ${isDarkMode ? '#2d2d2d' : '#f0f0f0'}; border-radius: 4px; overflow: hidden;">
                    <div id="merge-progress-bar" style="width: 0%; height: 20px; background: #0f9d58;"></div>
                </div>
                <div id="merge-status">正在初始化...</div>
            </div>
        `;
        modal.appendChild(content);
        setTimeout(() => {
            startMergeProcess(indices, 'mp3', modal, true);
        }, 500);
    }

    // 更新状态区域
    function updateStatus(message) {
        const statusArea = document.getElementById('status-area');
        if (statusArea) statusArea.textContent = message;
    }

    // 更新音频计数
    function updateAudioCount() {
        // 更新展开模式的计数
        const countElement = document.getElementById('audio-count');
        if (countElement) countElement.textContent = capturedAudio.length;

        // 更新最小化模式的计数气泡
        const countBadge = document.querySelector('.audio-count-badge');
        if (countBadge) countBadge.textContent = capturedAudio.length;
    }

    // 开始监控网络请求
    function startMonitoring() {
        if (isMonitoring) return; // 防止重复挂钩
        isMonitoring = true;

        // 拦截 Audio 和 Video 元素的 src 属性设置
        try {
            const AudioProto = unsafeWindow.HTMLAudioElement.prototype;
            const VideoProto = unsafeWindow.HTMLVideoElement.prototype;
            const MediaProto = unsafeWindow.HTMLMediaElement.prototype;

            // 保存原始的 src 属性描述符
            const originalAudioSrcDescriptor = Object.getOwnPropertyDescriptor(MediaProto, 'src') ||
                                              Object.getOwnPropertyDescriptor(AudioProto, 'src');
            const originalVideoSrcDescriptor = Object.getOwnPropertyDescriptor(MediaProto, 'src') ||
                                              Object.getOwnPropertyDescriptor(VideoProto, 'src');

            // 拦截 Audio 元素的 src 设置
            if (originalAudioSrcDescriptor) {
                Object.defineProperty(AudioProto, 'src', {
                    get: function() {
                        return originalAudioSrcDescriptor.get.call(this);
                    },
                    set: function(value) {
                        if (isMonitoring && value && value.startsWith && value.startsWith('data:')) {
                            setTimeout(() => scanNodeForDataUrls(this), 0);
                        }
                        return originalAudioSrcDescriptor.set.call(this, value);
                    },
                    configurable: true
                });
            }

            // 拦截 Video 元素的 src 设置
            if (originalVideoSrcDescriptor) {
                Object.defineProperty(VideoProto, 'src', {
                    get: function() {
                        return originalVideoSrcDescriptor.get.call(this);
                    },
                    set: function(value) {
                        if (isMonitoring && value && value.startsWith && value.startsWith('data:')) {
                            setTimeout(() => scanNodeForDataUrls(this), 0);
                        }
                        return originalVideoSrcDescriptor.set.call(this, value);
                    },
                    configurable: true
                });
            }
        } catch (e) {
            console.log('无法拦截 Audio/Video src 属性:', e);
        }

        unsafeWindow.XMLHttpRequest = function() {
            const xhr = new originalXHR();
            const originalOpen = xhr.open;
            xhr.open = function() {
                this.url = arguments[1];
                return originalOpen.apply(this, arguments);
            };
            xhr.addEventListener('load', function() {
                if (!isMonitoring) return; // 检查是否仍在监控
                try {
                    const contentType = this.getResponseHeader('Content-Type') || '';
                    const isAudio = contentType.includes('audio') || contentType.includes('octet-stream') || (this.url && this.url.match(/\.(mp3|wav|ogg|aac|flac|m4a)($|\?)/i));
                    if (isAudio) {
                        captureAudioFromResponse(this.response, contentType, this.url);
                    }
                } catch (e) { console.error('处理XHR请求时出错:', e); }
            });
            return xhr;
        };

        unsafeWindow.fetch = function() {
            const url = arguments[0] instanceof Request ? arguments[0].url : arguments[0];
            return originalFetch.apply(this, arguments).then(response => {
                if (!isMonitoring) return response; // 检查是否仍在监控
                try {
                    const contentType = response.headers.get('Content-Type') || '';
                    const isAudio = contentType.includes('audio') || contentType.includes('octet-stream') || (url && url.match(/\.(mp3|wav|ogg|aac|flac|m4a)($|\?)/i));
                    if (isAudio) {
                        response.clone().arrayBuffer().then(buffer => {
                            captureAudioFromResponse(buffer, contentType, url);
                        });
                    }
                } catch (e) { console.error('处理Fetch请求时出错:', e); }
                return response;
            });
        };

        observer = new MutationObserver(mutations => {
            if (!isMonitoring) return; // 检查是否仍在监控
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO') {
                        // 立即捕获已有的 src
                        if (node.src && node.src.startsWith('data:')) {
                            scanNodeForDataUrls(node);
                        }

                        // 监听 play 事件
                        node.addEventListener('play', () => {
                            if (node.src) captureAudioFromMediaElement(node);
                        });

                        // 监听 loadstart 事件(音频开始加载时触发)
                        node.addEventListener('loadstart', () => {
                            if (node.src && node.src.startsWith('data:')) {
                                scanNodeForDataUrls(node);
                            }
                        });

                        // 监听 canplay 事件(音频可以播放时触发)
                        node.addEventListener('canplay', () => {
                            if (node.src && node.src.startsWith('data:')) {
                                scanNodeForDataUrls(node);
                            }
                        });

                        if (isCapturing && muteInterval) { // 只有主动模式才静音新元素
                            if (!node.dataset.originalVolume) {
                                node.dataset.originalVolume = node.volume;
                            }
                            node.volume = 0;
                            node.muted = true;
                        }
                    }
                    // 扫描新增节点中的 data URL
                    scanNodeForDataUrls(node);
                });
                // 检查属性变化(如元素的 src、href 等属性)
                if (mutation.type === 'attributes' && mutation.target) {
                    const target = mutation.target;
                    // 特别处理 Audio/Video 元素的 src 变化
                    if ((target.nodeName === 'AUDIO' || target.nodeName === 'VIDEO') &&
                        mutation.attributeName === 'src' &&
                        target.src &&
                        target.src.startsWith('data:')) {
                        scanNodeForDataUrls(target);
                    } else {
                        scanNodeForDataUrls(target);
                    }
                }
            });
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['src', 'href', 'data-src', 'data-url']
        });

        document.querySelectorAll('audio, video').forEach(mediaElement => {
            // 立即检查现有的 src
            if (mediaElement.src && mediaElement.src.startsWith('data:')) {
                scanNodeForDataUrls(mediaElement);
            }

            // 监听 play 事件
            mediaElement.addEventListener('play', () => {
                if (mediaElement.src) captureAudioFromMediaElement(mediaElement);
            });

            // 监听 loadstart 事件
            mediaElement.addEventListener('loadstart', () => {
                if (mediaElement.src && mediaElement.src.startsWith('data:')) {
                    scanNodeForDataUrls(mediaElement);
                }
            });

            // 监听 canplay 事件
            mediaElement.addEventListener('canplay', () => {
                if (mediaElement.src && mediaElement.src.startsWith('data:')) {
                    scanNodeForDataUrls(mediaElement);
                }
            });
        });

        scanPageForDataUrls();

        // 启动定期扫描(每500ms扫描一次新的 data URL)
        startDataUrlScanning();
    }

    // 启动定期扫描 data URL
    function startDataUrlScanning() {
        if (dataUrlScanInterval) return; // 防止重复启动
        dataUrlScanInterval = setInterval(() => {
            if (!isMonitoring) {
                stopDataUrlScanning();
                return;
            }
            scanPageForDataUrls();
        }, 500); // 每500ms扫描一次
    }

    // 停止定期扫描 data URL
    function stopDataUrlScanning() {
        if (dataUrlScanInterval) {
            clearInterval(dataUrlScanInterval);
            dataUrlScanInterval = null;
        }
    }

    // 停止监控
    function stopMonitoring() {
        if (!isMonitoring) return; // 防止重复卸载
        isMonitoring = false;
        unsafeWindow.XMLHttpRequest = originalXHR;
        unsafeWindow.fetch = originalFetch;
        if (observer) {
            observer.disconnect();
            observer = null;
        }
        stopDataUrlScanning(); // 停止定期扫描
    }

    // 从响应捕获音频
    function captureAudioFromResponse(response, contentType, url) {
        if (!isMonitoring) return; // 最终检查
        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);
        lastAudioCaptureTime = Date.now();
        updateAudioCount();
        saveAudioData();
        updateStatus(`捕获到新音频: ${getShortUrl(url)}`);
        resetAutoMergeTimer();
    }

    // 从媒体元素捕获音频
    function captureAudioFromMediaElement(mediaElement) {
        if (!isMonitoring) return; // 最终检查
        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);
        lastAudioCaptureTime = Date.now();
        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 path = new URL(url).pathname;
            return path.length > 20 ? path.substr(0, 17) + '...' : path;
        } catch (e) {
            return url.substr(0, 20) + '...';
        }
    }

    // 猜测音频格式
    function guessAudioFormat(contentType, url) {
        if (contentType.includes('mpeg') || contentType.includes('mp3')) return 'mp3';
        if (contentType.includes('wav')) return 'wav';
        if (contentType.includes('ogg')) return 'ogg';
        if (contentType.includes('aac')) return 'aac';
        if (contentType.includes('flac')) return 'flac';
        if (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';
    }

    // 保存音频数据
    function saveAudioData() {
        try {
            const serializedData = capturedAudio.map(({ id, source, url, contentType, timestamp, format, size }) =>
                ({ 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 URL
    function scanNodeForDataUrls(node) {
        if (!node) return;

        // Shadow DOM支持
        if (node.shadowRoot) {
            scanNodeForDataUrls(node.shadowRoot);
        }

        // 如果是元素节点或文档片段,检查其属性
        if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
            // 检查常见的包含 URL 的属性
            const attributes = ['src', 'href', 'data-src', 'data-url', 'data-audio'];
            attributes.forEach(attr => {
                const value = node.getAttribute?.(attr);
                if (value && value.startsWith('data:application/octet-stream;base64,')) {
                    if (!capturedAudio.some(audio => audio.url === value)) {
                        validateAudioDataUrl(value, () => captureDataUrl(value, 'application/octet-stream'));
                    }
                } else if (value && value.match(/^data:audio\/[^;]+;base64,/)) {
                    if (!capturedAudio.some(audio => audio.url === value)) {
                        const mimeType = value.split(';')[0].split(':')[1];
                        validateAudioDataUrl(value, () => captureDataUrl(value, mimeType));
                    }
                }
            });

            // 递归扫描子节点
            node.childNodes?.forEach(child => scanNodeForDataUrls(child));
        }

        // 如果是文本节点,检查其内容
        if (node.nodeType === Node.TEXT_NODE && node.textContent) {
            const dataUrlRegex = /data:(application\/octet-stream|audio\/[^;]+);base64,([\sA-Za-z0-9+/=]{40,})/gi;
            let match;
            while ((match = dataUrlRegex.exec(node.textContent)) !== null) {
                const dataUrl = `data:${match[1]};base64,${match[2].replace(/\s+/g, '')}`;
                if (!capturedAudio.some(audio => audio.url === dataUrl)) {
                    validateAudioDataUrl(dataUrl, () => captureDataUrl(dataUrl, match[1]));
                }
            }
        }
    }

    // 扫描页面中的data URLs
    function scanPageForDataUrls() {
        const dataUrlRegex = /data:(application\/octet-stream|audio\/[^;]+);base64,([\sA-Za-z0-9+/=]{40,})/gi;
        let match;
        const content = document.documentElement.innerHTML || '';
        while ((match = dataUrlRegex.exec(content)) !== null) {
            const dataUrl = `data:${match[1]};base64,${match[2].replace(/\s+/g, '')}`;
            if (!capturedAudio.some(audio => audio.url === dataUrl)) {
                validateAudioDataUrl(dataUrl, () => captureDataUrl(dataUrl, match[1]));
            }
        }
    }

    // 深度扫描整个DOM,强行读取所有data音频
    function performDeepScanForDataAudio() {
        const beforeCount = capturedAudio.length;
        try {
            scanNodeForDataUrls(document.body);
            scanPageForDataUrls();
            document.querySelectorAll('audio, video').forEach(node => {
                if (node.src && node.src.startsWith('data:')) {
                    scanNodeForDataUrls(node);
                }
            });
            document.querySelectorAll('iframe').forEach(frame => {
                try {
                    const doc = frame.contentDocument || frame.contentWindow?.document;
                    if (doc) {
                        scanNodeForDataUrls(doc.body);
                    }
                } catch (err) {
                    // 跨域iframe会抛错,忽略
                }
            });
        } catch (e) {
            console.error('深度扫描时出错:', e);
        }
        const added = capturedAudio.length - beforeCount;
        if (added > 0) {
            saveAudioData();
            updateAudioCount();
        }
        return added;
    }

    // 验证数据URL
    function validateAudioDataUrl(dataUrl, callback) {
        const audio = new Audio();
        audio.onloadedmetadata = () => { if (audio.duration > 0) callback(); };
        audio.onerror = () => {
            try {
                fetch(dataUrl).then(r => r.arrayBuffer()).then(buffer => {
                    if (checkAudioSignature(buffer)) callback();
                });
            } catch (e) {}
        };
        audio.src = dataUrl;
    }

    // 捕获data URL
    function captureDataUrl(dataUrl, mimeType) {
        if (!isMonitoring) {
            // 即使不在监控状态,也允许手动添加
            console.log('手动添加data URL音频');
        }

        // 检查是否已存在相同的URL
        if (capturedAudio.some(audio => audio.url === dataUrl)) {
            updateStatus('⚠ 该音频已在捕获列表中');
            return;
        }

        const audioItem = {
            id: generateId(),
            source: 'dataUrl',
            url: dataUrl,
            contentType: mimeType,
            timestamp: new Date().toISOString(),
            format: guessAudioFormat(mimeType, null),
            size: 'embedded'
        };

        capturedAudio.push(audioItem);
        lastAudioCaptureTime = Date.now();
        updateAudioCount();
        saveAudioData();

        // 不在监控状态时也重置自动合并计时器
        if (autoMergeEnabled && capturedAudio.length > 0) {
            resetAutoMergeTimer();
        }
    }

    // 检查音频签名
    function checkAudioSignature(buffer) {
        if (!buffer || buffer.byteLength < 8) return false;
        const view = new Uint8Array(buffer.slice(0, 16));
        const signatures = {
            'ID3': [0x49, 0x44, 0x33], 'MP3': [0xFF, 0xFB], 'RIFF': [0x52, 0x49, 0x46, 0x46],
            'OGG': [0x4F, 0x67, 0x67, 0x53], 'FLAC': [0x66, 0x4C, 0x61, 0x43],
            'M4A': [0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70], 'FLV': [0x46, 0x4C, 0x56, 0x01]
        };
        for (const sig of Object.values(signatures)) {
            if (sig.every((byte, i) => view[i] === byte)) return true;
        }
        try {
            const text = new TextDecoder('utf-8').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:')) {
            if (audioDataUrl) { // 如果用户输入了内容但不是data URL
                alert('请提供有效的data URL,格式为: data:application/octet-stream;base64,...');
            }
            return;
        }

        try {
            // 验证数据URL格式
            if (!audioDataUrl.includes('base64,')) {
                alert('数据URL格式不正确,必须包含base64编码的数据');
                return;
            }

            // 提取MIME类型
            const mimeTypeMatch = audioDataUrl.match(/^data:([^;]+);/);
            const mimeType = mimeTypeMatch ? mimeTypeMatch[1] : 'application/octet-stream';

            // 捕获到列表但不下载
            captureDataUrl(audioDataUrl, mimeType);
            updateStatus('✓ 音频已添加到捕获列表');

            // 可选:自动打开捕获列表让用户查看
            setTimeout(() => {
                if (capturedAudio.length > 0) {
                    showAudioManagementWindow();
                }
            }, 500);

        } catch (error) {
            console.error('处理data URL失败:', error);
            alert('处理data URL失败: ' + error.message);
            updateStatus('⚠ 处理data URL失败');
        }
    }

    // 处理Base64
    function handleBase64FromRequest() {
        const modal = createModal('处理Base64数据', {
            width: '95%',
            maxWidth: '420px',
            maxHeight: '70vh',
            stack: true,
            backdropOpacity: 0.35
        });
        const theme = getThemeStyles();
        const content = document.createElement('div');
        content.innerHTML = `
            <div style="margin-bottom: 16px;">
                <div style="font-size: 13px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'}; margin-bottom: 8px;">
                    将Base64文本粘贴到下面的输入框中,支持直接包含 data:audio/*;base64, 前缀的数据。
                </div>
                <textarea id="base64-input" placeholder="在此粘贴base64编码的音频数据"
                    style="width: 100%; height: 150px; padding: 12px; background: ${theme.buttonBg}; color: ${theme.color};
                    border: 1px solid ${theme.border}; border-radius: 6px; font-size: 13px; font-family: monospace;
                    resize: vertical; box-sizing: border-box; transition: all 0.2s;"
                    onfocus="this.style.borderColor='#3b82f6'; this.style.background='${isDarkMode ? '#1f2937' : '#ffffff'}'"
                    onblur="this.style.borderColor='${theme.border}'; this.style.background='${theme.buttonBg}'"></textarea>
                <div style="font-size: 11px; color: ${isDarkMode ? '#6b7280' : '#9ca3af'}; margin-top: 6px; padding-left: 4px;">
                    💡 如果文本中包含 data:audio/...;base64, 前缀,会自动提取并识别格式。
                </div>
            </div>
            <div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 16px;">
                <label for="format-select" style="font-size: 13px; color: ${isDarkMode ? '#d1d5db' : '#4b5563'};">保存格式:</label>
                <select id="format-select" style="padding: 10px 12px; background: ${theme.buttonBg}; color: ${theme.color};
                    border: 1px solid ${theme.border}; border-radius: 6px; font-size: 13px; transition: all 0.2s;"
                    onfocus="this.style.borderColor='#3b82f6'" onblur="this.style.borderColor='${theme.border}'">
                    <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: 8px;">
                <button id="cancel-base64" style="padding: 10px 20px; background: ${theme.buttonBg}; color: ${theme.color};
                    border: 1px solid ${theme.border}; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;"
                    onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='${theme.buttonBg}'">
                    取消
                </button>
                <button id="process-base64-btn" style="padding: 10px 20px; background: ${theme.primaryBg}; color: white;
                    border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s;
                    display: flex; align-items: center; gap: 6px;"
                    onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">
                    ${icons.download} <span>处理并下载</span>
                </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.includes('base64,') ? base64Data.split('base64,')[1] : base64Data;
            try {
                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 dataUrl = `data:${mimeTypes[format] || 'application/octet-stream'};base64,${cleanBase64}`;
                const a = document.createElement('a');
                a.href = dataUrl;
                a.download = `${fileNamePrefix}_${Date.now()}.${format}`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                captureDataUrl(dataUrl, mimeTypes[format]);
                closeModal(modal);
                updateStatus('音频处理并下载成功');
            } catch (e) { alert('无效的base64数据: ' + e.message); }
        });
    }

    // 复制Base64数据
    async function copyAudioData(id) {
        const audio = capturedAudio.find(a => a.id === id);
        if (!audio) {
            updateStatus('⚠ 未找到音频数据');
            return;
        }

        const btn = document.querySelector(`.copy-btn[data-id="${id}"]`);
        const originalHtml = btn ? btn.innerHTML : '';

        if (btn) {
            btn.innerHTML = `...`;
            btn.disabled = true;
        }

        try {
            let base64Data;
            if (audio.source === 'dataUrl') {
                if (audio.url.includes('base64,')) {
                    base64Data = audio.url.split('base64,')[1];
                } else {
                    updateStatus('⚠ 无法复制非Base64的Data URL');
                    if (btn) btn.innerHTML = originalHtml;
                    return;
                }
            } else if (audio.data instanceof ArrayBuffer || audio.url) {
                // 需要先获取缓冲区
                const buffer = await getAudioBuffer(audio);

                // 将ArrayBuffer转换为Base64
                const bytes = new Uint8Array(buffer);
                let binary = '';
                for (let i = 0; i < bytes.byteLength; i++) {
                    binary += String.fromCharCode(bytes[i]);
                }
                base64Data = window.btoa(binary);
            } else {
                updateStatus('⚠ 无法获取音频数据');
                if (btn) btn.innerHTML = originalHtml;
                return;
            }

            await navigator.clipboard.writeText(base64Data);
            const shortId = id.split('_')[1] || id;
            updateStatus(`✓ 音频 #${shortId} 的Base64已复制`);

            // 显示"已复制"的临时状态
            if (btn) {
                btn.innerHTML = `${icons.check} 已复制`;
                setTimeout(() => {
                    btn.innerHTML = originalHtml;
                    btn.disabled = false;
                }, 2000);
            }

        } catch (err) {
            console.error('复制Base64失败:', err);
            updateStatus('⚠ 复制失败: ' + err.message);
            alert('复制失败。请检查控制台获取更多信息。');
            if (btn) {
                btn.innerHTML = originalHtml;
                btn.disabled = false;
            }
        }
    }

    // 显示已捕获音频管理窗口
    function showAudioManagementWindow(options = {}) {
        // 注入拖拽排序样式
        injectCustomScrollbarStyles();

        const theme = getThemeStyles();
        const parseButtonBorderColor = isDarkMode ? '#4b5563' : theme.border;
        const parseButtonTextColor = isDarkMode ? '#f3f4f6' : '#111827';
        const parseButtonBaseStyle = `
            padding: 12px 16px;
            background: transparent;
            color: ${parseButtonTextColor};
            border: 1px solid ${parseButtonBorderColor};
            border-radius: 8px;
            cursor: pointer;
            font-size: 13px;
            font-weight: 500;
            display: flex;
            align-items: center;
            gap: 6px;
            transition: opacity 0.2s, border-color 0.2s;
            white-space: nowrap;
        `;
        const parseButtonLargeStyle = `${parseButtonBaseStyle} justify-content: center; gap: 8px; font-size: 14px;`;
        const parseButtonHoverOpacity = isDarkMode ? '0.85' : '0.75';
        const shouldAutoSelectAll = !!options.autoSelectAll;

        // 如果列表为空,显示添加音频的选项
        if (capturedAudio.length === 0) {
            const modal = createModal('已捕获音频管理');
            const content = document.createElement('div');
            content.innerHTML = `
                <div style="text-align: center; padding: 40px 20px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'};">
                    <div style="font-size: 48px; margin-bottom: 12px;">🎵</div>
                    <div style="font-size: 14px; margin-bottom: 8px;">暂无捕获的音频</div>
                    <div style="font-size: 12px; opacity: 0.7; margin-bottom: 20px;">您可以通过以下方式添加音频</div>
                    <div style="display: flex; flex-direction: column; gap: 12px; max-width: 300px; margin: 0 auto;">
                        <button id="parse-url-btn" style="${parseButtonLargeStyle}"
                            onmouseover="this.style.opacity='${parseButtonHoverOpacity}'" onmouseout="this.style.opacity='1'">
                            ${icons.link} <span>解析URL添加音频</span>
                        </button>
                        <button id="parse-base64-btn" style="${parseButtonLargeStyle}"
                            onmouseover="this.style.opacity='${parseButtonHoverOpacity}'" onmouseout="this.style.opacity='1'">
                            ${icons.code} <span>解析Base64文本</span>
                        </button>
                        <button id="deep-scan-empty" style="${parseButtonLargeStyle}"
                            onmouseover="this.style.opacity='${parseButtonHoverOpacity}'" onmouseout="this.style.opacity='1'">
                            ${icons.search} <span style="font-weight: 400;">深度扫描</span>
                        </button>
                        <div style="font-size: 11px; opacity: 0.7; text-align: center;">
                            或使用主面板的"一键获取"或"手动获取"功能
                        </div>
                    </div>
                </div>
                <div style="display: flex; justify-content: flex-end; margin-top: 20px;">
                    <button id="close-empty-list" style="padding: 8px 16px; background: ${theme.buttonBg}; color: ${theme.color};
                        border: 1px solid ${theme.border}; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;"
                        onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='${theme.buttonBg}'">
                        关闭
                    </button>
                </div>
            `;
            modal.appendChild(content);

            document.getElementById('close-empty-list').addEventListener('click', () => closeModal(modal));
            document.getElementById('parse-url-btn').addEventListener('click', () => {
                closeModal(modal);
                showParseUrlDialog();
            });
            document.getElementById('parse-base64-btn').addEventListener('click', () => {
                closeModal(modal);
                handleBase64FromRequest();
            });
            document.getElementById('deep-scan-empty').addEventListener('click', () => {
                const added = performDeepScanForDataAudio();
                if (added > 0) {
                    updateStatus(`深度扫描完成,新增 ${added} 个音频`);
                    closeModal(modal);
                    showAudioManagementWindow({ autoSelectAll: shouldAutoSelectAll });
                } else {
                    updateStatus('深度扫描完成,未发现新的音频');
                }
            });

            return;
        }

        // 完整的音频管理窗口
        const modal = createModal('已捕获音频管理');
        const content = document.createElement('div');
        content.innerHTML = `
            <div style="margin-bottom: 16px;">
                <div style="display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap;">
                    <div style="position: relative; flex: 1;">
                        <input type="text" id="search-audio" placeholder="🔍 搜索音频..."
                            style="width: 100%; padding: 12px 16px; background: ${theme.buttonBg}; color: ${theme.color};
                            border: 1px solid ${theme.border}; border-radius: 8px; font-size: 14px; transition: all 0.2s;"
                            onfocus="this.style.borderColor='#3b82f6'; this.style.background='${isDarkMode ? '#1f2937' : '#ffffff'}'"
                            onblur="this.style.borderColor='${theme.border}'; this.style.background='${theme.buttonBg}'">
                    </div>
                    <button id="parse-url-in-list" style="${parseButtonBaseStyle}"
                        onmouseover="this.style.opacity='${parseButtonHoverOpacity}'" onmouseout="this.style.opacity='1'">
                        ${icons.link} <span>解析URL</span>
                    </button>
                    <button id="parse-base64-in-list" style="${parseButtonBaseStyle}"
                        onmouseover="this.style.opacity='${parseButtonHoverOpacity}'" onmouseout="this.style.opacity='1'">
                        ${icons.code} <span>解析Base64</span>
                    </button>
                    <button id="deep-scan-btn" style="${parseButtonBaseStyle}"
                        onmouseover="this.style.opacity='${parseButtonHoverOpacity}'" onmouseout="this.style.opacity='1'">
                        ${icons.search} <span style="font-weight: 400;">深度扫描</span>
                    </button>
                </div>

                <!-- 合并下载区域 -->
                <div style="background: ${isDarkMode ? '#1f2937' : '#f3f4f6'}; padding: 16px; border-radius: 8px; margin-bottom: 12px;">
                    <div style="font-size: 14px; color: ${theme.color}; margin-bottom: 8px; font-weight: 500;">
                        📦 合并下载 (${capturedAudio.length} 个音频)
                    </div>
                    <div style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap;">
                        <input type="text" id="merge-range" placeholder="例如: 1-5,7,9-12"
                            style="flex: 1; min-width: 150px; padding: 10px 12px; background: ${theme.buttonBg}; color: ${theme.color};
                            border: 1px solid ${theme.border}; border-radius: 6px; font-size: 14px; transition: all 0.2s;"
                            onfocus="this.style.borderColor='#3b82f6'; this.style.background='${isDarkMode ? '#1f2937' : '#ffffff'}'"
                            onblur="this.style.borderColor='${theme.border}'; this.style.background='${theme.buttonBg}'">
                        <select id="merge-format" style="padding: 10px 12px; background: ${theme.buttonBg}; color: ${theme.color};
                            border: 1px solid ${theme.border}; border-radius: 6px; font-size: 14px; cursor: pointer; transition: all 0.2s;"
                            onfocus="this.style.borderColor='#3b82f6'" onblur="this.style.borderColor='${theme.border}'">
                            <option value="mp3">MP3</option>
                            <option value="wav">WAV</option>
                        </select>
                        <div style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;">
                            <div style="display: flex; gap: 4px; flex-wrap: wrap;">
                                <button id="select-all-btn" style="padding: 10px 16px; background: ${theme.buttonBg}; color: ${theme.color};
                                    border: 1px solid ${theme.border}; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s; white-space: nowrap;
                                    display: flex; align-items: center; justify-content: center; gap: 6px;"
                                    onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='${theme.buttonBg}'">
                                    ${icons.check} <span>全选</span>
                                </button>
                                <button id="deselect-all-btn" style="padding: 10px 16px; background: ${theme.buttonBg}; color: ${theme.color};
                                    border: 1px solid ${theme.border}; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s; white-space: nowrap;
                                    display: flex; align-items: center; justify-content: center; gap: 6px;"
                                    onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='${theme.buttonBg}'">
                                    ${icons.close} <span>全不选</span>
                                </button>
                            </div>
                            <button id="start-merge" style="padding: 10px 20px; background: ${theme.successBg};
                                color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500;
                                transition: all 0.2s; box-shadow: 0 2px 6px ${isDarkMode ? 'rgba(102, 187, 106, 0.25)' : 'rgba(102, 187, 106, 0.15)'};
                                display: flex; align-items: center; justify-content: center; gap: 8px;"
                                onmouseover="this.style.transform='translateY(-1px)'; this.style.background='${theme.successHover}'; this.style.boxShadow='0 4px 10px ${isDarkMode ? 'rgba(102, 187, 106, 0.3)' : 'rgba(102, 187, 106, 0.2)'}'"
                                onmouseout="this.style.transform='translateY(0)'; this.style.background='${theme.successBg}'; this.style.boxShadow='0 2px 6px ${isDarkMode ? 'rgba(102, 187, 106, 0.25)' : 'rgba(102, 187, 106, 0.15)'}'">
                                ${icons.download} <span>合并下载</span>
                            </button>
                        </div>
                    </div>
                    <div style="font-size: 11px; color: ${isDarkMode ? '#6b7280' : '#9ca3af'}; margin-top: 6px; padding-left: 4px;">
                        💡 拖拽音频项可重新排序,范围格式: 单个数字(如5)、范围(如1-5)或组合(如1-3,5,7-9)
                    </div>
                </div>

                <div style="display: flex; gap: 8px; justify-content: flex-end;">
                    <button id="close-audio-list" style="padding: 8px 16px; background: ${theme.buttonBg}; color: ${theme.color};
                        border: 1px solid ${theme.border}; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;"
                        onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='${theme.buttonBg}'">
                        关闭
                    </button>
                    <button id="clear-all" style="padding: 8px 16px; background: ${theme.dangerBg}; color: white;
                        border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s;"
                        onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">
                        清空列表
                    </button>
                </div>
            </div>
            <div id="audio-list-container" style="max-height: calc(75vh - 260px); overflow-y: auto; margin-top: 16px;"></div>
        `;
        modal.appendChild(content);

        document.getElementById('close-audio-list').addEventListener('click', () => closeModal(modal));
        document.getElementById('parse-url-in-list').addEventListener('click', () => {
            showParseUrlDialog();
        });
        document.getElementById('parse-base64-in-list').addEventListener('click', () => {
            handleBase64FromRequest();
        });
        document.getElementById('deep-scan-btn').addEventListener('click', () => {
            const searchInput = document.getElementById('search-audio');
            const currentSearch = searchInput ? searchInput.value : '';
            const added = performDeepScanForDataAudio();
            renderAudioList(currentSearch);
            const rangeInputEl = document.getElementById('merge-range');
            if (rangeInputEl) {
                if (rangeInputEl.value.trim()) {
                    rangeInputEl.dispatchEvent(new Event('input'));
                } else if (shouldAutoSelectAll) {
                    selectAllForMerge();
                }
            }
            updateStatus(added > 0 ? `深度扫描完成,新增 ${added} 个音频` : '深度扫描完成,未发现新的音频');
        });

        // 清空列表(无确认)
        document.getElementById('clear-all').addEventListener('click', function() {
            capturedAudio = [];
            updateAudioCount();
            saveAudioData();
            closeModal(modal);
            updateStatus('已清空音频列表');
        });

        renderAudioList();

        document.getElementById('search-audio').addEventListener('input', function() {
            renderAudioList(this.value);
        });

        const selectAllForMerge = () => {
            const mergeRangeInput = document.getElementById('merge-range');
            if (!mergeRangeInput) return;
            if (capturedAudio.length === 0) {
                mergeRangeInput.value = '';
                document.querySelectorAll('.merge-select').forEach(cb => { cb.checked = false; });
                return;
            }
            mergeRangeInput.value = `1-${capturedAudio.length}`;
            document.querySelectorAll('.merge-select').forEach(cb => { cb.checked = true; });
        };

        const clearAllSelections = () => {
            const mergeRangeInput = document.getElementById('merge-range');
            if (mergeRangeInput) mergeRangeInput.value = '';
            document.querySelectorAll('.merge-select').forEach(cb => { cb.checked = false; });
        };

        if (shouldAutoSelectAll) {
            selectAllForMerge();
        }

        // 全选按钮
        document.getElementById('select-all-btn').addEventListener('click', () => {
            selectAllForMerge();
        });

        const deselectButton = document.getElementById('deselect-all-btn');
        if (deselectButton) {
            deselectButton.addEventListener('click', () => {
                clearAllSelections();
            });
        }

        // 合并下载按钮
        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);
        });

        // 范围输入和复选框联动
        const rangeInput = document.getElementById('merge-range');
        rangeInput.addEventListener('input', function() {
            const indices = parseRangeString(this.value.trim(), capturedAudio.length);
            document.querySelectorAll('.merge-select').forEach(cb => {
                cb.checked = indices.includes(parseInt(cb.getAttribute('data-index')));
            });
        });

        function renderAudioList(searchTerm = '') {
            const theme = getThemeStyles();
            const container = document.getElementById('audio-list-container');
            container.innerHTML = '';

            // 添加使用提示
            if (capturedAudio.length > 0 && !searchTerm) {
                const tipElement = document.createElement('div');
                tipElement.style.cssText = `
                    background: ${isDarkMode ? '#1f2937' : '#f0f9ff'};
                    border: 1px solid ${isDarkMode ? '#374151' : '#bae6fd'};
                    border-radius: 6px;
                    padding: 12px;
                    margin-bottom: 12px;
                    font-size: 12px;
                    color: ${isDarkMode ? '#9ca3af' : '#0369a1'};
                `;
                const dragIconInline = `<span style="display:inline-flex; align-items:center; vertical-align:middle; color: ${theme.primaryBg}; font-weight: 500;">${icons.sort}</span>`;
                tipElement.innerHTML = `
                    <div style="display: flex; align-items: flex-start; gap: 8px;">
                        <span style="font-size: 14px; line-height: 1;">💡</span>
                        <div style="line-height: 1.6; word-break: break-word;">
                            <strong>提示:</strong>拖拽 ${dragIconInline} 图标可重新排序音频,勾选复选框选择要合并的音频。
                        </div>
                    </div>
                `;
                container.appendChild(tipElement);
            }

            const filteredAudio = searchTerm ?
                capturedAudio.filter(a => (a.url && a.url.toLowerCase().includes(searchTerm.toLowerCase())) ||
                                        a.format.toLowerCase().includes(searchTerm.toLowerCase()) ||
                                        a.source.toLowerCase().includes(searchTerm.toLowerCase())) :
                capturedAudio;

            if (filteredAudio.length === 0) {
                container.innerHTML = `<div style="text-align: center; padding: 40px 20px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'};">
                    <div style="font-size: 48px; margin-bottom: 12px;">${searchTerm ? '🔍' : '🎵'}</div>
                    <div style="font-size: 14px; margin-bottom: 8px;">${searchTerm ? '没有匹配的音频' : '暂无捕获的音频'}</div>
                    ${!searchTerm ? `
                        <div style="display: flex; gap: 8px; justify-content: center; margin-top: 12px;">
                            <button id="parse-url-empty" style="${parseButtonBaseStyle}"
                                onmouseover="this.style.opacity='${parseButtonHoverOpacity}'" onmouseout="this.style.opacity='1'">
                                ${icons.link} <span>解析URL</span>
                            </button>
                            <button id="parse-base64-empty" style="${parseButtonBaseStyle}"
                                onmouseover="this.style.opacity='${parseButtonHoverOpacity}'" onmouseout="this.style.opacity='1'">
                                ${icons.code} <span>解析Base64</span>
                            </button>
                        </div>
                    ` : ''}
                </div>`;

                if (!searchTerm) {
                    document.getElementById('parse-url-empty').addEventListener('click', () => {
                        showParseUrlDialog();
                    });
                    document.getElementById('parse-base64-empty').addEventListener('click', () => {
                        handleBase64FromRequest();
                    });
                }
                return;
            }

            // 渲染音频列表
            filteredAudio.forEach((audio, index) => {
                const originalIndex = capturedAudio.findIndex(a => a.id === audio.id);
                const item = document.createElement('div');
                item.className = 'audio-item';
                item.setAttribute('data-id', audio.id);
                item.setAttribute('draggable', 'true');
                item.style.cssText = `
                    background: ${isDarkMode ? '#2d2d2d' : '#f9fafb'};
                    border: 1px solid ${theme.border};
                    border-radius: 8px;
                    padding: 12px;
                    margin-bottom: 8px;
                    transition: all 0.2s;
                    cursor: move;
                `;

                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; align-items: center; gap: 8px; margin-bottom: 8px;">
                        <div class="drag-handle" style="display: flex; align-items: center; padding: 4px;">
                            ${icons.sort}
                        </div>
                        <input type="checkbox" class="merge-select" data-index="${originalIndex}"
                            style="cursor: pointer; width: 16px; height: 16px;">
                        <div style="display: flex; align-items: center; gap: 8px;">
                            <span style="background: ${theme.primaryBg}; color: white; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600;">
                                #${originalIndex + 1}
                            </span>
                            <span style="font-weight: 600; font-size: 14px;">${audio.format.toUpperCase()}</span>
                        </div>
                        <div style="flex: 1;"></div>
                        <div style="font-size: 11px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'};">${date}</div>
                    </div>
                    <div title="${audio.url}" style="font-size: 12px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'};
                        word-break: break-all; margin-bottom: 8px; padding: 6px 8px; background: ${isDarkMode ? '#1f2937' : '#ffffff'};
                        border-radius: 4px; font-family: monospace;">
                        ${getShortUrl(audio.url)}
                    </div>
                    <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px;">
                        <div style="font-size: 11px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'};">
                            来源: ${audio.source} | 大小: ${size}
                        </div>
                        <div style="display: flex; gap: 6px; flex-wrap: wrap;">
                            <button class="download-btn" data-id="${audio.id}" style="padding: 6px 12px; background: ${theme.primaryBg};
                                color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.2s;
                                display: flex; align-items: center; gap: 4px;"
                                onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">
                                ${icons.download} 下载
                            </button>
                            <button class="copy-btn" data-id="${audio.id}" style="padding: 6px 12px; background: ${theme.buttonBg};
                                color: ${theme.color}; border: 1px solid ${theme.border}; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.2s;
                                display: flex; align-items: center; gap: 4px;"
                                onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='${theme.buttonBg}'">
                                ${icons.copy} 复制
                            </button>
                            <button class="remove-btn" data-id="${audio.id}" style="padding: 6px 12px; background: ${isDarkMode ? '#7f1d1d' : '#fee2e2'};
                                color: ${isDarkMode ? '#fca5a5' : '#dc2626'}; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.2s;
                                display: flex; align-items: center; gap: 4px;"
                                onmouseover="this.style.background='${isDarkMode ? '#991b1b' : '#fecaca'}'" onmouseout="this.style.background='${isDarkMode ? '#7f1d1d' : '#fee2e2'}'">
                                ${icons.trash} 删除
                            </button>
                        </div>
                    </div>
                `;
                container.appendChild(item);

                // 设置拖拽事件
                setupDragAndDrop(item, originalIndex);
            });

            // 设置复选框变化事件
            document.querySelectorAll('.merge-select').forEach(cb => {
                cb.addEventListener('change', () => {
                    const selectedIndices = Array.from(document.querySelectorAll('.merge-select:checked'))
                        .map(c => parseInt(c.getAttribute('data-index')));
                    rangeInput.value = generateRangeString(selectedIndices);
                });
            });

            document.querySelectorAll('.download-btn').forEach(btn => btn.addEventListener('click', function() {
                downloadAudio(this.getAttribute('data-id'));
            }));
            document.querySelectorAll('.copy-btn').forEach(btn => btn.addEventListener('click', function() {
                copyAudioData(this.getAttribute('data-id'));
            }));
            document.querySelectorAll('.remove-btn').forEach(btn => btn.addEventListener('click', function() {
                removeAudio(this.getAttribute('data-id'));
                renderAudioList(searchTerm);
            }));
        }

        function setupDragAndDrop(item, currentIndex) {
            item.addEventListener('dragstart', (e) => {
                e.dataTransfer.setData('text/plain', currentIndex);
                item.classList.add('dragging');
                setTimeout(() => {
                    item.style.opacity = '0.4';
                }, 0);
            });

            item.addEventListener('dragend', () => {
                item.classList.remove('dragging');
                item.style.opacity = '1';
                // 更新所有复选框的data-index属性
                document.querySelectorAll('.audio-item').forEach((el, index) => {
                    const checkbox = el.querySelector('.merge-select');
                    if (checkbox) {
                        checkbox.setAttribute('data-index', index);
                    }
                    const indexBadge = el.querySelector('span[style*="background"]');
                    if (indexBadge) {
                        indexBadge.textContent = `#${index + 1}`;
                    }
                });
            });

            item.addEventListener('dragover', (e) => {
                e.preventDefault();
                item.classList.add('drag-over');
            });

            item.addEventListener('dragleave', () => {
                item.classList.remove('drag-over');
            });

            item.addEventListener('drop', (e) => {
                e.preventDefault();
                item.classList.remove('drag-over');

                const fromIndex = parseInt(e.dataTransfer.getData('text/plain'));
                const toIndex = currentIndex;

                if (fromIndex !== toIndex) {
                    // 重新排序数组
                    const [movedItem] = capturedAudio.splice(fromIndex, 1);
                    capturedAudio.splice(toIndex, 0, movedItem);

                    // 保存数据
                    saveAudioData();

                    // 重新渲染列表
                    renderAudioList(document.getElementById('search-audio').value);

                    updateStatus(`音频 #${fromIndex + 1} 已移动到位置 #${toIndex + 1}`);
                }
            });
        }

        function downloadAudio(id) {
            const audio = capturedAudio.find(a => a.id === id);
            if (!audio) return;
            if (audio.source === 'dataUrl') {
                const a = document.createElement('a');
                a.href = audio.url;
                a.download = `${fileNamePrefix}_${Date.now()}.${audio.format}`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                updateStatus(`已下载音频 #${capturedAudio.findIndex(a => a.id === id) + 1}`);
            } else if (audio.url) {
                GM_download({
                    url: audio.url,
                    name: `${fileNamePrefix}_${Date.now()}.${audio.format}`,
                    onload: () => updateStatus(`已下载音频 #${capturedAudio.findIndex(a => a.id === id) + 1}`),
                    onerror: (e) => {
                        console.error('下载失败:', e);
                        updateStatus('下载失败');
                    }
                });
            }
        }

        function removeAudio(id) {
            const index = capturedAudio.findIndex(a => a.id === id);
            if (index !== -1) {
                capturedAudio.splice(index, 1);
                updateAudioCount();
                saveAudioData();
                updateStatus('已删除音频');
            }
        }
    }

    // 解析URL对话框函数
    function showParseUrlDialog() {
        const modal = createModal('解析URL添加音频', {
            width: '95%',
            maxWidth: '420px',
            maxHeight: '70vh',
            stack: true,
            backdropOpacity: 0.35
        });
        const theme = getThemeStyles();
        const content = document.createElement('div');
        content.innerHTML = `
            <div style="margin-bottom: 16px;">
                <div style="font-size: 13px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'}; margin-bottom: 8px;">
                    粘贴data URL格式的音频链接(以 data:application/octet-stream;base64, 开头)
                </div>
                <textarea id="url-input" placeholder="data:application/octet-stream;base64,..."
                    style="width: 100%; height: 120px; padding: 12px; background: ${theme.buttonBg}; color: ${theme.color};
                    border: 1px solid ${theme.border}; border-radius: 6px; font-size: 13px; font-family: monospace;
                    resize: vertical; box-sizing: border-box; transition: all 0.2s;"
                    onfocus="this.style.borderColor='#3b82f6'; this.style.background='${isDarkMode ? '#1f2937' : '#ffffff'}'"
                    onblur="this.style.borderColor='${theme.border}'; this.style.background='${theme.buttonBg}'"></textarea>
                <div style="font-size: 11px; color: ${isDarkMode ? '#6b7280' : '#9ca3af'}; margin-top: 6px; padding-left: 4px;">
                    💡 提示:可以在浏览器开发者工具的Network面板中找到音频请求,复制Response中的data URL
                </div>
            </div>
            <div style="display: flex; justify-content: flex-end; gap: 8px;">
                <button id="cancel-parse" style="padding: 10px 20px; background: ${theme.buttonBg}; color: ${theme.color};
                    border: 1px solid ${theme.border}; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;"
                    onmouseover="this.style.background='${theme.buttonHover}'" onmouseout="this.style.background='${theme.buttonBg}'">
                    取消
                </button>
                <button id="confirm-parse" style="padding: 10px 20px; background: ${theme.primaryBg}; color: white;
                    border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s;
                    display: flex; align-items: center; gap: 6px;"
                    onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">
                    ${icons.link} <span>添加到列表</span>
                </button>
            </div>
        `;
        modal.appendChild(content);

        document.getElementById('cancel-parse').addEventListener('click', () => closeModal(modal));
        document.getElementById('confirm-parse').addEventListener('click', () => {
            const urlInput = document.getElementById('url-input').value.trim();
            if (!urlInput) {
                alert('请输入URL');
                return;
            }

            if (!urlInput.startsWith('data:')) {
                alert('请输入有效的data URL,格式为: data:application/octet-stream;base64,...');
                return;
            }

            try {
                // 验证数据URL格式
                if (!urlInput.includes('base64,')) {
                    alert('数据URL格式不正确,必须包含base64编码的数据');
                    return;
                }

                // 提取MIME类型
                const mimeTypeMatch = urlInput.match(/^data:([^;]+);/);
                const mimeType = mimeTypeMatch ? mimeTypeMatch[1] : 'application/octet-stream';

                // 捕获到列表
                captureDataUrl(urlInput, mimeType);
                closeModal(modal);
                updateStatus('✓ 音频已添加到捕获列表');

                // 重新打开管理窗口显示新添加的音频
                setTimeout(() => {
                    showAudioManagementWindow();
                }, 500);

            } catch (error) {
                console.error('处理data URL失败:', error);
                alert('处理data URL失败: ' + error.message);
                updateStatus('⚠ 处理data URL失败');
            }
        });
    }

    // 解析范围字符串
    function parseRangeString(rangeStr, maxValue) {
        const result = new Set();
        rangeStr.split(',').forEach(part => {
            part = part.trim();
            if (part.includes('-')) {
                const [start, end] = part.split('-').map(n => parseInt(n.trim()));
                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++) result.add(i);
                }
            } else {
                const index = parseInt(part) - 1;
                if (!isNaN(index) && index >= 0 && index < maxValue) result.add(index);
            }
        });
        return Array.from(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], end = indices[0];
        for (let i = 1; i < indices.length; i++) {
            if (indices[i] === end + 1) {
                end = indices[i];
            } else {
                ranges.push(start === end ? `${start + 1}` : `${start + 1}-${end + 1}`);
                start = end = indices[i];
            }
        }
        ranges.push(start === end ? `${start + 1}` : `${start + 1}-${end + 1}`);
        return ranges.join(',');
    }

    // 合并音频
    function mergeAudio(indices, format) {
        if (indices.length === 0) { alert('未选择任何音频'); return; }
        const modal = createModal('音频合并进度');
        const theme = getThemeStyles();
        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: ${isDarkMode ? '#2d2d2d' : '#f0f0f0'}; border-radius: 4px; overflow: hidden;">
                    <div id="merge-progress-bar" style="width: 0%; height: 20px; background: #0f9d58;"></div>
                </div>
                <div id="merge-status">正在初始化...</div>
            </div>
        `;
        modal.appendChild(content);
        setTimeout(() => {
            startMergeProcess(indices, format, modal, false);
        }, 500);
    }

    // 开始合并流程
    async function startMergeProcess(indices, format, modal, isAutoMerge = false) {
        try {
            updateMergeProgress(5, '开始下载音频数据...');
            const audioBuffers = [];
            for (let i = 0; i < indices.length; i++) {
                const index = indices[i];
                const progress = 5 + Math.floor(((i + 1) / indices.length) * 50);
                updateMergeProgress(progress, `正在处理第 ${i + 1}/${indices.length} 个音频...`);
                const audio = capturedAudio[index];
                if (!audio) continue;
                try {
                    const buffer = await getAudioBuffer(audio);
                    if (buffer && (format !== 'mp3' || (audio.format === 'mp3' || isValidMp3(buffer)))) {
                        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 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);

            let statusMsg = `已成功合并 ${audioBuffers.length} 个音频文件并下载`;
            if (autoClearList) {
                capturedAudio = [];
                updateAudioCount();
                saveAudioData();
                statusMsg = `合并下载完成,并已清空列表`;
            }
            updateMergeProgress(100, '合并完成,已开始下载!');
            updateStatus(statusMsg);

            if (isAutoMerge && (isMonitoring && isCapturing)) { // 仅在主动模式下自动停止
                setTimeout(() => {
                    stopMonitoring();
                    unmutePageAudio(true); // 主动模式,需要点击停止按钮
                    isCapturing = false;
                    isMonitoring = false;
                    updateCaptureUI();
                    updateStatus('✅ 自动合并完成,已停止获取');
                }, 1000);
            }

            setTimeout(() => closeModal(modal), 3000);
        } catch (error) {
            console.error('合并音频过程中出错:', error);
            updateMergeStatus(`合并失败: ${error.message}`);
        }
    }

    // 获取音频的ArrayBuffer数据
    async function getAudioBuffer(audio) {
        return new Promise(async (resolve, reject) => {
            try {
                if (audio.data instanceof ArrayBuffer) {
                    resolve(audio.data);
                } else if (audio.source === 'dataUrl') {
                    if (audio.url.startsWith('data:application/octet-stream;base64,') || audio.url.startsWith('data:audio/mpeg;base64,') || audio.url.includes('base64,')) {
                        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 {
                        fetch(audio.url).then(response => response.arrayBuffer()).then(buffer => resolve(buffer)).catch(reject);
                    }
                } else if (audio.url) {
                    GM_xmlhttpRequest({
                        method: 'GET', url: audio.url, responseType: 'arraybuffer',
                        onload: (response) => resolve(response.response),
                        onerror: (error) => reject(new Error('无法下载音频: ' + error))
                    });
                } else {
                    reject(new Error('无法获取音频数据'));
                }
            } catch (e) { reject(e); }
        });
    }

    // 合并音频缓冲区
    async function mergeAudioBuffers(audioBuffers, format) {
        return new Promise(async (resolve, reject) => {
            try {
                if (format === 'mp3') {
                    // MP3 快速合并
                    updateMergeStatus('正在直接合并MP3文件...');
                    const validMp3Buffers = [];
                    for (let i = 0; i < audioBuffers.length; i++) {
                        const buffer = audioBuffers[i];
                        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文件...`);
                    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(95, '合并完成,准备下载...');
                    resolve(mergedMp3.buffer);

                } else if (format === 'wav') {
                    // WAV 合并处理
                    updateMergeStatus('正在合并 WAV 文件...');

                    // WAV 头部参数
                    const RIFF = 0x46464952;
                    const WAVE = 0x45564157;
                    const fmt  = 0x20746D66;
                    const data = 0x61746164;

                    let totalDataSize = 0;
                    let sampleRate = 0;
                    let channels = 0;
                    let bitsPerSample = 0;

                    // 第一次遍历,获取音频参数和总数据大小
                    for (let i = 0; i < audioBuffers.length; i++) {
                        const buffer = audioBuffers[i];
                        const view = new DataView(buffer);

                        // 检查WAV文件头
                        if (view.getUint32(0, false) !== RIFF || view.getUint32(8, false) !== WAVE) {
                            continue;
                        }

                        // 获取音频参数(使用第一个有效WAV的参数)
                        if (!sampleRate) {
                            channels = view.getUint16(22, true);
                            sampleRate = view.getUint32(24, true);
                            bitsPerSample = view.getUint16(34, true);
                        }

                        // 找到数据块
                        let offset = 12;
                        while (offset < buffer.byteLength) {
                            const chunkId = view.getUint32(offset, false);
                            const chunkSize = view.getUint32(offset + 4, true);
                            if (chunkId === data) {
                                totalDataSize += chunkSize;
                                break;
                            }
                            offset += 8 + chunkSize;
                        }
                    }

                    if (totalDataSize === 0 || !sampleRate) {
                        reject(new Error('没有有效的WAV文件可以合并'));
                        return;
                    }

                    // 创建合并后的WAV文件
                    const headerLength = 44;
                    const totalLength = headerLength + totalDataSize;
                    const mergedBuffer = new ArrayBuffer(totalLength);
                    const mergedView = new DataView(mergedBuffer);

                    // 写入WAV头部
                    mergedView.setUint32(0, RIFF, false);                    // RIFF标识
                    mergedView.setUint32(4, totalLength - 8, true);         // 文件大小
                    mergedView.setUint32(8, WAVE, false);                   // WAVE标识
                    mergedView.setUint32(12, fmt, false);                   // fmt块标识
                    mergedView.setUint32(16, 16, true);                     // fmt块大小
                    mergedView.setUint16(20, 1, true);                      // 音频格式(PCM)
                    mergedView.setUint16(22, channels, true);               // 通道数
                    mergedView.setUint32(24, sampleRate, true);            // 采样率
                    mergedView.setUint32(28, sampleRate * channels * bitsPerSample / 8, true); // 字节率
                    mergedView.setUint16(32, channels * bitsPerSample / 8, true);             // 数据块对齐
                    mergedView.setUint16(34, bitsPerSample, true);         // 采样位数
                    mergedView.setUint32(36, data, false);                 // data块标识
                    mergedView.setUint32(40, totalDataSize, true);         // 数据大小

                    // 写入音频数据
                    let dataOffset = headerLength;
                    for (let i = 0; i < audioBuffers.length; i++) {
                        const buffer = audioBuffers[i];
                        const view = new DataView(buffer);

                        // 找到数据块并复制
                        let offset = 12;
                        while (offset < buffer.byteLength) {
                            const chunkId = view.getUint32(offset, false);
                            const chunkSize = view.getUint32(offset + 4, true);
                            if (chunkId === data) {
                                const dataArray = new Uint8Array(buffer, offset + 8, chunkSize);
                                new Uint8Array(mergedBuffer).set(dataArray, dataOffset);
                                dataOffset += chunkSize;
                                break;
                            }
                            offset += 8 + chunkSize;
                        }

                        updateMergeProgress(60 + Math.floor((i / audioBuffers.length) * 30),
                            `正在处理第 ${i + 1}/${audioBuffers.length} 个WAV文件...`);
                    }

                    updateMergeProgress(95, 'WAV合并完成,准备下载...');
                    resolve(mergedBuffer);
                } else {
                    reject(new Error(`不支持 ${format} 格式的合并`));
                }
            } catch (e) { reject(e); }
        });
    }

    // 简单检查是否为有效的MP3文件
    function isValidMp3(buffer) {
        if (!buffer || buffer.byteLength < 3) return false;
        const view = new Uint8Array(buffer);
        if (view[0] === 0x49 && view[1] === 0x44 && view[2] === 0x33) return true;
        for (let i = 0; i < Math.min(100, view.length - 1); i++) {
            if (view[i] === 0xFF && (view[i+1] & 0xE0) === 0xE0) return true;
        }
        return false;
    }

    // 更新合并进度
    function updateMergeProgress(percent, message) {
        const progressBar = document.getElementById('merge-progress-bar');
        const progressText = document.getElementById('merge-progress-text');
        if (progressBar) progressBar.style.width = `${percent}%`;
        if (progressText) progressText.textContent = message || `进度: ${percent}%`;
    }

    // 更新合并状态
    function updateMergeStatus(message) {
        const statusElement = document.getElementById('merge-status');
        if (statusElement) statusElement.textContent = message;
    }

    // 创建模态框
    function createModal(title, options = {}) {
        // 注入/更新滚动条样式
        injectCustomScrollbarStyles();

        const existingBackdrops = Array.from(document.querySelectorAll('.audio-capture-modal-backdrop'));
        if (!options.stack && existingBackdrops.length) {
            existingBackdrops.forEach(el => document.body.removeChild(el));
        }

        const theme = getThemeStyles();
        const backdropOpacity = typeof options.backdropOpacity === 'number' ? options.backdropOpacity : 0.5;
        const baseZIndex = options.stack ? 10000 + existingBackdrops.length : 10000;

        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: rgba(0, 0, 0, ${backdropOpacity});
            z-index: ${options.zIndex || baseZIndex};
            display: flex; justify-content: center;
        `;
        modalBackdrop.style.alignItems = options.alignTop ? 'flex-start' : 'center';
        if (options.alignTop) {
            modalBackdrop.style.paddingTop = options.offsetTop || '60px';
        }

        const modal = document.createElement('div');
        modal.className = 'audio-capture-modal';
        modal.style.cssText = `
            background: ${theme.background}; color: ${theme.color};
            border-radius: 8px; box-shadow: 0 0 20px ${theme.shadowColor};
            width: ${options.width || '80%'}; max-width: ${options.maxWidth || '700px'};
            max-height: ${options.maxHeight || '90vh'};
            min-height: ${options.minHeight || '60vh'};
            display: flex; flex-direction: column; /* 确保标题和内容正确布局 */
            z-index: ${(options.zIndex || baseZIndex) + 1};
        `;
        if (options.minWidth) modal.style.minWidth = options.minWidth;

        const titleElement = document.createElement('h3');
        titleElement.textContent = title;
        titleElement.style.cssText = `
            margin: 0; padding: 20px 20px 15px 20px;
            border-bottom: 1px solid ${theme.border};
            flex-shrink: 0; display: flex; justify-content: space-between; align-items: center;
        `;

        // 添加关闭按钮到标题右侧
        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = icons.close;
        closeBtn.style.cssText = `
            background: none; border: none; padding: 4px;
            cursor: pointer; opacity: 0.7; transition: opacity 0.2s;
            display: flex; align-items: center;
        `;
        closeBtn.onmouseover = () => closeBtn.style.opacity = '1';
        closeBtn.onmouseout = () => closeBtn.style.opacity = '0.7';
        closeBtn.onclick = () => closeModal(contentWrapper);
        titleElement.appendChild(closeBtn);

        // 创建一个可滚动的内容容器
        const contentWrapper = document.createElement('div');
        contentWrapper.style.cssText = `
            overflow-y: auto;
            padding: 20px;
            flex-grow: 1; /* 占据剩余空间 */
        `;

        modal.appendChild(titleElement);
        modal.appendChild(contentWrapper); // 内容将被添加到这个包装器中
        modalBackdrop.appendChild(modal);
        document.body.appendChild(modalBackdrop);

        // 返回内容包装器,以便调用者向其中添加内容
        return contentWrapper;
    }

    // 关闭模态框
    function closeModal(modalWrapper) {
        try {
            // modalWrapper 是我们返回的内容容器
            // 我们需要找到它的父级 .audio-capture-modal,然后再找到 .audio-capture-modal-backdrop
            const backdrop = modalWrapper.closest('.audio-capture-modal-backdrop');
            if (backdrop && document.body.contains(backdrop)) {
                document.body.removeChild(backdrop);
            }
        } catch (e) {
            console.error('关闭模态框时出错:', e);
            document.querySelectorAll('.audio-capture-modal-backdrop').forEach(el => el.remove());
        }
    }

    // 注册GM菜单
    GM_registerMenuCommand('🎵 打开音频下载窗口', createMainInterface);
    GM_registerMenuCommand('▶️ 一键获取', function() {
        document.getElementById('active-capture-btn')?.click();
    });
    GM_registerMenuCommand('⏱️ 手动获取', function() {
        document.getElementById('passive-capture-btn')?.click();
    });
    GM_registerMenuCommand('📋 已捕获音频管理', showAudioManagementWindow);
    GM_registerMenuCommand('🤖 切换是否自动合并', function() {
        autoMergeEnabled = !autoMergeEnabled;
        GM_setValue('autoMergeEnabled', autoMergeEnabled);
        // 同步UI中的checkbox
        syncAutoMergeCheckbox();
        // 保持使用 updateStatus,不弹窗
        updateStatus(autoMergeEnabled ? '✅ 自动合并已启用' : '❌ 自动合并已禁用');
    });
    GM_registerMenuCommand('📍 重置面板位置', function() {
        const defaultPosition = { bottom: 20, right: 20 };
        panelPosition = defaultPosition;  // 只临时修改当前值,不保存到存储
        const panel = document.getElementById('audio-capture-panel');
        if (panel) {
            panel.remove();
        }
        createMainInterface();
        alert('✅ 面板位置已重置到右下角');
    });

    // 初始化函数
    let isInitialized = false; // 添加初始化标记

    function initialize() {
        if (isInitialized) {
            console.log('豆包音频捕获工具已经初始化,跳过重复初始化');
            return;
        }

        try {
            console.log('开始初始化豆包音频捕获工具...');
            console.log('当前document.readyState:', document.readyState);

            loadAudioData();

            // 根据当前页面加载状态决定如何初始化
            const initUI = () => {
                console.log('准备创建主界面...');
                try {
                    createMainInterface();
                    console.log('✓ 主界面创建成功');

                    // 验证面板是否真的在DOM中
                    setTimeout(() => {
                        const panel = document.getElementById('audio-capture-panel');
                        if (panel) {
                            console.log('✓ 面板验证成功,面板存在于DOM中');
                            console.log('面板位置:', panel.getBoundingClientRect());
                            console.log('面板可见性:', window.getComputedStyle(panel).display);
                            console.log('面板z-index:', window.getComputedStyle(panel).zIndex);
                        } else {
                            console.error('✗ 面板验证失败,面板不存在于DOM中!');
                        }
                    }, 500);
                } catch (error) {
                    console.error('创建主界面时出错:', error);
                }
            };

            if (document.readyState === 'loading') {
                console.log('页面仍在加载中,等待DOMContentLoaded事件');
                document.addEventListener('DOMContentLoaded', () => {
                    console.log('DOMContentLoaded事件触发');
                    initUI();
                });
            } else {
                console.log('页面已加载完成,立即创建界面');
                // 延迟一小段时间,确保页面完全准备好
                setTimeout(initUI, 100);
            }

            isInitialized = true;
            console.log('豆包音频捕获工具初始化完成');
        } catch (error) {
            console.error('初始化失败:', error);
            isInitialized = false;
        }
    }

    // 启动初始化
    initialize();
})();