您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
统计百度盘文件(夹)数量大小
// ==UserScript== // @name BaiduPanFileList // @namespace https://greasyfork.org/zh-CN/scripts/5128-baidupanfilelist // @version 2.0.014 // @description 统计百度盘文件(夹)数量大小 // @match https://pan.baidu.com* // @include https://pan.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'; // 配置指定前缀和后缀数量统计 const PREFIX_TO_COUNT = ['', '']; const SUFFIX_TO_COUNT = ['', '']; const RANDOM_BUTTON_COLOR = true; const BAIDU_PAN_FILE_LIST_PATTERN = "%Path%%Tab%%FileSize%(%FileSizeInBytes% Bytes)"; const BUTTON_BACKGROUND_COLOR = [ '#007BFF', '#0ABAB5', '#50C878', '#FF7F50', '#D4A017', '#7B1FA2', '#FF69B4', '#228B22', '#948DD6', '#FF8C00', '#C71585', '#EF4444' ]; const BTN_WAITING_TEXT = "统计文件夹"; const BTN_RUNNING_TEXT = "处理中"; const BASE_URL_API = "https://pan.baidu.com/api/list?channel=chunlei&clienttype=0&web=1&dir="; // 预过滤有效的前缀和后缀,避免重复计算,并转为小写 const VALID_PREFIXES = PREFIX_TO_COUNT.filter(prefix => prefix && prefix.trim().length > 0).map(prefix => prefix.toLowerCase()); const VALID_SUFFIXES = SUFFIX_TO_COUNT.filter(suffix => suffix && suffix.trim().length > 0).map(suffix => suffix.toLowerCase()); // 按钮颜色 - 从预设颜色中随机选择 const buttonColorHex = RANDOM_BUTTON_COLOR ? BUTTON_BACKGROUND_COLOR[Math.floor(Math.random() * BUTTON_BACKGROUND_COLOR.length)] : BUTTON_BACKGROUND_COLOR[0]; // 将十六进制颜色转换为RGB function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } const buttonColorRgb = hexToRgb(buttonColorHex); const buttonColorRgba = `rgba(${buttonColorRgb.r}, ${buttonColorRgb.g}, ${buttonColorRgb.b}, 0.4)`; const buttonColorRgbaHover = `rgba(${buttonColorRgb.r}, ${buttonColorRgb.g}, ${buttonColorRgb.b}, 0.6)`; // 检查是否已存在按钮,避免重复创建 if (document.getElementById('baidupanfilelist-5128-floating-action-button')) { return; } // 检查是否在顶级窗口中,如果不是则退出(避免在iframe中重复创建) if (window !== window.top) { 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+点击 统计包含子文件夹<br/>⌨️ 快捷键:Q / Ctrl+Q'; // 按钮样式 const buttonStyles = { position: 'fixed', right: '20px', top: '50%', transform: 'translateY(-50%)', width: 'auto', minWidth: '80px', height: '36px', borderRadius: '18px', backgroundColor: buttonColorHex, color: 'white', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '0 12px', fontSize: '12px', fontWeight: 'bold', boxShadow: `0 4px 12px ${buttonColorRgba}`, 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 { // 调用原有的文件统计功能 showInfo(button, includeSubDir); } catch (error) { alert("❌ 处理失败\n\n💡 提示:直接点击按钮重试即可,无需刷新页面"); 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 ${buttonColorRgbaHover}`; } // 显示提示框 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 ${buttonColorRgba}`; } // 隐藏提示框 hideTooltip(); } }); // 显示提示框 function showTooltip() { const buttonRect = button.getBoundingClientRect(); // 动态获取提示框实际尺寸 tooltip.style.visibility = 'hidden'; tooltip.style.opacity = '1'; const tooltipRect = tooltip.getBoundingClientRect(); const tooltipWidth = tooltipRect.width || 160; // 提供默认值 const tooltipHeight = tooltipRect.height || 50; tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; // 计算按钮中心点 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 () { const rect = button.getBoundingClientRect(); const maxX = window.innerWidth - button.offsetWidth - 20; // 保持20px边距 const maxY = window.innerHeight - button.offsetHeight; // 如果按钮被挤出右边界,调整到安全位置 if (rect.right > window.innerWidth - 20) { if (button.style.left) { // 拖拽后的按钮 button.style.left = Math.max(20, maxX) + 'px'; } else { // 初始位置的按钮,切换到left定位 button.style.right = 'auto'; button.style.left = Math.max(20, maxX) + 'px'; } } // 垂直位置保护 if (rect.bottom > window.innerHeight) { button.style.top = Math.max(20, maxY) + 'px'; } }); // 键盘快捷键, 确保在按钮添加失败时依旧可用 document.addEventListener("keydown", function (e) { // 检查焦点元素,避免在输入框等元素中触发 const activeElement = document.activeElement; const isInputElement = activeElement && ( activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.contentEditable === 'true' ); // 如果焦点在输入元素上,不处理快捷键 if (isInputElement) { return; } // 使用标准的事件对象,无需兼容性处理 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) { // 是否处理错误 let isGetListHasError = false; if (isProcessing) { return; } lockButton(); // 记录开始时间 const startTime = Date.now(); let url = document.URL; while (url.includes("%25")) { url = url.replace("%25", "%"); } let listUrl = BASE_URL_API; let currentDir = ""; let strAlert = ""; let numOfAllFiles = 0; let numOfAllFolder = 0; let prefixCounts = {}; let suffixCounts = {}; // 根据预过滤的配置初始化计数器 VALID_PREFIXES.forEach(prefix => { prefixCounts[prefix] = 0; }); VALID_SUFFIXES.forEach(suffix => { suffixCounts[suffix] = 0; }); let allFilePath = []; let allFileSizeInBytes = 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); } let currNumOfAccessFolder = 1; // 请求数据 function getList(url) { if (isGetListHasError) { return; } try { GM_xmlhttpRequest({ method: 'GET', synchronous: false, url: url, timeout: 9999, onabort: function () { showError("⚠️ 网络请求被中断\n\n💡 提示:直接点击按钮重试即可"); }, onerror: function () { showError("❌ 网络请求失败\n\n💡 提示:请检查网络连接后重试"); }, ontimeout: function () { showError("⏰ 请求超时\n\n💡 提示:网络较慢,请稍后重试"); }, onload: function (reText) { let JSONObj = {}; try { JSONObj = JSON.parse(reText.responseText); if (JSONObj.errno !== 0) { showError("🚫 API响应错误\n\n错误码: " + JSONObj.errno + "\n💡 提示:可能是权限问题"); return; } } catch (parseError) { showError("📄 数据解析失败\n\n💡 提示:服务器返回的数据格式异常\n错误详情: " + parseError.message); 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) { numOfAllFolder++; allFilePath.push(curr_item.path); if (includeSubDir) { currNumOfAccessFolder++; getList(BASE_URL_API + encodeURIComponent(curr_item.path)); } } else { numOfAllFiles++; setButtonText(BTN_RUNNING_TEXT + "(" + numOfAllFiles + ")"); // 根据SUFFIX_TO_COUNT和PREFIX_TO_COUNT配置动态计数 let currItemServerFilename = curr_item.server_filename; // 前缀统计 for (let prefix of VALID_PREFIXES) { if (currItemServerFilename.toLowerCase().startsWith(prefix)) { prefixCounts[prefix]++; break; // 匹配到第一个前缀就停止,避免重复计数 } } // 后缀统计 for (let suffix of VALID_SUFFIXES) { if (currItemServerFilename.toLowerCase().endsWith(suffix)) { suffixCounts[suffix]++; break; // 匹配到第一个后缀就停止,避免重复计数 } } allFileSizeInBytes += curr_item.size; if (typeof BAIDU_PAN_FILE_LIST_PATTERN === "string") { allFilePath.push(BAIDU_PAN_FILE_LIST_PATTERN.replace("%FileName%", currItemServerFilename).replace("%Path%", curr_item.path).replace("%FileSizeInBytes%", curr_item.size).replace("%Tab%", "\t").replace("%FileSize%", getReadableFileSizeString(curr_item.size))); } else { allFilePath.push(curr_item.path + "\t" + getReadableFileSizeString(curr_item.size) + "(" + curr_item.size + " Bytes)"); } } } currNumOfAccessFolder--; if (currNumOfAccessFolder === 0) { const CTL = "\r\n"; let prefixCountsStr = ""; let suffixCountsStr = ""; // 按预过滤的顺序显示各前缀计数 VALID_PREFIXES.forEach(prefix => { prefixCountsStr += prefix + ": " + prefixCounts[prefix] + CTL; }); // 按预过滤的顺序显示各后缀计数 VALID_SUFFIXES.forEach(suffix => { suffixCountsStr += suffix + ": " + suffixCounts[suffix] + CTL; }); strAlert = currentDir + CTL + CTL + "文件夹数量: " + numOfAllFolder + ", 文件数量: " + numOfAllFiles + CTL + "大小: " + getReadableFileSizeString(allFileSizeInBytes) + " (" + allFileSizeInBytes.toLocaleString() + " Bytes)" + CTL + prefixCountsStr + suffixCountsStr; GM_setClipboard(strAlert + CTL + CTL + allFilePath.sort().join("\r\n") + "\r\n"); // 计算耗时 let durationSecondsStr = ((Date.now() - startTime) / 1000).toFixed(2); window.setTimeout(() => { alert("📊 统计完成" + (includeSubDir ? "(含子文件夹)" : "(仅当前文件夹)") + "!耗时 " + durationSecondsStr + " 秒\n\n" + strAlert.replace(/\r\n/g, "\n") + "\n\n✅ 详细文件列表已复制到剪贴板"); // 解锁悬浮按钮 unlockButton(); }, 0); } } }); } catch (error) { showError("🔧 GM_xmlhttpRequest 调用失败\n\n💡 提示:可能是API权限问题或者返回数据格式变更,请重试\n错误详情: " + error.message); } } // 错误提示 function showError(info) { isGetListHasError = true; alert(info); unlockButton(); } } // 锁定按钮的方法 function lockButton() { // 设置处理状态 isProcessing = true; setButtonText(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; setButtonText(BTN_WAITING_TEXT); button.style.backgroundColor = buttonColorHex; button.style.cursor = 'pointer'; button.style.boxShadow = `0 4px 12px ${buttonColorRgba}`; } // 解锁按钮的方法 function setButtonText(text) { button.innerHTML = text; button.style.width = 'auto'; void button.offsetWidth; } // 转换可读文件大小 function getReadableFileSizeString(fileSizeInBytes) { let size = fileSizeInBytes; // 使用局部变量,避免修改参数 let i = 0; const byteUnits = [' Bytes', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']; while (size >= 1024) { size = size / 1024; i++; } return size.toFixed(2) + byteUnits[i]; } })();