frostpv 1.0.9 → 1.0.11

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 +117 -204
  2. package/package.json +3 -3
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, youtube } = require("btch-downloader");
7
+ const { igdl, ttdl, fbdown, mediafire, capcut, gdrive, pinterest } = 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
 
@@ -58,13 +58,7 @@ const videoPlatforms = [
58
58
  "https://twitter.com",
59
59
  "https://www.twitter.com",
60
60
  "https://vm.tiktok.com/",
61
- "https://vt.tiktok.com/",
62
- "https://www.youtube.com",
63
- "https://youtube.com",
64
- "https://youtu.be",
65
- "https://m.youtube.com",
66
- "https://www.youtube.com/watch?"
67
-
61
+ "https://vt.tiktok.com/"
68
62
  ];
69
63
 
70
64
  // Blacklist links
@@ -79,7 +73,7 @@ const isVideoLink = (link) => {
79
73
  const u = new URL(link);
80
74
  const host = (u.hostname || '').toLowerCase();
81
75
  if (host.endsWith('pintere21313213st.com')) return true;
82
- } catch (_) { }
76
+ } catch (_) {}
83
77
  return videoPlatforms.some(platform => link.startsWith(platform));
84
78
  };
85
79
 
@@ -152,14 +146,14 @@ async function safeUnlinkWithRetry(filePath, maxRetries = 3) {
152
146
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
153
147
  try {
154
148
  const resolvedPath = path.resolve(filePath);
155
-
149
+
156
150
  if (fs.existsSync(resolvedPath)) {
157
151
  // Check if file is in use (Windows)
158
152
  if (process.platform === 'win32' && isFileInUse(resolvedPath)) {
159
153
  await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
160
154
  continue;
161
155
  }
162
-
156
+
163
157
  fs.rmSync(resolvedPath, { force: true });
164
158
  return true;
165
159
  } else {
@@ -231,7 +225,7 @@ async function validateVideoUrl(url, platform) {
231
225
  return status >= 200 && status < 400; // Accept redirects
232
226
  }
233
227
  });
234
-
228
+
235
229
  return true;
236
230
  } catch (error) {
237
231
  return false;
@@ -243,18 +237,18 @@ function validateTwitterUrl(url) {
243
237
  try {
244
238
  // Remove query parameters that might cause issues
245
239
  const cleanUrl = url.split('?')[0];
246
-
240
+
247
241
  // Ensure it's a valid Twitter/X URL format
248
242
  if (!cleanUrl.includes('x.com') && !cleanUrl.includes('twitter.com')) {
249
243
  throw new Error("Not a valid Twitter/X URL");
250
244
  }
251
-
245
+
252
246
  // Check if it has the required structure
253
247
  const urlParts = cleanUrl.split('/');
254
248
  if (urlParts.length < 5) {
255
249
  throw new Error("Invalid Twitter/X URL structure");
256
250
  }
257
-
251
+
258
252
  return cleanUrl;
259
253
  } catch (error) {
260
254
  throw new Error(`Twitter URL validation failed: ${error.message}`);
@@ -293,10 +287,10 @@ async function downloadYoutubeAudio(url, outputPath) {
293
287
  try {
294
288
  const ytdlp = new YtDlp({ ffmpegPath });
295
289
  const cookiesPath = getYoutubeCookiesPath();
296
-
290
+
297
291
  // Garantir que o nome do arquivo tenha a extensão correta
298
292
  const baseName = outputPath.replace(/\.[^/.]+$/, ""); // Remove extensão se existir
299
-
293
+
300
294
  // Primeiro, tentar com a API de opções
301
295
  try {
302
296
  const options = {
@@ -307,11 +301,11 @@ async function downloadYoutubeAudio(url, outputPath) {
307
301
  },
308
302
  output: outputPath
309
303
  };
310
-
304
+
311
305
  if (cookiesPath) {
312
306
  options.cookies = cookiesPath;
313
307
  }
314
-
308
+
315
309
  // Remover logs detalhados para download de áudio do YouTube
316
310
  const result = await ytdlp.downloadAsync(url, options);
317
311
  // Verificar se o arquivo foi criado
@@ -320,7 +314,7 @@ async function downloadYoutubeAudio(url, outputPath) {
320
314
  resolve(actualFileName);
321
315
  return;
322
316
  }
323
-
317
+
324
318
  // Tentar encontrar o arquivo com extensão diferente
325
319
  const files = fs.readdirSync('./');
326
320
  const downloadedFile = files.find(file => file.startsWith(baseName.split('/').pop()));
@@ -331,18 +325,18 @@ async function downloadYoutubeAudio(url, outputPath) {
331
325
  } catch (apiError) {
332
326
  console.log('API method failed, trying direct args method:', apiError.message);
333
327
  }
334
-
328
+
335
329
  // Fallback: usar argumentos diretos para máxima qualidade de áudio
336
330
  const args = [
337
331
  url,
338
332
  '-f', 'bestaudio[ext=mp3]/bestaudio',
339
333
  '-o', outputPath
340
334
  ];
341
-
335
+
342
336
  if (cookiesPath) {
343
337
  args.push('--cookies', cookiesPath);
344
338
  }
345
-
339
+
346
340
  // Remover logs detalhados para download de áudio do YouTube
347
341
  const result = await ytdlp.execAsync(args);
348
342
  // Verificar se o arquivo foi criado
@@ -369,53 +363,25 @@ async function downloadYoutubeAudio(url, outputPath) {
369
363
  // Enhanced function to get platform type from URL
370
364
  function getPlatformType(url) {
371
365
  const lowerUrl = url.toLowerCase();
372
-
366
+
373
367
  if (lowerUrl.includes("instagram.com") || lowerUrl.includes("instagr.am")) return "instagram";
374
368
  if (lowerUrl.includes("tiktok.com") || lowerUrl.includes("vm.tiktok.com") || lowerUrl.includes("vt.tiktok.com")) return "tiktok";
375
369
  if (lowerUrl.includes("facebook.com") || lowerUrl.includes("fb.watch") || lowerUrl.includes("fb.com")) return "facebook";
376
370
  if (lowerUrl.includes("mediafire.com")) return "mediafire";
377
371
  if (lowerUrl.includes("x.com") || lowerUrl.includes("twitter.com")) return "twitter";
378
- if (lowerUrl.includes("youtube.com") || lowerUrl.includes("youtu.be") || lowerUrl.includes("m.youtube.com")) return "youtube";
379
-
372
+ if (lowerUrl.includes("youtube.com") || lowerUrl.includes("you112t12u.be") || lowerUrl.includes("m.y11outu314be.com")) return "youtu1354be";
373
+
380
374
  return "unknown";
381
375
  }
382
376
 
383
- // Function to get direct video URL from Twitter using yt-dlp
384
- async function downloadTwitterWithYtDlp(url) {
385
- return new Promise(async (resolve, reject) => {
386
- try {
387
- const ytdlp = new YtDlp({ ffmpegPath });
388
- // Use -g to get URL, -f "best[ext=mp4]/best" to get best quality single file if possible
389
- const args = [
390
- url,
391
- '-g',
392
- '-f', 'best[ext=mp4]/best'
393
- ];
394
-
395
- const result = await ytdlp.execAsync(args);
396
- // yt-dlp might return multiple lines if it finds separate audio/video streams
397
- // We take the first one, but for Twitter it is usually a single mp4 file
398
- const videoUrl = result.trim().split('\n')[0];
399
-
400
- if (videoUrl && videoUrl.startsWith('http')) {
401
- resolve(videoUrl);
402
- } else {
403
- reject(new Error('yt-dlp did not return a valid URL'));
404
- }
405
- } catch (error) {
406
- reject(error);
407
- }
408
- });
409
- }
410
-
411
377
  // Enhanced fallback download function with platform-specific methods
412
378
  async function tryFallbackDownload(url, maxRetries = 3) {
413
379
  const platform = getPlatformType(url);
414
-
380
+
415
381
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
416
382
  try {
417
383
  let videoUrl = null;
418
-
384
+
419
385
  // Platform-specific fallback methods
420
386
  switch (platform) {
421
387
  case "instagram": {
@@ -461,7 +427,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
461
427
  }
462
428
  }
463
429
  ];
464
-
430
+
465
431
  for (const method of methods) {
466
432
  try {
467
433
  videoUrl = await method();
@@ -472,7 +438,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
472
438
  }
473
439
  break;
474
440
  }
475
-
441
+
476
442
  case "tiktok": {
477
443
  // Try multiple TikTok methods
478
444
  const methods = [
@@ -507,7 +473,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
507
473
  throw new Error("No valid video in Tiktok.Downloader v2 response");
508
474
  }
509
475
  ];
510
-
476
+
511
477
  for (const method of methods) {
512
478
  try {
513
479
  videoUrl = await method();
@@ -518,7 +484,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
518
484
  }
519
485
  break;
520
486
  }
521
-
487
+
522
488
  case "facebook": {
523
489
  // Try multiple Facebook methods
524
490
  const methods = [
@@ -542,7 +508,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
542
508
  throw new Error("No valid video in fbdown response with custom headers");
543
509
  }
544
510
  ];
545
-
511
+
546
512
  for (const method of methods) {
547
513
  try {
548
514
  videoUrl = await method();
@@ -553,46 +519,42 @@ async function tryFallbackDownload(url, maxRetries = 3) {
553
519
  }
554
520
  break;
555
521
  }
556
-
522
+
557
523
  case "twitter": {
558
- // Try using yt-dlp first (Most reliable), then fallback to existing methods
524
+ // Try multiple Twitter methods with better response handling
559
525
  const methods = [
560
- async () => {
561
- // Priority 1: yt-dlp
562
- return await downloadTwitterWithYtDlp(url);
563
- },
564
526
  async () => {
565
527
  const cleanUrl = validateTwitterUrl(url);
566
528
  const data = await TwitterDL(cleanUrl, {});
567
-
529
+
568
530
  // Check multiple possible response structures
569
531
  if (data && data.result) {
570
532
  // Structure 1: data.result.media[0].videos[0].url
571
- if (data.result.media && data.result.media[0] &&
572
- data.result.media[0].videos && data.result.media[0].videos[0] &&
573
- 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) {
574
536
  return data.result.media[0].videos[0].url;
575
537
  }
576
-
538
+
577
539
  // Structure 2: data.result.video[0].url
578
540
  if (data.result.video && data.result.video[0] && data.result.video[0].url) {
579
541
  return data.result.video[0].url;
580
542
  }
581
-
543
+
582
544
  // Structure 3: data.result.url (direct URL)
583
545
  if (data.result.url) {
584
546
  return data.result.url;
585
547
  }
586
-
548
+
587
549
  // Structure 4: data.result.media[0].url
588
550
  if (data.result.media && data.result.media[0] && data.result.media[0].url) {
589
551
  return data.result.media[0].url;
590
552
  }
591
-
553
+
592
554
  // Structure 5: Check for any video URL in the entire response
593
555
  const findVideoUrl = (obj) => {
594
- if (typeof obj === 'string' && obj.includes('http') &&
595
- (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
556
+ if (typeof obj === 'string' && obj.includes('http') &&
557
+ (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
596
558
  return obj;
597
559
  }
598
560
  if (typeof obj === 'object' && obj !== null) {
@@ -603,13 +565,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
603
565
  }
604
566
  return null;
605
567
  };
606
-
568
+
607
569
  const foundUrl = findVideoUrl(data.result);
608
570
  if (foundUrl) {
609
571
  return foundUrl;
610
572
  }
611
573
  }
612
-
574
+
613
575
  throw new Error("No valid video URL found in TwitterDL response structure");
614
576
  },
615
577
  async () => {
@@ -625,30 +587,30 @@ async function tryFallbackDownload(url, maxRetries = 3) {
625
587
  'Upgrade-Insecure-Requests': '1'
626
588
  }
627
589
  });
628
-
590
+
629
591
  // Same structure checking as above
630
592
  if (data && data.result) {
631
- if (data.result.media && data.result.media[0] &&
632
- data.result.media[0].videos && data.result.media[0].videos[0] &&
633
- data.result.media[0].videos[0].url) {
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) {
634
596
  return data.result.media[0].videos[0].url;
635
597
  }
636
-
598
+
637
599
  if (data.result.video && data.result.video[0] && data.result.video[0].url) {
638
600
  return data.result.video[0].url;
639
601
  }
640
-
602
+
641
603
  if (data.result.url) {
642
604
  return data.result.url;
643
605
  }
644
-
606
+
645
607
  if (data.result.media && data.result.media[0] && data.result.media[0].url) {
646
608
  return data.result.media[0].url;
647
609
  }
648
-
610
+
649
611
  const findVideoUrl = (obj) => {
650
- if (typeof obj === 'string' && obj.includes('http') &&
651
- (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
612
+ if (typeof obj === 'string' && obj.includes('http') &&
613
+ (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
652
614
  return obj;
653
615
  }
654
616
  if (typeof obj === 'object' && obj !== null) {
@@ -659,13 +621,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
659
621
  }
660
622
  return null;
661
623
  };
662
-
624
+
663
625
  const foundUrl = findVideoUrl(data.result);
664
626
  if (foundUrl) {
665
627
  return foundUrl;
666
628
  }
667
629
  }
668
-
630
+
669
631
  throw new Error("No valid video URL found in TwitterDL response with custom headers");
670
632
  },
671
633
  async () => {
@@ -701,9 +663,9 @@ async function tryFallbackDownload(url, maxRetries = 3) {
701
663
  const data = await btchOld[funcName](cleanUrl);
702
664
  // Check multiple possible response structures
703
665
  if (data && data.result) {
704
- if (data.result.media && data.result.media[0] &&
705
- data.result.media[0].videos && data.result.media[0].videos[0] &&
706
- data.result.media[0].videos[0].url) {
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) {
707
669
  return data.result.media[0].videos[0].url;
708
670
  }
709
671
  if (data.result.video && data.result.video[0] && data.result.video[0].url) {
@@ -730,7 +692,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
730
692
  }
731
693
  }
732
694
  ];
733
-
695
+
734
696
  for (const method of methods) {
735
697
  try {
736
698
  videoUrl = await method();
@@ -741,12 +703,12 @@ async function tryFallbackDownload(url, maxRetries = 3) {
741
703
  }
742
704
  break;
743
705
  }
744
-
706
+
745
707
  case "youtube": {
746
708
  // YouTube doesn't need fallback - it has its own specific method
747
709
  throw new Error("YouTube downloads should use the primary method, not fallback");
748
710
  }
749
-
711
+
750
712
  case "mediafire": {
751
713
  // Try MediaFire method
752
714
  try {
@@ -784,10 +746,10 @@ async function tryFallbackDownload(url, maxRetries = 3) {
784
746
  };
785
747
  mediaUrl = pickFrom(data);
786
748
  if (mediaUrl) return mediaUrl;
787
- } catch (_) { }
749
+ } catch (_) {}
788
750
  break;
789
751
  }
790
-
752
+
791
753
  default: {
792
754
  // Generic fallback for unknown platforms
793
755
  try {
@@ -803,18 +765,18 @@ async function tryFallbackDownload(url, maxRetries = 3) {
803
765
  break;
804
766
  }
805
767
  }
806
-
768
+
807
769
  // If we got a video URL, validate it
808
770
  if (videoUrl && videoUrl.includes("http")) {
809
771
  // Validate the URL is accessible
810
772
  try {
811
- const response = await axios.head(videoUrl, {
773
+ const response = await axios.head(videoUrl, {
812
774
  timeout: 10000,
813
775
  headers: {
814
776
  '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'
815
777
  }
816
778
  });
817
-
779
+
818
780
  if (response.status === 200) {
819
781
  return videoUrl;
820
782
  } else {
@@ -826,7 +788,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
826
788
  } else {
827
789
  throw new Error("No valid video URL obtained from any fallback method");
828
790
  }
829
-
791
+
830
792
  } catch (error) {
831
793
  if (attempt === maxRetries) {
832
794
  throw new Error(`Enhanced fallback method failed after ${maxRetries} attempts for ${platform}: ${error.message}`);
@@ -873,8 +835,9 @@ const MediaDownloader = async (url, options = {}) => {
873
835
 
874
836
  // Verificar se é YouTube e lançar erro customizado
875
837
  const platform = getPlatformType(url);
876
- // YouTube is now supported
877
-
838
+ if (platform === "youtube") {
839
+ throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook");
840
+ }
878
841
 
879
842
  await cleanupTempFiles(); // Clean up previous temp files
880
843
 
@@ -886,21 +849,21 @@ const MediaDownloader = async (url, options = {}) => {
886
849
  downloadedFilePath = await downloadSmartVideo(url, config);
887
850
  } catch (error) {
888
851
  const platform = getPlatformType(url);
889
-
890
- // YouTube doesn't use fallback method - it has its own specific implementation in downloadSmartVideo
852
+
853
+ // YouTube doesn't use fallback method - it has its own specific implementation
891
854
  if (platform === "youtube") {
892
855
  throw new Error(`YouTube download failed: ${error.message}`);
893
856
  }
894
-
857
+
895
858
  try {
896
859
  const fallbackUrl = await tryFallbackDownload(url);
897
-
860
+
898
861
  // Validate fallback URL
899
862
  const isValid = await validateVideoUrl(fallbackUrl, platform);
900
863
  if (!isValid) {
901
864
  throw new Error("Fallback URL validation failed");
902
865
  }
903
-
866
+
904
867
  downloadedFilePath = await downloadDirectVideo(fallbackUrl, config);
905
868
  } catch (fallbackError) {
906
869
  throw new Error(`Failed to download video with both methods: ${fallbackError.message}`);
@@ -1023,15 +986,15 @@ async function downloadSmartVideo(url, config) {
1023
986
  throw new Error("No video URLs found in Facebook response");
1024
987
  }
1025
988
  videoUrl = data.Normal_video || data.HD;
1026
-
989
+
1027
990
  // Validate the URL is accessible
1028
- const response = await axios.head(videoUrl, {
991
+ const response = await axios.head(videoUrl, {
1029
992
  timeout: 10000,
1030
993
  headers: {
1031
994
  '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'
1032
995
  }
1033
996
  }).catch(() => null);
1034
-
997
+
1035
998
  if (!response || response.status !== 200) {
1036
999
  throw new Error("Facebook video URL is not accessible");
1037
1000
  }
@@ -1103,12 +1066,12 @@ async function downloadSmartVideo(url, config) {
1103
1066
  // Validate and clean the Twitter URL
1104
1067
  const cleanUrl = validateTwitterUrl(url);
1105
1068
  const data = await TwitterDL(cleanUrl, {});
1106
-
1069
+
1107
1070
  if (data && data.result) {
1108
1071
  // Try multiple possible response structures
1109
- if (data.result.media && data.result.media[0] &&
1110
- data.result.media[0].videos && data.result.media[0].videos[0] &&
1111
- data.result.media[0].videos[0].url) {
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) {
1112
1075
  videoUrl = data.result.media[0].videos[0].url;
1113
1076
  } else if (data.result.video && data.result.video[0] && data.result.video[0].url) {
1114
1077
  videoUrl = data.result.video[0].url;
@@ -1119,8 +1082,8 @@ async function downloadSmartVideo(url, config) {
1119
1082
  } else {
1120
1083
  // Search for any video URL in the response
1121
1084
  const findVideoUrl = (obj) => {
1122
- if (typeof obj === 'string' && obj.includes('http') &&
1123
- (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
1085
+ if (typeof obj === 'string' && obj.includes('http') &&
1086
+ (obj.includes('.mp4') || obj.includes('video') || obj.includes('media'))) {
1124
1087
  return obj;
1125
1088
  }
1126
1089
  if (typeof obj === 'object' && obj !== null) {
@@ -1131,7 +1094,7 @@ async function downloadSmartVideo(url, config) {
1131
1094
  }
1132
1095
  return null;
1133
1096
  };
1134
-
1097
+
1135
1098
  const foundUrl = findVideoUrl(data.result);
1136
1099
  if (foundUrl) {
1137
1100
  videoUrl = foundUrl;
@@ -1147,56 +1110,6 @@ async function downloadSmartVideo(url, config) {
1147
1110
  }
1148
1111
  break;
1149
1112
  }
1150
- case "youtube": {
1151
- try {
1152
- const data = await youtube(url);
1153
- // Try to find the video URL in the response
1154
- // Common patterns: data.url, data.mp4, data.link, or direct string
1155
-
1156
- if (!data) throw new Error("No data returned from youtube downloader");
1157
-
1158
- // Recursive function to find a valid video URL in the object
1159
- const findVideoUrl = (obj) => {
1160
- if (!obj) return null;
1161
- if (typeof obj === 'string' && obj.startsWith('http')) return obj;
1162
-
1163
- if (Array.isArray(obj)) {
1164
- for (const item of obj) {
1165
- const found = findVideoUrl(item);
1166
- if (found) return found;
1167
- }
1168
- } else if (typeof obj === 'object') {
1169
- // Prioritize certain keys
1170
- const priorities = ['url', 'link', 'download', 'mp4', 'video', 'src'];
1171
- for (const key of priorities) {
1172
- if (obj[key]) {
1173
- const found = findVideoUrl(obj[key]);
1174
- if (found) return found;
1175
- }
1176
- }
1177
- // If not found in priorities, check all keys
1178
- for (const key of Object.keys(obj)) {
1179
- if (!priorities.includes(key)) {
1180
- const found = findVideoUrl(obj[key]);
1181
- if (found) return found;
1182
- }
1183
- }
1184
- }
1185
- return null;
1186
- };
1187
-
1188
- videoUrl = findVideoUrl(data);
1189
-
1190
- if (!videoUrl) {
1191
- console.log('YouTube data dump:', JSON.stringify(data, null, 2)); // Debug log since we can't test
1192
- throw new Error("Could not extract video URL from YouTube response");
1193
- }
1194
-
1195
- } catch (error) {
1196
- throw new Error(`YouTube download failed: ${error.message}`);
1197
- }
1198
- break;
1199
- }
1200
1113
  default:
1201
1114
  throw new Error("Platform not supported or invalid link.");
1202
1115
  }
@@ -1204,21 +1117,21 @@ async function downloadSmartVideo(url, config) {
1204
1117
  if (!videoUrl || !videoUrl.includes("http")) {
1205
1118
  throw new Error("Returned video URL is invalid or unavailable.");
1206
1119
  }
1207
-
1120
+
1208
1121
  // Download the video with better error handling
1209
- const response = await axios({
1210
- url: videoUrl,
1211
- method: "GET",
1122
+ const response = await axios({
1123
+ url: videoUrl,
1124
+ method: "GET",
1212
1125
  responseType: "stream",
1213
1126
  timeout: 30000,
1214
1127
  headers: {
1215
1128
  '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'
1216
1129
  }
1217
1130
  });
1218
-
1131
+
1219
1132
  // Create minimal unique file name in output dir
1220
- let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
1221
-
1133
+ let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
1134
+
1222
1135
  const videoWriter = fs.createWriteStream(fileName);
1223
1136
  response.data.pipe(videoWriter);
1224
1137
 
@@ -1243,19 +1156,19 @@ async function downloadSmartVideo(url, config) {
1243
1156
  // Function for direct video download
1244
1157
  async function downloadDirectVideo(url, config) {
1245
1158
  try {
1246
- const response = await axios({
1247
- url: url,
1248
- method: "GET",
1159
+ const response = await axios({
1160
+ url: url,
1161
+ method: "GET",
1249
1162
  responseType: "stream",
1250
1163
  timeout: 30000,
1251
1164
  headers: {
1252
1165
  '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'
1253
1166
  }
1254
1167
  });
1255
-
1168
+
1256
1169
  // Create minimal unique file name in output dir
1257
- let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
1258
-
1170
+ let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
1171
+
1259
1172
  const videoWriter = fs.createWriteStream(fileName);
1260
1173
  response.data.pipe(videoWriter);
1261
1174
 
@@ -1303,7 +1216,7 @@ async function downloadGenericFile(url, preferredExt = null) {
1303
1216
  else ext = 'bin';
1304
1217
  }
1305
1218
 
1306
- const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
1219
+ const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.${ext}`);
1307
1220
  const writer = fs.createWriteStream(fileName);
1308
1221
  response.data.pipe(writer);
1309
1222
 
@@ -1318,7 +1231,7 @@ async function downloadGenericFile(url, preferredExt = null) {
1318
1231
 
1319
1232
  // Function to rotate video
1320
1233
  async function rotateVideo(fileName, rotation) {
1321
- const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0, 4)}.mp4`);
1234
+ const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0,4)}.mp4`);
1322
1235
  let angle;
1323
1236
  switch (rotation.toLowerCase()) {
1324
1237
  case "left": angle = "transpose=2"; break;
@@ -1369,8 +1282,8 @@ async function cleanupTempFiles() {
1369
1282
  try {
1370
1283
  if (!fs.existsSync(TEMP_DIR)) return;
1371
1284
  const files = fs.readdirSync(TEMP_DIR);
1372
- const tempFiles = files.filter(file =>
1373
- /^temp_video.*\.mp4$/.test(file) ||
1285
+ const tempFiles = files.filter(file =>
1286
+ /^temp_video.*\.mp4$/.test(file) ||
1374
1287
  /_audio\.mp3$/.test(file) ||
1375
1288
  /_rotated\.mp4$/.test(file) ||
1376
1289
  /_cropped\.mp4$/.test(file) ||
@@ -1402,9 +1315,9 @@ async function cleanupOutputFiles() {
1402
1315
  if (now - st.mtimeMs > maxAgeMs) {
1403
1316
  await safeUnlinkWithRetry(full);
1404
1317
  }
1405
- } catch (_) { }
1318
+ } catch (_) {}
1406
1319
  }
1407
- } catch (_) { }
1320
+ } catch (_) {}
1408
1321
  }
1409
1322
 
1410
1323
  // Schedule periodic cleanup (every 10 minutes)
@@ -1418,7 +1331,7 @@ cleanupOutputFiles();
1418
1331
  // Function to auto-crop video
1419
1332
  async function autoCrop(fileName) {
1420
1333
  const inputPath = fileName;
1421
- const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0, 4)}.mp4`);
1334
+ const outputPath = path.join(OUTPUT_DIR, `vc_${uuidv4().slice(0,4)}.mp4`);
1422
1335
 
1423
1336
  return new Promise((resolve, reject) => {
1424
1337
  let cropValues = null;
@@ -1426,13 +1339,13 @@ async function autoCrop(fileName) {
1426
1339
  .outputOptions('-vf', 'cropdetect=24:16:0')
1427
1340
  .outputFormat('null')
1428
1341
  .output('-')
1429
- .on('stderr', function (stderrLine) {
1342
+ .on('stderr', function(stderrLine) {
1430
1343
  const cropMatch = stderrLine.match(/crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/);
1431
1344
  if (cropMatch) {
1432
1345
  cropValues = `crop=${cropMatch[1]}:${cropMatch[2]}:${cropMatch[3]}:${cropMatch[4]}`;
1433
1346
  }
1434
1347
  })
1435
- .on('end', function () {
1348
+ .on('end', function() {
1436
1349
  if (!cropValues) {
1437
1350
  resolve(inputPath);
1438
1351
  return;
@@ -1465,7 +1378,7 @@ async function checkAndCompressVideo(filePath, limitSizeMB, platform = null) {
1465
1378
  return filePath;
1466
1379
  }
1467
1380
 
1468
- const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0, 4)}.mp4`);
1381
+ const outputPath = path.join(OUTPUT_DIR, `vx_${uuidv4().slice(0,4)}.mp4`);
1469
1382
 
1470
1383
  return new Promise((resolve, reject) => {
1471
1384
  ffmpeg(filePath)
@@ -1500,17 +1413,17 @@ async function convertVideoFormat(inputPath, targetFormat) {
1500
1413
  const supported = ["mp4", "mov", "webm", "mkv"];
1501
1414
  if (!supported.includes(fmt)) return inputPath;
1502
1415
 
1503
- const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0, 4)}.${fmt}`);
1416
+ const outputPath = path.join(OUTPUT_DIR, `vf_${uuidv4().slice(0,4)}.${fmt}`);
1504
1417
 
1505
1418
  const ff = ffmpeg(inputPath);
1506
1419
  switch (fmt) {
1507
1420
  case "mp4":
1508
1421
  case "mov":
1509
1422
  case "mkv":
1510
- ff.outputOptions("-c:v", "libx264", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k");
1423
+ ff.outputOptions("-c:v","libx264","-pix_fmt","yuv420p","-c:a","aac","-b:a","192k");
1511
1424
  break;
1512
1425
  case "webm":
1513
- ff.outputOptions("-c:v", "libvpx-vp9", "-b:v", "0", "-crf", "30", "-c:a", "libopus", "-b:a", "128k");
1426
+ ff.outputOptions("-c:v","libvpx-vp9","-b:v","0","-crf","30","-c:a","libopus","-b:a","128k");
1514
1427
  break;
1515
1428
  default:
1516
1429
  return inputPath;
@@ -1567,14 +1480,14 @@ async function unshortenUrl(url) {
1567
1480
  return status >= 200 && status < 400; // Accept redirects
1568
1481
  }
1569
1482
  });
1570
-
1483
+
1571
1484
  // Get the final URL after all redirects
1572
1485
  const finalUrl = response.request.res.responseUrl || response.config.url;
1573
1486
  return finalUrl;
1574
1487
  }
1575
-
1488
+
1576
1489
  // For other URLs, use the original method
1577
- const response = await axios.head(url, {
1490
+ const response = await axios.head(url, {
1578
1491
  maxRedirects: 10,
1579
1492
  timeout: 10000,
1580
1493
  headers: {
@@ -1643,10 +1556,10 @@ const AudioDownloader = async (url, options = {}) => {
1643
1556
  if (downloadedFilePath) {
1644
1557
  // Extrair áudio em mp3
1645
1558
  audioFilePath = await extractAudioMp3(downloadedFilePath);
1646
-
1559
+
1647
1560
  // Remove o arquivo de vídeo temporário após extrair o áudio
1648
1561
  await safeUnlinkWithRetry(downloadedFilePath);
1649
-
1562
+
1650
1563
  const result = await uploadToGoFileIfNeeded(audioFilePath);
1651
1564
  return result;
1652
1565
  } else {
@@ -1662,7 +1575,7 @@ const AudioDownloader = async (url, options = {}) => {
1662
1575
  // Função para extrair áudio em mp3 usando ffmpeg
1663
1576
  async function extractAudioMp3(videoPath) {
1664
1577
  return new Promise((resolve, reject) => {
1665
- const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0, 4)}.mp3`);
1578
+ const audioPath = path.join(OUTPUT_DIR, `a_${uuidv4().slice(0,4)}.mp3`);
1666
1579
  ffmpeg(videoPath)
1667
1580
  .noVideo()
1668
1581
  .audioCodec('libmp3lame')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frostpv",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "downloads",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -13,7 +13,7 @@
13
13
  "license": "ISC",
14
14
  "dependencies": {
15
15
  "axios": "1.12.0",
16
- "btch-downloader": "6.0.23",
16
+ "btch-downloader": "6.0.25",
17
17
  "btch-downloader-old": "npm:btch-downloader@4.0.15",
18
18
  "express": "^5.1.0",
19
19
  "ffmpeg-ffprobe-static": "^6.1.1-rc.5",
@@ -22,7 +22,7 @@
22
22
  "path": "^0.12.7",
23
23
  "twitter-downloader": "^1.1.8",
24
24
  "uuid": "^11.1.0",
25
- "@tobyg74/tiktok-api-dl": "^1.3.2",
25
+ "@tobyg74/tiktok-api-dl": "^1.3.7",
26
26
  "ytdlp-nodejs": "2.3.4"
27
27
  }
28
28
  }