NOTICE: By continued use of this site you understand and agree to the binding Hizmet Şartları and Gizlilik Politikası.
// ==UserScript==
// @name 夸克网盘直链下载助手
// @namespace Quark-Direct-Link-Helper
// @version 1.6.6
// @description 解除夸克网盘分享页面的下载限制。支持批量导出直链,支持IDM/NDM,修复了 UA 和 Cookie 冲突问题。
// @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) => {
if (!dom) return null;
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 || props?.node;
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 || 0,
download_url: candidate.download_url
};
}
fiber = fiber.return;
attempts++;
}
return null;
},
post: (url, data) => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: url,
headers: {
"Content-Type": "application/json",
"User-Agent": CONFIG.UA,
"Cookie": document.cookie
},
data: JSON.stringify(data),
responseType: 'json',
withCredentials: true,
onload: res => {
if (res.status === 200) {
resolve(res.response);
} else {
reject(res);
}
},
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];
},
generateBatchLinks: (files) => {
return files.map(f => f.download_url).join('\n');
},
toast: (msg, type = 'success') => {
const div = document.createElement('div');
div.innerText = msg;
div.style.cssText = `
position: fixed; top: 10%; left: 50%; transform: translateX(-50%);
background: ${type === 'error' ? 'rgba(255, 77, 79, 0.9)' : 'rgba(0, 0, 0, 0.7)'};
color: white; padding: 10px 20px; border-radius: 4px; z-index: 9999999;
font-size: 14px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: opacity 0.3s;
`;
document.body.appendChild(div);
setTimeout(() => {
div.style.opacity = '0';
setTimeout(() => div.remove(), 300);
}, 2000);
}
};
const App = {
getSelectedFiles: () => {
const selectedFiles = new Map();
const checkBoxes = document.querySelectorAll('.ant-checkbox-wrapper-checked:not(.ant-checkbox-group-item), .file-item-selected, [aria-checked="true"]');
const targets = checkBoxes.length > 0 ? checkBoxes : document.querySelectorAll('.ant-checkbox-checked');
targets.forEach(box => {
if (box.closest('.ant-table-thead') || box.closest('.list-head')) 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();
files = files.filter(f => !f.isDir);
if (files.length === 0) {
Utils.toast('请先勾选需要下载的文件', 'error');
return;
}
btn.innerText = "⏳ 处理中...";
btn.style.background = "#666";
const res = await Utils.post(CONFIG.API, { fids: files.map(f => f.fid) });
if (res && res.code === 0) {
UI.showResultWindow(res.data);
} else {
Utils.toast(`解析失败: ${res?.message || '未知错误'}`, 'error');
}
} catch(e) {
console.error(e);
Utils.toast('网络请求失败,请检查网络', 'error');
} finally {
btn.innerText = originalText;
btn.style.background = "linear-gradient(135deg,#ff4d4f,#d9363e)";
}
},
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:all 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 allLinks = Utils.generateBatchLinks(data);
const contentHTML = data.map(f => {
const ua = CONFIG.UA;
const curl = `curl -L -C - "${f.download_url}" -o "${f.file_name}" -A "${ua}" -b "${document.cookie}"`;
const safeCurl = curl.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '"');
return `
<div style="background:#f9f9f9;padding:12px;margin-bottom:8px;border-radius:6px;border-left:4px solid #0d53ff;display:flex;justify-content:space-between;align-items:center;">
<div style="overflow:hidden;flex:1;margin-right:10px;">
<div style="font-weight:bold;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="${f.file_name}">📄 ${f.file_name}</div>
<div style="font-size:12px;color:#999;">${Utils.formatSize(f.size)}</div>
</div>
<div style="display:flex;gap:5px;flex-shrink:0;">
<a href="${f.download_url}" target="_blank" style="padding:5px 10px;background:#55af28;color:white;text-decoration:none;border-radius:4px;font-size:12px;">⬇️ IDM</a>
<button class="quark-copy-curl" data-curl="${safeCurl}" style="padding:5px 10px;background:#333;color:white;border:none;border-radius:4px;cursor:pointer;font-size:12px;">📋 cURL</button>
</div>
</div>`;
}).join('');
modal.innerHTML = `
<div style="background:white;width:650px;max-width:90%;max-height:85%;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;background:#fff;">
<h3 style="margin:0;color:#0d53ff;">🎉 解析成功 (${data.length}个文件)</h3>
<span onclick="document.getElementById('quark-result-modal').remove()" style="cursor:pointer;font-size:24px;color:#999;line-height:1;">×</span>
</div>
<div style="padding:10px 20px;background:#f0f7ff;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center;">
<span style="color:#0050b3;font-size:12px;">IDM UA: <b>quark-cloud-drive/2.5.20</b></span>
<button id="quark-batch-copy" style="padding:6px 15px;background:#0d53ff;color:white;border:none;border-radius:4px;cursor:pointer;font-size:13px;font-weight:bold;box-shadow:0 2px 5px rgba(13,83,255,0.3);">
📦 复制全部链接
</button>
</div>
<div style="padding:20px;overflow-y:auto;flex:1;">${contentHTML}</div>
</div>`;
document.body.appendChild(modal);
document.getElementById('quark-batch-copy').onclick = () => {
GM_setClipboard(allLinks);
Utils.toast('✅ 已复制全部链接');
};
modal.querySelectorAll('.quark-copy-curl').forEach(btn => {
btn.onclick = (e) => {
const curl = e.target.getAttribute('data-curl');
GM_setClipboard(curl);
Utils.toast('✅ cURL 命令已复制');
};
});
}
};
setTimeout(() => {
App.init();
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
setTimeout(App.init, 1000);
}
}).observe(document, {subtree: true, childList: true});
}, 1000);
})();