// ==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];
}
})();