frostpv 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +197 -116
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -4,7 +4,7 @@ const path = require("path");
4
4
  const os = require("os");
5
5
  const FormData = require("form-data");
6
6
  const crypto = require("crypto");
7
- const { igdl, ttdl, fbdown, mediafire, capcut, gdrive, pinterest } = require("btch-downloader");
7
+ const { igdl, ttdl, fbdown, mediafire, capcut, gdrive, pinterest, youtube } = require("btch-downloader");
8
8
  const { TwitterDL } = require("twitter-downloader");
9
9
  const btch = require("btch-downloader");
10
10
  const btchOld = require("btch-downloader-old");
@@ -21,10 +21,10 @@ ffmpeg.setFfmpegPath(pathToFfmpeg.ffmpegPath);
21
21
 
22
22
  // Output directory is the caller's working directory (keeps files next to the running app)
23
23
  const OUTPUT_DIR = process.cwd();
24
- try { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); } catch (_) {}
24
+ try { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); } catch (_) { }
25
25
  // Keep an OS temp directory for any future transient needs
26
26
  const TEMP_DIR = path.join(os.tmpdir(), "downloader-dl-bot");
27
- try { fs.mkdirSync(TEMP_DIR, { recursive: true }); } catch (_) {}
27
+ try { fs.mkdirSync(TEMP_DIR, { recursive: true }); } catch (_) { }
28
28
  // TTL (minutes) for files in OUTPUT_DIR (audio/video) before they are pruned
29
29
  const OUTPUT_RETENTION_MIN = Number(process.env.OUTPUT_RETENTION_MIN || 5);
30
30
 
@@ -73,7 +73,7 @@ const isVideoLink = (link) => {
73
73
  const u = new URL(link);
74
74
  const host = (u.hostname || '').toLowerCase();
75
75
  if (host.endsWith('pintere21313213st.com')) return true;
76
- } catch (_) {}
76
+ } catch (_) { }
77
77
  return videoPlatforms.some(platform => link.startsWith(platform));
78
78
  };
79
79
 
@@ -146,14 +146,14 @@ async function safeUnlinkWithRetry(filePath, maxRetries = 3) {
146
146
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
147
147
  try {
148
148
  const resolvedPath = path.resolve(filePath);
149
-
149
+
150
150
  if (fs.existsSync(resolvedPath)) {
151
151
  // Check if file is in use (Windows)
152
152
  if (process.platform === 'win32' && isFileInUse(resolvedPath)) {
153
153
  await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
154
154
  continue;
155
155
  }
156
-
156
+
157
157
  fs.rmSync(resolvedPath, { force: true });
158
158
  return true;
159
159
  } else {
@@ -225,7 +225,7 @@ async function validateVideoUrl(url, platform) {
225
225
  return status >= 200 && status < 400; // Accept redirects
226
226
  }
227
227
  });
228
-
228
+
229
229
  return true;
230
230
  } catch (error) {
231
231
  return false;
@@ -237,18 +237,18 @@ function validateTwitterUrl(url) {
237
237
  try {
238
238
  // Remove query parameters that might cause issues
239
239
  const cleanUrl = url.split('?')[0];
240
-
240
+
241
241
  // Ensure it's a valid Twitter/X URL format
242
242
  if (!cleanUrl.includes('x.com') && !cleanUrl.includes('twitter.com')) {
243
243
  throw new Error("Not a valid Twitter/X URL");
244
244
  }
245
-
245
+
246
246
  // Check if it has the required structure
247
247
  const urlParts = cleanUrl.split('/');
248
248
  if (urlParts.length < 5) {
249
249
  throw new Error("Invalid Twitter/X URL structure");
250
250
  }
251
-
251
+
252
252
  return cleanUrl;
253
253
  } catch (error) {
254
254
  throw new Error(`Twitter URL validation failed: ${error.message}`);
@@ -287,10 +287,10 @@ async function downloadYoutubeAudio(url, outputPath) {
287
287
  try {
288
288
  const ytdlp = new YtDlp({ ffmpegPath });
289
289
  const cookiesPath = getYoutubeCookiesPath();
290
-
290
+
291
291
  // Garantir que o nome do arquivo tenha a extensão correta
292
292
  const baseName = outputPath.replace(/\.[^/.]+$/, ""); // Remove extensão se existir
293
-
293
+
294
294
  // Primeiro, tentar com a API de opções
295
295
  try {
296
296
  const options = {
@@ -301,11 +301,11 @@ async function downloadYoutubeAudio(url, outputPath) {
301
301
  },
302
302
  output: outputPath
303
303
  };
304
-
304
+
305
305
  if (cookiesPath) {
306
306
  options.cookies = cookiesPath;
307
307
  }
308
-
308
+
309
309
  // Remover logs detalhados para download de áudio do YouTube
310
310
  const result = await ytdlp.downloadAsync(url, options);
311
311
  // Verificar se o arquivo foi criado
@@ -314,7 +314,7 @@ async function downloadYoutubeAudio(url, outputPath) {
314
314
  resolve(actualFileName);
315
315
  return;
316
316
  }
317
-
317
+
318
318
  // Tentar encontrar o arquivo com extensão diferente
319
319
  const files = fs.readdirSync('./');
320
320
  const downloadedFile = files.find(file => file.startsWith(baseName.split('/').pop()));
@@ -325,18 +325,18 @@ async function downloadYoutubeAudio(url, outputPath) {
325
325
  } catch (apiError) {
326
326
  console.log('API method failed, trying direct args method:', apiError.message);
327
327
  }
328
-
328
+
329
329
  // Fallback: usar argumentos diretos para máxima qualidade de áudio
330
330
  const args = [
331
331
  url,
332
332
  '-f', 'bestaudio[ext=mp3]/bestaudio',
333
333
  '-o', outputPath
334
334
  ];
335
-
335
+
336
336
  if (cookiesPath) {
337
337
  args.push('--cookies', cookiesPath);
338
338
  }
339
-
339
+
340
340
  // Remover logs detalhados para download de áudio do YouTube
341
341
  const result = await ytdlp.execAsync(args);
342
342
  // Verificar se o arquivo foi criado
@@ -363,25 +363,53 @@ async function downloadYoutubeAudio(url, outputPath) {
363
363
  // Enhanced function to get platform type from URL
364
364
  function getPlatformType(url) {
365
365
  const lowerUrl = url.toLowerCase();
366
-
366
+
367
367
  if (lowerUrl.includes("instagram.com") || lowerUrl.includes("instagr.am")) return "instagram";
368
368
  if (lowerUrl.includes("tiktok.com") || lowerUrl.includes("vm.tiktok.com") || lowerUrl.includes("vt.tiktok.com")) return "tiktok";
369
369
  if (lowerUrl.includes("facebook.com") || lowerUrl.includes("fb.watch") || lowerUrl.includes("fb.com")) return "facebook";
370
370
  if (lowerUrl.includes("mediafire.com")) return "mediafire";
371
371
  if (lowerUrl.includes("x.com") || lowerUrl.includes("twitter.com")) return "twitter";
372
- if (lowerUrl.includes("youtube.com") || lowerUrl.includes("you112t12u.be") || lowerUrl.includes("m.y11outu314be.com")) return "youtu1354be";
373
-
372
+ if (lowerUrl.includes("youtube.com") || lowerUrl.includes("youtu.be") || lowerUrl.includes("m.youtube.com")) return "youtube";
373
+
374
374
  return "unknown";
375
375
  }
376
376
 
377
+ // Function to get direct video URL from Twitter using yt-dlp
378
+ async function downloadTwitterWithYtDlp(url) {
379
+ return new Promise(async (resolve, reject) => {
380
+ try {
381
+ const ytdlp = new YtDlp({ ffmpegPath });
382
+ // Use -g to get URL, -f "best[ext=mp4]/best" to get best quality single file if possible
383
+ const args = [
384
+ url,
385
+ '-g',
386
+ '-f', 'best[ext=mp4]/best'
387
+ ];
388
+
389
+ const result = await ytdlp.execAsync(args);
390
+ // yt-dlp might return multiple lines if it finds separate audio/video streams
391
+ // We take the first one, but for Twitter it is usually a single mp4 file
392
+ const videoUrl = result.trim().split('\n')[0];
393
+
394
+ if (videoUrl && videoUrl.startsWith('http')) {
395
+ resolve(videoUrl);
396
+ } else {
397
+ reject(new Error('yt-dlp did not return a valid URL'));
398
+ }
399
+ } catch (error) {
400
+ reject(error);
401
+ }
402
+ });
403
+ }
404
+
377
405
  // Enhanced fallback download function with platform-specific methods
378
406
  async function tryFallbackDownload(url, maxRetries = 3) {
379
407
  const platform = getPlatformType(url);
380
-
408
+
381
409
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
382
410
  try {
383
411
  let videoUrl = null;
384
-
412
+
385
413
  // Platform-specific fallback methods
386
414
  switch (platform) {
387
415
  case "instagram": {
@@ -427,7 +455,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
427
455
  }
428
456
  }
429
457
  ];
430
-
458
+
431
459
  for (const method of methods) {
432
460
  try {
433
461
  videoUrl = await method();
@@ -438,7 +466,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
438
466
  }
439
467
  break;
440
468
  }
441
-
469
+
442
470
  case "tiktok": {
443
471
  // Try multiple TikTok methods
444
472
  const methods = [
@@ -473,7 +501,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
473
501
  throw new Error("No valid video in Tiktok.Downloader v2 response");
474
502
  }
475
503
  ];
476
-
504
+
477
505
  for (const method of methods) {
478
506
  try {
479
507
  videoUrl = await method();
@@ -484,7 +512,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
484
512
  }
485
513
  break;
486
514
  }
487
-
515
+
488
516
  case "facebook": {
489
517
  // Try multiple Facebook methods
490
518
  const methods = [
@@ -508,7 +536,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
508
536
  throw new Error("No valid video in fbdown response with custom headers");
509
537
  }
510
538
  ];
511
-
539
+
512
540
  for (const method of methods) {
513
541
  try {
514
542
  videoUrl = await method();
@@ -519,42 +547,46 @@ async function tryFallbackDownload(url, maxRetries = 3) {
519
547
  }
520
548
  break;
521
549
  }
522
-
550
+
523
551
  case "twitter": {
524
- // Try multiple Twitter methods with better response handling
552
+ // Try using yt-dlp first (Most reliable), then fallback to existing methods
525
553
  const methods = [
554
+ async () => {
555
+ // Priority 1: yt-dlp
556
+ return await downloadTwitterWithYtDlp(url);
557
+ },
526
558
  async () => {
527
559
  const cleanUrl = validateTwitterUrl(url);
528
560
  const data = await TwitterDL(cleanUrl, {});
529
-
561
+
530
562
  // Check multiple possible response structures
531
563
  if (data && data.result) {
532
564
  // Structure 1: data.result.media[0].videos[0].url
533
- if (data.result.media && data.result.media[0] &&
534
- data.result.media[0].videos && data.result.media[0].videos[0] &&
535
- data.result.media[0].videos[0].url) {
565
+ if (data.result.media && data.result.media[0] &&
566
+ data.result.media[0].videos && data.result.media[0].videos[0] &&
567
+ data.result.media[0].videos[0].url) {
536
568
  return data.result.media[0].videos[0].url;
537
569
  }
538
-
570
+
539
571
  // Structure 2: data.result.video[0].url
540
572
  if (data.result.video && data.result.video[0] && data.result.video[0].url) {
541
573
  return data.result.video[0].url;
542
574
  }
543
-
575
+
544
576
  // Structure 3: data.result.url (direct URL)
545
577
  if (data.result.url) {
546
578
  return data.result.url;
547
579
  }
548
-
580
+
549
581
  // Structure 4: data.result.media[0].url
550
582
  if (data.result.media && data.result.media[0] && data.result.media[0].url) {
551
583
  return data.result.media[0].url;
552
584
  }
553
-
585
+
554
586
  // Structure 5: Check for any video URL in the entire response
555
587
  const findVideoUrl = (obj) => {
556
- if (typeof obj === 'string' && obj.includes('http') &&
557
- (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
588
+ if (typeof obj === 'string' && obj.includes('http') &&
589
+ (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
558
590
  return obj;
559
591
  }
560
592
  if (typeof obj === 'object' && obj !== null) {
@@ -565,13 +597,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
565
597
  }
566
598
  return null;
567
599
  };
568
-
600
+
569
601
  const foundUrl = findVideoUrl(data.result);
570
602
  if (foundUrl) {
571
603
  return foundUrl;
572
604
  }
573
605
  }
574
-
606
+
575
607
  throw new Error("No valid video URL found in TwitterDL response structure");
576
608
  },
577
609
  async () => {
@@ -587,30 +619,30 @@ async function tryFallbackDownload(url, maxRetries = 3) {
587
619
  'Upgrade-Insecure-Requests': '1'
588
620
  }
589
621
  });
590
-
622
+
591
623
  // Same structure checking as above
592
624
  if (data && data.result) {
593
- if (data.result.media && data.result.media[0] &&
594
- data.result.media[0].videos && data.result.media[0].videos[0] &&
595
- data.result.media[0].videos[0].url) {
625
+ if (data.result.media && data.result.media[0] &&
626
+ data.result.media[0].videos && data.result.media[0].videos[0] &&
627
+ data.result.media[0].videos[0].url) {
596
628
  return data.result.media[0].videos[0].url;
597
629
  }
598
-
630
+
599
631
  if (data.result.video && data.result.video[0] && data.result.video[0].url) {
600
632
  return data.result.video[0].url;
601
633
  }
602
-
634
+
603
635
  if (data.result.url) {
604
636
  return data.result.url;
605
637
  }
606
-
638
+
607
639
  if (data.result.media && data.result.media[0] && data.result.media[0].url) {
608
640
  return data.result.media[0].url;
609
641
  }
610
-
642
+
611
643
  const findVideoUrl = (obj) => {
612
- if (typeof obj === 'string' && obj.includes('http') &&
613
- (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
644
+ if (typeof obj === 'string' && obj.includes('http') &&
645
+ (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
614
646
  return obj;
615
647
  }
616
648
  if (typeof obj === 'object' && obj !== null) {
@@ -621,13 +653,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
621
653
  }
622
654
  return null;
623
655
  };
624
-
656
+
625
657
  const foundUrl = findVideoUrl(data.result);
626
658
  if (foundUrl) {
627
659
  return foundUrl;
628
660
  }
629
661
  }
630
-
662
+
631
663
  throw new Error("No valid video URL found in TwitterDL response with custom headers");
632
664
  },
633
665
  async () => {
@@ -663,9 +695,9 @@ async function tryFallbackDownload(url, maxRetries = 3) {
663
695
  const data = await btchOld[funcName](cleanUrl);
664
696
  // Check multiple possible response structures
665
697
  if (data && data.result) {
666
- if (data.result.media && data.result.media[0] &&
667
- data.result.media[0].videos && data.result.media[0].videos[0] &&
668
- data.result.media[0].videos[0].url) {
698
+ if (data.result.media && data.result.media[0] &&
699
+ data.result.media[0].videos && data.result.media[0].videos[0] &&
700
+ data.result.media[0].videos[0].url) {
669
701
  return data.result.media[0].videos[0].url;
670
702
  }
671
703
  if (data.result.video && data.result.video[0] && data.result.video[0].url) {
@@ -692,7 +724,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
692
724
  }
693
725
  }
694
726
  ];
695
-
727
+
696
728
  for (const method of methods) {
697
729
  try {
698
730
  videoUrl = await method();
@@ -703,12 +735,12 @@ async function tryFallbackDownload(url, maxRetries = 3) {
703
735
  }
704
736
  break;
705
737
  }
706
-
738
+
707
739
  case "youtube": {
708
740
  // YouTube doesn't need fallback - it has its own specific method
709
741
  throw new Error("YouTube downloads should use the primary method, not fallback");
710
742
  }
711
-
743
+
712
744
  case "mediafire": {
713
745
  // Try MediaFire method
714
746
  try {
@@ -746,10 +778,10 @@ async function tryFallbackDownload(url, maxRetries = 3) {
746
778
  };
747
779
  mediaUrl = pickFrom(data);
748
780
  if (mediaUrl) return mediaUrl;
749
- } catch (_) {}
781
+ } catch (_) { }
750
782
  break;
751
783
  }
752
-
784
+
753
785
  default: {
754
786
  // Generic fallback for unknown platforms
755
787
  try {
@@ -765,18 +797,18 @@ async function tryFallbackDownload(url, maxRetries = 3) {
765
797
  break;
766
798
  }
767
799
  }
768
-
800
+
769
801
  // If we got a video URL, validate it
770
802
  if (videoUrl && videoUrl.includes("http")) {
771
803
  // Validate the URL is accessible
772
804
  try {
773
- const response = await axios.head(videoUrl, {
805
+ const response = await axios.head(videoUrl, {
774
806
  timeout: 10000,
775
807
  headers: {
776
808
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
777
809
  }
778
810
  });
779
-
811
+
780
812
  if (response.status === 200) {
781
813
  return videoUrl;
782
814
  } else {
@@ -788,7 +820,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
788
820
  } else {
789
821
  throw new Error("No valid video URL obtained from any fallback method");
790
822
  }
791
-
823
+
792
824
  } catch (error) {
793
825
  if (attempt === maxRetries) {
794
826
  throw new Error(`Enhanced fallback method failed after ${maxRetries} attempts for ${platform}: ${error.message}`);
@@ -835,9 +867,8 @@ const MediaDownloader = async (url, options = {}) => {
835
867
 
836
868
  // Verificar se é YouTube e lançar erro customizado
837
869
  const platform = getPlatformType(url);
838
- if (platform === "youtube") {
839
- throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook");
840
- }
870
+ // YouTube is now supported
871
+
841
872
 
842
873
  await cleanupTempFiles(); // Clean up previous temp files
843
874
 
@@ -849,21 +880,21 @@ const MediaDownloader = async (url, options = {}) => {
849
880
  downloadedFilePath = await downloadSmartVideo(url, config);
850
881
  } catch (error) {
851
882
  const platform = getPlatformType(url);
852
-
853
- // YouTube doesn't use fallback method - it has its own specific implementation
883
+
884
+ // YouTube doesn't use fallback method - it has its own specific implementation in downloadSmartVideo
854
885
  if (platform === "youtube") {
855
886
  throw new Error(`YouTube download failed: ${error.message}`);
856
887
  }
857
-
888
+
858
889
  try {
859
890
  const fallbackUrl = await tryFallbackDownload(url);
860
-
891
+
861
892
  // Validate fallback URL
862
893
  const isValid = await validateVideoUrl(fallbackUrl, platform);
863
894
  if (!isValid) {
864
895
  throw new Error("Fallback URL validation failed");
865
896
  }
866
-
897
+
867
898
  downloadedFilePath = await downloadDirectVideo(fallbackUrl, config);
868
899
  } catch (fallbackError) {
869
900
  throw new Error(`Failed to download video with both methods: ${fallbackError.message}`);
@@ -986,15 +1017,15 @@ async function downloadSmartVideo(url, config) {
986
1017
  throw new Error("No video URLs found in Facebook response");
987
1018
  }
988
1019
  videoUrl = data.Normal_video || data.HD;
989
-
1020
+
990
1021
  // Validate the URL is accessible
991
- const response = await axios.head(videoUrl, {
1022
+ const response = await axios.head(videoUrl, {
992
1023
  timeout: 10000,
993
1024
  headers: {
994
1025
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
995
1026
  }
996
1027
  }).catch(() => null);
997
-
1028
+
998
1029
  if (!response || response.status !== 200) {
999
1030
  throw new Error("Facebook video URL is not accessible");
1000
1031
  }
@@ -1066,12 +1097,12 @@ async function downloadSmartVideo(url, config) {
1066
1097
  // Validate and clean the Twitter URL
1067
1098
  const cleanUrl = validateTwitterUrl(url);
1068
1099
  const data = await TwitterDL(cleanUrl, {});
1069
-
1100
+
1070
1101
  if (data && data.result) {
1071
1102
  // Try multiple possible response structures
1072
- if (data.result.media && data.result.media[0] &&
1073
- data.result.media[0].videos && data.result.media[0].videos[0] &&
1074
- data.result.media[0].videos[0].url) {
1103
+ if (data.result.media && data.result.media[0] &&
1104
+ data.result.media[0].videos && data.result.media[0].videos[0] &&
1105
+ data.result.media[0].videos[0].url) {
1075
1106
  videoUrl = data.result.media[0].videos[0].url;
1076
1107
  } else if (data.result.video && data.result.video[0] && data.result.video[0].url) {
1077
1108
  videoUrl = data.result.video[0].url;
@@ -1082,8 +1113,8 @@ async function downloadSmartVideo(url, config) {
1082
1113
  } else {
1083
1114
  // Search for any video URL in the response
1084
1115
  const findVideoUrl = (obj) => {
1085
- if (typeof obj === 'string' && obj.includes('http') &&
1086
- (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
1116
+ if (typeof obj === 'string' && obj.includes('http') &&
1117
+ (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
1087
1118
  return obj;
1088
1119
  }
1089
1120
  if (typeof obj === 'object' && obj !== null) {
@@ -1094,7 +1125,7 @@ async function downloadSmartVideo(url, config) {
1094
1125
  }
1095
1126
  return null;
1096
1127
  };
1097
-
1128
+
1098
1129
  const foundUrl = findVideoUrl(data.result);
1099
1130
  if (foundUrl) {
1100
1131
  videoUrl = foundUrl;
@@ -1110,6 +1141,56 @@ async function downloadSmartVideo(url, config) {
1110
1141
  }
1111
1142
  break;
1112
1143
  }
1144
+ case "youtube": {
1145
+ try {
1146
+ const data = await youtube(url);
1147
+ // Try to find the video URL in the response
1148
+ // Common patterns: data.url, data.mp4, data.link, or direct string
1149
+
1150
+ if (!data) throw new Error("No data returned from youtube downloader");
1151
+
1152
+ // Recursive function to find a valid video URL in the object
1153
+ const findVideoUrl = (obj) => {
1154
+ if (!obj) return null;
1155
+ if (typeof obj === 'string' && obj.startsWith('http')) return obj;
1156
+
1157
+ if (Array.isArray(obj)) {
1158
+ for (const item of obj) {
1159
+ const found = findVideoUrl(item);
1160
+ if (found) return found;
1161
+ }
1162
+ } else if (typeof obj === 'object') {
1163
+ // Prioritize certain keys
1164
+ const priorities = ['url', 'link', 'download', 'mp4', 'video', 'src'];
1165
+ for (const key of priorities) {
1166
+ if (obj[key]) {
1167
+ const found = findVideoUrl(obj[key]);
1168
+ if (found) return found;
1169
+ }
1170
+ }
1171
+ // If not found in priorities, check all keys
1172
+ for (const key of Object.keys(obj)) {
1173
+ if (!priorities.includes(key)) {
1174
+ const found = findVideoUrl(obj[key]);
1175
+ if (found) return found;
1176
+ }
1177
+ }
1178
+ }
1179
+ return null;
1180
+ };
1181
+
1182
+ videoUrl = findVideoUrl(data);
1183
+
1184
+ if (!videoUrl) {
1185
+ console.log('YouTube data dump:', JSON.stringify(data, null, 2)); // Debug log since we can't test
1186
+ throw new Error("Could not extract video URL from YouTube response");
1187
+ }
1188
+
1189
+ } catch (error) {
1190
+ throw new Error(`YouTube download failed: ${error.message}`);
1191
+ }
1192
+ break;
1193
+ }
1113
1194
  default:
1114
1195
  throw new Error("Platform not supported or invalid link.");
1115
1196
  }
@@ -1117,21 +1198,21 @@ async function downloadSmartVideo(url, config) {
1117
1198
  if (!videoUrl || !videoUrl.includes("http")) {
1118
1199
  throw new Error("Returned video URL is invalid or unavailable.");
1119
1200
  }
1120
-
1201
+
1121
1202
  // Download the video with better error handling
1122
- const response = await axios({
1123
- url: videoUrl,
1124
- method: "GET",
1203
+ const response = await axios({
1204
+ url: videoUrl,
1205
+ method: "GET",
1125
1206
  responseType: "stream",
1126
1207
  timeout: 30000,
1127
1208
  headers: {
1128
1209
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
1129
1210
  }
1130
1211
  });
1131
-
1212
+
1132
1213
  // Create minimal unique file name in output dir
1133
- let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
1134
-
1214
+ let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
1215
+
1135
1216
  const videoWriter = fs.createWriteStream(fileName);
1136
1217
  response.data.pipe(videoWriter);
1137
1218
 
@@ -1156,19 +1237,19 @@ async function downloadSmartVideo(url, config) {
1156
1237
  // Function for direct video download
1157
1238
  async function downloadDirectVideo(url, config) {
1158
1239
  try {
1159
- const response = await axios({
1160
- url: url,
1161
- method: "GET",
1240
+ const response = await axios({
1241
+ url: url,
1242
+ method: "GET",
1162
1243
  responseType: "stream",
1163
1244
  timeout: 30000,
1164
1245
  headers: {
1165
1246
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
1166
1247
  }
1167
1248
  });
1168
-
1249
+
1169
1250
  // Create minimal unique file name in output dir
1170
- let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
1171
-
1251
+ let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
1252
+
1172
1253
  const videoWriter = fs.createWriteStream(fileName);
1173
1254
  response.data.pipe(videoWriter);
1174
1255
 
@@ -1216,7 +1297,7 @@ async function downloadGenericFile(url, preferredExt = null) {
1216
1297
  else ext = 'bin';
1217
1298
  }
1218
1299
 
1219
- const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.${ext}`);
1300
+ const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
1220
1301
  const writer = fs.createWriteStream(fileName);
1221
1302
  response.data.pipe(writer);
1222
1303
 
@@ -1231,7 +1312,7 @@ async function downloadGenericFile(url, preferredExt = null) {
1231
1312
 
1232
1313
  // Function to rotate video
1233
1314
  async function rotateVideo(fileName, rotation) {
1234
- const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0,4)}.mp4`);
1315
+ const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0, 4)}.mp4`);
1235
1316
  let angle;
1236
1317
  switch (rotation.toLowerCase()) {
1237
1318
  case "left": angle = "transpose=2"; break;
@@ -1282,8 +1363,8 @@ async function cleanupTempFiles() {
1282
1363
  try {
1283
1364
  if (!fs.existsSync(TEMP_DIR)) return;
1284
1365
  const files = fs.readdirSync(TEMP_DIR);
1285
- const tempFiles = files.filter(file =>
1286
- /^temp_video.*\.mp4$/.test(file) ||
1366
+ const tempFiles = files.filter(file =>
1367
+ /^temp_video.*\.mp4$/.test(file) ||
1287
1368
  /_audio\.mp3$/.test(file) ||
1288
1369
  /_rotated\.mp4$/.test(file) ||
1289
1370
  /_cropped\.mp4$/.test(file) ||
@@ -1315,9 +1396,9 @@ async function cleanupOutputFiles() {
1315
1396
  if (now - st.mtimeMs > maxAgeMs) {
1316
1397
  await safeUnlinkWithRetry(full);
1317
1398
  }
1318
- } catch (_) {}
1399
+ } catch (_) { }
1319
1400
  }
1320
- } catch (_) {}
1401
+ } catch (_) { }
1321
1402
  }
1322
1403
 
1323
1404
  // Schedule periodic cleanup (every 10 minutes)
@@ -1331,7 +1412,7 @@ cleanupOutputFiles();
1331
1412
  // Function to auto-crop video
1332
1413
  async function autoCrop(fileName) {
1333
1414
  const inputPath = fileName;
1334
- const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0,4)}.mp4`);
1415
+ const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0, 4)}.mp4`);
1335
1416
 
1336
1417
  return new Promise((resolve, reject) => {
1337
1418
  let cropValues = null;
@@ -1339,13 +1420,13 @@ async function autoCrop(fileName) {
1339
1420
  .outputOptions('-vf', 'cropdetect=24:16:0')
1340
1421
  .outputFormat('null')
1341
1422
  .output('-')
1342
- .on('stderr', function(stderrLine) {
1423
+ .on('stderr', function (stderrLine) {
1343
1424
  const cropMatch = stderrLine.match(/crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/);
1344
1425
  if (cropMatch) {
1345
1426
  cropValues = `crop=${cropMatch[1]}:${cropMatch[2]}:${cropMatch[3]}:${cropMatch[4]}`;
1346
1427
  }
1347
1428
  })
1348
- .on('end', function() {
1429
+ .on('end', function () {
1349
1430
  if (!cropValues) {
1350
1431
  resolve(inputPath);
1351
1432
  return;
@@ -1378,7 +1459,7 @@ async function checkAndCompressVideo(filePath, limitSizeMB, platform = null) {
1378
1459
  return filePath;
1379
1460
  }
1380
1461
 
1381
- const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0,4)}.mp4`);
1462
+ const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0, 4)}.mp4`);
1382
1463
 
1383
1464
  return new Promise((resolve, reject) => {
1384
1465
  ffmpeg(filePath)
@@ -1413,17 +1494,17 @@ async function convertVideoFormat(inputPath, targetFormat) {
1413
1494
  const supported = ["mp4", "mov", "webm", "mkv"];
1414
1495
  if (!supported.includes(fmt)) return inputPath;
1415
1496
 
1416
- const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0,4)}.${fmt}`);
1497
+ const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0, 4)}.${fmt}`);
1417
1498
 
1418
1499
  const ff = ffmpeg(inputPath);
1419
1500
  switch (fmt) {
1420
1501
  case "mp4":
1421
1502
  case "mov":
1422
1503
  case "mkv":
1423
- ff.outputOptions("-c:v","libx264","-pix_fmt","yuv420p","-c:a","aac","-b:a","192k");
1504
+ ff.outputOptions("-c:v", "libx264", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k");
1424
1505
  break;
1425
1506
  case "webm":
1426
- ff.outputOptions("-c:v","libvpx-vp9","-b:v","0","-crf","30","-c:a","libopus","-b:a","128k");
1507
+ ff.outputOptions("-c:v", "libvpx-vp9", "-b:v", "0", "-crf", "30", "-c:a", "libopus", "-b:a", "128k");
1427
1508
  break;
1428
1509
  default:
1429
1510
  return inputPath;
@@ -1480,14 +1561,14 @@ async function unshortenUrl(url) {
1480
1561
  return status >= 200 && status < 400; // Accept redirects
1481
1562
  }
1482
1563
  });
1483
-
1564
+
1484
1565
  // Get the final URL after all redirects
1485
1566
  const finalUrl = response.request.res.responseUrl || response.config.url;
1486
1567
  return finalUrl;
1487
1568
  }
1488
-
1569
+
1489
1570
  // For other URLs, use the original method
1490
- const response = await axios.head(url, {
1571
+ const response = await axios.head(url, {
1491
1572
  maxRedirects: 10,
1492
1573
  timeout: 10000,
1493
1574
  headers: {
@@ -1556,10 +1637,10 @@ const AudioDownloader = async (url, options = {}) => {
1556
1637
  if (downloadedFilePath) {
1557
1638
  // Extrair áudio em mp3
1558
1639
  audioFilePath = await extractAudioMp3(downloadedFilePath);
1559
-
1640
+
1560
1641
  // Remove o arquivo de vídeo temporário após extrair o áudio
1561
1642
  await safeUnlinkWithRetry(downloadedFilePath);
1562
-
1643
+
1563
1644
  const result = await uploadToGoFileIfNeeded(audioFilePath);
1564
1645
  return result;
1565
1646
  } else {
@@ -1575,7 +1656,7 @@ const AudioDownloader = async (url, options = {}) => {
1575
1656
  // Função para extrair áudio em mp3 usando ffmpeg
1576
1657
  async function extractAudioMp3(videoPath) {
1577
1658
  return new Promise((resolve, reject) => {
1578
- const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0,4)}.mp3`);
1659
+ const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0, 4)}.mp3`);
1579
1660
  ffmpeg(videoPath)
1580
1661
  .noVideo()
1581
1662
  .audioCodec('libmp3lame')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frostpv",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "downloads",
5
5
  "main": "index.js",
6
6
  "scripts": {