quark.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. import req from './req.js';
  2. import {ENV} from './env.js';
  3. import COOKIE from './cookieManager.js';
  4. import '../libs_drpy/crypto-js.js';
  5. import {join} from 'path';
  6. import fs from 'fs';
  7. import {PassThrough} from 'stream';
  8. class QuarkHandler {
  9. constructor() {
  10. this.regex = /https:\/\/pan\.quark\.cn\/s\/([^\\|#/]+)/;
  11. this.pr = 'pr=ucpro&fr=pc';
  12. this.baseHeader = {
  13. 'User-Agent': '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',
  14. Referer: 'https://pan.quark.cn',
  15. };
  16. this.apiUrl = 'https://drive.quark.cn/1/clouddrive/';
  17. this.shareTokenCache = {};
  18. this.saveDirName = 'drpy';
  19. this.saveDirId = null;
  20. this.saveFileIdCaches = {};
  21. this.currentUrlKey = '';
  22. this.cacheRoot = (process.env['NODE_PATH'] || '.') + '/quark_cache';
  23. this.maxCache = 1024 * 1024 * 100;
  24. this.urlHeadCache = {};
  25. this.subtitleExts = ['.srt', '.ass', '.scc', '.stl', '.ttml'];
  26. }
  27. // 使用 getter 定义动态属性
  28. get cookie() {
  29. // console.log('env.cookie.quark:',ENV.get('quark_cookie'));
  30. return ENV.get('quark_cookie');
  31. }
  32. getShareData(url) {
  33. let matches = this.regex.exec(url);
  34. if (matches.indexOf("?") > 0) {
  35. matches = matches.split('?')[0];
  36. }
  37. if (matches) {
  38. return {
  39. shareId: matches[1],
  40. folderId: '0',
  41. };
  42. }
  43. return null;
  44. }
  45. async initQuark(db, cfg) {
  46. if (this.cookie) {
  47. console.log("cookie 获取成功");
  48. } else {
  49. console.log("cookie 获取失败")
  50. }
  51. }
  52. lcs(str1, str2) {
  53. if (!str1 || !str2) {
  54. return {
  55. length: 0,
  56. sequence: '',
  57. offset: 0,
  58. };
  59. }
  60. var sequence = '';
  61. var str1Length = str1.length;
  62. var str2Length = str2.length;
  63. var num = new Array(str1Length);
  64. var maxlen = 0;
  65. var lastSubsBegin = 0;
  66. for (var i = 0; i < str1Length; i++) {
  67. var subArray = new Array(str2Length);
  68. for (var j = 0; j < str2Length; j++) {
  69. subArray[j] = 0;
  70. }
  71. num[i] = subArray;
  72. }
  73. var thisSubsBegin = null;
  74. for (i = 0; i < str1Length; i++) {
  75. for (j = 0; j < str2Length; j++) {
  76. if (str1[i] !== str2[j]) {
  77. num[i][j] = 0;
  78. } else {
  79. if (i === 0 || j === 0) {
  80. num[i][j] = 1;
  81. } else {
  82. num[i][j] = 1 + num[i - 1][j - 1];
  83. }
  84. if (num[i][j] > maxlen) {
  85. maxlen = num[i][j];
  86. thisSubsBegin = i - num[i][j] + 1;
  87. if (lastSubsBegin === thisSubsBegin) {
  88. sequence += str1[i];
  89. } else {
  90. lastSubsBegin = thisSubsBegin;
  91. sequence = ''; // clear it
  92. sequence += str1.substr(lastSubsBegin, i + 1 - lastSubsBegin);
  93. }
  94. }
  95. }
  96. }
  97. }
  98. return {
  99. length: maxlen,
  100. sequence: sequence,
  101. offset: thisSubsBegin,
  102. };
  103. }
  104. findBestLCS(mainItem, targetItems) {
  105. const results = [];
  106. let bestMatchIndex = 0;
  107. for (let i = 0; i < targetItems.length; i++) {
  108. const currentLCS = this.lcs(mainItem.name, targetItems[i].name);
  109. results.push({target: targetItems[i], lcs: currentLCS});
  110. if (currentLCS.length > results[bestMatchIndex].lcs.length) {
  111. bestMatchIndex = i;
  112. }
  113. }
  114. const bestMatch = results[bestMatchIndex];
  115. return {allLCS: results, bestMatch: bestMatch, bestMatchIndex: bestMatchIndex};
  116. }
  117. delay(ms) {
  118. return new Promise((resolve) => setTimeout(resolve, ms));
  119. }
  120. async api(url, data, headers, method, retry) {
  121. let cookie = this.cookie || '';
  122. headers = headers || {};
  123. Object.assign(headers, this.baseHeader);
  124. Object.assign(headers, {
  125. Cookie: cookie
  126. });
  127. method = method || 'post';
  128. const resp =
  129. method === 'get' ? await req.get(`${this.apiUrl}/${url}`, {
  130. headers: headers,
  131. }).catch((err) => {
  132. console.error(err.message);
  133. return err.response || {status: 500, data: {}};
  134. }) : await req.post(`${this.apiUrl}/${url}`, data, {
  135. headers: headers,
  136. }).catch((err) => {
  137. console.error(err.message);
  138. return err.response || {status: 500, data: {}};
  139. });
  140. const leftRetry = retry || 3;
  141. if (resp.headers['set-cookie']) {
  142. const puus = resp.headers['set-cookie'].join(';;;').match(/__puus=([^;]+)/);
  143. if (puus) {
  144. if (cookie.match(/__puus=([^;]+)/)[1] !== puus[1]) {
  145. cookie = cookie.replace(/__puus=[^;]+/, `__puus=${puus[1]}`);
  146. console.log('[quark] api:更新cookie:', cookie);
  147. ENV.set('quark_cookie', cookie);
  148. }
  149. }
  150. }
  151. if (resp.status === 429 && leftRetry > 0) {
  152. await this.delay(1000);
  153. return await this.api(url, data, headers, method, leftRetry - 1);
  154. }
  155. return resp.data || {};
  156. }
  157. async clearSaveDir() {
  158. const listData = await this.api(`file/sort?${this.pr}&pdir_fid=${this.saveDirId}&_page=1&_size=200&_sort=file_type:asc,updated_at:desc`, {}, {}, 'get');
  159. if (listData.data && listData.data.list && listData.data.list.length > 0) {
  160. const del = await this.api(`file/delete?${this.pr}`, {
  161. action_type: 2,
  162. filelist: listData.data.list.map((v) => v.fid),
  163. exclude_fids: [],
  164. });
  165. // console.log(del);
  166. }
  167. }
  168. async createSaveDir(clean) {
  169. if (this.saveDirId) {
  170. if (clean) await this.clearSaveDir();
  171. return;
  172. }
  173. const listData = await this.api(`file/sort?${this.pr}&pdir_fid=0&_page=1&_size=200&_sort=file_type:asc,updated_at:desc`, {}, {}, 'get');
  174. if (listData.data && listData.data.list)
  175. for (const item of listData.data.list) {
  176. if (item.file_name === this.saveDirName) {
  177. this.saveDirId = item.fid;
  178. await this.clearSaveDir();
  179. break;
  180. }
  181. }
  182. if (!this.saveDirId) {
  183. const create = await this.api(`file?${this.pr}`, {
  184. pdir_fid: '0',
  185. file_name: this.saveDirName,
  186. dir_path: '',
  187. dir_init_lock: false,
  188. });
  189. console.log(create);
  190. if (create.data && create.data.fid) {
  191. this.saveDirId = create.data.fid;
  192. }
  193. }
  194. }
  195. async getShareToken(shareData) {
  196. if (!this.shareTokenCache[shareData.shareId]) {
  197. delete this.shareTokenCache[shareData.shareId];
  198. const shareToken = await this.api(`share/sharepage/token?${this.pr}`, {
  199. pwd_id: shareData.shareId,
  200. passcode: shareData.sharePwd || '',
  201. });
  202. if (shareToken.data && shareToken.data.stoken) {
  203. this.shareTokenCache[shareData.shareId] = shareToken.data;
  204. }
  205. }
  206. }
  207. async getFilesByShareUrl(shareInfo) {
  208. const shareData = typeof shareInfo === 'string' ? this.getShareData(shareInfo) : shareInfo;
  209. if (!shareData) return [];
  210. await this.getShareToken(shareData);
  211. if (!this.shareTokenCache[shareData.shareId]) return [];
  212. const videos = [];
  213. const subtitles = [];
  214. const listFile = async (shareId, folderId, page) => {
  215. const prePage = 200;
  216. page = page || 1;
  217. const listData = await this.api(`share/sharepage/detail?${this.pr}&pwd_id=${shareId}&stoken=${encodeURIComponent(this.shareTokenCache[shareId].stoken)}&pdir_fid=${folderId}&force=0&_page=${page}&_size=${prePage}&_sort=file_type:asc,file_name:asc`, {}, {}, 'get');
  218. if (!listData.data) return [];
  219. const items = listData.data.list;
  220. if (!items) return [];
  221. const subDir = [];
  222. for (const item of items) {
  223. if (item.dir === true) {
  224. subDir.push(item);
  225. } else if (item.file === true && item.obj_category === 'video') {
  226. if (item.size < 1024 * 1024 * 5) continue;
  227. item.stoken = this.shareTokenCache[shareData.shareId].stoken;
  228. videos.push(item);
  229. } else if (item.type === 'file' && this.subtitleExts.some((x) => item.file_name.endsWith(x))) {
  230. subtitles.push(item);
  231. }
  232. }
  233. if (page < Math.ceil(listData.metadata._total / prePage)) {
  234. const nextItems = await listFile(shareId, folderId, page + 1);
  235. for (const item of nextItems) {
  236. items.push(item);
  237. }
  238. }
  239. for (const dir of subDir) {
  240. const subItems = await listFile(shareId, dir.fid);
  241. for (const item of subItems) {
  242. items.push(item);
  243. }
  244. }
  245. return items;
  246. };
  247. await listFile(shareData.shareId, shareData.folderId);
  248. if (subtitles.length > 0) {
  249. videos.forEach((item) => {
  250. var matchSubtitle = this.findBestLCS(item, subtitles);
  251. if (matchSubtitle.bestMatch) {
  252. item.subtitle = matchSubtitle.bestMatch.target;
  253. }
  254. });
  255. }
  256. return videos;
  257. }
  258. async save(shareId, stoken, fileId, fileToken, clean) {
  259. await this.createSaveDir(clean);
  260. if (clean) {
  261. const saves = Object.keys(this.saveFileIdCaches);
  262. for (const save of saves) {
  263. delete this.saveFileIdCaches[save];
  264. }
  265. }
  266. if (!this.saveDirId) return null;
  267. if (!stoken) {
  268. await this.getShareToken({
  269. shareId: shareId,
  270. });
  271. if (!this.shareTokenCache[shareId]) return null;
  272. }
  273. const saveResult = await this.api(`share/sharepage/save?${this.pr}`, {
  274. fid_list: [fileId],
  275. fid_token_list: [fileToken],
  276. to_pdir_fid: this.saveDirId,
  277. pwd_id: shareId,
  278. stoken: stoken || this.shareTokenCache[shareId].stoken,
  279. pdir_fid: '0',
  280. scene: 'link',
  281. });
  282. if (saveResult.data && saveResult.data.task_id) {
  283. let retry = 0;
  284. while (true) {
  285. const taskResult = await this.api(`task?${this.pr}&task_id=${saveResult.data.task_id}&retry_index=${retry}`, {}, {}, 'get');
  286. if (taskResult.data && taskResult.data.save_as && taskResult.data.save_as.save_as_top_fids && taskResult.data.save_as.save_as_top_fids.length > 0) {
  287. return taskResult.data.save_as.save_as_top_fids[0];
  288. }
  289. retry++;
  290. if (retry > 5) break;
  291. await this.delay(1000);
  292. }
  293. }
  294. return true;
  295. }
  296. async refreshQuarkCookie(from = '') {
  297. const nowCookie = this.cookie;
  298. const cookieSelfRes = await axios({
  299. url: "https://drive-pc.quark.cn/1/clouddrive/file/sort?pr=ucpro&fr=pc&uc_param_str=&pdir_fid=0&_page=1&_size=50&_fetch_total=1&_fetch_sub_dirs=0&_sort=file_type:asc,updated_at:desc",
  300. method: "GET",
  301. headers: {
  302. "User-Agent": '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',
  303. Origin: 'https://pan.quark.cn',
  304. Referer: 'https://pan.quark.cn/',
  305. Cookie: nowCookie
  306. }
  307. });
  308. const cookieResDataSelf = cookieSelfRes.headers;
  309. const resCookie = cookieResDataSelf['set-cookie'];
  310. if (!resCookie) {
  311. console.log(`${from}自动更新夸克 cookie: 没返回新的cookie`);
  312. return
  313. }
  314. const cookieObject = COOKIE.parse(resCookie);
  315. // console.log(cookieObject);
  316. if (cookieObject.__puus) {
  317. const oldCookie = COOKIE.parse(nowCookie);
  318. const newCookie = COOKIE.stringify({
  319. __pus: oldCookie.__pus,
  320. __puus: cookieObject.__puus,
  321. });
  322. console.log(`${from}自动更新夸克 cookie: ${newCookie}`);
  323. ENV.set('quark_cookie', newCookie);
  324. }
  325. }
  326. async getLiveTranscoding(shareId, stoken, fileId, fileToken) {
  327. if (!this.saveFileIdCaches[fileId]) {
  328. const saveFileId = await this.save(shareId, stoken, fileId, fileToken, true);
  329. if (!saveFileId) return null;
  330. this.saveFileIdCaches[fileId] = saveFileId;
  331. }
  332. const transcoding = await this.api(`file/v2/play?${this.pr}`, {
  333. fid: this.saveFileIdCaches[fileId],
  334. resolutions: 'normal,low,high,super,2k,4k',
  335. supports: 'fmp4',
  336. });
  337. if (transcoding.data && transcoding.data.video_list) {
  338. const low_url = transcoding.data.video_list.slice(-1)[0].video_info.url;
  339. const low_cookie = this.cookie;
  340. const low_headers = {
  341. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
  342. 'origin': 'https://pan.quark.cn',
  343. 'referer': 'https://pan.quark.cn/',
  344. 'Cookie': low_cookie
  345. };
  346. // console.log('low_url:', low_url);
  347. // console.log('low_cookie:', low_cookie);
  348. const test_result = await this.testSupport(low_url, low_headers);
  349. // console.log(test_result);
  350. if (!test_result[0]) {
  351. try {
  352. await this.refreshQuarkCookie('getLiveTranscoding');
  353. } catch (e) {
  354. console.log(`getLiveTranscoding:自动刷新夸克cookie失败:${e.message}`);
  355. console.error(e);
  356. }
  357. }
  358. return transcoding.data.video_list;
  359. }
  360. return null;
  361. }
  362. async getDownload(shareId, stoken, fileId, fileToken, clean) {
  363. if (!this.saveFileIdCaches[fileId]) {
  364. const saveFileId = await this.save(shareId, stoken, fileId, fileToken, clean);
  365. if (!saveFileId) return null;
  366. this.saveFileIdCaches[fileId] = saveFileId;
  367. }
  368. const down = await this.api(`file/download?${this.pr}`, {
  369. fids: [this.saveFileIdCaches[fileId]],
  370. });
  371. if (down.data) {
  372. const low_url = down.data[0].download_url;
  373. const low_cookie = this.cookie;
  374. const low_headers = {
  375. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
  376. 'origin': 'https://pan.quark.cn',
  377. 'referer': 'https://pan.quark.cn/',
  378. 'Cookie': low_cookie
  379. };
  380. // console.log('low_url:', low_url);
  381. // console.log('low_cookie:', low_cookie);
  382. const test_result = await this.testSupport(low_url, low_headers);
  383. // console.log('test_result:', test_result);
  384. if (!test_result[0]) {
  385. try {
  386. await this.refreshQuarkCookie('getDownload');
  387. } catch (e) {
  388. console.log(`getDownload:自动刷新Quark cookie失败:${e.message}`)
  389. }
  390. }
  391. return down.data[0];
  392. }
  393. return null;
  394. }
  395. async testSupport(url, headers) {
  396. const resp = await req
  397. .get(url, {
  398. responseType: 'stream',
  399. headers: Object.assign(
  400. {
  401. Range: 'bytes=0-0',
  402. },
  403. headers,
  404. ),
  405. })
  406. .catch((err) => {
  407. // console.error(err);
  408. console.error('[testSupport] error:', err.message);
  409. return err.response || {status: 500, data: {}};
  410. });
  411. if (resp && (resp.status === 206 || resp.status === 200)) {
  412. const isAccept = resp.headers['accept-ranges'] === 'bytes';
  413. const contentRange = resp.headers['content-range'];
  414. const contentLength = parseInt(resp.headers['content-length']);
  415. const isSupport = isAccept || !!contentRange || contentLength === 1 || resp.status === 200;
  416. const length = contentRange ? parseInt(contentRange.split('/')[1]) : contentLength;
  417. delete resp.headers['content-range'];
  418. delete resp.headers['content-length'];
  419. if (length) resp.headers['content-length'] = length.toString();
  420. return [isSupport, resp.headers];
  421. } else {
  422. console.log('[testSupport] resp.status:', resp.status);
  423. return [false, null];
  424. }
  425. }
  426. delAllCache(keepKey) {
  427. try {
  428. fs.readdir(this.cacheRoot, (_, files) => {
  429. if (files)
  430. for (const file of files) {
  431. if (file === keepKey) continue;
  432. const dir = join(this.cacheRoot, file);
  433. fs.stat(dir, (_, stats) => {
  434. if (stats && stats.isDirectory()) {
  435. fs.readdir(dir, (_, subFiles) => {
  436. if (subFiles)
  437. for (const subFile of subFiles) {
  438. if (!subFile.endsWith('.p')) {
  439. fs.rm(join(dir, subFile), {recursive: true}, () => {
  440. });
  441. }
  442. }
  443. });
  444. }
  445. });
  446. }
  447. });
  448. } catch (error) {
  449. console.error(error);
  450. }
  451. }
  452. async chunkStream(inReq, outResp, url, urlKey, headers, option) {
  453. urlKey = urlKey || CryptoJS.enc.Hex.stringify(CryptoJS.MD5(url)).toString();
  454. if (this.currentUrlKey !== urlKey) {
  455. this.delAllCache(urlKey);
  456. this.currentUrlKey = urlKey;
  457. }
  458. if (!this.urlHeadCache[urlKey]) {
  459. const [isSupport, urlHeader] = await this.testSupport(url, headers);
  460. if (!isSupport || !urlHeader['content-length']) {
  461. outResp.redirect(url);
  462. return;
  463. }
  464. this.urlHeadCache[urlKey] = urlHeader;
  465. }
  466. let exist = true;
  467. await fs.promises.access(join(this.cacheRoot, urlKey)).catch((_) => (exist = false));
  468. if (!exist) {
  469. await fs.promises.mkdir(join(this.cacheRoot, urlKey), {recursive: true});
  470. }
  471. const contentLength = parseInt(this.urlHeadCache[urlKey]['content-length']);
  472. let byteStart = 0;
  473. let byteEnd = contentLength - 1;
  474. const streamHeader = {};
  475. if (inReq.headers.range) {
  476. const ranges = inReq.headers.range.trim().split(/=|-/);
  477. if (ranges.length > 2 && ranges[2]) {
  478. byteEnd = parseInt(ranges[2]);
  479. }
  480. byteStart = parseInt(ranges[1]);
  481. Object.assign(streamHeader, this.urlHeadCache[urlKey]);
  482. streamHeader['content-length'] = (byteEnd - byteStart + 1).toString();
  483. streamHeader['content-range'] = `bytes ${byteStart}-${byteEnd}/${contentLength}`;
  484. outResp.code(206);
  485. } else {
  486. Object.assign(streamHeader, this.urlHeadCache[urlKey]);
  487. outResp.code(200);
  488. }
  489. option = option || {chunkSize: 1024 * 256, poolSize: 5, timeout: 1000 * 10};
  490. const chunkSize = option.chunkSize;
  491. const poolSize = option.poolSize;
  492. const timeout = option.timeout;
  493. let chunkCount = Math.ceil(contentLength / chunkSize);
  494. let chunkDownIdx = Math.floor(byteStart / chunkSize);
  495. let chunkReadIdx = chunkDownIdx;
  496. let stop = false;
  497. const dlFiles = {};
  498. for (let i = 0; i < poolSize && i < chunkCount; i++) {
  499. new Promise((resolve) => {
  500. (async function doDLTask(spChunkIdx) {
  501. if (stop || chunkDownIdx >= chunkCount) {
  502. resolve();
  503. return;
  504. }
  505. if (spChunkIdx === undefined && (chunkDownIdx - chunkReadIdx) * chunkSize >= this.maxCache) {
  506. setTimeout(doDLTask, 5);
  507. return;
  508. }
  509. const chunkIdx = spChunkIdx || chunkDownIdx++;
  510. const taskId = `${inReq.id}-${chunkIdx}`;
  511. try {
  512. const dlFile = join(this.cacheRoot, urlKey, `${inReq.id}-${chunkIdx}.p`);
  513. let exist = true;
  514. await fs.promises.access(dlFile).catch((_) => (exist = false));
  515. if (!exist) {
  516. const start = chunkIdx * chunkSize;
  517. const end = Math.min(contentLength - 1, (chunkIdx + 1) * chunkSize - 1);
  518. console.log(inReq.id, chunkIdx);
  519. const dlResp = await req.get(url, {
  520. responseType: 'stream',
  521. timeout: timeout,
  522. headers: Object.assign(
  523. {
  524. Range: `bytes=${start}-${end}`,
  525. },
  526. headers,
  527. ),
  528. });
  529. const dlCache = join(this.cacheRoot, urlKey, `${inReq.id}-${chunkIdx}.dl`);
  530. const writer = fs.createWriteStream(dlCache);
  531. const readTimeout = setTimeout(() => {
  532. writer.destroy(new Error(`${taskId} read timeout`));
  533. }, timeout);
  534. const downloaded = new Promise((resolve) => {
  535. writer.on('finish', async () => {
  536. if (stop) {
  537. await fs.promises.rm(dlCache).catch((e) => console.error(e));
  538. } else {
  539. await fs.promises.rename(dlCache, dlFile).catch((e) => console.error(e));
  540. dlFiles[taskId] = dlFile;
  541. }
  542. resolve(true);
  543. });
  544. writer.on('error', async (e) => {
  545. console.error(e);
  546. await fs.promises.rm(dlCache).catch((e1) => console.error(e1));
  547. resolve(false);
  548. });
  549. });
  550. dlResp.data.pipe(writer);
  551. const result = await downloaded;
  552. clearTimeout(readTimeout);
  553. if (!result) {
  554. setTimeout(() => {
  555. doDLTask(chunkIdx);
  556. }, 15);
  557. return;
  558. }
  559. }
  560. setTimeout(doDLTask, 5);
  561. } catch (error) {
  562. console.error(error);
  563. setTimeout(() => {
  564. doDLTask(chunkIdx);
  565. }, 15);
  566. }
  567. })();
  568. });
  569. }
  570. outResp.headers(streamHeader);
  571. const stream = new PassThrough();
  572. new Promise((resolve) => {
  573. let writeMore = true;
  574. (async function waitReadFile() {
  575. try {
  576. if (chunkReadIdx >= chunkCount || stop) {
  577. stream.end();
  578. resolve();
  579. return;
  580. }
  581. if (!writeMore) {
  582. setTimeout(waitReadFile, 5);
  583. return;
  584. }
  585. const taskId = `${inReq.id}-${chunkReadIdx}`;
  586. if (!dlFiles[taskId]) {
  587. setTimeout(waitReadFile, 5);
  588. return;
  589. }
  590. const chunkByteStart = chunkReadIdx * chunkSize;
  591. const chunkByteEnd = Math.min(contentLength - 1, (chunkReadIdx + 1) * chunkSize - 1);
  592. const readFileStart = Math.max(byteStart, chunkByteStart) - chunkByteStart;
  593. const dlFile = dlFiles[taskId];
  594. delete dlFiles[taskId];
  595. const fd = await fs.promises.open(dlFile, 'r');
  596. const buffer = Buffer.alloc(chunkByteEnd - chunkByteStart - readFileStart + 1);
  597. await fd.read(buffer, 0, chunkByteEnd - chunkByteStart - readFileStart + 1, readFileStart);
  598. await fd.close().catch((e) => console.error(e));
  599. await fs.promises.rm(dlFile).catch((e) => console.error(e));
  600. writeMore = stream.write(buffer);
  601. if (!writeMore) {
  602. stream.once('drain', () => {
  603. writeMore = true;
  604. });
  605. }
  606. chunkReadIdx++;
  607. setTimeout(waitReadFile, 5);
  608. } catch (error) {
  609. setTimeout(waitReadFile, 5);
  610. }
  611. })();
  612. });
  613. stream.on('close', async () => {
  614. Object.keys(dlFiles).forEach((reqKey) => {
  615. if (reqKey.startsWith(inReq.id)) {
  616. fs.rm(dlFiles[reqKey], {recursive: true}, () => {
  617. });
  618. delete dlFiles[reqKey];
  619. }
  620. });
  621. stop = true;
  622. });
  623. return stream;
  624. }
  625. }
  626. export const Quark = new QuarkHandler();