统计百度盘文件(夹)数量大小
目前為
// ==UserScript== // @name BaiduPanFileList // @namespace https://greasyfork.org/scripts/5128-baidupanfilelist/code/BaiduPanFileList.user.js // @version 1.3.3 // @description 统计百度盘文件(夹)数量大小 // @match * // @include * // @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; 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 PREFIX_TO_COUNT = []; const SUFFIX_TO_COUNT = [' (JPG).zip', '.zip']; // 预过滤有效的前缀和后缀,避免重复计算 const VALID_PREFIXES = PREFIX_TO_COUNT.filter(prefix => prefix && prefix.trim().length > 0); const VALID_SUFFIXES = SUFFIX_TO_COUNT.filter(suffix => suffix && suffix.trim().length > 0); // 检查是否已存在按钮,避免重复创建 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+点击 统计包含子文件夹<br/>⌨️ 快捷键:Q / Ctrl+Q'; // 按钮样式 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) { 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 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(); // 动态获取提示框实际尺寸 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 () { 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) { // 检查焦点元素,避免在输入框等元素中触发 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') { lockButton(); if (e.ctrlKey) { showInfo(button, true); } else { showInfo(button, false); } // 阻止默认行为 e.preventDefault(); } }, false); // 处理按钮和快捷键 function showInfo(button, includeSubDir) { if (true) { let iconDemo = "📊 统计完成!\n\n"; iconDemo += "✅ 详细文件列表已复制到剪贴板\n"; iconDemo += "📁 点击统计当前文件夹\n"; iconDemo += "🔍 Ctrl+点击 统计包含子文件夹\n"; iconDemo += "⌨️ 快捷键:Q / Ctrl+Q\n"; iconDemo += "❌ 处理失败\n"; iconDemo += "💡 提示:直接点击按钮重试即可\n"; iconDemo += "⚠️ 网络请求被中断\n"; iconDemo += "⏰ 请求超时\n"; iconDemo += "🚫 API响应错误\n"; iconDemo += "\n包含子目录: " + includeSubDir; alert(iconDemo); // 等待2秒后解锁按钮 setTimeout(() => { unlockButton(); }, 2000); return; } // 是否处理错误 let isGetListHasError = false; if (isProcessing) { return; } url = document.URL; while (url.includes("%25")) { url = url.replace("%25", "%"); } let listUrl = BASE_URL_API; let folderAccessTimes = 0; 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); } // 请求数据 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) { folderAccessTimes++; getList(BASE_URL_API + encodeURIComponent(curr_item.path)); } } else { numOfAllFiles++; // 根据SUFFIX_TO_COUNT和PREFIX_TO_COUNT配置动态计数 let currItemServerFilename = curr_item.server_filename; // 前缀统计 for (let prefix of VALID_PREFIXES) { if (currItemServerFilename.startsWith(prefix)) { prefixCounts[prefix]++; break; // 匹配到第一个前缀就停止,避免重复计数 } } // 后缀统计 for (let suffix of VALID_SUFFIXES) { if (currItemServerFilename.endsWith(suffix)) { suffixCounts[suffix]++; break; // 匹配到第一个后缀就停止,避免重复计数 } } allFileSizeInBytes += curr_item.size; if (typeof _BaiduPanFileList_Pattern === "string") { allFilePath.push(_BaiduPanFileList_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)"); } } } folderAccessTimes--; if (folderAccessTimes + 1 === 0) { let 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 + "files: " + numOfAllFiles + ", folders: " + numOfAllFolder + CTL + "size: " + getReadableFileSizeString(allFileSizeInBytes) + " (" + allFileSizeInBytes.toLocaleString() + " Bytes)" + CTL+ prefixCountsStr + suffixCountsStr; GM_setClipboard(strAlert + CTL + CTL + allFilePath.sort().join("\r\n") + "\r\n"); alert("📊 统计完成!\n\n" + strAlert.replace(/\r\n/g, "\n") + "\n\n✅ 详细文件列表已复制到剪贴板"); // 解锁悬浮按钮 unlockButton(); } } }); } catch (error) { showError("🔧 GM_xmlhttpRequest 调用失败\n\n💡 提示:可能是API权限问题或者返回数据格式变更,请重试\n错误详情: " + error.message); } } // 错误提示 function showError(info) { isGetListHasError = true; 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 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]; } })();