Greasy Fork

Greasy Fork is available in English.

BaiduPanFileList

统计百度盘文件(夹)数量大小

当前为 2025-09-03 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name       BaiduPanFileList
// @namespace  http://greasyfork.icu/scripts/5128-baidupanfilelist/code/BaiduPanFileList.user.js
// @version    1.2.5
// @description  统计百度盘文件(夹)数量大小
// @match	https://www.baidu.com*
// @include	https://www.baidu.com*
// @match	https://www.baidu.com/*
// @include	https://www.baidu.com/*
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @run-at document-end
// @copyright  2014+, [email protected]
// ==/UserScript==

// %Path% = 文件路径
// %FileName% = 文件名
// %Tab% = Tab键
// %FileSize% = 可读文件大小(带单位保留两位小数,如:6.18 MiB)
// %FileSizeInBytes% = 文件大小字节数(为一个非负整数)
(function () {
    'use strict';

    let _BaiduPanFileList_Pattern = "%Path%%Tab%%FileSize%(%FileSizeInBytes% Bytes)";

    let url = document.URL;
    let BTN_WAITING_TEXT = "点击统计";
    let BTN_RUNNING_TEXT = "处理中...";
    let BASE_URL_API = "https://pan.baidu.com/api/list?channel=chunlei&clienttype=0&web=1&dir=";

    // 检查是否已存在按钮,避免重复创建
    if (document.getElementById('baidupanfilelist-5128-floating-action-button')) {
        return;
    }

    // 创建按钮元素
    const button = document.createElement('div');
    button.id = 'baidupanfilelist-5128-floating-action-button';
    button.innerHTML = BTN_WAITING_TEXT;

    // 创建提示框
    const tooltip = document.createElement('div');
    tooltip.id = 'floating-button-tooltip';
    tooltip.innerHTML = '点击统计当前文件夹<br/>按住CTRL点击统计子文件夹';

    // 按钮样式
    const buttonStyles = {
        position: 'fixed',
        right: '20px',
        top: '50%',
        transform: 'translateY(-50%)',
        width: '80px',
        height: '36px',
        borderRadius: '18px',
        backgroundColor: '#007bff',
        color: 'white',
        border: 'none',
        cursor: 'pointer',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: '12px',
        fontWeight: 'bold',
        boxShadow: '0 4px 12px rgba(0, 123, 255, 0.4)',
        zIndex: '10000',
        transition: 'background-color 0.2s ease, box-shadow 0.2s ease',
        userSelect: 'none',
        WebkitUserSelect: 'none',
        MozUserSelect: 'none',
        msUserSelect: 'none'
    };

    // 提示框样式
    const tooltipStyles = {
        position: 'fixed',
        backgroundColor: 'rgba(0, 0, 0, 0.8)',
        color: 'white',
        padding: '8px 12px',
        borderRadius: '6px',
        fontSize: '12px',
        lineHeight: '1.4',
        whiteSpace: 'nowrap',
        zIndex: '10001',
        opacity: '0',
        visibility: 'hidden',
        transition: 'all 0.3s ease',
        pointerEvents: 'none',
        transform: 'translateY(-50%)'
    };

    // 应用样式
    Object.assign(button.style, buttonStyles);
    Object.assign(tooltip.style, tooltipStyles);

    // 按钮状态
    let isProcessing = false;
    // 拖拽相关变量
    let isDragging = false;
    let hasMoved = false;
    let dragStartX, dragStartY;
    let buttonStartX, buttonStartY;
    let dragThreshold = 3; // 降低拖拽阈值,提高响应速度

    // 鼠标按下事件
    button.addEventListener('mousedown', function (e) {
        if (isProcessing) return; // 处理中不允许拖拽

        isDragging = true;
        hasMoved = false;
        dragStartX = e.clientX;
        dragStartY = e.clientY;

        const rect = button.getBoundingClientRect();
        buttonStartX = rect.left;
        buttonStartY = rect.top;

        button.style.cursor = 'grabbing';

        // 拖拽开始时隐藏提示框
        hideTooltip();

        e.preventDefault();
    });

    // 鼠标移动事件 - 优化为更流畅的拖拽
    document.addEventListener('mousemove', function (e) {
        if (!isDragging || isProcessing) return;

        const deltaX = e.clientX - dragStartX;
        const deltaY = e.clientY - dragStartY;

        // 降低拖拽阈值,提高响应速度
        if (Math.abs(deltaX) > dragThreshold || Math.abs(deltaY) > dragThreshold) {
            hasMoved = true;
        }

        const newX = buttonStartX + deltaX;
        const newY = buttonStartY + deltaY;

        // 限制按钮在视窗内
        const maxX = window.innerWidth - button.offsetWidth;
        const maxY = window.innerHeight - button.offsetHeight;

        const constrainedX = Math.max(0, Math.min(newX, maxX));
        const constrainedY = Math.max(0, Math.min(newY, maxY));

        // 使用 translate3d 进行硬件加速,提高性能
        button.style.left = constrainedX + 'px';
        button.style.top = constrainedY + 'px';
        button.style.right = 'auto';
        button.style.transform = 'translate3d(0, 0, 0)';

        e.preventDefault();
    });

    // 鼠标释放事件
    document.addEventListener('mouseup', function (e) {
        if (!isDragging) return;

        isDragging = false;
        button.style.cursor = isProcessing ? 'not-allowed' : 'pointer';

        // 如果没有移动,则触发点击事件
        if (!hasMoved && !isProcessing) {
            handleClick(e);
        }

        // 重置transform
        if (hasMoved) {
            button.style.transform = 'translate3d(0, 0, 0)';
        } else {
            button.style.transform = button.style.left ? 'translate3d(0, 0, 0)' : 'translateY(-50%)';
        }
    });

    // 点击处理函数 - 调用原有的文件统计功能
    async function handleClick(e) {
        if (isProcessing) return; // 防止重复点击

        // 检查是否按住了 Ctrl 键
        const includeSubDir = e && e.ctrlKey;

        try {
            lockButton();
            // 调用原有的文件统计功能
            showInfo(button, includeSubDir);
        } catch (error) {
            showError("处理失败,请刷新重试")
            unlockButton();
        }
    }

    // 悬停效果
    button.addEventListener('mouseenter', function () {
        if (!isDragging && !isProcessing) {
            button.style.transform = button.style.transform.includes('translateY') ?
                'translateY(-50%) scale(1.05)' : 'scale(1.05)';

            if (!isProcessing) {
                button.style.boxShadow = '0 6px 16px rgba(0, 123, 255, 0.6)';
            }

            // 显示提示框
            showTooltip();
        }
    });

    button.addEventListener('mouseleave', function () {
        if (!isDragging) {
            button.style.transform = button.style.transform.includes('translateY') ?
                'translateY(-50%)' : (button.style.left ? 'translate3d(0, 0, 0)' : 'none');

            if (!isProcessing) {
                button.style.boxShadow = '0 4px 12px rgba(0, 123, 255, 0.4)';
            }

            // 隐藏提示框
            hideTooltip();
        }
    });

    // 显示提示框
    function showTooltip() {
        const buttonRect = button.getBoundingClientRect();
        const tooltipWidth = 80; // 预估提示框宽度
        const tooltipHeight = 40; // 预估提示框高度

        // 计算按钮中心点
        const buttonCenterX = buttonRect.left + buttonRect.width / 2;
        const buttonCenterY = buttonRect.top + buttonRect.height / 2;

        // 计算屏幕中心点
        const screenCenterX = window.innerWidth / 2;
        const screenCenterY = window.innerHeight / 2;

        // 默认位置:按钮左侧
        let tooltipX = buttonRect.left - tooltipWidth - 10;
        let tooltipY = buttonCenterY - tooltipHeight / 2;

        // 判断按钮相对于屏幕中心的位置,调整提示框位置
        if (buttonCenterX > screenCenterX) {
            // 按钮在屏幕右侧,提示框显示在左侧
            tooltipX = buttonRect.left - tooltipWidth - 5;
        } else {
            // 按钮在屏幕左侧,提示框显示在右侧
            tooltipX = buttonRect.right + 5;
        }

        if (buttonCenterY > screenCenterY) {
            // 按钮在屏幕下方,提示框显示在上方
            tooltipY = buttonRect.top - tooltipHeight - 5;
        } else {
            // 按钮在屏幕上方,提示框显示在下方
            tooltipY = buttonRect.bottom + 5;
        }

        // 防止提示框超出屏幕边界
        if (tooltipX < 10) {
            tooltipX = 10;
        }
        if (tooltipX + tooltipWidth > window.innerWidth - 10) {
            tooltipX = window.innerWidth - tooltipWidth - 10;
        }
        if (tooltipY < 10) {
            tooltipY = 10;
        }
        if (tooltipY + tooltipHeight > window.innerHeight - 10) {
            tooltipY = window.innerHeight - tooltipHeight - 10;
        }

        // 应用位置
        tooltip.style.left = tooltipX + 'px';
        tooltip.style.top = tooltipY + 'px';
        tooltip.style.right = 'auto';
        tooltip.style.transform = 'none';
        tooltip.style.opacity = '1';
        tooltip.style.visibility = 'visible';
    }

    // 隐藏提示框
    function hideTooltip() {
        tooltip.style.opacity = '0';
        tooltip.style.visibility = 'hidden';
    }

    // 禁用右键菜单,防止 Ctrl+点击时弹出菜单
    button.addEventListener('contextmenu', function (e) {
        e.preventDefault();
        return false;
    });

    // 添加到页面
    document.body.appendChild(button);
    document.body.appendChild(tooltip);

    // 防止页面滚动时按钮位置错乱
    window.addEventListener('scroll', function () {
        if (!button.style.left) {
            // 如果按钮还在初始位置(右侧中间),保持fixed定位
            return;
        }
    });

    // 窗口大小改变时调整按钮位置
    window.addEventListener('resize', function () {
        if (button.style.left) {
            const rect = button.getBoundingClientRect();
            const maxX = window.innerWidth - button.offsetWidth;
            const maxY = window.innerHeight - button.offsetHeight;

            if (rect.left > maxX) {
                button.style.left = maxX + 'px';
            }
            if (rect.top > maxY) {
                button.style.top = maxY + 'px';
            }
        }
    });

    // 键盘快捷键, 确保在按钮添加失败时依旧可用
    document.addEventListener("keydown", function (e) {
        // 使用标准的事件对象,无需兼容性处理
        let key = e.key || e.code;

        // 检测 Q 键 (Q 或 q)
        if (key === 'q' || key === 'Q' || key === 'KeyQ') {
            if (e.ctrlKey) {
                showInfo(button, true);
            } else {
                showInfo(button, false);
            }
            // 阻止默认行为
            e.preventDefault();
        }
    }, false);

    // 处理按钮和快捷键
    function showInfo(button, includeSubDir) {
        if (true) {
            alert(includeSubDir);
            unlockButton();
            return;
        }

        url = document.URL;
        while (url.includes("%25")) {
            url = url.replace("%25", "%");
        }
        let listurl = BASE_URL_API;
        let folder_access_times = 0;
        let currentDir = "";

        let str_alert = "";
        let num_all_files = 0;
        let num_all_folder = 0;
        let num_jpg = 0;
        let num_original = 0;
        let name_all = [];
        let size_all = 0;
        // 百度api
        // http://pan.baidu.com/api/list?channel=chunlei&clienttype=0&web=1&num=100&page=1&dir=<PATH>&order=time&desc=1&showempty=0&_=1404279060517&bdstoken=9c11ad34c365fb633fc249d71982968f&app_id=250528
        // 测试url
        // http://pan.baidu.com/disk/home#dir/path=<PATH>
        // http://pan.baidu.com/disk/home#from=share_pan_logo&path=<PATH>
        // http://pan.baidu.com/disk/home#key=<KEY>
        // http://pan.baidu.com/disk/home#path=<PATH>
        // http://pan.baidu.com/disk/home
        // http://pan.baidu.com/disk/home#path=<PATH>&key=<KEY>
        if (!url.includes("path=")) {
            listurl += "%2F";
            currentDir = "/";
            getList(listurl);
        } else if (url.includes("path=")) {
            let path = url.substring(url.indexOf("path=") + 5);
            if (path.includes("&")) {
                path = path.substring(0, path.indexOf("&"));
            }
            listurl += path;
            currentDir = decodeURIComponent(path);
            getList(listurl);
        }

        // 请求数据
        function getList(url) {
            GM_xmlhttpRequest({
                method: 'GET', synchronous: false, url: url, timeout: 9999,
                onabort: function () {
                    showError(decodeURIComponent(url.replace(BASE_URL_API, "")) + "\n\n意外终止, 请刷新重试");
                }, onerror: function () {
                    showError(decodeURIComponent(url.replace(BASE_URL_API, "")) + "\n\n未知错误, 请刷新重试");
                }, ontimeout: function () {
                    showError(decodeURIComponent(url.replace(BASE_URL_API, "")) + "\n\n请求超时, 请刷新重试");
                }, onload: function (reText) {
                    let JSONobj = JSON.parse(reText.responseText);
                    if (JSONobj.errno !== 0) {
                        showError("读取目录: " + decodeURIComponent(url.replace(BASE_URL_API, "")) + "  失败, 错误码: " + JSONobj.errno);
                        return;
                    }
                    let size_list = JSONobj.list.length;
                    let curr_item = null;
                    for (let i = 0; i < size_list; i++) {
                        curr_item = JSONobj.list[i];
                        if (curr_item.isdir === 1) {
                            num_all_folder++;
                            name_all.push(curr_item.path);
                            if (includeSubDir) {
                                folder_access_times++;
                                getList(BASE_URL_API + encodeURIComponent(curr_item.path));
                            }
                        } else {
                            num_all_files++;
                            if (curr_item.server_filename.includes(" (JPG).zip")) {
                                num_jpg++;
                            } else if (curr_item.server_filename.includes(".zip")) {
                                num_original++;
                            }
                            size_all += curr_item.size;
                            if (typeof _BaiduPanFileList_Pattern === "string") {
                                name_all.push(_BaiduPanFileList_Pattern.replace("%FileName%", curr_item.server_filename).replace("%Path%", curr_item.path).replace("%FileSizeInBytes%", curr_item.size).replace("%Tab%", "\t").replace("%FileSize%", getReadableFileSizeString(curr_item.size)));
                            } else {
                                name_all.push(curr_item.path + "\t" + getReadableFileSizeString(curr_item.size) + "(" + curr_item.size + " Bytes)");
                            }
                        }
                    }
                    folder_access_times--;
                    if (folder_access_times + 1 === 0) {
                        let CTL = "\r\n";
                        str_alert = currentDir + CTL + CTL + "files: " + num_all_files + ", folders: " + num_all_folder + CTL + "xxx (JPG).zip: " + num_jpg + CTL + "xxx.zip: " + num_original + CTL + "size: " + getReadableFileSizeString(size_all) + "  (" + size_all.toLocaleString() + " Bytes)" + CTL;
                        GM_setClipboard(str_alert + CTL + CTL + name_all.sort().join("\r\n") + "\r\n");
                        alert(str_alert.replace(/\r\n/g, "\n"));
                        // 解锁悬浮按钮
                        unlockButton();
                    }
                }
            });
        }
    }
    // 错误提示
    function showError(info) {
        alert(info);
        unlockButton();
    }

    // 锁定按钮的方法
    function lockButton(){
        // 设置处理状态
        isProcessing = true;
        button.innerHTML = BTN_RUNNING_TEXT;
        button.style.backgroundColor = '#6c757d';
        button.style.cursor = 'not-allowed';
        button.style.boxShadow = '0 4px 12px rgba(108, 117, 125, 0.4)';
    }

    // 解锁按钮的方法
    function unlockButton() {
        isProcessing = false;
        button.innerHTML = BTN_WAITING_TEXT;
        button.style.backgroundColor = '#007bff';
        button.style.cursor = 'pointer';
        button.style.boxShadow = '0 4px 12px rgba(0, 123, 255, 0.4)';
    }

    // 转换可读文件大小
    function getReadableFileSizeString(fileSizeInBytes) {
        let i = 0;
        const byteUnits = [' Bytes', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB'];
        while (fileSizeInBytes >= 1024) {
            fileSizeInBytes = fileSizeInBytes / 1024;
            i++;
        }
        return fileSizeInBytes.toFixed(2) + byteUnits[i];
    }
})();