// ==UserScript== // @name BaiduPanFileList // @namespace https://greasyfork.org/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+, icgeass@hotmail.com // @downloadURL none // ==/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 = '点击统计当前文件夹
按住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=&order=time&desc=1&showempty=0&_=1404279060517&bdstoken=9c11ad34c365fb633fc249d71982968f&app_id=250528 // 测试url // http://pan.baidu.com/disk/home#dir/path= // http://pan.baidu.com/disk/home#from=share_pan_logo&path= // http://pan.baidu.com/disk/home#key= // http://pan.baidu.com/disk/home#path= // http://pan.baidu.com/disk/home // http://pan.baidu.com/disk/home#path=&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]; } })();