Greasy Fork

Greasy Fork is available in English.

夸克网盘直链下载助手

专治夸克网盘“分享页面”无法识别选中文件的问题。独创 Fiber Walker 技术,能够从复杂的 React 组件树中精准挖掘被选中的文件 ID,无需依赖页面 DOM 结构,稳定性极强。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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, '&quot;');
                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);
})();