frostpv 1.0.10 → 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 -246
  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,98 +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
-
1155
- if (!data) throw new Error("No data returned from youtube downloader");
1156
-
1157
- // Helper to check if a value looks like a video URL
1158
- const isVideoUrl = (val) => typeof val === 'string' && val.startsWith('http') && !val.includes('.mp3');
1159
-
1160
- let bestUrl = null;
1161
- let bestScore = -1;
1162
-
1163
- // Function to score keys based on quality
1164
- const getScore = (key) => {
1165
- const k = key.toLowerCase();
1166
- if (k.includes('1080')) return 100;
1167
- if (k.includes('720')) return 80;
1168
- if (k.includes('480')) return 60;
1169
- if (k.includes('hd')) return 90;
1170
- if (k.includes('sd')) return 50;
1171
- if (k.includes('360')) return 40;
1172
- if (k.includes('mp4')) return 30;
1173
- if (k.includes('video')) return 20;
1174
- return 10;
1175
- };
1176
-
1177
- // Recursive function to gather all candidates
1178
- const gatherCandidates = (obj) => {
1179
- if (!obj) return;
1180
-
1181
- if (typeof obj === 'object') {
1182
- for (const key in obj) {
1183
- const val = obj[key];
1184
- if (isVideoUrl(val)) {
1185
- const score = getScore(key);
1186
- if (score > bestScore) {
1187
- bestScore = score;
1188
- bestUrl = val;
1189
- }
1190
- } else if (typeof val === 'object') {
1191
- // Check if this object represents a format (e.g. { quality: '720p', url: '...' })
1192
- if (val.url && isVideoUrl(val.url)) {
1193
- let score = getScore(key); // Score from key name
1194
- if (val.quality || val.resolution) {
1195
- score = Math.max(score, getScore(String(val.quality || val.resolution)));
1196
- }
1197
- if (score > bestScore) {
1198
- bestScore = score;
1199
- bestUrl = val.url;
1200
- }
1201
- }
1202
- // Recurse
1203
- gatherCandidates(val);
1204
- }
1205
- }
1206
- }
1207
- };
1208
-
1209
- gatherCandidates(data);
1210
-
1211
- // If no scored candidate found, try direct simple extraction with priority keys
1212
- if (!bestUrl) {
1213
- const priorities = ['mp4', 'url', 'link', 'download', 'video'];
1214
- const findSimple = (obj) => {
1215
- if (!obj) return null;
1216
- for (const key of priorities) {
1217
- if (obj[key] && isVideoUrl(obj[key])) return obj[key];
1218
- }
1219
- for (const key in obj) {
1220
- if (typeof obj[key] === 'object') {
1221
- const found = findSimple(obj[key]);
1222
- if (found) return found;
1223
- }
1224
- }
1225
- return null;
1226
- };
1227
- bestUrl = findSimple(data);
1228
- }
1229
-
1230
- videoUrl = bestUrl;
1231
-
1232
- if (!videoUrl) {
1233
- console.log('YouTube data dump:', JSON.stringify(data, null, 2)); // Debug log since we can't test
1234
- throw new Error("Could not extract video URL from YouTube response");
1235
- }
1236
-
1237
- } catch (error) {
1238
- throw new Error(`YouTube download failed: ${error.message}`);
1239
- }
1240
- break;
1241
- }
1242
1113
  default:
1243
1114
  throw new Error("Platform not supported or invalid link.");
1244
1115
  }
@@ -1246,21 +1117,21 @@ async function downloadSmartVideo(url, config) {
1246
1117
  if (!videoUrl || !videoUrl.includes("http")) {
1247
1118
  throw new Error("Returned video URL is invalid or unavailable.");
1248
1119
  }
1249
-
1120
+
1250
1121
  // Download the video with better error handling
1251
- const response = await axios({
1252
- url: videoUrl,
1253
- method: "GET",
1122
+ const response = await axios({
1123
+ url: videoUrl,
1124
+ method: "GET",
1254
1125
  responseType: "stream",
1255
1126
  timeout: 30000,
1256
1127
  headers: {
1257
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'
1258
1129
  }
1259
1130
  });
1260
-
1131
+
1261
1132
  // Create minimal unique file name in output dir
1262
- let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
1263
-
1133
+ let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
1134
+
1264
1135
  const videoWriter = fs.createWriteStream(fileName);
1265
1136
  response.data.pipe(videoWriter);
1266
1137
 
@@ -1285,19 +1156,19 @@ async function downloadSmartVideo(url, config) {
1285
1156
  // Function for direct video download
1286
1157
  async function downloadDirectVideo(url, config) {
1287
1158
  try {
1288
- const response = await axios({
1289
- url: url,
1290
- method: "GET",
1159
+ const response = await axios({
1160
+ url: url,
1161
+ method: "GET",
1291
1162
  responseType: "stream",
1292
1163
  timeout: 30000,
1293
1164
  headers: {
1294
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'
1295
1166
  }
1296
1167
  });
1297
-
1168
+
1298
1169
  // Create minimal unique file name in output dir
1299
- let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
1300
-
1170
+ let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
1171
+
1301
1172
  const videoWriter = fs.createWriteStream(fileName);
1302
1173
  response.data.pipe(videoWriter);
1303
1174
 
@@ -1345,7 +1216,7 @@ async function downloadGenericFile(url, preferredExt = null) {
1345
1216
  else ext = 'bin';
1346
1217
  }
1347
1218
 
1348
- 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}`);
1349
1220
  const writer = fs.createWriteStream(fileName);
1350
1221
  response.data.pipe(writer);
1351
1222
 
@@ -1360,7 +1231,7 @@ async function downloadGenericFile(url, preferredExt = null) {
1360
1231
 
1361
1232
  // Function to rotate video
1362
1233
  async function rotateVideo(fileName, rotation) {
1363
- 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`);
1364
1235
  let angle;
1365
1236
  switch (rotation.toLowerCase()) {
1366
1237
  case "left": angle = "transpose=2"; break;
@@ -1411,8 +1282,8 @@ async function cleanupTempFiles() {
1411
1282
  try {
1412
1283
  if (!fs.existsSync(TEMP_DIR)) return;
1413
1284
  const files = fs.readdirSync(TEMP_DIR);
1414
- const tempFiles = files.filter(file =>
1415
- /^temp_video.*\.mp4$/.test(file) ||
1285
+ const tempFiles = files.filter(file =>
1286
+ /^temp_video.*\.mp4$/.test(file) ||
1416
1287
  /_audio\.mp3$/.test(file) ||
1417
1288
  /_rotated\.mp4$/.test(file) ||
1418
1289
  /_cropped\.mp4$/.test(file) ||
@@ -1444,9 +1315,9 @@ async function cleanupOutputFiles() {
1444
1315
  if (now - st.mtimeMs > maxAgeMs) {
1445
1316
  await safeUnlinkWithRetry(full);
1446
1317
  }
1447
- } catch (_) { }
1318
+ } catch (_) {}
1448
1319
  }
1449
- } catch (_) { }
1320
+ } catch (_) {}
1450
1321
  }
1451
1322
 
1452
1323
  // Schedule periodic cleanup (every 10 minutes)
@@ -1460,7 +1331,7 @@ cleanupOutputFiles();
1460
1331
  // Function to auto-crop video
1461
1332
  async function autoCrop(fileName) {
1462
1333
  const inputPath = fileName;
1463
- 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`);
1464
1335
 
1465
1336
  return new Promise((resolve, reject) => {
1466
1337
  let cropValues = null;
@@ -1468,13 +1339,13 @@ async function autoCrop(fileName) {
1468
1339
  .outputOptions('-vf', 'cropdetect=24:16:0')
1469
1340
  .outputFormat('null')
1470
1341
  .output('-')
1471
- .on('stderr', function (stderrLine) {
1342
+ .on('stderr', function(stderrLine) {
1472
1343
  const cropMatch = stderrLine.match(/crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/);
1473
1344
  if (cropMatch) {
1474
1345
  cropValues = `crop=${cropMatch[1]}:${cropMatch[2]}:${cropMatch[3]}:${cropMatch[4]}`;
1475
1346
  }
1476
1347
  })
1477
- .on('end', function () {
1348
+ .on('end', function() {
1478
1349
  if (!cropValues) {
1479
1350
  resolve(inputPath);
1480
1351
  return;
@@ -1507,7 +1378,7 @@ async function checkAndCompressVideo(filePath, limitSizeMB, platform = null) {
1507
1378
  return filePath;
1508
1379
  }
1509
1380
 
1510
- 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`);
1511
1382
 
1512
1383
  return new Promise((resolve, reject) => {
1513
1384
  ffmpeg(filePath)
@@ -1542,17 +1413,17 @@ async function convertVideoFormat(inputPath, targetFormat) {
1542
1413
  const supported = ["mp4", "mov", "webm", "mkv"];
1543
1414
  if (!supported.includes(fmt)) return inputPath;
1544
1415
 
1545
- 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}`);
1546
1417
 
1547
1418
  const ff = ffmpeg(inputPath);
1548
1419
  switch (fmt) {
1549
1420
  case "mp4":
1550
1421
  case "mov":
1551
1422
  case "mkv":
1552
- 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");
1553
1424
  break;
1554
1425
  case "webm":
1555
- 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");
1556
1427
  break;
1557
1428
  default:
1558
1429
  return inputPath;
@@ -1609,14 +1480,14 @@ async function unshortenUrl(url) {
1609
1480
  return status >= 200 && status < 400; // Accept redirects
1610
1481
  }
1611
1482
  });
1612
-
1483
+
1613
1484
  // Get the final URL after all redirects
1614
1485
  const finalUrl = response.request.res.responseUrl || response.config.url;
1615
1486
  return finalUrl;
1616
1487
  }
1617
-
1488
+
1618
1489
  // For other URLs, use the original method
1619
- const response = await axios.head(url, {
1490
+ const response = await axios.head(url, {
1620
1491
  maxRedirects: 10,
1621
1492
  timeout: 10000,
1622
1493
  headers: {
@@ -1685,10 +1556,10 @@ const AudioDownloader = async (url, options = {}) => {
1685
1556
  if (downloadedFilePath) {
1686
1557
  // Extrair áudio em mp3
1687
1558
  audioFilePath = await extractAudioMp3(downloadedFilePath);
1688
-
1559
+
1689
1560
  // Remove o arquivo de vídeo temporário após extrair o áudio
1690
1561
  await safeUnlinkWithRetry(downloadedFilePath);
1691
-
1562
+
1692
1563
  const result = await uploadToGoFileIfNeeded(audioFilePath);
1693
1564
  return result;
1694
1565
  } else {
@@ -1704,7 +1575,7 @@ const AudioDownloader = async (url, options = {}) => {
1704
1575
  // Função para extrair áudio em mp3 usando ffmpeg
1705
1576
  async function extractAudioMp3(videoPath) {
1706
1577
  return new Promise((resolve, reject) => {
1707
- 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`);
1708
1579
  ffmpeg(videoPath)
1709
1580
  .noVideo()
1710
1581
  .audioCodec('libmp3lame')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frostpv",
3
- "version": "1.0.10",
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
  }