diff --git a/src/app/api/openlist/play/route.ts b/src/app/api/openlist/play/route.ts index 9952c16..a7b756f 100644 --- a/src/app/api/openlist/play/route.ts +++ b/src/app/api/openlist/play/route.ts @@ -168,11 +168,18 @@ export async function GET(request: NextRequest) { throw new Error('未找到已完成的播放链接'); } - // 如果指定了 format=json,返回 JSON 格式 + // 如果指定了 format=json,尝试解析到最终直链后再返回 JSON if (format === 'json') { + const resolvedQualities = await Promise.all( + qualities.map(async (quality: any) => ({ + ...quality, + url: await getFinalUrl(quality.url), + })) + ); + return NextResponse.json({ - url: qualities[0].url, - qualities + url: resolvedQualities[0].url, + qualities: resolvedQualities, }); } diff --git a/src/app/api/quark/instant-play/route.ts b/src/app/api/quark/instant-play/route.ts index 5a281bc..75c2407 100644 --- a/src/app/api/quark/instant-play/route.ts +++ b/src/app/api/quark/instant-play/route.ts @@ -50,6 +50,26 @@ export async function POST(request: NextRequest) { result.folderName ); + if ( + config.OpenListConfig?.Enabled && + config.OpenListConfig.URL && + config.OpenListConfig.Username && + config.OpenListConfig.Password + ) { + try { + const { OpenListClient } = await import('@/lib/openlist.client'); + const openListClient = new OpenListClient( + config.OpenListConfig.URL, + config.OpenListConfig.Username, + config.OpenListConfig.Password + ); + await openListClient.refreshDirectory(quarkConfig.OpenListTempPath || '/'); + await openListClient.refreshDirectory(openlistFolderPath); + } catch (refreshError) { + console.warn('[quark instant-play] 刷新 OpenList 临时目录失败:', refreshError); + } + } + return NextResponse.json({ success: true, source: 'quark-temp', diff --git a/src/app/api/source-detail/route.ts b/src/app/api/source-detail/route.ts index ebed2ec..e0688c5 100644 --- a/src/app/api/source-detail/route.ts +++ b/src/app/api/source-detail/route.ts @@ -307,6 +307,26 @@ export async function GET(request: NextRequest) { const videoExtensions = ['.mp4', '.mkv', '.avi', '.m3u8', '.flv', '.ts', '.mov', '.wmv', '.webm', '.rmvb', '.rm', '.mpg', '.mpeg', '.3gp', '.f4v', '.m4v', '.vob']; + const listTempDirectory = async (currentPath: string, page: number, pageSize: number) => { + const load = async (refresh = false) => client.listDirectory(currentPath, page, pageSize, refresh); + + let response = await load(page === 1); + if (response.code === 200) { + return response; + } + + const parentPath = currentPath.substring(0, currentPath.lastIndexOf('/')) || '/'; + await client.refreshDirectory(parentPath); + response = await load(true); + + if (response.code !== 200) { + const message = response.message || '目录不存在或 OpenList 路径未映射'; + throw new Error(`读取临时目录失败: ${message}(路径: ${currentPath})`); + } + + return response; + }; + const collectFiles = async (currentPath: string): Promise> => { const allFiles: Array<{ path: string; name: string }> = []; let currentPage = 1; @@ -314,10 +334,7 @@ export async function GET(request: NextRequest) { let hasMore = true; while (hasMore) { - const response = await client.listDirectory(currentPath, currentPage, pageSize); - if (response.code !== 200) { - throw new Error('读取临时目录失败'); - } + const response = await listTempDirectory(currentPath, currentPage, pageSize); for (const item of response.data.content) { const itemPath = `${currentPath}${currentPath.endsWith('/') ? '' : '/'}${item.name}`; diff --git a/src/lib/quark.client.ts b/src/lib/quark.client.ts index 4e2dc0c..6adef65 100644 --- a/src/lib/quark.client.ts +++ b/src/lib/quark.client.ts @@ -22,6 +22,8 @@ export interface QuarkTransferTaskResult { fileCount: number; targetPath: string; folderName?: string; + skipped?: boolean; + reused?: boolean; } const VIDEO_EXTENSIONS = [ @@ -208,12 +210,14 @@ async function fetchShareFolderItems( async function fetchDriveFolderItems( cookie: string, - pdirFid = '0' + pdirFid = '0', + page = 1, + size = 200 ): Promise { const query = new URLSearchParams({ pdir_fid: pdirFid, - _page: '1', - _size: '200', + _page: String(page), + _size: String(size), _sort: 'file_type:asc,file_name:asc', }); @@ -230,6 +234,45 @@ async function fetchDriveFolderItems( return data?.data?.list || []; } +async function fetchAllDriveFolderItems( + cookie: string, + pdirFid = '0' +): Promise { + const allItems: any[] = []; + const pageSize = 200; + + for (let page = 1; page < 100; page += 1) { + const items = await fetchDriveFolderItems(cookie, pdirFid, page, pageSize); + allItems.push(...items); + + if (items.length < pageSize) { + break; + } + } + + return allItems; +} + +function getDriveItemName(item: any): string { + return String(item?.file_name || item?.name || ''); +} + +function buildInstantPlayFolderName(pwdId: string, title?: string) { + const baseName = sanitizeFolderName(title || 'quark-temp') || 'quark-temp'; + return `${baseName}_${pwdId}`.slice(0, 120); +} + +async function findDirectoryByName( + cookie: string, + parentFid: string, + folderName: string +): Promise { + const items = await fetchAllDriveFolderItems(cookie, parentFid); + return items.find( + (item: any) => Boolean(item.dir || item.is_dir) && getDriveItemName(item) === folderName + ) || null; +} + export async function validateQuarkCookieReadable(cookie: string): Promise { const safeCookie = assertQuarkCookieHeaderSafe(cookie); await fetchDriveFolderItems(safeCookie, '0'); @@ -421,7 +464,19 @@ export async function transferQuarkShare( const { stoken } = await fetchShareToken(safeCookie, share); const topLevelItems = await fetchShareFolderItems(safeCookie, share.pwdId, stoken, '0'); const target = await ensureQuarkDrivePath(safeCookie, input.savePath); - const taskId = await submitSaveTask(safeCookie, share, stoken, target.fid, topLevelItems); + const existedItems = await fetchAllDriveFolderItems(safeCookie, target.fid); + const existedNames = new Set(existedItems.map((item: any) => getDriveItemName(item))); + const pendingItems = topLevelItems.filter((item) => !existedNames.has(item.fileName)); + + if (pendingItems.length === 0) { + return { + fileCount: 0, + targetPath: target.path, + skipped: true, + }; + } + + const taskId = await submitSaveTask(safeCookie, share, stoken, target.fid, pendingItems); if (taskId) { await pollTask(safeCookie, taskId); @@ -429,7 +484,7 @@ export async function transferQuarkShare( return { taskId, - fileCount: topLevelItems.length, + fileCount: pendingItems.length, targetPath: target.path, }; } @@ -454,7 +509,18 @@ export async function createQuarkInstantPlayFolder( } const tempRoot = await ensureQuarkDrivePath(safeCookie, input.playTempSavePath); - const folderName = `${sanitizeFolderName(input.title || shareTitle || 'quark-temp')}_${Date.now()}`; + const folderName = buildInstantPlayFolderName(share.pwdId, input.title || shareTitle); + const existedFolder = await findDirectoryByName(safeCookie, tempRoot.fid, folderName); + + if (existedFolder) { + return { + fileCount: videoItems.length, + targetPath: joinPath(tempRoot.path, folderName), + folderName, + reused: true, + }; + } + const folderFid = await createDriveFolder(safeCookie, tempRoot.fid, folderName); const taskId = await submitSaveTask(safeCookie, share, stoken, folderFid, videoItems); @@ -462,10 +528,12 @@ export async function createQuarkInstantPlayFolder( await pollTask(safeCookie, taskId); } + const targetPath = joinPath(tempRoot.path, folderName); + return { taskId, fileCount: videoItems.length, - targetPath: joinPath(tempRoot.path, folderName), + targetPath, folderName, }; }