// @ts-nocheck import * as quark_api from "./quark-api"; /** * 分享模块 — 分享链接解析、转存任务、创建分享链接。 */ const BASE_URL = 'https://drive-pc.quark.cn'; // ==================== Acquire Stoken ==================== /** * Acquire stoken for a share link (needed for detail/save). */ export async function acquireStoken(cookie, pwdId) { for (let attempt = 0; attempt < 3; attempt++) { try { const params = new URLSearchParams(quark_api.getCommonParams()); const resp = await fetch(`${BASE_URL}/1/clouddrive/share/sharepage/token?${params.toString()}`, { method: 'POST', headers: { ...quark_api.getHeaders(cookie), 'Content-Type': 'application/json' }, body: JSON.stringify({ pwd_id: pwdId, passcode: '' }), signal: AbortSignal.timeout(10000), }); if (!resp.ok) { if (attempt < 2) continue; return null; } const data = await resp.json(); if (data.status === 200 && data.data?.stoken) { return data.data.stoken; } return null; } catch { if (attempt >= 2) return null; await new Promise(r => setTimeout(r, 500 * (attempt + 1))); } } return null; } // ==================== Get Share Files ==================== /** * Fetch detail at a given pdir_fid within a share. */ export async function getDetailAt(cookie, pwdId, stoken, pdirFid) { const params = new URLSearchParams({ ...quark_api.getCommonParams(), pwd_id: pwdId, stoken, pdir_fid: pdirFid, force: '0', _page: '1', _size: '50', _fetch_banner: '0', _fetch_share: '1', _fetch_total: '1', _sort: 'file_type:asc,updated_at:desc', ver: '2', fetch_share_full_path: '0', }); const resp = await fetch(`${BASE_URL}/1/clouddrive/share/sharepage/detail?${params.toString()}`, { headers: quark_api.getHeaders(cookie), signal: AbortSignal.timeout(15000) }); if (!resp.ok) return []; const data = await resp.json(); if (data.status !== 200) return []; return (data.data?.list || []).filter((f) => f.fid).map((f) => ({ fid: f.fid, file_name: f.file_name, share_fid_token: f.share_fid_token || '', dir: f.dir || false, size: f.size || 0, })); } /** * Recursively collect files from a share. * If the share contains a single directory, drill into it to list contents * but still save the directory itself. */ export async function getShareFiles(cookie, pwdId, stoken) { try { const topLevel = await getDetailAt(cookie, pwdId, stoken, '0'); if (!topLevel || topLevel.length === 0) return null; // If the share is a single directory, we save the directory itself // and fetch its contents for renaming later if (topLevel.length === 1 && topLevel[0].dir) { const innerFiles = await getDetailAt(cookie, pwdId, stoken, topLevel[0].fid); return { files: topLevel, topDir: true, childFiles: innerFiles || [], }; } // Multiple top-level items: save them directly return { files: topLevel, topDir: false, }; } catch { return null; } } // ==================== Save Files (share → cloud) ==================== /** * Save shared files to the user's cloud directory. */ export async function saveFiles(cookie, pwdId, stoken, fids, fidTokens, toPdirFid) { try { const resp = await fetch(`${BASE_URL}/1/clouddrive/share/sharepage/save?${quark_api.makeQuery()}`, { method: 'POST', headers: { ...quark_api.getHeaders(cookie), 'Content-Type': 'application/json' }, body: JSON.stringify({ fid_list: fids, fid_token_list: fidTokens, to_pdir_fid: toPdirFid, pwd_id: pwdId, stoken, pdir_fid: '0', scene: 'link', }), signal: AbortSignal.timeout(30000), }); const data = await resp.json(); if (data.status === 200 && data.data?.task_id) { return { success: true, message: 'Save task created', taskId: data.data.task_id }; } return { success: false, message: data.message === 'require login [guest]' ? '夸克网盘 Cookie 已过期,请在后台重新配置 Cookie' : (data.message || `API 返回错误 (status=${data.status}, code=${data.code})`), }; } catch (err) { return { success: false, message: err.message || 'Network error' }; } } // ==================== Wait for Save Task ==================== /** * Poll task status until complete or timeout. * Returns the saved file FIDs (save_as_top_fids). */ export async function waitForTask(cookie, taskId, timeoutMs) { const start = Date.now(); let retryIndex = 0; while (Date.now() - start < timeoutMs) { try { const params = new URLSearchParams({ ...quark_api.getCommonParams(), uc_param_str: '', task_id: taskId, retry_index: String(retryIndex), __dt: String(Math.floor(Math.random() * 240000 + 60000)), __t: String(Date.now() / 1000), }); const resp = await fetch(`${BASE_URL}/1/clouddrive/task?${params.toString()}`, { headers: quark_api.getHeaders(cookie), signal: AbortSignal.timeout(10000) }); const data = await resp.json(); if (data.status === 200) { if (data.data?.status === 2) { // Task completed const savedFids = data.data?.save_as?.save_as_top_fids || []; return savedFids; } // Still in progress retryIndex++; } } catch { // Network error, retry } await new Promise(r => setTimeout(r, 1000)); } return null; // Timeout } // ==================== Rename File ==================== /** * Rename a file by its FID. */ export async function renameFile(cookie, fid, newName) { try { const resp = await fetch(`${BASE_URL}/1/clouddrive/file/rename?${quark_api.makeQuery()}`, { method: 'POST', headers: { ...quark_api.getHeaders(cookie), 'Content-Type': 'application/json' }, body: JSON.stringify({ fid, file_name: newName }), signal: AbortSignal.timeout(10000), }); const data = await resp.json(); return data.status === 200 || data.code === 0; } catch { return false; } } // ==================== Create Share Link ==================== /** * Create a share link for a file/folder. * Flow: create task → poll for share_id → submit to get short URL. */ export async function createShareLink(cookie, fileId) { try { const sharePwd = quark_api.randomSharePwd(); // Try different share_type values (1=7天, 0=无限制) const shareTypes = ['1', '0']; let lastError = ''; for (const st of shareTypes) { await quark_api.humanDelay(); // Step 1: Create share task - get task_id const response = await fetch(`${BASE_URL}/1/clouddrive/share?${quark_api.makeQuery()}`, { method: 'POST', headers: { ...quark_api.getHeaders(cookie), 'Content-Type': 'application/json' }, body: JSON.stringify({ fid_list: [fileId], share_type: st, url_type: '1', share_pwd: sharePwd, }), signal: AbortSignal.timeout(15000), }); const data = await response.json(); const taskId = data.data?.task_id; if (!taskId) { lastError = data.message || `share_type=${st} 失败`; console.error('[Quark] Create share task failed (type=%s):', st, data.message || JSON.stringify(data).slice(0, 200)); continue; } // Step 2: Poll task until complete const result = await waitForShareTask(cookie, taskId, 20000); if (!result?.shareId) { lastError = result?.message || '任务超时'; console.error('[Quark] Wait for share task failed (type=%s):', st, result?.message || 'unknown'); continue; } // Step 3: Submit share via /password endpoint const shareUrl = await submitShare(cookie, result.shareId, sharePwd); if (shareUrl) { return { success: true, shareUrl, sharePwd, message: `分享链接已生成(密码:${sharePwd})`, }; } lastError = '提交密码后未获取到短链接'; } return { success: false, message: lastError || '🤷 各种姿势都试过了,就是分享不出来…' }; } catch (err) { console.error('[Quark] createShareLink error:', err.message); return { success: false, message: err.message || '🌩️ 网络开小差了,再试试?' }; } } /** * Submit share via /password endpoint to get the actual short URL. */ async function submitShare(cookie, shareId, sharePwd) { try { const response = await fetch(`${BASE_URL}/1/clouddrive/share/password?${quark_api.makeQuery()}`, { method: 'POST', headers: { ...quark_api.getHeaders(cookie), 'Content-Type': 'application/json' }, body: JSON.stringify({ share_id: shareId, share_pwd: sharePwd || '' }), signal: AbortSignal.timeout(15000), }); const data = await response.json(); if (data.status === 200 && data.data?.share_url) { console.log('[Quark] Share short URL:', data.data.share_url); return data.data.share_url; } console.log('[Quark] /password response:', JSON.stringify(data).slice(0, 300)); console.error('[Quark] /password FAIL status=%s msg=%s', data.status, data.message || ''); return null; } catch (err) { console.log('[Quark] /password error:', err); return null; } } /** * Poll share task until complete and extract share URL/shortcode. */ async function waitForShareTask(cookie, taskId, timeoutMs) { const start = Date.now(); let retryIndex = 0; while (Date.now() - start < timeoutMs) { try { const params = new URLSearchParams({ ...quark_api.getCommonParams(), uc_param_str: '', task_id: taskId, retry_index: String(retryIndex), __dt: String(Math.floor(Math.random() * 240000 + 60000)), __t: String(Date.now() / 1000), }); const resp = await fetch(`${BASE_URL}/1/clouddrive/task?${params.toString()}`, { headers: quark_api.getHeaders(cookie), signal: AbortSignal.timeout(10000) }); const data = await resp.json(); if (data.data?.status === 2) { // Task completed — try multiple extraction approaches // 1. Direct share_url field if (data.data?.share_url) { const match = data.data.share.match(/\/s\/([a-zA-Z0-9]+)/); if (match) return { shareId: match[1] }; } // 2. Nested share object if (data.data?.share?.url) { const match = data.data.share.url.match(/\/s\/([a-zA-Z0-9]+)/); if (match) return { shareId: match[1] }; } if (data.data?.share?.short_url) { const match = data.data.share.short.match(/\/s\/([a-zA-Z0-9]+)/); if (match) return { shareId: match[1] }; } // 3. share_id — validate it's a reasonable short code (8-20 chars, not UUID-like) const shareId = data.data?.share_id; if (shareId && shareId.length <= 20 && shareId.length >= 8) { return { shareId }; } // 4. Regex search through the full response for a URL pattern const str = JSON.stringify(data); const urlMatch = str.match(/https?:\/\/pan\.quark\.cn\/s\/([a-zA-Z0-9]{6,16})/); if (urlMatch) { return { shareId: urlMatch[1] }; } // 5. Extract from any URL field in the response const urlFields = ['url', 'link', 'share_url', 'short_url', 'share_link']; for (const field of urlFields) { const val = data.data?.[field] || data.data?.share?.[field]; if (typeof val === 'string' && val.includes('pan.quark.cn/s/')) { const m = val.match(/\/s\/([a-zA-Z0-9]+)/); if (m) return { shareId: m[1] }; } } // 6. Log full share task response for debugging console.log('[Quark] Full share task response:', JSON.stringify(data, null, 2).slice(0, 2000)); // 7. Even if shareId is UUID-like (32 hex chars), use it anyway as last resort if (shareId) { return { shareId }; } return { message: 'Share task completed but no share URL found' }; } if (data.data?.status === 3) { return { message: data.message || 'Share task failed' }; } retryIndex++; } catch { // Retry } await new Promise(r => setTimeout(r, 1000)); } return { message: 'Share task timed out' }; }