Greasy Fork is available in English.

NBNT: 新版百度网盘共享文件库目录导出工具

用于导出百度网盘共享文件库目录和文件列表

// ==UserScript==
// @name         NBNT: 新版百度网盘共享文件库目录导出工具
// @namespace    http://tampermonkey.net/
// @version      0.270
// @description  用于导出百度网盘共享文件库目录和文件列表
// @author       UJiN
// @license      MIT
// @match        https://pan.baidu.com/disk*
// @icon         https://nd-static.bdstatic.com/m-static/v20-main/favicon-main.ico
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @require      https://unpkg.com/xlsx/dist/xlsx.full.min.js
// ==/UserScript==

(function () {
    'use strict';

    let directories = []; // 存储解析后的目录数据
    let depthSetting = 1; // 默认层数设置

    // 添加并发控制池
    class RequestPool {
        constructor() {
            this.maxConcurrent = config.maxConcurrent;
            this.currentRequests = 0;
            this.queue = [];
            this.requestInterval = config.requestInterval;
            this.lastRequestTime = 0;
        }

        async add(fn) {
            if (this.currentRequests >= this.maxConcurrent) {
                await new Promise(resolve => this.queue.push(resolve));
            }

            const now = Date.now();
            const timeSinceLastRequest = now - this.lastRequestTime;
            if (timeSinceLastRequest < this.requestInterval) {
                await new Promise(resolve =>
                    setTimeout(resolve, this.requestInterval - timeSinceLastRequest)
                );
            }

            this.currentRequests++;
            this.lastRequestTime = Date.now();

            try {
                return await fn();
            } finally {
                this.currentRequests--;
                if (this.queue.length > 0) {
                    const next = this.queue.shift();
                    next();
                }
            }
        }
    }

    // 添加默认配置
    const defaultConfig = {
        maxConcurrent: 2,           // 最大并发请求数
        requestInterval: 3000,      // 请求间隔(毫秒)
        maxRetries: 3,             // 最大重试次数
        defaultDepth: 1,           // 默认获取层数
        indentStyle: 'tree'        // 目录分级样式:tree(树形)或 tab(制表符)
    };

    // 获取配置(如果没有则使用默认值)
    let config = {
        ...defaultConfig,
        ...GM_getValue('nbntConfig', {})
    };

    // 保存配置
    function saveConfig() {
        GM_setValue('nbntConfig', config);
    }

    // 创建配置面板
    function createConfigPanel() {
        const panel = document.createElement('div');
        panel.style.cssText = `
            position: fixed;
            top: 60px;
            right: 60px;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 10000;
            width: 380px;
            display: none;
            font-family: "Microsoft YaHei", sans-serif;
        `;

        // 添加标签页样式
        const style = document.createElement('style');
        style.textContent = `
            .config-tabs {
                display: flex;
                border-bottom: 1px solid #ddd;
                margin-bottom: 20px;
            }
            .config-tab {
                padding: 8px 16px;
                cursor: pointer;
                color: #666;
                border-bottom: 2px solid transparent;
                margin-bottom: -1px;
            }
            .config-tab.active {
                color: #06a7ff;
                border-bottom-color: #06a7ff;
            }
            .config-content {
                display: none;
            }
            .config-content.active {
                display: block;
            }
        `;
        document.head.appendChild(style);

        panel.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                <h3 style="margin: 0; font-size: 16px; color: #333;">NBNT 配置</h3>
                <button id="closeConfig" style="border: none; background: none; cursor: pointer; padding: 4px;">
                    <i class="u-icon-close" style="font-size: 16px; color: #666;"></i>
                </button>
            </div>
            <div class="config-tabs">
                <div class="config-tab active" data-tab="features">功能设置</div>
                <div class="config-tab" data-tab="params">参数设置</div>
            </div>
            <div id="featuresContent" class="config-content active">
                <div style="margin-bottom: 20px;">
                    <label style="display: block; margin-bottom: 8px; color: #666;">目录分级样式</label>
                    <select id="indentStyle" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; outline: none; transition: all 0.3s;">
                        <option value="tree" ${config.indentStyle === 'tree' ? 'selected' : ''}>树形样式 (├── │   └──)</option>
                        <option value="tab" ${config.indentStyle === 'tab' ? 'selected' : ''}>制表符 (Tab)</option>
                    </select>
                </div>
                <div style="margin-bottom: 20px;">
                    <label style="display: flex; align-items: center; color: #666; cursor: pointer;">
                        <input type="checkbox" id="showDirSize" ${config.showDirSize ? 'checked' : ''} 
                            style="margin-right: 8px; width: 16px; height: 16px;">
                        显示目录大小(仅在导出全部时生效)
                    </label>
                </div>
            </div>
            <div id="paramsContent" class="config-content">
                <div style="margin-bottom: 16px;">
                    <label style="display: block; margin-bottom: 8px; color: #666;">最大并发请求数</label>
                    <input type="number" id="maxConcurrent" value="${config.maxConcurrent}" min="1" max="5" 
                        style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; outline: none; transition: all 0.3s;">
                </div>
                <div style="margin-bottom: 16px;">
                    <label style="display: block; margin-bottom: 8px; color: #666;">请求间隔(毫秒)</label>
                    <input type="number" id="requestInterval" value="${config.requestInterval}" min="1000" step="500" 
                        style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; outline: none; transition: all 0.3s;">
                </div>
                <div style="margin-bottom: 16px;">
                    <label style="display: block; margin-bottom: 8px; color: #666;">最大重试次数</label>
                    <input type="number" id="maxRetries" value="${config.maxRetries}" min="1" max="5" 
                        style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; outline: none; transition: all 0.3s;">
                </div>
                <div style="margin-bottom: 16px;">
                    <label style="display: block; margin-bottom: 8px; color: #666;">默认获取层数</label>
                    <input type="number" id="defaultDepth" value="${config.defaultDepth}" min="1" 
                        style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; outline: none; transition: all 0.3s;">
                </div>
            </div>
            <div style="text-align: right; border-top: 1px solid #eee; padding-top: 16px;">
                <button id="saveConfig" 
                    style="padding: 8px 24px; background: #06a7ff; color: white; border: none; border-radius: 4px; cursor: pointer; transition: all 0.3s;">
                    保存
                </button>
            </div>
        `;
        document.body.appendChild(panel);

        // 添加标签页切换功能
        const tabs = panel.querySelectorAll('.config-tab');
        tabs.forEach(tab => {
            tab.addEventListener('click', () => {
                tabs.forEach(t => t.classList.remove('active'));
                tab.classList.add('active');
                
                const contents = panel.querySelectorAll('.config-content');
                contents.forEach(content => content.classList.remove('active'));
                panel.querySelector(`#${tab.dataset.tab}Content`).classList.add('active');
            });
        });        

        // 添加输入框焦点样式
        const inputs = panel.querySelectorAll('input[type="number"]');
        inputs.forEach(input => {
            input.addEventListener('focus', () => {
                input.style.borderColor = '#06a7ff';
                input.style.boxShadow = '0 0 0 2px rgba(6,167,255,0.2)';
            });
            input.addEventListener('blur', () => {
                input.style.borderColor = '#ddd';
                input.style.boxShadow = 'none';
            });
        });

        // 创建一个通用的关闭面板函数
        const closePanel = () => {
            // 重置面板内容为当前保存的配置
            document.getElementById('maxConcurrent').value = config.maxConcurrent;
            document.getElementById('requestInterval').value = config.requestInterval;
            document.getElementById('maxRetries').value = config.maxRetries;
            document.getElementById('defaultDepth').value = config.defaultDepth;
            document.getElementById('showDirSize').checked = config.showDirSize;
            document.getElementById('indentStyle').value = config.indentStyle;
            
            panel.style.display = 'none';
            const configButton = document.querySelector('#nbnt-config-button');
            if (configButton) {
                configButton.classList.remove('is-select');
            }
        };

        // 保存配置并关闭面板
        const saveAndClose = () => {
            config.maxConcurrent = parseInt(document.getElementById('maxConcurrent').value);
            config.requestInterval = parseInt(document.getElementById('requestInterval').value);
            config.maxRetries = parseInt(document.getElementById('maxRetries').value);
            config.defaultDepth = parseInt(document.getElementById('defaultDepth').value);
            config.showDirSize = document.getElementById('showDirSize').checked;
            config.indentStyle = document.getElementById('indentStyle').value;
            saveConfig();
            closePanel();
        };        
        
        // 绑定事件
        document.getElementById('saveConfig').onclick = saveAndClose;
        document.getElementById('closeConfig').onclick = closePanel;

        // 添加点击外部关闭面板
        const handleOutsideClick = function(e) {
            if (!panel.contains(e.target) && !e.target.closest('#nbnt-config-button')) {
                closePanel();
            }
        };

        // 添加事件监听器
        document.addEventListener('click', handleOutsideClick);        

        return {
            show: () => panel.style.display = 'block',
            hide: closePanel,
            destroy: () => {
                document.removeEventListener('click', handleOutsideClick);
                if (panel.parentNode) {
                    panel.parentNode.removeChild(panel);
                }
            }
        };
    }
    // 添加进度条组件
    function createProgressBar() {
        const progressContainer = document.createElement('div');
        progressContainer.id = 'directory-progress';
        progressContainer.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 9999;
            display: none;
            width: 350px;
            font-family: "Microsoft YaHei", sans-serif;
        `;

        const titleDiv = document.createElement('div');
        titleDiv.style.cssText = `
            font-weight: bold;
            margin-bottom: 15px;
            color: #333;
            font-size: 14px;
        `;
        titleDiv.textContent = '目录获取进度';

        const progressText = document.createElement('div');
        progressText.id = 'progress-text';
        progressText.style.cssText = `
            margin-bottom: 10px;
            color: #666;
            font-size: 13px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            max-width: 280px;
        `;
        progressText.textContent = '正在获取目录信息...';

        const progressBarOuter = document.createElement('div');
        progressBarOuter.style.cssText = `
            width: 100%;
            height: 6px;
            background: #f0f0f0;
            border-radius: 3px;
            overflow: hidden;
        `;

        const progressBarInner = document.createElement('div');
        progressBarInner.id = 'progress-bar';
        progressBarInner.style.cssText = `
            width: 0%;
            height: 100%;
            background: linear-gradient(90deg, #2196F3, #00BCD4);
            transition: width 0.3s ease;
            border-radius: 3px;
        `;

        progressBarOuter.appendChild(progressBarInner);
        progressContainer.appendChild(titleDiv);
        progressContainer.appendChild(progressText);
        progressContainer.appendChild(progressBarOuter);
        document.body.appendChild(progressContainer);

        return {
            show: () => progressContainer.style.display = 'block',
            hide: () => progressContainer.style.display = 'none',
            remove: () => {
                if (progressContainer.parentNode) {
                    progressContainer.parentNode.removeChild(progressContainer);
                }
            },
            updateProgress: (current, total) => {
                const percentage = Math.min((current / total) * 100, 100);
                progressBarInner.style.width = `${percentage}%`;
                progressText.textContent = `进度:${current}/${total} (${percentage.toFixed(1)}%)`;
            },
            updateText: (text) => {
                progressText.textContent = text;
            }
        };
    }

    // 等待文件库按钮和标题加载,并添加点击事件监听
    function waitForLibraryElements() {
        let isProcessing = false;

        // 检查并添加按钮到操作栏
        function checkAndAddOperationButtons() {
            if (isProcessing) return;
            isProcessing = true;

            try {
    
                const actionContainer = document.querySelector('.im-r-contain__actions');
                if (!actionContainer) {
                    isProcessing = false;
                    return;
                }

                const operateDiv = document.querySelector('.im-file-nav__operate');
                const downloadButton = operateDiv?.querySelector('.u-icon-download')?.closest('button');
                
                if (!operateDiv || !downloadButton) {
                    isProcessing = false;
                    return;
                }

                const existingCheckButton = document.querySelector('#check-dir-button');
                const existingFetchButton = document.querySelector('#fetch-dir-button');
                const existingConfigButton = document.querySelector('#nbnt-config-button');
                
                // 如果操作按钮已存在则返回
                if (existingCheckButton || existingFetchButton) {
                    isProcessing = false;
                    return;
                }

                // 如果配置按钮已存在则移除
                if (existingConfigButton) {
                    existingConfigButton.remove();
                    if (window.currentConfigPanel) {
                        window.currentConfigPanel.destroy();
                    }
                }

                // 添加样式
                const style = document.createElement('style');
                style.textContent = `
                    .export-dropdown {
                        position: relative;
                        display: inline-flex;
                        align-items: center;
                        cursor: pointer;
                        height: 24px;
                        line-height: 24px;
                    }
                    .export-dropdown::after {
                        content: '';
                        position: absolute;
                        right: -12px;
                        top: 6px;
                        width: 1px;
                        height: 12px;
                        background-color: rgb(217, 217, 217);
                    }
                    .export-dropdown-menu {
                        display: none;
                        position: absolute;
                        top: 100%;
                        left: 50%;
                        transform: translateX(-50%);
                        background: white;
                        box-shadow: 0 2px 8px rgba(0,0,0,0.15);
                        padding: 4px;
                        z-index: 99999;
                        margin-top: 2px;
                        border: 1px solid #e8e8e8;
                        white-space: nowrap;
                        flex-direction: row;
                    }
                    .export-dropdown-menu.show {
                        display: flex;
                    }
                    .export-item {
                        padding: 4px 12px;
                        cursor: pointer;
                        color: #333;
                        font-size: 12px;
                        line-height: 1.5;
                        border-right: 1px solid #e8e8e8;
                    }
                    .export-item:last-child {
                        border-right: none;
                    }
                    .export-item:hover {
                        background: #f5f5f5;
                    }
                `;
                document.head.appendChild(style);

                const checkButton = document.createElement('button');
                checkButton.id = 'check-dir-button';
                checkButton.type = 'button';
                checkButton.className = 'u-button u-button--default u-button--mini';
                checkButton.innerHTML = `
                    <i class="u-icon-search"></i>
                    <span>检查目录</span>
                `;
                
                const exportDropdown = document.createElement('div');
                exportDropdown.className = 'export-dropdown u-button u-button--default u-button--mini';
                exportDropdown.innerHTML = `
                    <i class="u-icon-folder"></i>
                    <span>导出目录</span>
                    <i class="u-icon-arrow-down" style="margin-left: 4px;"></i>
                    <div class="export-dropdown-menu">
                        <div class="export-item" data-type="txt">导出为TXT</div>
                        <div class="export-item" data-type="xlsx">导出为Excel</div>
                    </div>
                `;

                const fetchAllDropdown = document.createElement('div');
                fetchAllDropdown.className = 'export-dropdown u-button u-button--default u-button--mini';
                fetchAllDropdown.innerHTML = `
                    <i class="u-icon-download-bold"></i>
                    <span>导出全部</span>
                    <i class="u-icon-arrow-down" style="margin-left: 4px;"></i>
                    <div class="export-dropdown-menu">
                        <div class="export-item" data-type="txt">导出为TXT</div>
                        <div class="export-item" data-type="xlsx">导出为Excel</div>
                    </div>
                `;

                // 创建配置按钮
                const configButton = document.createElement('div');
                configButton.id = 'nbnt-config-button';
                configButton.innerHTML = `
                    <span class="u-tooltip u-uicon is-hover" tabindex="0" aria-describedby="nbnt-config-tooltip">
                        <i class="u-uicon__font u-icon-setting" style="color: #666;"></i>
                    </span>
                    <div class="u-tooltip__popper" id="nbnt-config-tooltip">NBNT 配置</div>
                `;
                configButton.style.cssText = `
                    display: inline-flex;
                    margin: 40px 8px;
                    cursor: pointer;
                    position: relative;
                `;
                
                // 添加悬停提示样式
                const tooltipStyle = document.createElement('style');
                tooltipStyle.textContent = `
                    .u-tooltip__popper {
                        display: none;
                        position: absolute;
                        background: rgba(51, 51, 51, 0.9);
                        color: #fff;
                        padding: 5px 12px;
                        border-radius: 4px;
                        font-size: 12px;
                        line-height: 1.6;
                        white-space: nowrap;
                        top: 50%;
                        right: 100%;
                        transform: translateY(-50%);
                        margin-right: 8px;
                        z-index: 10001;
                        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
                    }
                    .u-tooltip__popper::after {
                        content: '';
                        position: absolute;
                        right: -4px;
                        top: 50%;
                        transform: translateY(-50%);
                        border-width: 4px;
                        border-style: solid;
                        border-color: transparent transparent transparent rgba(51, 51, 51, 0.9);
                    }
                    #nbnt-config-button:hover .u-tooltip__popper {
                        display: block;
                    }
                    #nbnt-config-button.is-select i {
                        color: #06a7ff !important;
                    }
                `;
                document.head.appendChild(tooltipStyle);

                const configPanel = createConfigPanel();
                window.currentConfigPanel = configPanel;
                configButton.onclick = () => {
                    const isActive = configButton.classList.contains('is-select');
                    if (isActive) {
                        configButton.classList.remove('is-select');
                        configPanel.hide();
                    } else {
                        configButton.classList.add('is-select');
                        configPanel.show();
                    }
                };
    
                // 将配置按钮添加到操作区域
                actionContainer.appendChild(configButton);                

                checkButton.onclick = function() {
                    const selected = getSelectedDirectory();
                    if (!selected) {
                        alert('请选中一个目录!');
                        return;
                    }
                    const { dirInfo, title } = selected;
                    checkDirectoryInfo(dirInfo.msg_id, title);
                };

                // 处理导出选项点击
                exportDropdown.addEventListener('click', function(e) {
                    e.stopPropagation();
                    const menu = this.querySelector('.export-dropdown-menu');
                    menu.classList.toggle('show');
                });

                // 点击其他地方关闭菜单
                document.addEventListener('click', function() {
                    const menus = document.querySelectorAll('.export-dropdown-menu');
                    menus.forEach(menu => menu.classList.remove('show'));
                });            

                // 防止菜单项点击事件冒泡
                exportDropdown.querySelector('.export-dropdown-menu').addEventListener('click', function(e) {
                    e.stopPropagation();
                });

                exportDropdown.querySelector('.export-dropdown-menu').addEventListener('click', async function(e) {
                    const exportType = e.target.dataset.type;
                    if (!exportType) return;

                    try {
                        const selected = getSelectedDirectory();
                        if (!selected) {
                            alert('请选中一个目录!');
                            return;
                        }

                        const { dirInfo, title } = selected;
                        console.log("选中的目录信息:", dirInfo);

                        const uk = dirInfo.uk;
                        const fsId = dirInfo.fs_id;
                        const gid = dirInfo.group_id;
                        const msgId = dirInfo.msg_id;

                        depthSetting = parseInt(prompt("请输入要获取的子目录层数:", config.defaultDepth), 10);
                        if (isNaN(depthSetting) || depthSetting < 1) {
                            alert("请输入有效的层数!");
                            return;
                        }

                        const result = await fetchSubdirectories(uk, msgId, fsId, gid, title, depthSetting);
                        if (!window.cancelOperation && result) {
                            if (exportType === 'txt') {
                                const formattedContent = formatDirectoryTree(result.tree);
                                saveAsTxt(formattedContent, title);
                            } else if (exportType === 'xlsx') {
                                saveAsExcel(result, title);
                            }
                        }
                    } finally {
                        cleanup();
                    }
                });

                // 处理导出全部按钮的点击事件
                fetchAllDropdown.addEventListener('click', function(e) {
                    e.stopPropagation();
                    const menu = this.querySelector('.export-dropdown-menu');
                    menu.classList.toggle('show');
                });

                // 防止菜单项点击事件冒泡
                fetchAllDropdown.querySelector('.export-dropdown-menu').addEventListener('click', function(e) {
                    e.stopPropagation();
                });

                // 修改导出全部选项的点击处理
                fetchAllDropdown.querySelector('.export-dropdown-menu').addEventListener('click', async function(e) {
                    const exportType = e.target.dataset.type;
                    if (!exportType) return;

                    try {
                        const selected = getSelectedDirectory();
                        if (!selected) {
                            alert('请选中一个目录!');
                            return;
                        }

                        const { dirInfo, title } = selected;
                        console.log("选中的目录信息:", dirInfo);

                        const uk = dirInfo.uk;
                        const fsId = dirInfo.fs_id;
                        const gid = dirInfo.group_id;
                        const msgId = dirInfo.msg_id;

                        depthSetting = parseInt(prompt("请输入要获取的层数:", config.defaultDepth), 10);
                        if (isNaN(depthSetting) || depthSetting < 1) {
                            alert("请输入有效的层数!");
                            return;
                        }

                        const result = await fetchAllContent(uk, msgId, fsId, gid, title, depthSetting);
                        if (!window.cancelOperation && result) {
                            if (exportType === 'txt') {
                                // TXT 导出时进行格式化
                                const formattedContent = formatAllContent(result.tree);
                                saveAsTxt(formattedContent, title + "_完整");
                            } else if (exportType === 'xlsx') {
                                // Excel 导出使用原始数据结构
                                saveAsExcel(result, title + "_完整");
                            }
                        }
                    } finally {
                        cleanup();
                    }
                });

                // 修改按钮插入顺序
                requestAnimationFrame(() => {
                    downloadButton.after(fetchAllDropdown);
                    downloadButton.after(document.createTextNode(' ')); // 添加空格
                    downloadButton.after(exportDropdown);
                    downloadButton.after(document.createTextNode(' ')); // 添加空格
                    downloadButton.after(checkButton);
                });

            } finally {
                isProcessing = false;
            }
        }

        // 创建一个防抖函数
        function debounce(func, wait) {
            let timeout;
            return function executedFunction(...args) {
                const later = () => {
                    clearTimeout(timeout);
                    func(...args);
                };
                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        }

        // 使用防抖包装检查函数
        const debouncedCheck = debounce(checkAndAddOperationButtons, 200);

        // 修改 MutationObserver 的配置
        const observer = new MutationObserver((mutations) => {
            // 只在有相关变化时触发检查
            const hasRelevantChanges = mutations.some(mutation => {
                return mutation.addedNodes.length > 0 && 
                       Array.from(mutation.addedNodes).some(node => {
                           return node.classList?.contains('im-file-nav__operate') ||
                                  node.querySelector?.('.im-file-nav__operate');
                       });
            });

            if (hasRelevantChanges) {
                debouncedCheck();
            }
        });

        // 使用更具体的观察配置
        observer.observe(document.body, { 
            childList: true, 
            subtree: true,
            attributes: false,
            characterData: false
        });

        // 初始检查
        checkAndAddOperationButtons();
        
        // 拦截请求(只需要执行一次)
        interceptNetworkRequests();
    }

    // 获取当前选中的目录
    function getSelectedDirectory() {
        // 同时支持根目录和子目录的选择器
        const selectedDirs = document.querySelectorAll('.im-pan-table__body-row.selected, .im-pan-list__item.selected');
        if (selectedDirs.length !== 1) return null;
        
        const selectedDir = selectedDirs[0];
        const title = selectedDir.querySelector('.im-pan-list__file-name-title-text')?.innerText;
        
        if (!title) return null;
        
        // 在 directories 中查找匹配的记录
        const matchedDir = directories.find(dir => dir.server_filename === title);
        if (!matchedDir) {
            console.error(`未找到目录 "${title}" 的记录`);
            return null;
        }
        
        return {
            element: selectedDir,
            title: title,
            dirInfo: matchedDir
        };
    }

    // 检查目录信息并显示相关信息
    function checkDirectoryInfo(msgId, title) {
        console.log(`检查目录: ${title}, msgId: ${msgId}`);

        const matchedDir = directories.find(dir => dir.msg_id === msgId);
        console.log("当前目录数据:", directories);

        if (matchedDir) {
            alert(`匹配到目录: ${title}\nfs_id: ${matchedDir.fs_id}\ngroup_id: ${matchedDir.group_id}\nuk: ${matchedDir.uk}`);
            console.log("匹配的目录信息:", matchedDir);
        } else {
            alert(`未找到与目录 "${title}" 匹配的记录。`);
        }
    }

    // 拦截 XMLHttpRequest 请求
    function interceptNetworkRequests() {
        const originalOpen = XMLHttpRequest.prototype.open; // 保存原始 XMLHttpRequest.open

        XMLHttpRequest.prototype.open = function (method, url, ...rest) {
            if (url.includes('mbox/group/listshare')) {
                console.log("准备拦截 XMLHttpRequest 请求:", url);
                this.addEventListener('load', function () {
                    try {
                        const data = this.responseType === 'json' ? this.response : JSON.parse(this.responseText);
                        console.debug("完整的响应数据:", data); // 调试输出完整数据
                        processLibraryData(data);
                    } catch (e) {
                        console.error("解析响应失败:", e);
                    }
                });
            }

            // 拦截进入目录的请求
            if (url.includes('mbox/msg/shareinfo')) {
                console.log("准备拦截进入目录的 XMLHttpRequest 请求:", url);
                this.addEventListener('load', function () {
                    try {
                        const data = this.responseType === 'json' ? this.response : JSON.parse(this.responseText);
                        console.debug("完整的响应数据:", data); // 调试输出完整数据
                        processDirectoryData(data); 
                    } catch (e) {
                        console.error("解析响应失败:", e);
                    }
                });
            }

            return originalOpen.apply(this, [method, url, ...rest]);
        };
    }

    // 处理文件库数据:取需要的信息并存储
    function processLibraryData(data) {
        if (!data || data.errno !== 0) {
            console.error("文件库数据获取失败,错误码:", data?.errno);
            return;
        }

        // 在获取新的文件库数据时才清空旧数据
        directories = []; 
        
        const msgList = data.records?.msg_list || [];

        msgList.forEach((msg, index) => {
            const group_id = msg.group_id;
            const uk = msg.uk;

            msg.file_list.forEach(file => {
                if (parseInt(file.isdir) === 1) {
                    directories.push({
                        fs_id: file.fs_id,
                        server_filename: file.server_filename,
                        group_id: group_id,
                        msg_id: msg.msg_id,
                        uk: uk
                    });
                }
            });
        });
    }

    // 处理目录数据:提取需要的信息并存储
    function processDirectoryData(data) {
        if (!data || data.errno !== 0) {
            console.error("目录数据获取失败,错误码:", data?.errno);
            return;
        }

        const records = data.records || [];

        records.forEach(record => {
            // 保存所有目录信息,包括子目录
            if (parseInt(record.isdir) === 1) {
                // 处理路径,移除"我的资源"前缀
                let processedPath = record.path;
                if (processedPath.startsWith('/我的资源/')) {
                    processedPath = processedPath.substring('/我的资源'.length);
                }
                
                // 从处理后的路径中提取各级目录
                const pathParts = processedPath.split('/').filter(p => p);
                const rootName = pathParts[0];
                
                // 查找根目录信息
                const rootDir = directories.find(d => d.server_filename === rootName);
                
                if (rootDir) {
                    // 检查是否已存在相同的记录
                    const existingRecord = directories.find(d => d.fs_id === record.fs_id);
                    if (!existingRecord) {
                        // 构建完整的目录信息
                        const dirInfo = {
                            fs_id: record.fs_id,
                            server_filename: record.server_filename,
                            path: processedPath,
                            group_id: rootDir.group_id,
                            msg_id: rootDir.msg_id,
                            uk: rootDir.uk,
                            parent_path: pathParts.slice(0, -1).join('/'),
                            level: pathParts.length - 1  // 添加层级信息
                        };

                        // 添加到目录列表
                        directories.push(dirInfo);
                    }
                } else {
                    // 如果是根目录级别的分享,直接添加
                    if (pathParts.length === 1) {
                        const dirInfo = {
                            fs_id: record.fs_id,
                            server_filename: record.server_filename,
                            path: processedPath,
                            group_id: record.group_id,
                            msg_id: record.msg_id,
                            uk: record.uk,
                            level: 0
                        };
                        directories.push(dirInfo);
                    }
                }
            }
        });

        // 按层级排序,方便调试查看
        directories.sort((a, b) => (a.level || 0) - (b.level || 0));
    }

    // 统一的获取内容函数
    async function fetchContent(uk, msgId, fsId, gid, title, depth, fetchMode = 'directory') {
        console.log(`开始获取内容: ${title}, 深度: ${depth}, 模式: ${fetchMode}`);
        const startTime = performance.now();
        const progressBar = createProgressBar();
        progressBar.show();

        let result = {
            name: title,
            children: [],
            level: 0,
            isRoot: true,
            isDir: true,
            startTime: startTime
        };

        let totalItems = 0;
        let processedItems = 0;

        async function fetchItems(parentDir, currentDepth) {
            if (currentDepth >= depth) return;

            let page = 1;
            let hasMore = true;
            const allRecords = [];
            const maxRetries = config.maxRetries;
            const requestPool = new RequestPool();

            while (hasMore) {
                progressBar.updateText(`正在获取 "${parentDir.name}" 的第 ${page} 页数据...`);
                console.log(`[${parentDir.name}] 正在获取第 ${page} 页数据...`);

                const url = `https://pan.baidu.com/mbox/msg/shareinfo?from_uk=${encodeURIComponent(uk)}&msg_id=${encodeURIComponent(msgId)}&type=2&num=100&page=${page}&fs_id=${encodeURIComponent(parentDir.fs_id || fsId)}&gid=${encodeURIComponent(gid)}&limit=100&desc=1&clienttype=0&app_id=250528&web=1`;

                let retryCount = 0;
                let success = false;

                while (retryCount < maxRetries && !success) {
                    try {
                        const data = await requestPool.add(async () => {
                            const response = await fetch(url, { 
                                timeout: 30000,
                                headers: {
                                    'Cache-Control': 'no-cache',
                                    'Pragma': 'no-cache'
                                }
                            });
                            if (!response.ok) {
                                throw new Error(`HTTP error! status: ${response.status}`);
                            }
                            return response.json();
                        });

                        if (data.errno !== 0) {
                            throw new Error(`API error: ${data.errno}`);
                        }

                        // 根据模式过滤记录
                        const filteredRecords = fetchMode === 'directory' 
                            ? data.records.filter(record => parseInt(record.isdir) === 1)
                            : data.records;

                        allRecords.push(...filteredRecords);
                        hasMore = data.has_more === 1;
                        success = true;

                        console.log(`[${parentDir.name}] 第 ${page} 页获取成功,本页记录数: ${filteredRecords.length},hasMore: ${hasMore}`);
                    } catch (error) {
                        retryCount++;
                        console.error(`[${parentDir.name}] 页面 ${page} 获取失败 (${retryCount}/${maxRetries}):`, error);
                        
                        if (retryCount < maxRetries) {
                            const delay = Math.min(1000 * Math.pow(2, retryCount), 10000);
                            progressBar.updateText(`请求失败,${delay/1000}秒后重试...`);
                            await new Promise(resolve => setTimeout(resolve, delay));
                        } else {
                            progressBar.updateText(`获取 "${parentDir.name}" 第 ${page} 页失败,跳过...`);
                            hasMore = false;
                        }
                    }
                }

                if (success) {
                    page++;
                    await new Promise(resolve => setTimeout(resolve, 2000));
                }
            }

            totalItems += allRecords.length;
            
            const promises = allRecords.map(async record => {
                const childItem = {
                    name: record.server_filename,
                    fs_id: record.fs_id,
                    isDir: parseInt(record.isdir) === 1,
                    size: record.size,
                    children: [],
                    level: currentDepth + 1,
                    parentLevel: currentDepth
                };
                parentDir.children.push(childItem);

                if (childItem.isDir && currentDepth + 1 < depth) {
                    await fetchItems(childItem, currentDepth + 1);
                }
                
                processedItems++;
                progressBar.updateProgress(processedItems, totalItems);
            });

            await Promise.all(promises);
        }

        try {
            await fetchItems(result, 0);
            progressBar.updateText('内容获取完成!');
            setTimeout(() => progressBar.hide(), 2000);
            
            return {
                tree: result,
                startTime: startTime
            };
        } finally {
            progressBar.remove();
            result = null;
            cleanup();
        }
    }
    
    // 获取子目录信息
    async function fetchSubdirectories(uk, msgId, fsId, gid, title, depth) {
        return fetchContent(uk, msgId, fsId, gid, title, depth, 'directory');
    }

    // 添加清理文件名的函数
    function cleanFileName(name) {
        // 移除零宽空格和其他不可见字符
        return name.replace(/[\u200b\u200c\u200d\u200e\u200f\ufeff]/g, '');
    }

    // 添加通用的排序函数
    function sortTreeNodes(node) {
        if (node.children && node.children.length > 0) {
            node.children.sort((a, b) => {
                const numA = extractNumber(a.name);
                const numB = extractNumber(b.name);
                
                if (numA !== numB) {
                    return numA - numB;
                }
                return a.name.localeCompare(b.name, 'zh-CN');
            });
            node.children.forEach(sortTreeNodes);
        }
    }    

    function extractNumber(str) {
        const match = str.match(/^(\d+)\./);
        return match ? parseInt(match[1]) : Infinity;
    }    

    function formatDirectoryTree(dir) {
        const formatStartTime = performance.now();
        // 在格式化之前先排序
        sortTreeNodes(dir);

        const SYMBOLS = config.indentStyle === 'tree' ? {
            space:  '    ',
            branch: '│   ',
            tee:    '├──',
            last:   '└──'
        } : {
            space:  '\t',
            branch: '\t',
            tee:    '',
            last:   ''
        };
        
        let result = '';
        const currentTime = new Date().toLocaleString();
        
        // 添加标题和信息头
        result += `目录结构导出清单\n`;
        result += `导出时间:${currentTime}\n`;
        result += `根目录:${dir.name}\n`;
        result += `${'='.repeat(50)}\n\n`;

        // 内部函数,用于格式化目录
        function formatDir(node, prefix = '', isLastArray = []) {
            if (node.isRoot) {
                result += `${cleanFileName(node.name)}\n`;
                if (node.children && node.children.length > 0) {
                    node.children.forEach((child, index) => {
                        const isLast = index === node.children.length - 1;
                        formatDir(child, SYMBOLS.space, [isLast]);
                    });
                }
            } else {
                const connector = config.indentStyle === 'tree'
                    ? (isLastArray[isLastArray.length - 1] ? SYMBOLS.last : SYMBOLS.tee)
                    : '';
                result += `${prefix}${connector}${cleanFileName(node.name)}\n`;

                if (node.children && node.children.length > 0) {
                    node.children.forEach((child, index) => {
                        const isLast = index === node.children.length - 1;
                        const newPrefix = prefix + (isLastArray[isLastArray.length - 1] ? SYMBOLS.space : SYMBOLS.branch);
                        formatDir(child, newPrefix, [...isLastArray, isLast]);
                    });
                }
            }
        }

        formatDir(dir, '', []);
        
        const endTime = performance.now();
        const formatTime = ((endTime - formatStartTime) / 1000).toFixed(2); // 格式化耗时
        const totalTime = ((endTime - (dir.startTime || formatStartTime)) / 1000).toFixed(2); // 总耗时
        
        // 添加页脚和统计信息
        result += `\n${'='.repeat(50)}\n`;
        result += `统计信息:\n`;
        result += `目录数量:${countDirectories(dir)} 个\n`;
        result += `格式化耗时:${formatTime} 秒\n`;
        if (dir.startTime) {
            result += `总处理耗时:${totalTime} 秒\n`;
        }
        
        return result;
    }

    // 添加统计目录数量的辅助函数
    function countDirectories(dir) {
        let count = 0;
        
        function traverse(node) {
            if (node.children && node.children.length > 0) {
                count += node.children.length;
                node.children.forEach(traverse);
            }
        }
        
        traverse(dir);
        return count;
    }

    // 保存为 TXT 文件
    function saveAsTxt(content, title) {
        const blob = new Blob([content], { type: 'text/plain' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `${title}.txt`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        console.log(`已保存文件: ${title}.txt`);
    }

    // 添加获取全部内容的函数
    async function fetchAllContent(uk, msgId, fsId, gid, title, depth) {
        return fetchContent(uk, msgId, fsId, gid, title, depth, 'all');
    }

    function formatAllContent(dir) {
        const formatStartTime = performance.now();
        // 在格式化之前先排序
        sortTreeNodes(dir);
        let result = '';
        const currentTime = new Date().toLocaleString();
        
        const SYMBOLS = config.indentStyle === 'tree' ? {
            space:  '    ',
            branch: '│   ',
            tee:    '├──',
            last:   '└──'
        } : {
            space:  '\t',
            branch: '\t',
            tee:    '',
            last:   ''
        };
        
        result += `完整目录结构导出清单\n`;
        result += `导出时间:${currentTime}\n`;
        result += `根目录:${dir.name}\n`;
        result += `${'='.repeat(50)}\n\n`;

        let fileCount = 0;
        let dirCount = 0;
        let totalSize = 0;

        // 计算每个目录的总大小
        function calculateDirSize(node) {
            let size = 0;
            if (!node.isDir) {
                return parseInt(node.size) || 0;
            }
            if (node.children && node.children.length > 0) {
                node.children.forEach(child => {
                    size += calculateDirSize(child);
                });
            }
            node.totalSize = size;
            return size;
        }

        calculateDirSize(dir);
        
        function formatItem(node, prefix = '', isLastArray = []) {
            if (node.isRoot) {
                const sizeStr = config.showDirSize ? ` (${formatSize(node.totalSize || 0)})` : '';
                result += `${cleanFileName(node.name)}/${sizeStr}\n`;
                if (node.children && node.children.length > 0) {
                    node.children.forEach((child, index) => {
                        const isLast = index === node.children.length - 1;
                        formatItem(child, SYMBOLS.space, [isLast]);
                    });
                }
            } else {
                const connector = config.indentStyle === 'tree'
                    ? (isLastArray[isLastArray.length - 1] ? SYMBOLS.last : SYMBOLS.tee)
                    : '';
                const cleanName = cleanFileName(node.name);
                const itemName = node.isDir ? `${cleanName}/` : cleanName;
                let sizeStr = '';

                if (node.isDir) {
                    sizeStr = config.showDirSize ? ` (${formatSize(node.totalSize || 0)})` : '';
                } else {
                    sizeStr = ` (${formatSize(parseInt(node.size) || 0)})`;
                }
                
                result += `${prefix}${connector}${itemName}${sizeStr}\n`;

                if (node.isDir) {
                    dirCount++;
                } else {
                    fileCount++;
                    totalSize += parseInt(node.size) || 0;
                }

                if (node.children && node.children.length > 0) {
                    node.children.forEach((child, index) => {
                        const isLast = index === node.children.length - 1;
                        const newPrefix = prefix + (isLastArray[isLastArray.length - 1] ? SYMBOLS.space : SYMBOLS.branch);
                        formatItem(child, newPrefix, [...isLastArray, isLast]);
                    });
                }
            }
        }

        formatItem(dir, '', []);
        
        const endTime = performance.now();
        const formatTime = ((endTime - formatStartTime) / 1000).toFixed(2);
        const totalTime = ((endTime - dir.startTime) / 1000).toFixed(2);
        
        result += `\n${'='.repeat(50)}\n`;
        result += `统计信息:\n`;
        result += `目录数量:${dirCount}\n`;
        result += `文件数量:${fileCount}\n`;
        result += `文件大小:${formatSize(totalSize)}\n`;
        result += `处理总计:${dirCount + fileCount} 个项目\n`;
        result += `格式化耗时:${formatTime} 秒\n`;
        result += `总处理耗时:${totalTime} 秒\n`;
        
        return result;
    }

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

    function cleanup() {
        const progressBar = document.getElementById('directory-progress');
        if (progressBar && progressBar.parentNode) {
            progressBar.parentNode.removeChild(progressBar);
        }
    }

    function saveAsExcel(data, title) {
        const wb = XLSX.utils.book_new();
        
        const excelData = [];
        
        excelData.push(['目录结构导出清单']);
        excelData.push([`导出时间: ${new Date().toLocaleString()}`]);
        
        let fileCount = 0;
        let dirCount = 0;
        let totalSize = 0;
        
        function countItems(node) {
            if (!node.isRoot) {
                if (node.isDir) {
                    dirCount++;
                } else {
                    fileCount++;
                    totalSize += node.size || 0;
                }
            }
            if (node.children && node.children.length > 0) {
                node.children.forEach(countItems);
            }
        }
        
        countItems(data.tree);
        
        excelData.push(['统计信息']);
        excelData.push([`目录数量: ${dirCount}`]);
        if (fileCount > 0) {
            excelData.push([`文件数量: ${fileCount}`]);
            excelData.push([`文件大小: ${formatSize(totalSize)}`]);
            excelData.push([`处理总计: ${dirCount + fileCount} 个项目`]);
        }
        excelData.push([`格式化耗时: ${((performance.now() - data.startTime) / 1000).toFixed(2)} 秒`]);
        excelData.push(['']);
        
        function getMaxDepth(node, currentDepth = 0) {
            if (!node.children || node.children.length === 0) {
                return currentDepth;
            }
            return Math.max(...node.children.map(child => 
                getMaxDepth(child, currentDepth + 1)
            ));
        }
        
        const actualDepth = Math.min(depthSetting, getMaxDepth(data.tree) + 1);
        
        const headers = [];
        for (let i = 1; i <= actualDepth; i++) {
            headers.push(`${i}级目录`);
        }
        excelData.push(headers);
        
        const allRows = [];


        function processNode(node, level = 0, parentRow = []) {
            if (level >= actualDepth) return;
            
            const currentRow = [...parentRow];
            
            if (!node.isRoot) {
                currentRow[level] = node.name;
                allRows.push([...currentRow]);
            }
            
            if (node.children && node.children.length > 0) {
                if (node.isRoot) {
                    sortTreeNodes(node); 
                    node.children.forEach(child => {
                        const newRow = new Array(actualDepth).fill('');
                        newRow[0] = child.name;
                        allRows.push([...newRow]);
                        
                        if (child.children && child.children.length > 0) {
                            child.children.forEach(grandChild => {
                                processNode(grandChild, 1, newRow);
                            });
                        }
                    });
                } else {
                    node.children.forEach(child => {
                        processNode(child, level + 1, currentRow);
                    });
                }
            }
        }
        
        processNode(data.tree, 0, new Array(actualDepth).fill(''));
        
        excelData.push(...allRows);
        
        const ws = XLSX.utils.aoa_to_sheet(excelData);
        
        const colWidths = [];
        for (let i = 0; i < actualDepth; i++) {
            colWidths.push({ wch: 45 });
        }
        ws['!cols'] = colWidths;
        
        ws['!merges'] = [
            { s: { r: 0, c: 0 }, e: { r: 0, c: actualDepth - 1 } },  // 标题行
            { s: { r: 1, c: 0 }, e: { r: 1, c: actualDepth - 1 } },  // 时间行
            { s: { r: 2, c: 0 }, e: { r: 2, c: actualDepth - 1 } },  // 统计信息标题
            { s: { r: 3, c: 0 }, e: { r: 3, c: actualDepth - 1 } },  // 目录数量
        ];
        
        if (fileCount > 0) {
            ws['!merges'].push(
                { s: { r: 4, c: 0 }, e: { r: 4, c: actualDepth - 1 } },  // 文件数量
                { s: { r: 5, c: 0 }, e: { r: 5, c: actualDepth - 1 } },  // 文件大小
                { s: { r: 6, c: 0 }, e: { r: 6, c: actualDepth - 1 } },  // 处理总计
                { s: { r: 7, c: 0 }, e: { r: 7, c: actualDepth - 1 } },  // 格式化耗时
                { s: { r: 8, c: 0 }, e: { r: 8, c: actualDepth - 1 } }   // 空行
            );
        } else {
            ws['!merges'].push(
                { s: { r: 4, c: 0 }, e: { r: 4, c: actualDepth - 1 } },  // 格式化耗时
                { s: { r: 5, c: 0 }, e: { r: 5, c: actualDepth - 1 } }   // 空行
            );
        }
        
        XLSX.utils.book_append_sheet(wb, ws, '目录结构');
        
        XLSX.writeFile(wb, `${title}.xlsx`);
    }

    waitForLibraryElements();
})();