Greasy Fork is available in English.
Generate direct links for selected files on Quark Drive share pages.
// ==UserScript==
// @name Quark Direct Link Helper
// @namespace Quark-Direct-Link-Helper
// @version 1.8.6
// @description Generate direct links for selected files on Quark Drive share pages.
// @author Mustafa Hakan
// @license MIT
// @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 THEME = {
primary: '#a7c7e7',
primaryDark: '#8fb3d9',
surface: '#f8fbff',
surface2: '#eef5fb',
text: '#2f3a4a',
muted: '#6f7b8a',
border: '#dbe7f3',
success: '#b8d8ba',
danger: '#f2b8b5'
};
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 || 'Unnamed file',
isDir: candidate.dir === true || candidate.is_dir === true || candidate.type === 'folder',
size: candidate.size || 0,
download_url: candidate.download_url || ''
};
}
fiber = fiber.return;
attempts += 1;
}
return null;
},
post(url, data) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url,
headers: {
'Content-Type': 'application/json',
'User-Agent': CONFIG.UA
},
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) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${units[i]}`;
},
generateBatchLinks(files) {
return files.map(f => f.download_url).filter(Boolean).join('\n');
},
buildCurl(file) {
return `curl -L -C - "${file.download_url}" -o "${file.file_name}" -A "${CONFIG.UA}"`;
},
toast(message, type = 'info') {
const div = document.createElement('div');
div.textContent = message;
div.style.cssText = [
'position:fixed',
'top:18px',
'left:50%',
'transform:translateX(-50%)',
'z-index:2147483647',
'padding:10px 14px',
'border-radius:12px',
'font:14px/1.4 Arial, sans-serif',
'color:#2f3a4a',
'background:' + (type === 'error' ? THEME.danger : type === 'success' ? THEME.success : THEME.surface),
'border:1px solid ' + THEME.border,
'box-shadow:0 8px 24px rgba(47,58,74,.12)'
].join(';');
document.body.appendChild(div);
setTimeout(() => {
div.style.opacity = '0';
div.style.transition = 'opacity .25s ease';
setTimeout(() => div.remove(), 260);
}, 1800);
},
copy(text) {
if (typeof GM_setClipboard === 'function') {
GM_setClipboard(text);
} else {
navigator.clipboard.writeText(text);
}
}
};
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());
},
async run() {
const btn = document.getElementById('quark-helper-btn');
const originalText = btn.innerText;
try {
let files = App.getSelectedFiles().filter(f => !f.isDir);
if (files.length === 0) {
Utils.toast('Please select at least one file.', 'error');
return;
}
btn.innerText = 'Processing';
btn.style.background = THEME.primaryDark;
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(`Parsing failed: ${res?.message || 'Unknown error'}`, 'error');
}
} catch (e) {
console.error(e);
Utils.toast('Request failed. Please check your network.', 'error');
} finally {
btn.innerText = originalText;
btn.style.background = THEME.primary;
}
}
};
const UI = {
createFloatButton() {
if (document.getElementById('quark-helper-btn')) return;
const btn = document.createElement('button');
btn.id = 'quark-helper-btn';
btn.innerText = 'Download Helper';
btn.style.cssText = [
'position:fixed',
'top:40%',
'left:12px',
'z-index:2147483647',
'background:' + THEME.primary,
'color:' + THEME.text,
'font-size:14px',
'font-weight:600',
'padding:12px 18px',
'border:1px solid ' + THEME.border,
'border-radius:999px',
'cursor:pointer',
'box-shadow:0 8px 20px rgba(47,58,74,.10)',
'transition:all .2s ease',
'user-select:none'
].join(';');
btn.onclick = App.run;
btn.onmouseenter = () => {
btn.style.transform = 'scale(1.03)';
};
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(47,58,74,.28)',
'z-index:2147483648',
'display:flex',
'align-items:center',
'justify-content:center'
].join(';');
const allLinks = Utils.generateBatchLinks(data);
const contentHTML = data.map(f => {
const curl = Utils.buildCurl(f);
const safeCurl = curl.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '"');
return `
<div style="background:${THEME.surface};padding:12px;margin-bottom:10px;border-radius:12px;border:1px solid ${THEME.border};display:flex;justify-content:space-between;align-items:center;gap:12px;">
<div style="overflow:hidden;flex:1;">
<div style="font-weight:600;color:${THEME.text};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="${f.file_name}">${f.file_name}</div>
<div style="font-size:12px;color:${THEME.muted};margin-top:4px;">${Utils.formatSize(f.size)}</div>
</div>
<div style="display:flex;gap:8px;flex-shrink:0;">
<a href="${f.download_url}" target="_blank" style="padding:6px 10px;background:${THEME.success};color:${THEME.text};text-decoration:none;border-radius:10px;font-size:12px;border:1px solid ${THEME.border};">IDM</a>
<button class="quark-copy-curl" data-curl="${safeCurl}" style="padding:6px 10px;background:${THEME.primary};color:${THEME.text};border:1px solid ${THEME.border};border-radius:10px;cursor:pointer;font-size:12px;">cURL</button>
</div>
</div>
`;
}).join('');
modal.innerHTML = `
<div style="background:#fff;width:680px;max-width:92%;max-height:86%;border-radius:18px;box-shadow:0 20px 50px rgba(47,58,74,.18);display:flex;flex-direction:column;overflow:hidden;font-family:Arial,sans-serif;">
<div style="padding:16px 20px;border-bottom:1px solid ${THEME.border};display:flex;justify-content:space-between;align-items:center;background:${THEME.surface};">
<h3 style="margin:0;color:${THEME.text};font-size:16px;">Parsing complete (${data.length} files)</h3>
<button id="quark-close-modal" style="border:0;background:transparent;color:${THEME.muted};font-size:18px;cursor:pointer;">Close</button>
</div>
<div style="padding:12px 20px;background:${THEME.surface2};border-bottom:1px solid ${THEME.border};display:flex;justify-content:space-between;align-items:center;gap:12px;">
<span style="color:${THEME.muted};font-size:12px;">IDM User Agent: <b>${CONFIG.UA}</b></span>
<button id="quark-batch-copy" style="padding:8px 14px;background:${THEME.primary};color:${THEME.text};border:1px solid ${THEME.border};border-radius:12px;cursor:pointer;font-size:13px;font-weight:600;">Copy all links</button>
</div>
<div style="padding:18px;overflow-y:auto;flex:1;background:#fff;">${contentHTML}</div>
</div>
`;
document.body.appendChild(modal);
document.getElementById('quark-close-modal').onclick = () => modal.remove();
document.getElementById('quark-batch-copy').onclick = () => {
GM_setClipboard(allLinks);
Utils.toast('All links copied.', 'success');
};
modal.querySelectorAll('.quark-copy-curl').forEach(btn => {
btn.onclick = e => {
const curl = e.target.getAttribute('data-curl');
GM_setClipboard(curl);
Utils.toast('cURL copied.', 'success');
};
});
}
};
setTimeout(() => {
UI.createFloatButton();
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
setTimeout(() => UI.createFloatButton(), 800);
}
}).observe(document, { subtree: true, childList: true });
}, 800);
})();