// ==UserScript==
// @name 2026最新可用-百度网盘SVIP高速解析直链的不限速下载助手 | HcxBaiduDownload
// @namespace https://tampermonkey.net/
// @author huancaixi
// @version 1.0.5
// @description 百度网盘文件高速下载工具,自动处理文件重命名,支持小于150M文件快速下载
// @match https://pan.baidu.com/disk/main*
// @run-at document-idle
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_download
// @grant unsafeWindow
// @connect pan.baidu.com
// @connect baidupcs.com
// @connect fc-mp-29d4e8f2-0ccc-4944-8cc2-6d0cd0564714.next.bspapp.com
// @license MIT
// @icon data:image/jpg;base64,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
// @homepage https://greasyfork.org/zh-CN/scripts/559462-2026%E6%9C%80%E6%96%B0%E5%8F%AF%E7%94%A8-%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98svip%E9%AB%98%E9%80%9F%E8%A7%A3%E6%9E%90%E7%9B%B4%E9%93%BE%E7%9A%84%E4%B8%8D%E9%99%90%E9%80%9F%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B-hcxbaidudownload
// @downloadURL https://update.greasyfork.icu/scripts/559462/2026%E6%9C%80%E6%96%B0%E5%8F%AF%E7%94%A8-%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98SVIP%E9%AB%98%E9%80%9F%E8%A7%A3%E6%9E%90%E7%9B%B4%E9%93%BE%E7%9A%84%E4%B8%8D%E9%99%90%E9%80%9F%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B%20%7C%20HcxBaiduDownload.user.js
// @updateURL https://update.greasyfork.icu/scripts/559462/2026%E6%9C%80%E6%96%B0%E5%8F%AF%E7%94%A8-%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98SVIP%E9%AB%98%E9%80%9F%E8%A7%A3%E6%9E%90%E7%9B%B4%E9%93%BE%E7%9A%84%E4%B8%8D%E9%99%90%E9%80%9F%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B%20%7C%20HcxBaiduDownload.meta.js
// ==/UserScript==
(function () {
"use strict";
const CONFIG = {
DEBUG_MODE: false,
LOG_PREFIX: "[HcxBaiduDownload]",
FILE_PATTERN: /^.+\..+\.pdf$/i,
WAIT_TIME_AFTER_RENAME: 3000,
BUTTON_TEXT: "极速下载",
BUTTON_CLASS: "hcx-baidu-download-btn",
DOWNLOAD_COUNT_KEY: "hcx_download_count",
VERIFY_INTERVAL: 3,
QRCODE_URL: "https://huancaixi.vip/assets/image/wx_miniapp_pan.jpg",
};
const Logger = {
info: (...args) =>
CONFIG.DEBUG_MODE && console.log(CONFIG.LOG_PREFIX, ...args),
warn: (...args) =>
CONFIG.DEBUG_MODE && console.warn(CONFIG.LOG_PREFIX, ...args),
error: (...args) => console.error(CONFIG.LOG_PREFIX, ...args),
};
// ==================== DOM 工具类 ====================
class DomHelper {
static getFileListTable() {
const xpath =
"/html/body/div[1]/div[1]/div[2]/div[2]/div[2]/div[1]/div/div[2]/div[1]/div[2]/div/div/div/div[2]/table/tbody";
const result = document.evaluate(
xpath,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
);
return result.singleNodeValue;
}
static getFileNameFromRow(row) {
const linkElement = row.querySelector("td:nth-child(2) a[title]");
if (linkElement) {
return (
linkElement.getAttribute("title") ||
linkElement.textContent ||
""
).trim();
}
return "";
}
static getFileIdFromRow(row) {
const id = row.getAttribute("data-id") || row.getAttribute("node-key");
return id ? parseInt(id) : null;
}
static isFileRow(fileName) {
// 文件名包含扩展名(有点号),文件夹通常没有扩展名
return fileName.indexOf(".") >= 0;
}
static getLastCellFromRow(row) {
const cells = row.querySelectorAll("td");
return cells.length > 0 ? cells[cells.length - 1] : null;
}
}
// ==================== 路径工具类 ====================
class PathHelper {
static getCurrentDirectory() {
const hashMatch = location.hash.match(/[?&]path=([^&]+)/);
let path = hashMatch ? decodeURIComponent(hashMatch[1]) : "/";
if (!path.startsWith("/")) path = "/" + path;
if (path !== "/" && path.endsWith("/")) path = path.slice(0, -1);
return path;
}
static buildFullPath(fileName) {
const directory = this.getCurrentDirectory();
const cleanName = String(fileName || "").replace(/^\/+/, "");
return directory === "/" ? `/${cleanName}` : `${directory}/${cleanName}`;
}
}
// ==================== Token 管理类 ====================
class TokenManager {
static getBdsToken() {
try {
const win = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
if (win?.locals?.userInfo?.bdstoken) {
Logger.info("Token 获取成功:", win.locals.userInfo.bdstoken);
return win.locals.userInfo.bdstoken;
}
Logger.warn("无法获取 BDS Token");
return null;
} catch (err) {
Logger.error("获取 Token 异常:", err);
return null;
}
}
}
// ==================== API 请求类 ====================
class BaiduPanAPI {
static buildDownloadUrl(filePath) {
const url = new URL("https://pan.baidu.com/api/locatedownload");
const params = {
clienttype: "0",
app_id: "250528",
web: "1",
channel: "chunlei",
path: filePath,
origin: "pdf",
use: "1",
};
Object.entries(params).forEach(([key, value]) =>
url.searchParams.set(key, value)
);
const token = TokenManager.getBdsToken();
if (token) url.searchParams.set("bdstoken", token);
return url.toString();
}
static async renameFileOnServer(fileId, originalPath, newName) {
const url = new URL("https://pan.baidu.com/api/filemanager");
const params = {
async: "2",
onnest: "fail",
opera: "rename",
clienttype: "0",
app_id: "250528",
web: "1",
};
Object.entries(params).forEach(([key, value]) =>
url.searchParams.set(key, value)
);
const token = TokenManager.getBdsToken();
if (token) url.searchParams.set("bdstoken", token);
const fileList = JSON.stringify([
{
id: fileId,
path: originalPath,
newname: newName,
},
]);
Logger.info("重命名请求:", { fileId, originalPath, newName });
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: url.toString(),
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
Referer: "https://pan.baidu.com/",
"X-Requested-With": "XMLHttpRequest",
},
data: `filelist=${encodeURIComponent(fileList)}`,
responseType: "json",
anonymous: false,
onload: (response) => {
try {
const data =
response.response ?? JSON.parse(response.responseText || "{}");
Logger.info("重命名响应:", data);
if (data.errno === 0) {
resolve(data);
} else {
reject(
new Error(
`重命名失败 [${data.errno}]: ${
data.show_msg || data.errmsg || "未知错误"
}`
)
);
}
} catch (err) {
Logger.error("解析响应失败:", err);
reject(err);
}
},
onerror: (err) => {
Logger.error("请求失败:", err);
reject(err);
},
ontimeout: (err) => {
Logger.error("请求超时:", err);
reject(err);
},
});
});
}
static async getDownloadLink(filePath) {
const apiUrl = this.buildDownloadUrl(filePath);
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
responseType: "json",
anonymous: false,
headers: {
Referer: "https://pan.baidu.com/",
"X-Requested-With": "XMLHttpRequest",
},
onload: (response) => {
try {
const data =
response.response ?? JSON.parse(response.responseText || "{}");
Logger.info("下载链接响应:", data);
const dlink = this._extractDownloadLink(data);
if (dlink) {
resolve(dlink);
} else {
// 提示基础版无法下载大于150mb文件
const errorMsg =
"基础版无法下载大于150MB文件,请升级账户或选择较小的文件";
this._showErrorDialog(errorMsg);
reject(new Error(errorMsg));
}
} catch (err) {
reject(err);
}
},
onerror: reject,
ontimeout: reject,
});
});
}
static _extractDownloadLink(obj) {
if (!obj || typeof obj !== "object") return null;
if (typeof obj.dlink === "string" && obj.dlink) return obj.dlink;
if (obj.data?.dlink) return obj.data.dlink;
for (const key in obj) {
if (obj[key] && typeof obj[key] === "object") {
const result = this._extractDownloadLink(obj[key]);
if (result) return result;
}
}
return null;
}
static _showErrorDialog(message) {
// 创建遮罩层
const overlay = document.createElement("div");
overlay.className = "hcx-error-overlay";
// 创建错误提示弹窗
const modal = document.createElement("div");
modal.className = "hcx-error-modal";
modal.innerHTML = `
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
const confirmBtn = modal.querySelector(".hcx-error-confirm");
// 关闭弹窗函数
const closeModal = () => {
overlay.remove();
};
// 确定按钮
confirmBtn.addEventListener("click", () => {
closeModal();
});
// 点击遮罩层关闭
overlay.addEventListener("click", (e) => {
if (e.target === overlay) {
closeModal();
}
});
// 自动聚焦按钮
setTimeout(() => confirmBtn.focus(), 100);
}
}
// ==================== 下载管理类 ====================
class DownloadManager {
static async downloadFile(downloadUrl, fileName, onProgress) {
const saveName = fileName.replace(/\.pdf$/i, "");
try {
await this._downloadViaBlob(downloadUrl, saveName, onProgress);
Logger.info("下载完成:", saveName);
} catch (err) {
Logger.warn("Blob下载失败,尝试备用方案:", err);
try {
await this._downloadViaGM(downloadUrl, saveName);
} catch (err2) {
Logger.error("所有下载方式均失败:", err2);
throw err2;
}
}
}
static _downloadViaBlob(url, saveName, onProgress) {
return new Promise((resolve, reject) => {
Logger.info("使用 Blob 下载:", saveName);
GM_xmlhttpRequest({
method: "GET",
url: url,
responseType: "arraybuffer",
anonymous: false,
headers: { Referer: "https://pan.baidu.com/" },
onprogress: (event) => {
if (event.lengthComputable) {
const progress = event.loaded / event.total;
if (onProgress) onProgress(progress);
const progressPercent = (progress * 100).toFixed(1);
if (progressPercent % 10 < 0.2)
Logger.info(`下载进度: ${progressPercent}%`);
}
},
onload: (response) => {
try {
const contentType =
(response.responseHeaders || "")
.match(/content-type:\s*([^\r\n]+)/i)?.[1]
?.trim() || "application/octet-stream";
const buffer = response.response;
if (!buffer) throw new Error("响应数据为空");
const blob = new Blob([buffer], { type: contentType });
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = blobUrl;
link.download = saveName;
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(blobUrl), 60000);
resolve();
} catch (err) {
reject(err);
}
},
onerror: reject,
ontimeout: reject,
});
});
}
static _downloadViaGM(url, saveName) {
return new Promise((resolve, reject) => {
try {
GM_download({
url: url,
name: saveName,
headers: { Referer: "https://pan.baidu.com/" },
onload: () => {
Logger.info("GM_download 完成");
resolve();
},
onerror: (err) => {
Logger.warn("GM_download 失败:", err);
reject(err);
},
ontimeout: () => {
Logger.warn("GM_download 超时");
reject(new Error("下载超时"));
},
});
} catch (err) {
reject(err);
}
});
}
}
// ==================== 验证码管理类 ====================
class VerifyManager {
static getDownloadCount() {
const count = localStorage.getItem(CONFIG.DOWNLOAD_COUNT_KEY);
return count ? parseInt(count) : 0;
}
static incrementDownloadCount() {
const count = this.getDownloadCount() + 1;
localStorage.setItem(CONFIG.DOWNLOAD_COUNT_KEY, count.toString());
return count;
}
static resetDownloadCount() {
localStorage.setItem(CONFIG.DOWNLOAD_COUNT_KEY, "0");
}
static needsVerification() {
const count = this.getDownloadCount();
return count > 0 && count % 3 === 0;
}
static async verifyCode(code) {
return new Promise((resolve, reject) => {
const verifyUrl = `https://fc-mp-29d4e8f2-0ccc-4944-8cc2-6d0cd0564714.next.bspapp.com/api/system/checkPanCode?code=${encodeURIComponent(
code
)}`;
Logger.info("验证验证码:", code);
GM_xmlhttpRequest({
method: "GET",
url: verifyUrl,
responseType: "json",
timeout: 10000,
onload: (response) => {
try {
const data =
response.response ?? JSON.parse(response.responseText || "{}");
Logger.info("验证响应:", data);
if (data.errCode === 0) {
if (data.data === true) {
Logger.info("验证码正确");
resolve(true);
} else {
Logger.warn("验证码错误");
reject(new Error("验证码错误或已过期"));
}
} else {
reject(new Error(`验证失败: ${data.errMsg || "未知错误"}`));
}
} catch (err) {
Logger.error("解析验证响应失败:", err);
reject(err);
}
},
onerror: (err) => {
Logger.error("验证请求失败:", err);
reject(new Error("网络请求失败"));
},
ontimeout: () => {
Logger.error("验证请求超时");
reject(new Error("验证超时,请重试"));
},
});
});
}
static showVerifyModal() {
return new Promise((resolve, reject) => {
// 创建遮罩层
const overlay = document.createElement("div");
overlay.className = "hcx-verify-overlay";
// 创建弹窗
const modal = document.createElement("div");
modal.className = "hcx-verify-modal";
modal.innerHTML = `
为了支持开发者持续更新,请前往小程序观看广告后获取验证码
您已使用 ${this.getDownloadCount()} 次下载功能
扫码进入小程序
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
const input = modal.querySelector(".hcx-verify-input");
const submitBtn = modal.querySelector(".hcx-verify-submit");
const closeBtn = modal.querySelector(".hcx-verify-close");
const errorDiv = modal.querySelector(".hcx-verify-error");
// 关闭弹窗函数
const closeModal = () => {
overlay.remove();
};
// 显示错误提示
const showError = (message) => {
errorDiv.textContent = message;
errorDiv.style.display = "block";
input.style.borderColor = "#fcc";
};
// 隐藏错误提示
const hideError = () => {
errorDiv.style.display = "none";
input.style.borderColor = "#e0e0e0";
};
// 提交验证码
submitBtn.addEventListener("click", async () => {
const code = input.value.trim();
if (!code) {
showError("请输入验证码");
input.focus();
return;
}
hideError();
submitBtn.disabled = true;
const originalText = submitBtn.textContent;
submitBtn.textContent = "验证中...";
try {
await this.verifyCode(code);
Logger.info("验证通过");
this.resetDownloadCount();
closeModal();
resolve(true);
} catch (err) {
Logger.warn("验证失败:", err.message);
showError(err.message);
submitBtn.disabled = false;
submitBtn.textContent = originalText;
input.focus();
input.select();
}
});
// 按Enter键提交
input.addEventListener("keypress", (e) => {
if (e.key === "Enter" && !submitBtn.disabled) {
submitBtn.click();
}
});
// 输入时隐藏错误提示
input.addEventListener("input", hideError);
// 关闭按钮
closeBtn.addEventListener("click", () => {
closeModal();
reject(new Error("用户取消验证"));
});
// 点击遮罩层关闭
overlay.addEventListener("click", (e) => {
if (e.target === overlay) {
closeModal();
reject(new Error("用户取消验证"));
}
});
// 自动聚焦输入框
setTimeout(() => input.focus(), 100);
});
}
}
// ==================== 文件处理类 ====================
class FileProcessor {
static needsRename(fileName) {
return !CONFIG.FILE_PATTERN.test(fileName);
}
static async process(row, button, onProgress) {
const originalName = DomHelper.getFileNameFromRow(row);
if (!originalName) throw new Error("无法获取文件名");
let currentName = originalName;
let shouldRestore = false;
// 开始处理
if (onProgress) onProgress(0); // 0%
// 检查是否需要重命名
if (this.needsRename(currentName)) {
const fileId = DomHelper.getFileIdFromRow(row);
if (!fileId) throw new Error("无法获取文件ID");
const renamedName = `${currentName}.pdf`;
const originalPath = PathHelper.buildFullPath(currentName);
Logger.info(`准备重命名: ${currentName} -> ${renamedName}`);
if (onProgress) onProgress(0.05); // 5%
await BaiduPanAPI.renameFileOnServer(fileId, originalPath, renamedName);
currentName = renamedName;
shouldRestore = true;
if (onProgress) onProgress(0.1); // 10%
// 等待服务器更新
await this._sleep(CONFIG.WAIT_TIME_AFTER_RENAME);
}
if (onProgress) onProgress(0.15); // 15%
try {
// 获取下载链接
const filePath = PathHelper.buildFullPath(currentName);
const downloadUrl = await BaiduPanAPI.getDownloadLink(filePath);
if (onProgress) onProgress(0.2); // 20%
// 下载文件 (占80%的进度,从20%到100%)
// this._updateButtonText(button, '下载中........');
await DownloadManager.downloadFile(
downloadUrl,
currentName,
(downloadProgress) => {
// 下载进度占 20% - 100%
if (onProgress) onProgress(0.2 + downloadProgress * 0.8);
}
);
} catch (error) {}
// 下载完成后,如果需要恢复文件名
if (shouldRestore) {
const fileId = DomHelper.getFileIdFromRow(row);
const renamedPath = PathHelper.buildFullPath(currentName);
await BaiduPanAPI.renameFileOnServer(fileId, renamedPath, originalName);
Logger.info(`已恢复原文件名: ${currentName} -> ${originalName}`);
}
if (onProgress) onProgress(1); // 100%
}
static _updateButtonText(button, text) {
button.textContent = text;
}
static _sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
// ==================== 按钮管理类 ====================
class ButtonManager {
static createDownloadButton() {
const button = document.createElement("button");
button.type = "button";
button.className = CONFIG.BUTTON_CLASS;
// 创建按钮内部结构
button.innerHTML = `
${CONFIG.BUTTON_TEXT}
0%
`;
return button;
}
static updateProgress(button, percent) {
// 更新进度条
const translateValue = -100 + percent;
button.style.setProperty("--progress-x", `${translateValue}%`);
// 更新进度文本
const progressText = button.querySelector(".hcx-btn-progress-text");
if (progressText) {
progressText.textContent = `${Math.round(percent)}%`;
progressText.style.opacity = percent > 0 ? "1" : "0";
}
// 更新按钮文本(可选)
const btnText = button.querySelector(".hcx-btn-text");
if (btnText && percent > 0 && percent < 100) {
if (percent < 20) {
btnText.textContent = "准备中...";
} else if (percent < 100) {
btnText.textContent = "下载中...";
}
}
}
static resetProgress(button) {
button.style.setProperty("--progress-x", "-100%");
const progressText = button.querySelector(".hcx-btn-progress-text");
if (progressText) {
progressText.textContent = "0%";
progressText.style.opacity = "0";
}
const btnText = button.querySelector(".hcx-btn-text");
if (btnText) {
btnText.textContent = CONFIG.BUTTON_TEXT;
}
}
static async handleClick(row, button) {
try {
// 检查是否需要验证
if (VerifyManager.needsVerification()) {
try {
const btnText = button.querySelector(".hcx-btn-text");
if (btnText) btnText.textContent = "需要验证...";
await VerifyManager.showVerifyModal();
Logger.info("验证通过,继续下载");
} catch (err) {
Logger.warn("验证取消:", err.message);
this.resetProgress(button);
return;
}
}
button.disabled = true;
await FileProcessor.process(row, button, (progress) => {
// 直接使用 FileProcessor 返回的进度 (0 - 1)
this.updateProgress(button, progress * 100);
});
// 完成:100%
this.updateProgress(button, 100);
// 显示完成状态
const btnText = button.querySelector(".hcx-btn-text");
if (btnText) btnText.textContent = "✓ 完成";
// 增加下载次数
VerifyManager.incrementDownloadCount();
button.disabled = false;
// 重置进度条
setTimeout(() => this.resetProgress(button), 1500);
} catch (err) {
Logger.error("处理失败:", err);
const btnText = button.querySelector(".hcx-btn-text");
if (btnText) btnText.textContent = "✗ 失败,重试";
this.resetProgress(button);
setTimeout(() => {
this.resetProgress(button);
button.disabled = false;
}, 2000);
}
}
static injectButton(row, index) {
// 避免重复注入
if (row.querySelector(`.${CONFIG.BUTTON_CLASS}`)) return;
const fileName = DomHelper.getFileNameFromRow(row);
if (!fileName) return;
// 只为文件添加按钮(文件名包含扩展名)
if (!DomHelper.isFileRow(fileName)) {
return;
}
const targetCell = DomHelper.getLastCellFromRow(row);
if (!targetCell) return;
const button = this.createDownloadButton();
button.addEventListener("click", (event) => {
event.stopPropagation();
this.handleClick(row, button);
});
targetCell.appendChild(button);
Logger.info(`已注入按钮 #${index}:`, fileName);
}
}
// ==================== 页面监控类 ====================
class PageMonitor {
static injectAllButtons() {
const tableBody = DomHelper.getFileListTable();
if (!tableBody) return;
const rows = tableBody.querySelectorAll(":scope > tr");
Logger.info(`扫描文件列表,共 ${rows.length} 行`);
rows.forEach((row, index) => {
ButtonManager.injectButton(row, index);
});
}
static startMonitoring() {
// 初始注入
this.injectAllButtons();
// 创建防抖函数
const debounce = (fn, delay = 50) => {
let timer = null;
return () => {
if (timer) return;
timer = setTimeout(() => {
timer = null;
fn();
}, delay);
};
};
// 监听 DOM 变化
const observer = new MutationObserver(
debounce(() => this.injectAllButtons(), 50)
);
observer.observe(document.documentElement || document.body, {
childList: true,
subtree: true,
});
// 监听历史记录变化
const originalPushState = history.pushState;
history.pushState = function (...args) {
const result = originalPushState.apply(this, args);
setTimeout(() => PageMonitor.injectAllButtons(), 100);
return result;
};
window.addEventListener("popstate", () => {
setTimeout(() => PageMonitor.injectAllButtons(), 100);
});
}
}
// ==================== 样式注入 ====================
// 直接在 DOM 中插入 CSS link 标签
function injectStyles() {
const cssUrl = 'https://huancaixi.vip/assets/css/baidu_download.css';
// 创建 link 元素
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = cssUrl;
link.type = 'text/css';
link.onerror = () => {
Logger.warn('外部CSS加载失败,使用备用样式');
};
// 插入到 head 中
document.head.appendChild(link);
Logger.info('CSS link 已插入到 DOM');
}
// ==================== 启动脚本 ====================
Logger.info("脚本已加载,开始监控页面...");
// 注入样式
injectStyles();
// 启动页面监控
PageMonitor.startMonitoring();
})();