Greasy Fork is available in English.
专治夸克网盘“分享页面”无法识别选中文件的问题。独创 Fiber Walker 技术,能够从复杂的 React 组件树中精准挖掘被选中的文件 ID,无需依赖页面 DOM 结构,稳定性极强。
// ==UserScript==
// @name 夸克网盘直链下载助手
// @namespace Quark-Direct-Link-Helper
// @version 1.6.2
// @description 专治夸克网盘“分享页面”无法识别选中文件的问题。独创 Fiber Walker 技术,能够从复杂的 React 组件树中精准挖掘被选中的文件 ID,无需依赖页面 DOM 结构,稳定性极强。
// @author okhsjjsji
// @license MIT
// @icon https://pan.quark.cn/favicon.ico
// @match *://pan.quark.cn/*
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant unsafeWindow
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const CONFIG = {
API: "https://drive.quark.cn/1/clouddrive/file/download?pr=ucpro&fr=pc",
UA: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch",
DEPTH: 25
};
const Utils = {
getFidFromFiber: (dom) => {
const key = Object.keys(dom).find(k => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
if (!key) return null;
let fiber = dom[key];
let attempts = 0;
while (fiber && attempts < CONFIG.DEPTH) {
const props = fiber.memoizedProps || fiber.pendingProps;
const candidate = props?.record || props?.file || props?.item || props?.data;
if (candidate && (candidate.fid || candidate.id)) {
return {
fid: candidate.fid || candidate.id,
name: candidate.file_name || candidate.name || candidate.title || "未命名文件",
isDir: candidate.dir === true || candidate.is_dir === true || candidate.type === 'folder',
size: candidate.size
};
}
fiber = fiber.return;
attempts++;
}
return null;
},
post: (url, data, headers) => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST", url, headers, data: JSON.stringify(data), responseType: 'json',
onload: res => resolve(res.response),
onerror: err => reject(err)
});
});
},
formatSize: (bytes) => {
if (bytes === 0) return '0 B';
const k = 1024, i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
}
};
const App = {
getSelectedFiles: () => {
const selectedFiles = new Map();
const checkBoxes = document.querySelectorAll('.ant-checkbox-checked, .ant-checkbox-wrapper-checked');
checkBoxes.forEach(box => {
if (box.closest('.ant-table-thead')) return;
const fileData = Utils.getFidFromFiber(box);
if (fileData && fileData.fid) selectedFiles.set(fileData.fid, fileData);
});
return Array.from(selectedFiles.values());
},
run: async () => {
const btn = document.getElementById('quark-helper-btn');
const originalText = btn.innerText;
try {
let files = App.getSelectedFiles();
const folderCount = files.filter(f => f.isDir).length;
files = files.filter(f => !f.isDir);
if (files.length === 0) {
alert(`❌ 未检测到选中文件!${folderCount > 0 ? '\n(暂不支持文件夹下载)' : '\n请先勾选文件,或刷新页面重试。'}`);
return;
}
btn.innerText = "⏳ 解析中...";
const res = await Utils.post(CONFIG.API, { fids: files.map(f => f.fid) }, {
"User-Agent": CONFIG.UA, "Content-Type": "application/json"
});
if (res && res.code === 0) {
UI.showResultWindow(res.data);
} else {
alert(`❌ 解析失败 (Code: ${res?.code})`);
}
} catch(e) {
console.error(e);
alert("❌ 网络请求错误");
} finally {
btn.innerText = originalText;
}
},
init: () => {
UI.createFloatButton();
}
};
const UI = {
createFloatButton: () => {
if (document.getElementById('quark-helper-btn')) return;
const btn = document.createElement('button');
btn.id = 'quark-helper-btn';
btn.innerText = '⚡️ 下载助手';
btn.style.cssText = `position:fixed;top:40%;left:10px;z-index:2147483647;background:linear-gradient(135deg,#ff4d4f,#d9363e);color:white;font-size:14px;font-weight:bold;padding:12px 20px;border:2px solid rgba(255,255,255,0.8);border-radius:50px;cursor:pointer;box-shadow:0 4px 15px rgba(255,77,79,0.4);transition:transform 0.2s;user-select:none;`;
btn.onclick = App.run;
btn.onmouseenter = () => btn.style.transform = "scale(1.05)";
btn.onmouseleave = () => btn.style.transform = "scale(1)";
document.body.appendChild(btn);
},
showResultWindow: (data) => {
const old = document.getElementById('quark-result-modal');
if(old) old.remove();
const modal = document.createElement('div');
modal.id = 'quark-result-modal';
modal.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:2147483648;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(3px);`;
const contentHTML = data.map(f => {
const curl = `curl -L -C - "${f.download_url}" -o "${f.file_name}" -A "${CONFIG.UA}" -e "https://pan.quark.cn/" -b "${document.cookie}"`;
const safeCurl = curl.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '"');
return `<div style="background:#f9f9f9;padding:15px;margin-bottom:10px;border-radius:8px;border-left:4px solid #0d53ff;"><div style="font-weight:bold;color:#333;margin-bottom:8px;word-break:break-all;">📄 ${f.file_name} <span style="font-size:12px;color:#999;">(${Utils.formatSize(f.size)})</span></div><div style="display:flex;gap:10px;"><a href="${f.download_url}" target="_blank" style="padding:6px 12px;background:#55af28;color:white;text-decoration:none;border-radius:4px;font-size:13px;display:flex;align-items:center;">⬇️ IDM 下载</a><button onclick="navigator.clipboard.writeText('${safeCurl}').then(()=>alert('✅ cURL 已复制'))" style="padding:6px 12px;background:#333;color:white;border:none;border-radius:4px;cursor:pointer;font-size:13px;">📋 复制 cURL</button></div></div>`;
}).join('');
modal.innerHTML = `<div style="background:white;width:600px;max-width:90%;max-height:80%;border-radius:12px;box-shadow:0 10px 30px rgba(0,0,0,0.3);display:flex;flex-direction:column;overflow:hidden;font-family:sans-serif;"><div style="padding:15px 20px;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center;"><h3 style="margin:0;color:#0d53ff;">🎉 解析成功 (${data.length})</h3><span onclick="document.getElementById('quark-result-modal').remove()" style="cursor:pointer;font-size:20px;color:#999;padding:0 5px;">✕</span></div><div style="padding:15px;background:#e6f7ff;color:#0050b3;font-size:13px;">💡 推荐使用 IDM/NDM 下载。若浏览器直接下载慢,请复制 cURL。</div><div style="padding:20px;overflow-y:auto;flex:1;">${contentHTML}</div></div>`;
document.body.appendChild(modal);
}
};
setInterval(App.init, 1000);
})();