Source brute
max / 威软夸克助手

// ==UserScript==
// @name         威软夸克助手
// @namespace    Weiruan-Quark-Helper
// @version      1.0.9
// @description  夸克网盘增强下载助手。支持批量下载、直链导出、aria2/IDM/cURL、下载历史、文件过滤、深色模式、快捷键操作。
// @author       威软科技
// @license      MIT
// @icon         https://pan.quark.cn/favicon.ico
// @match        *://pan.quark.cn/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        unsafeWindow
// @run-at       document-end
// @connect      drive.quark.cn
// @homepage     https://github.com/weiruankeji2025/weiruan-quark
// ==/UserScript==

(function() {
    'use strict';

    // ==================== 配置 ====================
    const CONFIG = {
        // 个人网盘下载 API
        API: "https://drive.quark.cn/1/clouddrive/file/download?pr=ucpro&fr=pc",
        // 分享页面下载 API (POST)
        SHARE_DOWNLOAD_API: "https://drive.quark.cn/1/clouddrive/share/sharepage/download?pr=ucpro&fr=pc",
        // 文件夹内容列表 API
        FOLDER_LIST_API: "https://drive.quark.cn/1/clouddrive/file/sort?pr=ucpro&fr=pc&uc_param_str=&pdir_fid=",
        UA: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch",
        DEPTH: 25,
        VERSION: "1.0.9",
        DEBUG: false, // 调试模式
        HISTORY_MAX: 100,
        FOLDER_MAX_DEPTH: 5, // 文件夹递归最大深度
        FOLDER_MAX_FILES: 500, // 文件夹最大文件数
        SHORTCUTS: {
            DOWNLOAD: 'ctrl+d',
            CLOSE: 'Escape'
        }
    };

    // ==================== 国际化 ====================
    const i18n = {
        zh: {
            title: '威软夸克助手',
            downloadHelper: '下载助手',
            processing: '处理中...',
            success: '解析成功',
            error: '错误',
            noFiles: '请先勾选需要下载的文件',
            networkError: '网络请求失败,请检查网络',
            parseError: '解析失败',
            copied: '已复制到剪贴板',
            copyAll: '复制全部链接',
            copyAria2: '导出 aria2',
            copyCurl: '导出 cURL',
            download: '下载',
            fileName: '文件名',
            fileSize: '大小',
            action: '操作',
            history: '历史记录',
            clearHistory: '清空历史',
            settings: '设置',
            darkMode: '深色模式',
            language: '语言',
            filterByType: '按类型筛选',
            filterBySize: '按大小筛选',
            all: '全部',
            video: '视频',
            audio: '音频',
            image: '图片',
            document: '文档',
            archive: '压缩包',
            other: '其他',
            noHistory: '暂无下载历史',
            close: '关闭',
            files: '个文件',
            idmTip: 'IDM UA: quark-cloud-drive/2.5.20',
            quickDownload: '快速下载',
            batchExport: '批量导出',
            totalSize: '总大小',
            selectAll: '全选',
            deselectAll: '取消全选',
            confirm: '确定',
            cancel: '取消',
            auto: '跟随系统',
            light: '浅色',
            dark: '深色',
            starMap: '星际历史图',
            starMapTitle: '星际历史图',
            starMapDesc: '探索你的下载宇宙',
            totalDownloads: '总下载',
            fileTypes: '文件类型',
            timeline: '时间线',
            galaxy: '星系',
            clickToExplore: '点击星球查看详情',
            downloadTime: '下载时间',
            starSize: '星球大小由文件大小决定',
            folder: '文件夹',
            scanningFolder: '正在扫描文件夹...',
            folderContains: '文件夹包含',
            filesInFolder: '个文件',
            foldersSelected: '个文件夹',
            expandingFolders: '正在展开文件夹',
            folderTooDeep: '文件夹层级过深,已跳过部分内容',
            folderTooMany: '文件数量过多,已达到上限',
            includeFolders: '包含文件夹'
        },
        en: {
            title: 'Weiruan Quark Helper',
            downloadHelper: 'Download Helper',
            processing: 'Processing...',
            success: 'Parse Success',
            error: 'Error',
            noFiles: 'Please select files to download',
            networkError: 'Network error, please check connection',
            parseError: 'Parse failed',
            copied: 'Copied to clipboard',
            copyAll: 'Copy All Links',
            copyAria2: 'Export aria2',
            copyCurl: 'Export cURL',
            download: 'Download',
            fileName: 'Filename',
            fileSize: 'Size',
            action: 'Action',
            history: 'History',
            clearHistory: 'Clear History',
            settings: 'Settings',
            darkMode: 'Dark Mode',
            language: 'Language',
            filterByType: 'Filter by Type',
            filterBySize: 'Filter by Size',
            all: 'All',
            video: 'Video',
            audio: 'Audio',
            image: 'Image',
            document: 'Document',
            archive: 'Archive',
            other: 'Other',
            noHistory: 'No download history',
            close: 'Close',
            files: 'files',
            idmTip: 'IDM UA: quark-cloud-drive/2.5.20',
            quickDownload: 'Quick Download',
            batchExport: 'Batch Export',
            totalSize: 'Total Size',
            selectAll: 'Select All',
            deselectAll: 'Deselect All',
            confirm: 'Confirm',
            cancel: 'Cancel',
            auto: 'Auto',
            light: 'Light',
            dark: 'Dark',
            starMap: 'Star History Map',
            starMapTitle: 'Star History Map',
            starMapDesc: 'Explore your download universe',
            totalDownloads: 'Total Downloads',
            fileTypes: 'File Types',
            timeline: 'Timeline',
            galaxy: 'Galaxy',
            clickToExplore: 'Click a planet to see details',
            downloadTime: 'Download Time',
            starSize: 'Planet size represents file size',
            folder: 'Folder',
            scanningFolder: 'Scanning folder...',
            folderContains: 'Folder contains',
            filesInFolder: 'files',
            foldersSelected: 'folders',
            expandingFolders: 'Expanding folders',
            folderTooDeep: 'Folder too deep, some content skipped',
            folderTooMany: 'Too many files, limit reached',
            includeFolders: 'Include Folders'
        }
    };

    // ==================== 状态管理 ====================
    const State = {
        lang: GM_getValue('weiruan_lang', 'zh'),
        theme: GM_getValue('weiruan_theme', 'auto'),
        history: GM_getValue('weiruan_history', []),

        getLang() {
            return i18n[this.lang] || i18n.zh;
        },

        setLang(lang) {
            this.lang = lang;
            GM_setValue('weiruan_lang', lang);
        },

        setTheme(theme) {
            this.theme = theme;
            GM_setValue('weiruan_theme', theme);
            UI.applyTheme();
        },

        isDark() {
            if (this.theme === 'auto') {
                return window.matchMedia('(prefers-color-scheme: dark)').matches;
            }
            return this.theme === 'dark';
        },

        addHistory(files) {
            const newHistory = files.map(f => ({
                name: f.file_name,
                size: f.size,
                time: Date.now()
            }));
            this.history = [...newHistory, ...this.history].slice(0, CONFIG.HISTORY_MAX);
            GM_setValue('weiruan_history', this.history);
        },

        clearHistory() {
            this.history = [];
            GM_setValue('weiruan_history', []);
        }
    };

    // ==================== 工具函数 ====================
    const Utils = {
        log: (...args) => {
            if (CONFIG.DEBUG) {
                console.log('[威软夸克助手]', ...args);
            }
        },

        // 检测是否在分享页面
        isSharePage: () => {
            return location.pathname.includes('/s/') || location.search.includes('pwd_id');
        },

        // 获取分享页面参数
        getShareParams: () => {
            // 从 URL 获取 pwd_id
            let pwdId = null;
            const pathMatch = location.pathname.match(/\/s\/([a-zA-Z0-9]+)/);
            if (pathMatch) {
                pwdId = pathMatch[1];
            } else {
                const urlParams = new URLSearchParams(location.search);
                pwdId = urlParams.get('pwd_id');
            }

            // 从 cookie 或页面获取 stoken
            let stoken = '';
            const stokenMatch = document.cookie.match(/__puus=([^;]+)/);
            if (stokenMatch) {
                stoken = decodeURIComponent(stokenMatch[1]);
            }

            // 尝试从页面 window 对象获取
            if (!stoken && unsafeWindow?.__INITIAL_STATE__?.shareToken) {
                stoken = unsafeWindow.__INITIAL_STATE__.shareToken;
            }

            // 从页面 script 标签中查找
            if (!stoken) {
                const scripts = document.querySelectorAll('script');
                for (const script of scripts) {
                    const match = script.textContent?.match(/stoken["']?\s*[:=]\s*["']([^"']+)["']/);
                    if (match) {
                        stoken = match[1];
                        break;
                    }
                }
            }

            console.log('[威软夸克助手] 分享参数:', { pwdId, stoken: stoken ? '已获取' : '未获取' });
            return { pwdId, stoken };
        },

        // 从 React Fiber 中提取文件信息
        getFidFromFiber: (dom) => {
            if (!dom) return null;

            // 尝试从当前元素及其父元素查找
            let currentDom = dom;
            for (let domAttempt = 0; domAttempt < 10 && currentDom; domAttempt++) {
                const key = Object.keys(currentDom).find(k =>
                    k.startsWith('__reactFiber$') ||
                    k.startsWith('__reactInternalInstance$') ||
                    k.startsWith('__reactProps$')
                );

                if (key) {
                    let fiber = currentDom[key];
                    let attempts = 0;

                    while (fiber && attempts < CONFIG.DEPTH) {
                        const props = fiber.memoizedProps || fiber.pendingProps || fiber;

                        // 尝试多种可能的属性名
                        const candidates = [
                            props?.record,
                            props?.file,
                            props?.item,
                            props?.data,
                            props?.node,
                            props?.fileInfo,
                            props?.fileData,
                            props?.children?.props?.record,
                            props?.children?.props?.file,
                            fiber?.memoizedState?.memoizedState,
                            fiber?.stateNode?.props?.record,
                            fiber?.stateNode?.props?.file
                        ].filter(Boolean);

                        for (const candidate of candidates) {
                            if (candidate && (candidate.fid || candidate.id || candidate.file_id)) {
                                // 判断是否为文件夹 - 更保守的判断
                                const isDirectory =
                                    candidate.dir === true ||
                                    candidate.is_dir === true ||
                                    candidate.type === 'folder' ||
                                    candidate.obj_category === 'folder' ||
                                    (candidate.category !== undefined && candidate.category === 0);

                                const fileData = {
                                    fid: candidate.fid || candidate.id || candidate.file_id,
                                    name: candidate.file_name || candidate.name || candidate.title || candidate.fileName || "未命名文件",
                                    isDir: isDirectory,
                                    size: candidate.size || candidate.file_size || 0,
                                    download_url: candidate.download_url
                                };
                                Utils.log('找到文件:', fileData.name, 'isDir:', fileData.isDir, '原始数据:', candidate);
                                return fileData;
                            }
                        }

                        fiber = fiber.return;
                        attempts++;
                    }
                }
                currentDom = currentDom.parentElement;
            }
            return null;
        },

        // 从行元素中提取文件信息
        getFileFromRow: (row) => {
            if (!row) return null;

            // 方法1: 从 React Fiber 获取
            const fiberData = Utils.getFidFromFiber(row);
            if (fiberData) return fiberData;

            // 方法2: 从 data 属性获取
            const dataFid = row.getAttribute('data-fid') || row.getAttribute('data-id') || row.getAttribute('data-file-id');
            if (dataFid) {
                const fileName = row.querySelector('.file-name, .name, [class*="fileName"], [class*="file_name"]')?.textContent?.trim();
                return {
                    fid: dataFid,
                    name: fileName || '未命名文件',
                    isDir: row.classList.contains('folder') || row.getAttribute('data-type') === 'folder',
                    size: 0
                };
            }

            // 方法3: 遍历子元素查找
            const allElements = row.querySelectorAll('*');
            for (const el of allElements) {
                const data = Utils.getFidFromFiber(el);
                if (data) return data;
            }

            return null;
        },

        post: (url, data) => {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "POST",
                    url: url,
                    headers: {
                        "Content-Type": "application/json",
                        "User-Agent": CONFIG.UA,
                        "Cookie": document.cookie
                    },
                    data: JSON.stringify(data),
                    responseType: 'json',
                    withCredentials: true,
                    onload: res => {
                        if (res.status === 200) {
                            resolve(res.response);
                        } else {
                            reject(res);
                        }
                    },
                    onerror: err => reject(err)
                });
            });
        },

        // GET 请求
        get: (url) => {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url,
                    headers: {
                        "User-Agent": CONFIG.UA,
                        "Cookie": document.cookie
                    },
                    responseType: 'json',
                    withCredentials: true,
                    onload: res => {
                        if (res.status === 200) {
                            resolve(res.response);
                        } else {
                            reject(res);
                        }
                    },
                    onerror: err => reject(err)
                });
            });
        },

        // 获取文件夹内容列表
        getFolderContents: async (fid, page = 1, pageSize = 100) => {
            const url = `${CONFIG.FOLDER_LIST_API}${fid}&_page=${page}&_size=${pageSize}&_sort=file_type:asc,updated_at:desc`;
            try {
                const res = await Utils.get(url);
                if (res && res.code === 0 && res.data && res.data.list) {
                    return {
                        list: res.data.list,
                        total: res.metadata?._total || res.data.list.length,
                        hasMore: res.data.list.length >= pageSize
                    };
                }
                return { list: [], total: 0, hasMore: false };
            } catch (e) {
                Utils.log('获取文件夹内容失败:', e);
                return { list: [], total: 0, hasMore: false };
            }
        },

        // 递归获取文件夹内所有文件
        getAllFilesInFolder: async (fid, folderName = '', depth = 0, onProgress = null) => {
            const allFiles = [];
            const L = State.getLang();

            if (depth >= CONFIG.FOLDER_MAX_DEPTH) {
                Utils.log('达到最大递归深度:', depth);
                return { files: allFiles, warning: 'depth' };
            }

            let page = 1;
            let hasMore = true;
            let warning = null;

            while (hasMore && allFiles.length < CONFIG.FOLDER_MAX_FILES) {
                const result = await Utils.getFolderContents(fid, page, 100);

                for (const item of result.list) {
                    if (allFiles.length >= CONFIG.FOLDER_MAX_FILES) {
                        warning = 'count';
                        break;
                    }

                    const isDir = item.dir === true || item.is_dir === true ||
                                  item.file_type === 0 || item.obj_category === 'folder';

                    if (isDir) {
                        // 递归获取子文件夹
                        const subPath = folderName ? `${folderName}/${item.file_name}` : item.file_name;
                        if (onProgress) {
                            onProgress(`${L.scanningFolder} ${subPath}`);
                        }
                        const subResult = await Utils.getAllFilesInFolder(
                            item.fid,
                            subPath,
                            depth + 1,
                            onProgress
                        );
                        allFiles.push(...subResult.files);
                        if (subResult.warning) warning = subResult.warning;
                    } else {
                        // 添加文件,保留文件夹路径
                        allFiles.push({
                            fid: item.fid,
                            name: item.file_name,
                            file_name: item.file_name,
                            size: item.size || 0,
                            isDir: false,
                            folderPath: folderName,
                            fullPath: folderName ? `${folderName}/${item.file_name}` : item.file_name
                        });
                    }
                }

                hasMore = result.hasMore && allFiles.length < CONFIG.FOLDER_MAX_FILES;
                page++;
            }

            return { files: allFiles, warning };
        },

        formatSize: (bytes) => {
            if (bytes === 0) return '0 B';
            const k = 1024, i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
        },

        formatDate: (timestamp) => {
            const d = new Date(timestamp);
            return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
        },

        getFileType: (filename) => {
            const ext = filename.split('.').pop().toLowerCase();
            const types = {
                video: ['mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'webm', 'rmvb', 'rm', 'm4v', '3gp'],
                audio: ['mp3', 'wav', 'flac', 'aac', 'ogg', 'wma', 'm4a', 'ape'],
                image: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico', 'tiff'],
                document: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv'],
                archive: ['zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz']
            };

            for (const [type, exts] of Object.entries(types)) {
                if (exts.includes(ext)) return type;
            }
            return 'other';
        },

        getFileIcon: (filename) => {
            const type = Utils.getFileType(filename);
            const icons = {
                video: '🎬',
                audio: '🎵',
                image: '🖼️',
                document: '📄',
                archive: '📦',
                other: '📁'
            };
            return icons[type] || '📁';
        },

        generateBatchLinks: (files) => {
            return files.map(f => f.download_url).join('\n');
        },

        generateAria2Commands: (files) => {
            return files.map(f => {
                const ua = CONFIG.UA;
                // 如果有文件夹路径,使用完整路径
                const outputPath = f.fullPath || f.file_name;
                // 如果有子目录,先创建目录
                const dirCmd = f.folderPath ? `mkdir -p "${f.folderPath}" && ` : '';
                return `${dirCmd}aria2c -c -x 16 -s 16 "${f.download_url}" -o "${outputPath}" -U "${ua}" --header="Cookie: ${document.cookie}"`;
            }).join('\n\n');
        },

        generateCurlCommands: (files) => {
            return files.map(f => {
                const ua = CONFIG.UA;
                // 如果有文件夹路径,使用完整路径
                const outputPath = f.fullPath || f.file_name;
                // 如果有子目录,先创建目录
                const dirCmd = f.folderPath ? `mkdir -p "${f.folderPath}" && ` : '';
                return `${dirCmd}curl -L -C - "${f.download_url}" -o "${outputPath}" -A "${ua}" -b "${document.cookie}"`;
            }).join('\n\n');
        },

        toast: (msg, type = 'success') => {
            const existingToast = document.querySelector('.weiruan-toast');
            if (existingToast) existingToast.remove();

            const div = document.createElement('div');
            div.className = 'weiruan-toast';
            div.innerText = msg;

            const colors = {
                success: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
                error: 'linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%)',
                info: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)'
            };

            div.style.cssText = `
                position: fixed; top: 80px; left: 50%; transform: translateX(-50%);
                background: ${colors[type] || colors.success};
                color: white; padding: 12px 24px; border-radius: 8px; z-index: 2147483649;
                font-size: 14px; box-shadow: 0 4px 20px rgba(0,0,0,0.25);
                animation: weiruan-toast-in 0.3s ease-out;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            `;
            document.body.appendChild(div);
            setTimeout(() => {
                div.style.animation = 'weiruan-toast-out 0.3s ease-out forwards';
                setTimeout(() => div.remove(), 300);
            }, 2500);
        },

        debounce: (fn, delay) => {
            let timer = null;
            return function(...args) {
                if (timer) clearTimeout(timer);
                timer = setTimeout(() => fn.apply(this, args), delay);
            };
        }
    };

    // ==================== 应用逻辑 ====================
    const App = {
        // 从全局状态获取选中的文件(解决虚拟滚动问题)
        getSelectedFilesFromState: () => {
            const files = [];

            try {
                // 方法1: 从 Redux store 获取
                const win = unsafeWindow || window;

                // 尝试获取 Redux store
                if (win.__REDUX_STORE__ || win.store || win.__store__) {
                    const store = win.__REDUX_STORE__ || win.store || win.__store__;
                    const state = store.getState?.();
                    Utils.log('Redux state:', state);

                    if (state?.file?.selectedFiles) {
                        return state.file.selectedFiles;
                    }
                    if (state?.selection?.selected) {
                        return state.selection.selected;
                    }
                }

                // 方法2: 从 React 根节点获取状态
                const rootEl = document.getElementById('root') || document.getElementById('app');
                if (rootEl) {
                    const fiberKey = Object.keys(rootEl).find(k =>
                        k.startsWith('__reactContainer$') ||
                        k.startsWith('__reactFiber$')
                    );

                    if (fiberKey) {
                        let fiber = rootEl[fiberKey];
                        let attempts = 0;

                        while (fiber && attempts < 50) {
                            const state = fiber.memoizedState;

                            // 查找包含选中文件信息的状态
                            if (state?.memoizedState?.selectedKeys ||
                                state?.memoizedState?.selectedRowKeys ||
                                state?.memoizedState?.checkedKeys) {
                                Utils.log('找到选中状态:', state.memoizedState);
                            }

                            // 查找文件列表状态
                            if (state?.memoizedState?.fileList ||
                                state?.memoizedState?.dataSource ||
                                state?.memoizedState?.list) {
                                const fileList = state.memoizedState.fileList ||
                                                state.memoizedState.dataSource ||
                                                state.memoizedState.list;
                                Utils.log('找到文件列表:', fileList?.length);
                            }

                            fiber = fiber.child || fiber.sibling || fiber.return;
                            attempts++;
                        }
                    }
                }

                // 方法3: 从全局变量获取
                const possibleVars = ['__INITIAL_STATE__', '__DATA__', '__APP_DATA__', 'pageData', 'appData'];
                for (const varName of possibleVars) {
                    if (win[varName]) {
                        Utils.log(`全局变量 ${varName}:`, win[varName]);
                        const data = win[varName];
                        if (data.selectedFiles) return data.selectedFiles;
                        if (data.file?.selectedFiles) return data.file.selectedFiles;
                        if (data.list?.selectedFiles) return data.list.selectedFiles;
                    }
                }

                // 方法4: 尝试从表格组件获取(Ant Design Table)
                const tableWrapper = document.querySelector('.ant-table-wrapper, [class*="table-wrapper"]');
                if (tableWrapper) {
                    const tableKey = Object.keys(tableWrapper).find(k =>
                        k.startsWith('__reactFiber$') || k.startsWith('__reactProps$')
                    );

                    if (tableKey) {
                        let fiber = tableWrapper[tableKey];
                        let attempts = 0;

                        while (fiber && attempts < 30) {
                            const props = fiber.memoizedProps || fiber.pendingProps;

                            // Ant Design Table 的 dataSource 和 selectedRowKeys
                            if (props?.dataSource && Array.isArray(props.dataSource)) {
                                const selectedKeys = props.rowSelection?.selectedRowKeys ||
                                                    props.selectedRowKeys || [];
                                Utils.log('Table dataSource:', props.dataSource.length, '选中:', selectedKeys.length);

                                if (selectedKeys.length > 0) {
                                    const selectedFiles = props.dataSource.filter(item =>
                                        selectedKeys.includes(item.fid || item.id || item.key)
                                    );

                                    if (selectedFiles.length > 0) {
                                        return selectedFiles.map(f => ({
                                            fid: f.fid || f.id || f.file_id,
                                            name: f.file_name || f.name || f.fileName || '未命名',
                                            isDir: f.dir === true || f.is_dir === true || f.type === 'folder',
                                            size: f.size || f.file_size || 0,
                                            download_url: f.download_url
                                        }));
                                    }
                                }
                            }

                            fiber = fiber.return;
                            attempts++;
                        }
                    }
                }

            } catch (e) {
                Utils.log('从状态获取文件失败:', e);
            }

            return files;
        },

        // 深度遍历 React Fiber 树获取所有选中文件
        getSelectedFilesFromFiberTree: () => {
            const files = [];
            const visited = new Set();

            try {
                const win = unsafeWindow || window;

                // 查找包含文件列表的组件
                const containers = document.querySelectorAll(
                    '.file-list, [class*="fileList"], [class*="FileList"], ' +
                    '.ant-table-body, [class*="table"], [class*="list-view"], ' +
                    '[class*="ListView"], [class*="content-list"]'
                );

                for (const container of containers) {
                    const key = Object.keys(container).find(k =>
                        k.startsWith('__reactFiber$') ||
                        k.startsWith('__reactInternalInstance$')
                    );

                    if (!key) continue;

                    // BFS 遍历 Fiber 树
                    const queue = [container[key]];
                    let iterations = 0;
                    const maxIterations = 500;

                    while (queue.length > 0 && iterations < maxIterations) {
                        iterations++;
                        const fiber = queue.shift();
                        if (!fiber || visited.has(fiber)) continue;
                        visited.add(fiber);

                        // 检查 memoizedProps
                        const props = fiber.memoizedProps || fiber.pendingProps || {};

                        // 查找 dataSource(完整数据列表)
                        if (props.dataSource && Array.isArray(props.dataSource) && props.dataSource.length > 0) {
                            const selectedKeys = props.rowSelection?.selectedRowKeys ||
                                                props.selectedRowKeys ||
                                                props.checkedKeys || [];

                            Utils.log('找到 dataSource:', props.dataSource.length, '选中keys:', selectedKeys.length);

                            if (selectedKeys.length > 0) {
                                for (const item of props.dataSource) {
                                    const itemKey = item.fid || item.id || item.key || item.file_id;
                                    if (selectedKeys.includes(itemKey)) {
                                        files.push({
                                            fid: item.fid || item.id || item.file_id,
                                            name: item.file_name || item.name || item.fileName || '未命名',
                                            isDir: item.dir === true || item.is_dir === true ||
                                                   item.type === 'folder' || item.obj_category === 'folder',
                                            size: item.size || item.file_size || 0,
                                            download_url: item.download_url
                                        });
                                    }
                                }
                            }
                        }

                        // 查找文件列表数据
                        if (props.list && Array.isArray(props.list)) {
                            Utils.log('找到 list:', props.list.length);
                        }

                        // 查找 items
                        if (props.items && Array.isArray(props.items)) {
                            Utils.log('找到 items:', props.items.length);
                        }

                        // 检查 memoizedState
                        let state = fiber.memoizedState;
                        while (state) {
                            if (state.memoizedState) {
                                const ms = state.memoizedState;
                                // 查找选中状态
                                if (ms.selectedRowKeys || ms.selectedKeys || ms.checkedKeys) {
                                    Utils.log('State 中找到选中keys:', ms.selectedRowKeys || ms.selectedKeys || ms.checkedKeys);
                                }
                                // 查找文件数据
                                if (ms.fileList || ms.dataSource || ms.list) {
                                    Utils.log('State 中找到文件列表');
                                }
                            }
                            state = state.next;
                        }

                        // 添加子节点到队列
                        if (fiber.child) queue.push(fiber.child);
                        if (fiber.sibling) queue.push(fiber.sibling);
                        if (fiber.return && !visited.has(fiber.return)) queue.push(fiber.return);
                    }
                }

                // 去重
                const uniqueFiles = [];
                const seenFids = new Set();
                for (const f of files) {
                    if (!seenFids.has(f.fid)) {
                        seenFids.add(f.fid);
                        uniqueFiles.push(f);
                    }
                }

                return uniqueFiles;

            } catch (e) {
                Utils.log('Fiber树遍历失败:', e);
            }

            return files;
        },

        getSelectedFiles: () => {
            const selectedFiles = new Map();

            // 方法1: 从全局状态获取(解决虚拟滚动问题)
            const stateFiles = App.getSelectedFilesFromState();
            if (stateFiles.length > 0) {
                Utils.log('从状态获取到文件:', stateFiles.length);
                stateFiles.forEach(f => {
                    if (f.fid && !selectedFiles.has(f.fid)) {
                        selectedFiles.set(f.fid, f);
                    }
                });
            }

            // 方法2: 从 Fiber 树深度遍历获取
            if (selectedFiles.size === 0) {
                const fiberFiles = App.getSelectedFilesFromFiberTree();
                if (fiberFiles.length > 0) {
                    Utils.log('从Fiber树获取到文件:', fiberFiles.length);
                    fiberFiles.forEach(f => {
                        if (f.fid && !selectedFiles.has(f.fid)) {
                            selectedFiles.set(f.fid, f);
                        }
                    });
                }
            }

            // 方法3: 从DOM获取(可见的文件)
            // 选择器列表 - 覆盖各种可能的选中状态
            const checkboxSelectors = [
                // Ant Design 复选框
                '.ant-checkbox-wrapper-checked',
                '.ant-checkbox-checked',
                '[class*="checkbox"][class*="checked"]',
                // 选中状态的行
                '.file-item-selected',
                '[class*="selected"]',
                '[class*="active"]',
                // aria 属性
                '[aria-checked="true"]',
                '[aria-selected="true"]',
                // 夸克特定选择器
                '.file-list-item.selected',
                '.list-item.selected',
                '[class*="fileItem"][class*="selected"]',
                '[class*="file-item"][class*="selected"]',
                // 复选框输入
                'input[type="checkbox"]:checked'
            ];

            // 行容器选择器
            const rowSelectors = [
                '.ant-table-row',
                '.file-list-item',
                '.file-item',
                '.list-item',
                '[class*="fileItem"]',
                '[class*="file-item"]',
                '[class*="ListItem"]',
                '[class*="tableRow"]',
                'tr[data-row-key]',
                '[data-fid]',
                '[data-id]'
            ];

            Utils.log('开始查找选中的文件...');

            // 方法1: 通过选中的复选框查找
            for (const selector of checkboxSelectors) {
                try {
                    const elements = document.querySelectorAll(selector);
                    Utils.log(`选择器 "${selector}" 找到 ${elements.length} 个元素`);

                    elements.forEach(el => {
                        // 跳过表头
                        if (el.closest('.ant-table-thead') ||
                            el.closest('.list-head') ||
                            el.closest('[class*="header"]') ||
                            el.closest('[class*="Header"]')) {
                            return;
                        }

                        // 找到所属的行
                        let row = el;
                        for (const rowSelector of rowSelectors) {
                            const found = el.closest(rowSelector);
                            if (found) {
                                row = found;
                                break;
                            }
                        }

                        // 尝试获取文件数据
                        const fileData = Utils.getFileFromRow(row) || Utils.getFidFromFiber(el);
                        if (fileData && fileData.fid && !selectedFiles.has(fileData.fid)) {
                            Utils.log('找到选中文件:', fileData.name);
                            selectedFiles.set(fileData.fid, fileData);
                        }
                    });
                } catch (e) {
                    Utils.log('选择器错误:', selector, e);
                }
            }

            // 方法2: 直接查找带有选中样式的行
            if (selectedFiles.size === 0) {
                Utils.log('方法1未找到文件,尝试方法2...');
                for (const rowSelector of rowSelectors) {
                    try {
                        const rows = document.querySelectorAll(rowSelector);
                        rows.forEach(row => {
                            // 检查行是否有选中样式
                            const isSelected = row.classList.contains('selected') ||
                                row.classList.contains('checked') ||
                                row.classList.contains('active') ||
                                row.querySelector('.ant-checkbox-checked') ||
                                row.querySelector('[aria-checked="true"]') ||
                                row.querySelector('input:checked');

                            if (isSelected) {
                                const fileData = Utils.getFileFromRow(row);
                                if (fileData && fileData.fid && !selectedFiles.has(fileData.fid)) {
                                    selectedFiles.set(fileData.fid, fileData);
                                }
                            }
                        });
                    } catch (e) {
                        Utils.log('行选择器错误:', rowSelector, e);
                    }
                }
            }

            // 方法3: 扫描所有可能的文件元素,检查视觉选中状态
            if (selectedFiles.size === 0) {
                Utils.log('方法2未找到文件,尝试方法3...');
                const allRows = document.querySelectorAll('[class*="file"], [class*="File"], [class*="item"], [class*="Item"], [class*="row"], [class*="Row"]');
                allRows.forEach(row => {
                    // 检查复选框
                    const checkbox = row.querySelector('input[type="checkbox"], .ant-checkbox, [class*="checkbox"], [class*="Checkbox"]');
                    if (checkbox) {
                        const isChecked = checkbox.checked ||
                            checkbox.classList.contains('ant-checkbox-checked') ||
                            checkbox.closest('.ant-checkbox-wrapper-checked') ||
                            checkbox.getAttribute('aria-checked') === 'true';

                        if (isChecked) {
                            const fileData = Utils.getFileFromRow(row);
                            if (fileData && fileData.fid && !selectedFiles.has(fileData.fid)) {
                                selectedFiles.set(fileData.fid, fileData);
                            }
                        }
                    }
                });
            }

            Utils.log(`共找到 ${selectedFiles.size} 个选中的文件`);
            return Array.from(selectedFiles.values());
        },

        run: async (filterType = 'all') => {
            const btn = document.getElementById('weiruan-btn');
            const L = State.getLang();

            try {
                let files = App.getSelectedFiles();
                console.log('[威软夸克助手] 找到的原始文件:', files);
                console.log('[威软夸克助手] 文件详情:', files.map(f => ({name: f.name, isDir: f.isDir, fid: f.fid})));

                // 分离文件和文件夹
                const folders = files.filter(f => f.isDir);
                let regularFiles = files.filter(f => !f.isDir);
                console.log(`[威软夸克助手] 文件: ${regularFiles.length}, 文件夹: ${folders.length}`);

                // 如果有文件夹,展开获取所有文件
                if (folders.length > 0) {
                    if (btn) {
                        btn.innerHTML = `<span class="weiruan-spinner"></span> ${L.expandingFolders}...`;
                        btn.disabled = true;
                    }

                    let folderWarning = null;
                    for (const folder of folders) {
                        Utils.toast(`${L.scanningFolder} ${folder.name}`, 'info');
                        const result = await Utils.getAllFilesInFolder(
                            folder.fid,
                            folder.name,
                            0,
                            (msg) => {
                                if (btn) btn.innerHTML = `<span class="weiruan-spinner"></span> ${msg}`;
                            }
                        );
                        regularFiles.push(...result.files);
                        if (result.warning) folderWarning = result.warning;

                        // 检查是否超过文件数限制
                        if (regularFiles.length >= CONFIG.FOLDER_MAX_FILES) {
                            folderWarning = 'count';
                            break;
                        }
                    }

                    // 显示警告
                    if (folderWarning === 'depth') {
                        Utils.toast(L.folderTooDeep, 'info');
                    } else if (folderWarning === 'count') {
                        Utils.toast(L.folderTooMany, 'info');
                    }

                    console.log(`[威软夸克助手] 展开文件夹后共 ${regularFiles.length} 个文件`);
                }

                files = regularFiles;

                // 应用文件类型过滤
                if (filterType !== 'all') {
                    files = files.filter(f => Utils.getFileType(f.name || f.file_name) === filterType);
                }

                if (files.length === 0) {
                    // 提供更详细的错误信息
                    const checkboxCount = document.querySelectorAll('.ant-checkbox-checked, .ant-checkbox-wrapper-checked, [aria-checked="true"]').length;
                    if (checkboxCount > 0) {
                        Utils.toast('检测到选中项,但无法获取文件信息。请尝试刷新页面后重试', 'error');
                    } else {
                        Utils.toast(L.noFiles, 'error');
                    }
                    return;
                }

                if (btn) {
                    btn.innerHTML = `<span class="weiruan-spinner"></span> ${L.processing}`;
                    btn.disabled = true;
                }

                let res;
                const isShare = Utils.isSharePage();
                console.log('[威软夸克助手] 页面类型:', isShare ? '分享页面' : '个人网盘');
                console.log('[威软夸克助手] 准备请求API, fids:', files.map(f => f.fid));

                if (isShare) {
                    // 分享页面处理
                    const { pwdId, stoken } = Utils.getShareParams();
                    console.log('[威软夸克助手] 分享信息:', { pwdId, hasStoken: !!stoken });

                    // 方法1: 先尝试标准下载API(如果用户已登录可能直接可用)
                    console.log('[威软夸克助手] 尝试标准API...');
                    res = await Utils.post(CONFIG.API, { fids: files.map(f => f.fid) });
                    console.log('[威软夸克助手] 标准API返回:', res);

                    // 方法2: 如果标准API失败,检查文件是否已有下载链接
                    if (!res || res.code !== 0 || !res.data || res.data.length === 0) {
                        console.log('[威软夸克助手] 标准API未返回数据,检查文件自带的下载链接...');
                        const filesWithUrl = files.filter(f => f.download_url);
                        console.log('[威软夸克助手] 已有下载链接的文件数:', filesWithUrl.length);

                        if (filesWithUrl.length > 0) {
                            res = {
                                code: 0,
                                data: filesWithUrl.map(f => ({
                                    fid: f.fid,
                                    file_name: f.name,
                                    size: f.size || 0,
                                    download_url: f.download_url
                                }))
                            };
                        }
                    }

                    // 最终检查
                    if (!res || res.code !== 0 || !res.data || res.data.length === 0) {
                        Utils.toast('分享文件需要先「保存到网盘」后才能获取下载链接', 'info');
                        return;
                    }
                } else {
                    // 个人网盘处理
                    res = await Utils.post(CONFIG.API, { fids: files.map(f => f.fid) });
                    console.log('[威软夸克助手] API返回:', res);
                }

                if (res && res.code === 0 && res.data && res.data.length > 0) {
                    // 创建 fid 到原始文件信息的映射,保留文件夹路径
                    const fileInfoMap = new Map();
                    files.forEach(f => {
                        fileInfoMap.set(f.fid, {
                            folderPath: f.folderPath,
                            fullPath: f.fullPath
                        });
                    });

                    // 将文件夹路径信息合并到 API 返回结果中
                    const enrichedData = res.data.map(item => {
                        const info = fileInfoMap.get(item.fid);
                        return {
                            ...item,
                            folderPath: info?.folderPath || '',
                            fullPath: info?.fullPath || item.file_name
                        };
                    });

                    State.addHistory(enrichedData);
                    UI.showResultWindow(enrichedData);
                } else {
                    Utils.toast(`${L.parseError}: ${res?.message || '未获取到下载链接'}`, 'error');
                }
            } catch(e) {
                console.error('[威软夸克助手]', e);
                Utils.toast(L.networkError, 'error');
            } finally {
                if (btn) {
                    btn.innerHTML = `<span class="weiruan-icon">⚡</span> ${L.downloadHelper}`;
                    btn.disabled = false;
                }
            }
        },

        init: () => {
            UI.injectStyles();
            UI.createFloatButton();
            UI.applyTheme();
            App.bindShortcuts();
        },

        bindShortcuts: () => {
            document.addEventListener('keydown', (e) => {
                // Ctrl+D 快速下载
                if (e.ctrlKey && e.key === 'd') {
                    e.preventDefault();
                    App.run();
                }
                // Escape 关闭弹窗
                if (e.key === 'Escape') {
                    const modal = document.getElementById('weiruan-modal');
                    if (modal) modal.remove();
                }
            });
        }
    };

    // ==================== 界面 ====================
    const UI = {
        injectStyles: () => {
            GM_addStyle(`
                @keyframes weiruan-toast-in {
                    from { opacity: 0; transform: translate(-50%, -20px); }
                    to { opacity: 1; transform: translate(-50%, 0); }
                }
                @keyframes weiruan-toast-out {
                    from { opacity: 1; transform: translate(-50%, 0); }
                    to { opacity: 0; transform: translate(-50%, -20px); }
                }
                @keyframes weiruan-spin {
                    to { transform: rotate(360deg); }
                }
                @keyframes weiruan-pulse {
                    0%, 100% { transform: scale(1); }
                    50% { transform: scale(1.05); }
                }
                @keyframes weiruan-slide-in {
                    from { opacity: 0; transform: scale(0.9); }
                    to { opacity: 1; transform: scale(1); }
                }

                .weiruan-spinner {
                    display: inline-block;
                    width: 14px;
                    height: 14px;
                    border: 2px solid rgba(255,255,255,0.3);
                    border-top-color: white;
                    border-radius: 50%;
                    animation: weiruan-spin 0.8s linear infinite;
                    margin-right: 6px;
                    vertical-align: middle;
                }

                .weiruan-btn {
                    position: fixed;
                    top: 50%;
                    left: 0;
                    transform: translateY(-50%);
                    z-index: 2147483647;
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    color: white;
                    font-size: 14px;
                    font-weight: 600;
                    padding: 14px 18px;
                    border: none;
                    border-radius: 0 25px 25px 0;
                    cursor: pointer;
                    box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
                    transition: all 0.3s ease;
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                    display: flex;
                    align-items: center;
                    gap: 6px;
                }

                .weiruan-btn:hover {
                    padding-left: 22px;
                    box-shadow: 0 6px 25px rgba(102, 126, 234, 0.5);
                }

                .weiruan-btn:active {
                    transform: translateY(-50%) scale(0.98);
                }

                .weiruan-btn:disabled {
                    opacity: 0.7;
                    cursor: not-allowed;
                }

                .weiruan-icon {
                    font-size: 16px;
                }

                .weiruan-menu {
                    position: fixed;
                    top: calc(50% + 50px);
                    left: 0;
                    transform: translateY(-50%);
                    z-index: 2147483646;
                    display: flex;
                    flex-direction: column;
                    gap: 5px;
                    opacity: 0;
                    pointer-events: none;
                    transition: all 0.3s ease;
                }

                .weiruan-btn:hover + .weiruan-menu,
                .weiruan-menu:hover {
                    opacity: 1;
                    pointer-events: auto;
                    left: 5px;
                }

                .weiruan-menu-item {
                    background: rgba(255,255,255,0.95);
                    color: #333;
                    padding: 8px 14px;
                    border-radius: 20px;
                    font-size: 12px;
                    cursor: pointer;
                    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
                    transition: all 0.2s;
                    white-space: nowrap;
                }

                .weiruan-menu-item:hover {
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    color: white;
                    transform: translateX(5px);
                }

                /* Modal Styles */
                .weiruan-modal-overlay {
                    position: fixed;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    background: rgba(0, 0, 0, 0.6);
                    z-index: 2147483648;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    backdrop-filter: blur(5px);
                }

                .weiruan-modal {
                    background: var(--weiruan-bg, #ffffff);
                    width: 720px;
                    max-width: 92%;
                    max-height: 85vh;
                    border-radius: 16px;
                    box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                    animation: weiruan-slide-in 0.3s ease-out;
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                }

                .weiruan-tab-content {
                    display: none;
                    flex-direction: column;
                    min-height: 0;
                    flex: 1;
                    overflow: hidden;
                }

                .weiruan-tab-content.active {
                    display: flex;
                }

                .weiruan-modal-header {
                    padding: 18px 24px;
                    border-bottom: 1px solid var(--weiruan-border, #eee);
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    color: white;
                }

                .weiruan-modal-title {
                    margin: 0;
                    font-size: 18px;
                    font-weight: 600;
                    display: flex;
                    align-items: center;
                    gap: 8px;
                }

                .weiruan-modal-close {
                    cursor: pointer;
                    font-size: 28px;
                    line-height: 1;
                    opacity: 0.8;
                    transition: opacity 0.2s;
                    width: 32px;
                    height: 32px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    border-radius: 50%;
                    background: rgba(255,255,255,0.1);
                }

                .weiruan-modal-close:hover {
                    opacity: 1;
                    background: rgba(255,255,255,0.2);
                }

                .weiruan-toolbar {
                    padding: 12px 24px;
                    background: var(--weiruan-toolbar-bg, #f8f9ff);
                    border-bottom: 1px solid var(--weiruan-border, #eee);
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    flex-wrap: wrap;
                    gap: 10px;
                }

                .weiruan-toolbar-info {
                    font-size: 13px;
                    color: var(--weiruan-text-secondary, #666);
                }

                .weiruan-toolbar-actions {
                    display: flex;
                    gap: 8px;
                    flex-wrap: wrap;
                }

                .weiruan-btn-group {
                    display: flex;
                    gap: 6px;
                }

                .weiruan-action-btn {
                    padding: 8px 16px;
                    border: none;
                    border-radius: 6px;
                    cursor: pointer;
                    font-size: 13px;
                    font-weight: 500;
                    transition: all 0.2s;
                    display: flex;
                    align-items: center;
                    gap: 4px;
                }

                .weiruan-action-btn.primary {
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    color: white;
                }

                .weiruan-action-btn.primary:hover {
                    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
                    transform: translateY(-1px);
                }

                .weiruan-action-btn.secondary {
                    background: var(--weiruan-btn-secondary, #f0f0f0);
                    color: var(--weiruan-text, #333);
                }

                .weiruan-action-btn.secondary:hover {
                    background: var(--weiruan-btn-secondary-hover, #e0e0e0);
                }

                .weiruan-action-btn.success {
                    background: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
                    color: white;
                }

                .weiruan-action-btn.warning {
                    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
                    color: white;
                }

                .weiruan-modal-body {
                    padding: 16px 24px;
                    overflow-y: auto;
                    flex: 1;
                    min-height: 0;
                    max-height: 400px;
                    background: var(--weiruan-bg, #ffffff);
                }

                .weiruan-file-item {
                    background: var(--weiruan-item-bg, #f9f9f9);
                    padding: 14px 16px;
                    margin-bottom: 10px;
                    border-radius: 10px;
                    border-left: 4px solid #667eea;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    transition: all 0.2s;
                }

                .weiruan-file-item:hover {
                    transform: translateX(3px);
                    box-shadow: 0 2px 10px rgba(0,0,0,0.08);
                }

                .weiruan-file-info {
                    overflow: hidden;
                    flex: 1;
                    margin-right: 12px;
                }

                .weiruan-file-name {
                    font-weight: 600;
                    color: var(--weiruan-text, #333);
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    display: flex;
                    align-items: center;
                    gap: 6px;
                    font-size: 14px;
                }

                .weiruan-file-meta {
                    font-size: 12px;
                    color: var(--weiruan-text-secondary, #888);
                    margin-top: 4px;
                }

                .weiruan-file-actions {
                    display: flex;
                    gap: 6px;
                    flex-shrink: 0;
                }

                .weiruan-file-btn {
                    padding: 6px 12px;
                    border: none;
                    border-radius: 5px;
                    cursor: pointer;
                    font-size: 12px;
                    font-weight: 500;
                    transition: all 0.2s;
                    text-decoration: none;
                    display: inline-flex;
                    align-items: center;
                    gap: 3px;
                }

                .weiruan-file-btn.idm {
                    background: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
                    color: white;
                }

                .weiruan-file-btn.curl {
                    background: #333;
                    color: white;
                }

                .weiruan-file-btn.aria2 {
                    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
                    color: white;
                }

                .weiruan-file-btn:hover {
                    transform: scale(1.05);
                }

                /* Tab Styles */
                .weiruan-tabs {
                    display: flex;
                    gap: 0;
                    border-bottom: 1px solid var(--weiruan-border, #eee);
                    padding: 0 24px;
                    background: var(--weiruan-bg, #ffffff);
                }

                .weiruan-tab {
                    padding: 12px 20px;
                    cursor: pointer;
                    border: none;
                    background: none;
                    font-size: 14px;
                    font-weight: 500;
                    color: var(--weiruan-text-secondary, #666);
                    position: relative;
                    transition: all 0.2s;
                }

                .weiruan-tab:hover {
                    color: #667eea;
                }

                .weiruan-tab.active {
                    color: #667eea;
                }

                .weiruan-tab.active::after {
                    content: '';
                    position: absolute;
                    bottom: -1px;
                    left: 0;
                    right: 0;
                    height: 2px;
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    border-radius: 2px 2px 0 0;
                }

                /* History Styles */
                .weiruan-history-item {
                    padding: 12px 16px;
                    background: var(--weiruan-item-bg, #f9f9f9);
                    border-radius: 8px;
                    margin-bottom: 8px;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                }

                .weiruan-history-name {
                    font-size: 13px;
                    color: var(--weiruan-text, #333);
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    flex: 1;
                }

                .weiruan-history-meta {
                    font-size: 11px;
                    color: var(--weiruan-text-secondary, #999);
                    white-space: nowrap;
                    margin-left: 10px;
                }

                .weiruan-empty {
                    text-align: center;
                    padding: 40px;
                    color: var(--weiruan-text-secondary, #999);
                }

                .weiruan-empty-icon {
                    font-size: 48px;
                    margin-bottom: 12px;
                }

                /* Settings Styles */
                .weiruan-settings-item {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 14px 0;
                    border-bottom: 1px solid var(--weiruan-border, #eee);
                }

                .weiruan-settings-item:last-child {
                    border-bottom: none;
                }

                .weiruan-settings-label {
                    font-size: 14px;
                    color: var(--weiruan-text, #333);
                }

                .weiruan-select {
                    padding: 6px 12px;
                    border: 1px solid var(--weiruan-border, #ddd);
                    border-radius: 6px;
                    background: var(--weiruan-bg, #fff);
                    color: var(--weiruan-text, #333);
                    font-size: 13px;
                    cursor: pointer;
                }

                /* Filter Styles */
                .weiruan-filter {
                    display: flex;
                    gap: 6px;
                    flex-wrap: wrap;
                }

                .weiruan-filter-btn {
                    padding: 4px 10px;
                    border: 1px solid var(--weiruan-border, #ddd);
                    border-radius: 15px;
                    background: var(--weiruan-bg, #fff);
                    color: var(--weiruan-text-secondary, #666);
                    font-size: 12px;
                    cursor: pointer;
                    transition: all 0.2s;
                }

                .weiruan-filter-btn:hover,
                .weiruan-filter-btn.active {
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    color: white;
                    border-color: transparent;
                }

                /* Dark Mode */
                .weiruan-dark {
                    --weiruan-bg: #1a1a2e;
                    --weiruan-text: #e0e0e0;
                    --weiruan-text-secondary: #888;
                    --weiruan-border: #333;
                    --weiruan-item-bg: #252540;
                    --weiruan-toolbar-bg: #1e1e35;
                    --weiruan-btn-secondary: #333;
                    --weiruan-btn-secondary-hover: #444;
                }

                /* Star Map Styles */
                @keyframes weiruan-twinkle {
                    0%, 100% { opacity: 0.3; }
                    50% { opacity: 1; }
                }

                @keyframes weiruan-orbit {
                    from { transform: rotate(0deg); }
                    to { transform: rotate(360deg); }
                }

                @keyframes weiruan-float {
                    0%, 100% { transform: translateY(0); }
                    50% { transform: translateY(-10px); }
                }

                @keyframes weiruan-pulse-glow {
                    0%, 100% { box-shadow: 0 0 20px currentColor; }
                    50% { box-shadow: 0 0 40px currentColor, 0 0 60px currentColor; }
                }

                .weiruan-starmap-overlay {
                    position: fixed;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    background: radial-gradient(ellipse at center, #1a1a2e 0%, #0d0d1a 50%, #000000 100%);
                    z-index: 2147483648;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    overflow: hidden;
                }

                .weiruan-starmap-bg {
                    position: absolute;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    overflow: hidden;
                    pointer-events: none;
                }

                .weiruan-star {
                    position: absolute;
                    background: white;
                    border-radius: 50%;
                    animation: weiruan-twinkle var(--duration, 2s) ease-in-out infinite;
                    animation-delay: var(--delay, 0s);
                }

                .weiruan-starmap-container {
                    position: relative;
                    width: 90%;
                    max-width: 1000px;
                    height: 80vh;
                    max-height: 700px;
                    background: rgba(20, 20, 40, 0.8);
                    border-radius: 20px;
                    border: 1px solid rgba(102, 126, 234, 0.3);
                    box-shadow: 0 0 60px rgba(102, 126, 234, 0.2);
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                    backdrop-filter: blur(10px);
                }

                .weiruan-starmap-header {
                    padding: 20px 24px;
                    background: linear-gradient(135deg, rgba(102, 126, 234, 0.3) 0%, rgba(118, 75, 162, 0.3) 100%);
                    border-bottom: 1px solid rgba(102, 126, 234, 0.3);
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                }

                .weiruan-starmap-title {
                    color: #fff;
                    font-size: 20px;
                    font-weight: 600;
                    display: flex;
                    align-items: center;
                    gap: 10px;
                    margin: 0;
                }

                .weiruan-starmap-title-icon {
                    font-size: 28px;
                    animation: weiruan-float 3s ease-in-out infinite;
                }

                .weiruan-starmap-close {
                    width: 36px;
                    height: 36px;
                    border-radius: 50%;
                    background: rgba(255, 255, 255, 0.1);
                    border: none;
                    color: #fff;
                    font-size: 24px;
                    cursor: pointer;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    transition: all 0.3s;
                }

                .weiruan-starmap-close:hover {
                    background: rgba(255, 100, 100, 0.3);
                    transform: rotate(90deg);
                }

                .weiruan-starmap-stats {
                    padding: 16px 24px;
                    display: flex;
                    gap: 20px;
                    border-bottom: 1px solid rgba(102, 126, 234, 0.2);
                    flex-wrap: wrap;
                }

                .weiruan-starmap-stat {
                    background: rgba(102, 126, 234, 0.15);
                    padding: 12px 20px;
                    border-radius: 12px;
                    border: 1px solid rgba(102, 126, 234, 0.2);
                }

                .weiruan-starmap-stat-value {
                    font-size: 24px;
                    font-weight: 700;
                    color: #667eea;
                    display: block;
                }

                .weiruan-starmap-stat-label {
                    font-size: 12px;
                    color: rgba(255, 255, 255, 0.6);
                    margin-top: 4px;
                }

                .weiruan-starmap-body {
                    flex: 1;
                    display: flex;
                    overflow: hidden;
                    min-height: 0;
                }

                .weiruan-starmap-galaxy {
                    flex: 1;
                    position: relative;
                    overflow: hidden;
                    min-height: 300px;
                }

                .weiruan-starmap-center {
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    width: 60px;
                    height: 60px;
                    background: radial-gradient(circle, #ffd700 0%, #ff8c00 50%, #ff4500 100%);
                    border-radius: 50%;
                    box-shadow: 0 0 40px #ffd700, 0 0 80px #ff8c00;
                    animation: weiruan-pulse-glow 3s ease-in-out infinite;
                    z-index: 10;
                }

                .weiruan-starmap-center::before {
                    content: '☀️';
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    font-size: 30px;
                }

                .weiruan-starmap-orbit {
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    border: 1px dashed rgba(102, 126, 234, 0.3);
                    border-radius: 50%;
                    transform: translate(-50%, -50%);
                }

                .weiruan-starmap-planet {
                    position: absolute;
                    border-radius: 50%;
                    cursor: pointer;
                    transition: transform 0.3s, box-shadow 0.3s;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    font-size: var(--planet-font-size, 16px);
                    animation: weiruan-orbit var(--orbit-duration, 20s) linear infinite;
                    transform-origin: center center;
                }

                .weiruan-starmap-planet:hover {
                    transform: scale(1.3);
                    z-index: 100;
                }

                .weiruan-starmap-planet-inner {
                    width: 100%;
                    height: 100%;
                    border-radius: 50%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    background: var(--planet-bg);
                    box-shadow: 0 0 15px var(--planet-glow), inset -5px -5px 15px rgba(0,0,0,0.3);
                }

                .weiruan-starmap-planet.video { --planet-bg: linear-gradient(135deg, #667eea, #764ba2); --planet-glow: rgba(102, 126, 234, 0.6); }
                .weiruan-starmap-planet.audio { --planet-bg: linear-gradient(135deg, #11998e, #38ef7d); --planet-glow: rgba(56, 239, 125, 0.6); }
                .weiruan-starmap-planet.image { --planet-bg: linear-gradient(135deg, #fc466b, #3f5efb); --planet-glow: rgba(252, 70, 107, 0.6); }
                .weiruan-starmap-planet.document { --planet-bg: linear-gradient(135deg, #f093fb, #f5576c); --planet-glow: rgba(240, 147, 251, 0.6); }
                .weiruan-starmap-planet.archive { --planet-bg: linear-gradient(135deg, #4facfe, #00f2fe); --planet-glow: rgba(79, 172, 254, 0.6); }
                .weiruan-starmap-planet.other { --planet-bg: linear-gradient(135deg, #a8a8a8, #6a6a6a); --planet-glow: rgba(168, 168, 168, 0.6); }

                .weiruan-starmap-tooltip {
                    position: fixed;
                    background: rgba(20, 20, 40, 0.95);
                    border: 1px solid rgba(102, 126, 234, 0.5);
                    border-radius: 12px;
                    padding: 14px 18px;
                    color: #fff;
                    font-size: 13px;
                    max-width: 280px;
                    z-index: 2147483650;
                    pointer-events: none;
                    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
                    backdrop-filter: blur(10px);
                }

                .weiruan-starmap-tooltip-name {
                    font-weight: 600;
                    font-size: 14px;
                    margin-bottom: 8px;
                    word-break: break-all;
                    color: #667eea;
                }

                .weiruan-starmap-tooltip-meta {
                    display: flex;
                    gap: 15px;
                    color: rgba(255, 255, 255, 0.7);
                    font-size: 12px;
                }

                .weiruan-starmap-sidebar {
                    width: 260px;
                    background: rgba(10, 10, 25, 0.6);
                    border-left: 1px solid rgba(102, 126, 234, 0.2);
                    padding: 16px;
                    overflow-y: auto;
                }

                .weiruan-starmap-legend-title {
                    color: rgba(255, 255, 255, 0.8);
                    font-size: 13px;
                    font-weight: 600;
                    margin-bottom: 12px;
                    display: flex;
                    align-items: center;
                    gap: 6px;
                }

                .weiruan-starmap-legend-item {
                    display: flex;
                    align-items: center;
                    gap: 10px;
                    padding: 8px 0;
                    color: rgba(255, 255, 255, 0.7);
                    font-size: 12px;
                }

                .weiruan-starmap-legend-dot {
                    width: 14px;
                    height: 14px;
                    border-radius: 50%;
                    flex-shrink: 0;
                }

                .weiruan-starmap-legend-dot.video { background: linear-gradient(135deg, #667eea, #764ba2); }
                .weiruan-starmap-legend-dot.audio { background: linear-gradient(135deg, #11998e, #38ef7d); }
                .weiruan-starmap-legend-dot.image { background: linear-gradient(135deg, #fc466b, #3f5efb); }
                .weiruan-starmap-legend-dot.document { background: linear-gradient(135deg, #f093fb, #f5576c); }
                .weiruan-starmap-legend-dot.archive { background: linear-gradient(135deg, #4facfe, #00f2fe); }
                .weiruan-starmap-legend-dot.other { background: linear-gradient(135deg, #a8a8a8, #6a6a6a); }

                .weiruan-starmap-recent {
                    margin-top: 20px;
                }

                .weiruan-starmap-recent-item {
                    padding: 10px;
                    background: rgba(102, 126, 234, 0.1);
                    border-radius: 8px;
                    margin-bottom: 8px;
                    border: 1px solid rgba(102, 126, 234, 0.15);
                    transition: all 0.2s;
                }

                .weiruan-starmap-recent-item:hover {
                    background: rgba(102, 126, 234, 0.2);
                    border-color: rgba(102, 126, 234, 0.3);
                }

                .weiruan-starmap-recent-name {
                    color: #fff;
                    font-size: 12px;
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    margin-bottom: 4px;
                }

                .weiruan-starmap-recent-meta {
                    color: rgba(255, 255, 255, 0.5);
                    font-size: 10px;
                }

                .weiruan-starmap-empty {
                    text-align: center;
                    padding: 60px 20px;
                    color: rgba(255, 255, 255, 0.5);
                }

                .weiruan-starmap-empty-icon {
                    font-size: 64px;
                    margin-bottom: 16px;
                    animation: weiruan-float 3s ease-in-out infinite;
                }

                .weiruan-starmap-empty-text {
                    font-size: 16px;
                    margin-bottom: 8px;
                }

                .weiruan-starmap-empty-hint {
                    font-size: 13px;
                    color: rgba(255, 255, 255, 0.3);
                }

                .weiruan-starmap-footer {
                    padding: 12px 24px;
                    border-top: 1px solid rgba(102, 126, 234, 0.2);
                    text-align: center;
                    font-size: 12px;
                    color: rgba(255, 255, 255, 0.4);
                }

                /* Footer */
                .weiruan-footer {
                    padding: 12px 24px;
                    border-top: 1px solid var(--weiruan-border, #eee);
                    background: var(--weiruan-bg, #ffffff);
                    text-align: center;
                    font-size: 12px;
                    color: var(--weiruan-text-secondary, #999);
                }

                .weiruan-footer a {
                    color: #667eea;
                    text-decoration: none;
                }

                .weiruan-footer a:hover {
                    text-decoration: underline;
                }
            `);
        },

        applyTheme: () => {
            const modal = document.getElementById('weiruan-modal');
            if (modal) {
                if (State.isDark()) {
                    modal.classList.add('weiruan-dark');
                } else {
                    modal.classList.remove('weiruan-dark');
                }
            }
        },

        createFloatButton: () => {
            if (document.getElementById('weiruan-btn')) return;

            const L = State.getLang();
            const btn = document.createElement('button');
            btn.id = 'weiruan-btn';
            btn.className = 'weiruan-btn';
            btn.innerHTML = `<span class="weiruan-icon">⚡</span> ${L.downloadHelper}`;
            btn.onclick = () => App.run();

            document.body.appendChild(btn);

            // 添加快捷菜单
            const menu = document.createElement('div');
            menu.className = 'weiruan-menu';
            menu.innerHTML = `
                <div class="weiruan-menu-item" data-action="starmap">🌌 ${L.starMap}</div>
                <div class="weiruan-menu-item" data-action="history">📜 ${L.history}</div>
                <div class="weiruan-menu-item" data-action="settings">⚙️ ${L.settings}</div>
                <div class="weiruan-menu-item" data-action="debug">🔧 调试模式</div>
            `;

            menu.addEventListener('click', (e) => {
                const action = e.target.getAttribute('data-action');
                if (action === 'starmap') {
                    UI.showStarMap();
                } else if (action === 'history') {
                    UI.showHistoryWindow();
                } else if (action === 'settings') {
                    UI.showSettingsWindow();
                } else if (action === 'debug') {
                    CONFIG.DEBUG = !CONFIG.DEBUG;
                    Utils.toast(`调试模式已${CONFIG.DEBUG ? '开启' : '关闭'},查看控制台获取详细信息`, 'info');
                    if (CONFIG.DEBUG) {
                        // 输出详细的页面分析
                        console.log('==========================================');
                        console.log('[威软夸克助手] 调试信息 v' + CONFIG.VERSION);
                        console.log('==========================================');
                        console.log('页面类型:', Utils.isSharePage() ? '分享页面' : '个人网盘');
                        console.log('URL:', location.href);

                        // 复选框分析
                        const checkboxes = document.querySelectorAll('.ant-checkbox, [class*="checkbox"]');
                        const checkedBoxes = document.querySelectorAll('.ant-checkbox-checked, .ant-checkbox-wrapper-checked, [aria-checked="true"]');
                        console.log('复选框总数:', checkboxes.length);
                        console.log('选中的复选框:', checkedBoxes.length);

                        // 文件行分析
                        const rows = document.querySelectorAll('.ant-table-row, [class*="file-item"], [class*="fileItem"], [class*="list-item"]');
                        console.log('文件行元素:', rows.length);

                        // 尝试分析全局状态
                        const win = unsafeWindow || window;
                        console.log('全局变量检测:');
                        ['__REDUX_STORE__', 'store', '__store__', '__INITIAL_STATE__', '__DATA__'].forEach(v => {
                            if (win[v]) console.log(`  - ${v}: 存在`);
                        });

                        // 尝试获取文件
                        console.log('尝试获取选中文件...');
                        const files = App.getSelectedFiles();
                        console.log('获取到的文件:', files.length);
                        files.forEach((f, i) => {
                            console.log(`  ${i+1}. ${f.name} (fid: ${f.fid}, isDir: ${f.isDir})`);
                        });

                        console.log('==========================================');
                    }
                }
            });

            document.body.appendChild(menu);
        },

        showResultWindow: (data) => {
            const L = State.getLang();
            UI.removeModal();

            const totalSize = data.reduce((sum, f) => sum + f.size, 0);
            const modal = document.createElement('div');
            modal.id = 'weiruan-modal';
            modal.className = `weiruan-modal-overlay ${State.isDark() ? 'weiruan-dark' : ''}`;

            const allLinks = Utils.generateBatchLinks(data);
            const aria2Commands = Utils.generateAria2Commands(data);
            const curlCommands = Utils.generateCurlCommands(data);

            const fileListHTML = data.map((f, index) => {
                const icon = Utils.getFileIcon(f.file_name);
                const safeUrl = f.download_url.replace(/"/g, '&quot;');
                // 显示文件夹路径
                const folderPathHTML = f.folderPath
                    ? `<span style="color:var(--weiruan-text-secondary);font-size:11px;margin-left:4px;">📁 ${f.folderPath}/</span>`
                    : '';

                return `
                <div class="weiruan-file-item" data-type="${Utils.getFileType(f.file_name)}">
                    <div class="weiruan-file-info">
                        <div class="weiruan-file-name" title="${f.fullPath || f.file_name}">
                            <span>${icon}</span>
                            <span>${f.file_name}</span>
                            ${folderPathHTML}
                        </div>
                        <div class="weiruan-file-meta">${Utils.formatSize(f.size)}</div>
                    </div>
                    <div class="weiruan-file-actions">
                        <a href="${safeUrl}" target="_blank" class="weiruan-file-btn idm">⬇️ IDM</a>
                        <button class="weiruan-file-btn curl" data-index="${index}">📋 cURL</button>
                        <button class="weiruan-file-btn aria2" data-index="${index}">🚀 aria2</button>
                    </div>
                </div>`;
            }).join('');

            const historyHTML = State.history.length > 0
                ? State.history.slice(0, 20).map(h => `
                    <div class="weiruan-history-item">
                        <span class="weiruan-history-name" title="${h.name}">${Utils.getFileIcon(h.name)} ${h.name}</span>
                        <span class="weiruan-history-meta">${Utils.formatSize(h.size)} · ${Utils.formatDate(h.time)}</span>
                    </div>
                `).join('')
                : `<div class="weiruan-empty"><div class="weiruan-empty-icon">📭</div>${L.noHistory}</div>`;

            modal.innerHTML = `
            <div class="weiruan-modal">
                <div class="weiruan-modal-header">
                    <h3 class="weiruan-modal-title">
                        <span>🎉</span>
                        <span>${L.success} (${data.length} ${L.files})</span>
                    </h3>
                    <span class="weiruan-modal-close" onclick="document.getElementById('weiruan-modal').remove()">&times;</span>
                </div>

                <div class="weiruan-tabs">
                    <button class="weiruan-tab active" data-tab="files">📁 ${L.files}</button>
                    <button class="weiruan-tab" data-tab="history">📜 ${L.history}</button>
                    <button class="weiruan-tab" data-tab="settings">⚙️ ${L.settings}</button>
                </div>

                <div class="weiruan-tab-content active" data-content="files">
                    <div class="weiruan-toolbar">
                        <div class="weiruan-toolbar-info">
                            <span>${L.totalSize}: <strong>${Utils.formatSize(totalSize)}</strong></span>
                            <span style="margin-left:15px;font-size:12px;color:#888;">${L.idmTip}</span>
                        </div>
                        <div class="weiruan-toolbar-actions">
                            <div class="weiruan-filter">
                                <button class="weiruan-filter-btn active" data-filter="all">${L.all}</button>
                                <button class="weiruan-filter-btn" data-filter="video">${L.video}</button>
                                <button class="weiruan-filter-btn" data-filter="audio">${L.audio}</button>
                                <button class="weiruan-filter-btn" data-filter="image">${L.image}</button>
                                <button class="weiruan-filter-btn" data-filter="document">${L.document}</button>
                                <button class="weiruan-filter-btn" data-filter="archive">${L.archive}</button>
                            </div>
                        </div>
                    </div>
                    <div class="weiruan-toolbar" style="border-top:none;padding-top:0;">
                        <div class="weiruan-btn-group">
                            <button class="weiruan-action-btn primary" id="weiruan-copy-all">📦 ${L.copyAll}</button>
                            <button class="weiruan-action-btn success" id="weiruan-copy-aria2">🚀 ${L.copyAria2}</button>
                            <button class="weiruan-action-btn secondary" id="weiruan-copy-curl">📋 ${L.copyCurl}</button>
                        </div>
                    </div>
                    <div class="weiruan-modal-body" id="weiruan-file-list">
                        ${fileListHTML}
                    </div>
                </div>

                <div class="weiruan-tab-content" data-content="history">
                    <div class="weiruan-toolbar">
                        <span>${L.history}</span>
                        <button class="weiruan-action-btn warning" id="weiruan-clear-history">🗑️ ${L.clearHistory}</button>
                    </div>
                    <div class="weiruan-modal-body" id="weiruan-history-list">
                        ${historyHTML}
                    </div>
                </div>

                <div class="weiruan-tab-content" data-content="settings">
                    <div class="weiruan-modal-body">
                        <div class="weiruan-settings-item">
                            <span class="weiruan-settings-label">🌙 ${L.darkMode}</span>
                            <select class="weiruan-select" id="weiruan-theme-select">
                                <option value="auto" ${State.theme === 'auto' ? 'selected' : ''}>${L.auto}</option>
                                <option value="light" ${State.theme === 'light' ? 'selected' : ''}>${L.light}</option>
                                <option value="dark" ${State.theme === 'dark' ? 'selected' : ''}>${L.dark}</option>
                            </select>
                        </div>
                        <div class="weiruan-settings-item">
                            <span class="weiruan-settings-label">🌐 ${L.language}</span>
                            <select class="weiruan-select" id="weiruan-lang-select">
                                <option value="zh" ${State.lang === 'zh' ? 'selected' : ''}>中文</option>
                                <option value="en" ${State.lang === 'en' ? 'selected' : ''}>English</option>
                            </select>
                        </div>
                        <div class="weiruan-settings-item">
                            <span class="weiruan-settings-label">⌨️ 快捷键</span>
                            <span style="color:var(--weiruan-text-secondary);font-size:13px;">Ctrl+D 下载 / Esc 关闭</span>
                        </div>
                    </div>
                </div>

                <div class="weiruan-footer">
                    ${L.title} v${CONFIG.VERSION} ·
                    <a href="https://github.com/weiruankeji2025/weiruan-quark" target="_blank">GitHub</a>
                </div>
            </div>`;

            document.body.appendChild(modal);

            // 绑定事件
            UI.bindModalEvents(modal, data, allLinks, aria2Commands, curlCommands);
        },

        bindModalEvents: (modal, data, allLinks, aria2Commands, curlCommands) => {
            const L = State.getLang();

            // Tab切换
            modal.querySelectorAll('.weiruan-tab').forEach(tab => {
                tab.addEventListener('click', (e) => {
                    modal.querySelectorAll('.weiruan-tab').forEach(t => t.classList.remove('active'));
                    modal.querySelectorAll('.weiruan-tab-content').forEach(c => c.classList.remove('active'));

                    e.target.classList.add('active');
                    const tabName = e.target.getAttribute('data-tab');
                    modal.querySelector(`[data-content="${tabName}"]`).classList.add('active');
                });
            });

            // 复制链接
            document.getElementById('weiruan-copy-all')?.addEventListener('click', () => {
                GM_setClipboard(allLinks);
                Utils.toast(`✅ ${L.copied}`);
            });

            document.getElementById('weiruan-copy-aria2')?.addEventListener('click', () => {
                GM_setClipboard(aria2Commands);
                Utils.toast(`✅ aria2 ${L.copied}`);
            });

            document.getElementById('weiruan-copy-curl')?.addEventListener('click', () => {
                GM_setClipboard(curlCommands);
                Utils.toast(`✅ cURL ${L.copied}`);
            });

            // 单文件复制
            modal.querySelectorAll('.weiruan-file-btn.curl').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const index = parseInt(e.target.getAttribute('data-index'));
                    const f = data[index];
                    const curl = `curl -L -C - "${f.download_url}" -o "${f.file_name}" -A "${CONFIG.UA}" -b "${document.cookie}"`;
                    GM_setClipboard(curl);
                    Utils.toast(`✅ cURL ${L.copied}`);
                });
            });

            modal.querySelectorAll('.weiruan-file-btn.aria2').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const index = parseInt(e.target.getAttribute('data-index'));
                    const f = data[index];
                    const aria2 = `aria2c -c -x 16 -s 16 "${f.download_url}" -o "${f.file_name}" -U "${CONFIG.UA}" --header="Cookie: ${document.cookie}"`;
                    GM_setClipboard(aria2);
                    Utils.toast(`✅ aria2 ${L.copied}`);
                });
            });

            // 文件过滤
            modal.querySelectorAll('.weiruan-filter-btn').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    modal.querySelectorAll('.weiruan-filter-btn').forEach(b => b.classList.remove('active'));
                    e.target.classList.add('active');

                    const filter = e.target.getAttribute('data-filter');
                    modal.querySelectorAll('.weiruan-file-item').forEach(item => {
                        const type = item.getAttribute('data-type');
                        item.style.display = (filter === 'all' || type === filter) ? 'flex' : 'none';
                    });
                });
            });

            // 清空历史
            document.getElementById('weiruan-clear-history')?.addEventListener('click', () => {
                State.clearHistory();
                document.getElementById('weiruan-history-list').innerHTML =
                    `<div class="weiruan-empty"><div class="weiruan-empty-icon">📭</div>${L.noHistory}</div>`;
                Utils.toast(`✅ ${L.clearHistory}`);
            });

            // 设置
            document.getElementById('weiruan-theme-select')?.addEventListener('change', (e) => {
                State.setTheme(e.target.value);
            });

            document.getElementById('weiruan-lang-select')?.addEventListener('change', (e) => {
                State.setLang(e.target.value);
                Utils.toast('语言已更改,刷新页面后生效');
            });

            // 点击遮罩关闭
            modal.addEventListener('click', (e) => {
                if (e.target === modal) {
                    modal.remove();
                }
            });
        },

        showHistoryWindow: () => {
            UI.showResultWindow([]);  // 显示空结果,自动切换到历史tab
            setTimeout(() => {
                document.querySelector('[data-tab="history"]')?.click();
            }, 100);
        },

        showSettingsWindow: () => {
            UI.showResultWindow([]);
            setTimeout(() => {
                document.querySelector('[data-tab="settings"]')?.click();
            }, 100);
        },

        removeModal: () => {
            const old = document.getElementById('weiruan-modal');
            if (old) old.remove();
        },

        showStarMap: () => {
            const L = State.getLang();

            // 移除已有的星际图
            const existing = document.getElementById('weiruan-starmap');
            if (existing) existing.remove();

            const history = State.history;
            const overlay = document.createElement('div');
            overlay.id = 'weiruan-starmap';
            overlay.className = 'weiruan-starmap-overlay';

            // 生成背景星星
            const starsHTML = Array.from({ length: 150 }, () => {
                const x = Math.random() * 100;
                const y = Math.random() * 100;
                const size = Math.random() * 2 + 1;
                const duration = Math.random() * 3 + 2;
                const delay = Math.random() * 3;
                return `<div class="weiruan-star" style="left:${x}%;top:${y}%;width:${size}px;height:${size}px;--duration:${duration}s;--delay:${delay}s;"></div>`;
            }).join('');

            // 统计数据
            const typeStats = { video: 0, audio: 0, image: 0, document: 0, archive: 0, other: 0 };
            let totalSize = 0;
            history.forEach(h => {
                const type = Utils.getFileType(h.name);
                typeStats[type]++;
                totalSize += h.size || 0;
            });

            // 生成图例
            const legendItems = [
                { type: 'video', label: L.video, icon: '🎬' },
                { type: 'audio', label: L.audio, icon: '🎵' },
                { type: 'image', label: L.image, icon: '🖼️' },
                { type: 'document', label: L.document, icon: '📄' },
                { type: 'archive', label: L.archive, icon: '📦' },
                { type: 'other', label: L.other, icon: '📁' }
            ].filter(item => typeStats[item.type] > 0);

            const legendHTML = legendItems.map(item => `
                <div class="weiruan-starmap-legend-item">
                    <div class="weiruan-starmap-legend-dot ${item.type}"></div>
                    <span>${item.icon} ${item.label} (${typeStats[item.type]})</span>
                </div>
            `).join('');

            // 最近下载
            const recentHTML = history.slice(0, 5).map(h => `
                <div class="weiruan-starmap-recent-item">
                    <div class="weiruan-starmap-recent-name" title="${h.name}">${Utils.getFileIcon(h.name)} ${h.name}</div>
                    <div class="weiruan-starmap-recent-meta">${Utils.formatSize(h.size)} · ${Utils.formatDate(h.time)}</div>
                </div>
            `).join('');

            // 空状态
            const emptyHTML = `
                <div class="weiruan-starmap-empty">
                    <div class="weiruan-starmap-empty-icon">🌌</div>
                    <div class="weiruan-starmap-empty-text">${L.noHistory}</div>
                    <div class="weiruan-starmap-empty-hint">${L.starMapDesc}</div>
                </div>
            `;

            overlay.innerHTML = `
                <div class="weiruan-starmap-bg">${starsHTML}</div>
                <div class="weiruan-starmap-container">
                    <div class="weiruan-starmap-header">
                        <h3 class="weiruan-starmap-title">
                            <span class="weiruan-starmap-title-icon">🌌</span>
                            <span>${L.starMapTitle}</span>
                        </h3>
                        <button class="weiruan-starmap-close" id="weiruan-starmap-close">&times;</button>
                    </div>
                    <div class="weiruan-starmap-stats">
                        <div class="weiruan-starmap-stat">
                            <span class="weiruan-starmap-stat-value">${history.length}</span>
                            <span class="weiruan-starmap-stat-label">${L.totalDownloads}</span>
                        </div>
                        <div class="weiruan-starmap-stat">
                            <span class="weiruan-starmap-stat-value">${Utils.formatSize(totalSize)}</span>
                            <span class="weiruan-starmap-stat-label">${L.totalSize}</span>
                        </div>
                        <div class="weiruan-starmap-stat">
                            <span class="weiruan-starmap-stat-value">${legendItems.length}</span>
                            <span class="weiruan-starmap-stat-label">${L.fileTypes}</span>
                        </div>
                    </div>
                    <div class="weiruan-starmap-body">
                        <div class="weiruan-starmap-galaxy" id="weiruan-starmap-galaxy">
                            ${history.length === 0 ? emptyHTML : '<div class="weiruan-starmap-center"></div>'}
                        </div>
                        <div class="weiruan-starmap-sidebar">
                            <div class="weiruan-starmap-legend">
                                <div class="weiruan-starmap-legend-title">🏷️ ${L.fileTypes}</div>
                                ${legendHTML || '<div style="color:rgba(255,255,255,0.4);font-size:12px;">-</div>'}
                            </div>
                            <div class="weiruan-starmap-recent">
                                <div class="weiruan-starmap-legend-title">⏱️ ${L.timeline}</div>
                                ${recentHTML || '<div style="color:rgba(255,255,255,0.4);font-size:12px;">-</div>'}
                            </div>
                            <div style="margin-top:20px;padding:12px;background:rgba(102,126,234,0.1);border-radius:8px;border:1px dashed rgba(102,126,234,0.3);">
                                <div style="color:rgba(255,255,255,0.6);font-size:11px;">💡 ${L.starSize}</div>
                            </div>
                        </div>
                    </div>
                    <div class="weiruan-starmap-footer">
                        ${L.title} v${CONFIG.VERSION} · ${L.starMapDesc}
                    </div>
                </div>
            `;

            document.body.appendChild(overlay);

            // 生成行星(如果有历史记录)
            if (history.length > 0) {
                UI.generatePlanets(history);
            }

            // 绑定事件
            document.getElementById('weiruan-starmap-close').addEventListener('click', () => {
                overlay.remove();
            });

            overlay.addEventListener('click', (e) => {
                if (e.target === overlay) {
                    overlay.remove();
                }
            });

            // ESC 关闭
            const escHandler = (e) => {
                if (e.key === 'Escape') {
                    overlay.remove();
                    document.removeEventListener('keydown', escHandler);
                }
            };
            document.addEventListener('keydown', escHandler);
        },

        generatePlanets: (history) => {
            const galaxy = document.getElementById('weiruan-starmap-galaxy');
            if (!galaxy) return;

            const rect = galaxy.getBoundingClientRect();
            const centerX = rect.width / 2;
            const centerY = rect.height / 2;
            const maxRadius = Math.min(centerX, centerY) - 40;

            // 限制显示的历史数量
            const displayHistory = history.slice(0, 30);

            // 生成轨道
            const orbitCount = Math.min(5, Math.ceil(displayHistory.length / 6));
            for (let i = 1; i <= orbitCount; i++) {
                const orbitRadius = (maxRadius / orbitCount) * i;
                const orbit = document.createElement('div');
                orbit.className = 'weiruan-starmap-orbit';
                orbit.style.width = `${orbitRadius * 2}px`;
                orbit.style.height = `${orbitRadius * 2}px`;
                galaxy.appendChild(orbit);
            }

            // 计算最大文件大小(用于缩放)
            const maxSize = Math.max(...displayHistory.map(h => h.size || 1));

            // 为每个文件创建行星
            displayHistory.forEach((file, index) => {
                const type = Utils.getFileType(file.name);
                const icon = Utils.getFileIcon(file.name);

                // 计算行星大小(基于文件大小)
                const fileSize = file.size || 1;
                const sizeRatio = Math.sqrt(fileSize / maxSize);
                const planetSize = Math.max(24, Math.min(50, 24 + sizeRatio * 26));

                // 计算轨道位置
                const orbitIndex = Math.floor(index / 6);
                const orbitRadius = (maxRadius / orbitCount) * (orbitIndex + 1);
                const angleOffset = (index % 6) * (360 / 6) + (orbitIndex * 30);

                // 计算动画时长(外层轨道更慢)
                const orbitDuration = 30 + orbitIndex * 15;

                const planet = document.createElement('div');
                planet.className = `weiruan-starmap-planet ${type}`;
                planet.style.cssText = `
                    width: ${planetSize}px;
                    height: ${planetSize}px;
                    --planet-font-size: ${Math.max(12, planetSize * 0.5)}px;
                    --orbit-duration: ${orbitDuration}s;
                    left: ${centerX}px;
                    top: ${centerY}px;
                    margin-left: -${planetSize / 2}px;
                    margin-top: -${planetSize / 2}px;
                    transform: rotate(${angleOffset}deg) translateX(${orbitRadius}px) rotate(-${angleOffset}deg);
                `;

                planet.innerHTML = `<div class="weiruan-starmap-planet-inner">${icon}</div>`;

                // 添加悬停提示
                planet.addEventListener('mouseenter', (e) => {
                    UI.showPlanetTooltip(e, file);
                });

                planet.addEventListener('mousemove', (e) => {
                    UI.movePlanetTooltip(e);
                });

                planet.addEventListener('mouseleave', () => {
                    UI.hidePlanetTooltip();
                });

                galaxy.appendChild(planet);
            });
        },

        showPlanetTooltip: (e, file) => {
            let tooltip = document.getElementById('weiruan-starmap-tooltip');
            if (!tooltip) {
                tooltip = document.createElement('div');
                tooltip.id = 'weiruan-starmap-tooltip';
                tooltip.className = 'weiruan-starmap-tooltip';
                document.body.appendChild(tooltip);
            }

            const L = State.getLang();
            tooltip.innerHTML = `
                <div class="weiruan-starmap-tooltip-name">${Utils.getFileIcon(file.name)} ${file.name}</div>
                <div class="weiruan-starmap-tooltip-meta">
                    <span>📦 ${Utils.formatSize(file.size)}</span>
                    <span>📅 ${Utils.formatDate(file.time)}</span>
                </div>
            `;

            tooltip.style.display = 'block';
            UI.movePlanetTooltip(e);
        },

        movePlanetTooltip: (e) => {
            const tooltip = document.getElementById('weiruan-starmap-tooltip');
            if (tooltip) {
                const x = e.clientX + 15;
                const y = e.clientY + 15;

                // 防止超出屏幕
                const rect = tooltip.getBoundingClientRect();
                const maxX = window.innerWidth - rect.width - 20;
                const maxY = window.innerHeight - rect.height - 20;

                tooltip.style.left = `${Math.min(x, maxX)}px`;
                tooltip.style.top = `${Math.min(y, maxY)}px`;
            }
        },

        hidePlanetTooltip: () => {
            const tooltip = document.getElementById('weiruan-starmap-tooltip');
            if (tooltip) {
                tooltip.style.display = 'none';
            }
        }
    };

    // ==================== 初始化 ====================
    setTimeout(() => {
        App.init();
        console.log(`[威软夸克助手] v${CONFIG.VERSION} 已加载`);

        // 监听URL变化(SPA应用)
        let lastUrl = location.href;
        new MutationObserver(() => {
            const url = location.href;
            if (url !== lastUrl) {
                lastUrl = url;
                setTimeout(App.init, 1000);
            }
        }).observe(document, { subtree: true, childList: true });

        // 监听系统主题变化
        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
            if (State.theme === 'auto') {
                UI.applyTheme();
            }
        });
    }, 1000);
})();