// ==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 // @downloadURL https://update.greasyfork.icu/scripts/558825/%E5%A4%B8%E5%85%8B%E7%BD%91%E7%9B%98%E7%9B%B4%E9%93%BE%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B.user.js // @updateURL https://update.greasyfork.icu/scripts/558825/%E5%A4%B8%E5%85%8B%E7%BD%91%E7%9B%98%E7%9B%B4%E9%93%BE%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B.meta.js // ==/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 `