frostpv 1.0.6 → 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 +282 -117
  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 } = 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
 
@@ -67,6 +67,16 @@ const linkCant = [{
67
67
  reason: "Use real link like 'https://www.tiktok.com'. (just click on your link and copy the link in the browser)"
68
68
  }];
69
69
 
70
+ // Function to check if a link corresponds to a video platform
71
+ const isVideoLink = (link) => {
72
+ try {
73
+ const u = new URL(link);
74
+ const host = (u.hostname || '').toLowerCase();
75
+ if (host.endsWith('pintere21313213st.com')) return true;
76
+ } catch (_) { }
77
+ return videoPlatforms.some(platform => link.startsWith(platform));
78
+ };
79
+
70
80
  // Function to check if a link is blacklisted
71
81
  const blacklistLink = (link) => {
72
82
  return linkCant.find(item => link.includes(item.link));
@@ -136,14 +146,14 @@ async function safeUnlinkWithRetry(filePath, maxRetries = 3) {
136
146
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
137
147
  try {
138
148
  const resolvedPath = path.resolve(filePath);
139
-
149
+
140
150
  if (fs.existsSync(resolvedPath)) {
141
151
  // Check if file is in use (Windows)
142
152
  if (process.platform === 'win32' && isFileInUse(resolvedPath)) {
143
153
  await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
144
154
  continue;
145
155
  }
146
-
156
+
147
157
  fs.rmSync(resolvedPath, { force: true });
148
158
  return true;
149
159
  } else {
@@ -215,7 +225,7 @@ async function validateVideoUrl(url, platform) {
215
225
  return status >= 200 && status < 400; // Accept redirects
216
226
  }
217
227
  });
218
-
228
+
219
229
  return true;
220
230
  } catch (error) {
221
231
  return false;
@@ -227,18 +237,18 @@ function validateTwitterUrl(url) {
227
237
  try {
228
238
  // Remove query parameters that might cause issues
229
239
  const cleanUrl = url.split('?')[0];
230
-
240
+
231
241
  // Ensure it's a valid Twitter/X URL format
232
242
  if (!cleanUrl.includes('x.com') && !cleanUrl.includes('twitter.com')) {
233
243
  throw new Error("Not a valid Twitter/X URL");
234
244
  }
235
-
245
+
236
246
  // Check if it has the required structure
237
247
  const urlParts = cleanUrl.split('/');
238
248
  if (urlParts.length < 5) {
239
249
  throw new Error("Invalid Twitter/X URL structure");
240
250
  }
241
-
251
+
242
252
  return cleanUrl;
243
253
  } catch (error) {
244
254
  throw new Error(`Twitter URL validation failed: ${error.message}`);
@@ -277,10 +287,10 @@ async function downloadYoutubeAudio(url, outputPath) {
277
287
  try {
278
288
  const ytdlp = new YtDlp({ ffmpegPath });
279
289
  const cookiesPath = getYoutubeCookiesPath();
280
-
290
+
281
291
  // Garantir que o nome do arquivo tenha a extensão correta
282
292
  const baseName = outputPath.replace(/\.[^/.]+$/, ""); // Remove extensão se existir
283
-
293
+
284
294
  // Primeiro, tentar com a API de opções
285
295
  try {
286
296
  const options = {
@@ -291,11 +301,11 @@ async function downloadYoutubeAudio(url, outputPath) {
291
301
  },
292
302
  output: outputPath
293
303
  };
294
-
304
+
295
305
  if (cookiesPath) {
296
306
  options.cookies = cookiesPath;
297
307
  }
298
-
308
+
299
309
  // Remover logs detalhados para download de áudio do YouTube
300
310
  const result = await ytdlp.downloadAsync(url, options);
301
311
  // Verificar se o arquivo foi criado
@@ -304,7 +314,7 @@ async function downloadYoutubeAudio(url, outputPath) {
304
314
  resolve(actualFileName);
305
315
  return;
306
316
  }
307
-
317
+
308
318
  // Tentar encontrar o arquivo com extensão diferente
309
319
  const files = fs.readdirSync('./');
310
320
  const downloadedFile = files.find(file => file.startsWith(baseName.split('/').pop()));
@@ -315,18 +325,18 @@ async function downloadYoutubeAudio(url, outputPath) {
315
325
  } catch (apiError) {
316
326
  console.log('API method failed, trying direct args method:', apiError.message);
317
327
  }
318
-
328
+
319
329
  // Fallback: usar argumentos diretos para máxima qualidade de áudio
320
330
  const args = [
321
331
  url,
322
332
  '-f', 'bestaudio[ext=mp3]/bestaudio',
323
333
  '-o', outputPath
324
334
  ];
325
-
335
+
326
336
  if (cookiesPath) {
327
337
  args.push('--cookies', cookiesPath);
328
338
  }
329
-
339
+
330
340
  // Remover logs detalhados para download de áudio do YouTube
331
341
  const result = await ytdlp.execAsync(args);
332
342
  // Verificar se o arquivo foi criado
@@ -353,25 +363,53 @@ async function downloadYoutubeAudio(url, outputPath) {
353
363
  // Enhanced function to get platform type from URL
354
364
  function getPlatformType(url) {
355
365
  const lowerUrl = url.toLowerCase();
356
-
366
+
357
367
  if (lowerUrl.includes("instagram.com") || lowerUrl.includes("instagr.am")) return "instagram";
358
368
  if (lowerUrl.includes("tiktok.com") || lowerUrl.includes("vm.tiktok.com") || lowerUrl.includes("vt.tiktok.com")) return "tiktok";
359
369
  if (lowerUrl.includes("facebook.com") || lowerUrl.includes("fb.watch") || lowerUrl.includes("fb.com")) return "facebook";
360
370
  if (lowerUrl.includes("mediafire.com")) return "mediafire";
361
371
  if (lowerUrl.includes("x.com") || lowerUrl.includes("twitter.com")) return "twitter";
362
- if (lowerUrl.includes("youtube.com") || lowerUrl.includes("you112t12u.be") || lowerUrl.includes("m.y11outu314be.com")) return "youtu1354be";
363
-
372
+ if (lowerUrl.includes("youtube.com") || lowerUrl.includes("youtu.be") || lowerUrl.includes("m.youtube.com")) return "youtube";
373
+
364
374
  return "unknown";
365
375
  }
366
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
+
367
405
  // Enhanced fallback download function with platform-specific methods
368
406
  async function tryFallbackDownload(url, maxRetries = 3) {
369
407
  const platform = getPlatformType(url);
370
-
408
+
371
409
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
372
410
  try {
373
411
  let videoUrl = null;
374
-
412
+
375
413
  // Platform-specific fallback methods
376
414
  switch (platform) {
377
415
  case "instagram": {
@@ -417,7 +455,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
417
455
  }
418
456
  }
419
457
  ];
420
-
458
+
421
459
  for (const method of methods) {
422
460
  try {
423
461
  videoUrl = await method();
@@ -428,7 +466,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
428
466
  }
429
467
  break;
430
468
  }
431
-
469
+
432
470
  case "tiktok": {
433
471
  // Try multiple TikTok methods
434
472
  const methods = [
@@ -463,7 +501,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
463
501
  throw new Error("No valid video in Tiktok.Downloader v2 response");
464
502
  }
465
503
  ];
466
-
504
+
467
505
  for (const method of methods) {
468
506
  try {
469
507
  videoUrl = await method();
@@ -474,7 +512,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
474
512
  }
475
513
  break;
476
514
  }
477
-
515
+
478
516
  case "facebook": {
479
517
  // Try multiple Facebook methods
480
518
  const methods = [
@@ -498,7 +536,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
498
536
  throw new Error("No valid video in fbdown response with custom headers");
499
537
  }
500
538
  ];
501
-
539
+
502
540
  for (const method of methods) {
503
541
  try {
504
542
  videoUrl = await method();
@@ -509,42 +547,46 @@ async function tryFallbackDownload(url, maxRetries = 3) {
509
547
  }
510
548
  break;
511
549
  }
512
-
550
+
513
551
  case "twitter": {
514
- // Try multiple Twitter methods with better response handling
552
+ // Try using yt-dlp first (Most reliable), then fallback to existing methods
515
553
  const methods = [
554
+ async () => {
555
+ // Priority 1: yt-dlp
556
+ return await downloadTwitterWithYtDlp(url);
557
+ },
516
558
  async () => {
517
559
  const cleanUrl = validateTwitterUrl(url);
518
560
  const data = await TwitterDL(cleanUrl, {});
519
-
561
+
520
562
  // Check multiple possible response structures
521
563
  if (data && data.result) {
522
564
  // Structure 1: data.result.media[0].videos[0].url
523
- if (data.result.media && data.result.media[0] &&
524
- data.result.media[0].videos && data.result.media[0].videos[0] &&
525
- 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) {
526
568
  return data.result.media[0].videos[0].url;
527
569
  }
528
-
570
+
529
571
  // Structure 2: data.result.video[0].url
530
572
  if (data.result.video && data.result.video[0] && data.result.video[0].url) {
531
573
  return data.result.video[0].url;
532
574
  }
533
-
575
+
534
576
  // Structure 3: data.result.url (direct URL)
535
577
  if (data.result.url) {
536
578
  return data.result.url;
537
579
  }
538
-
580
+
539
581
  // Structure 4: data.result.media[0].url
540
582
  if (data.result.media && data.result.media[0] && data.result.media[0].url) {
541
583
  return data.result.media[0].url;
542
584
  }
543
-
585
+
544
586
  // Structure 5: Check for any video URL in the entire response
545
587
  const findVideoUrl = (obj) => {
546
- if (typeof obj === 'string' && obj.includes('http') &&
547
- (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'))) {
548
590
  return obj;
549
591
  }
550
592
  if (typeof obj === 'object' && obj !== null) {
@@ -555,13 +597,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
555
597
  }
556
598
  return null;
557
599
  };
558
-
600
+
559
601
  const foundUrl = findVideoUrl(data.result);
560
602
  if (foundUrl) {
561
603
  return foundUrl;
562
604
  }
563
605
  }
564
-
606
+
565
607
  throw new Error("No valid video URL found in TwitterDL response structure");
566
608
  },
567
609
  async () => {
@@ -577,30 +619,30 @@ async function tryFallbackDownload(url, maxRetries = 3) {
577
619
  'Upgrade-Insecure-Requests': '1'
578
620
  }
579
621
  });
580
-
622
+
581
623
  // Same structure checking as above
582
624
  if (data && data.result) {
583
- if (data.result.media && data.result.media[0] &&
584
- data.result.media[0].videos && data.result.media[0].videos[0] &&
585
- 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) {
586
628
  return data.result.media[0].videos[0].url;
587
629
  }
588
-
630
+
589
631
  if (data.result.video && data.result.video[0] && data.result.video[0].url) {
590
632
  return data.result.video[0].url;
591
633
  }
592
-
634
+
593
635
  if (data.result.url) {
594
636
  return data.result.url;
595
637
  }
596
-
638
+
597
639
  if (data.result.media && data.result.media[0] && data.result.media[0].url) {
598
640
  return data.result.media[0].url;
599
641
  }
600
-
642
+
601
643
  const findVideoUrl = (obj) => {
602
- if (typeof obj === 'string' && obj.includes('http') &&
603
- (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'))) {
604
646
  return obj;
605
647
  }
606
648
  if (typeof obj === 'object' && obj !== null) {
@@ -611,13 +653,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
611
653
  }
612
654
  return null;
613
655
  };
614
-
656
+
615
657
  const foundUrl = findVideoUrl(data.result);
616
658
  if (foundUrl) {
617
659
  return foundUrl;
618
660
  }
619
661
  }
620
-
662
+
621
663
  throw new Error("No valid video URL found in TwitterDL response with custom headers");
622
664
  },
623
665
  async () => {
@@ -653,9 +695,9 @@ async function tryFallbackDownload(url, maxRetries = 3) {
653
695
  const data = await btchOld[funcName](cleanUrl);
654
696
  // Check multiple possible response structures
655
697
  if (data && data.result) {
656
- if (data.result.media && data.result.media[0] &&
657
- data.result.media[0].videos && data.result.media[0].videos[0] &&
658
- 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) {
659
701
  return data.result.media[0].videos[0].url;
660
702
  }
661
703
  if (data.result.video && data.result.video[0] && data.result.video[0].url) {
@@ -682,7 +724,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
682
724
  }
683
725
  }
684
726
  ];
685
-
727
+
686
728
  for (const method of methods) {
687
729
  try {
688
730
  videoUrl = await method();
@@ -693,12 +735,12 @@ async function tryFallbackDownload(url, maxRetries = 3) {
693
735
  }
694
736
  break;
695
737
  }
696
-
738
+
697
739
  case "youtube": {
698
740
  // YouTube doesn't need fallback - it has its own specific method
699
741
  throw new Error("YouTube downloads should use the primary method, not fallback");
700
742
  }
701
-
743
+
702
744
  case "mediafire": {
703
745
  // Try MediaFire method
704
746
  try {
@@ -713,7 +755,33 @@ async function tryFallbackDownload(url, maxRetries = 3) {
713
755
  }
714
756
  break;
715
757
  }
716
-
758
+ case "pinterest": {
759
+ try {
760
+ const data = await pinterest(url);
761
+ let mediaUrl = null;
762
+ const pickFrom = (obj) => {
763
+ if (!obj) return null;
764
+ if (typeof obj === 'string' && obj.startsWith('http')) return obj;
765
+ if (Array.isArray(obj)) {
766
+ for (const it of obj) {
767
+ const got = pickFrom(it);
768
+ if (got) return got;
769
+ }
770
+ } else if (typeof obj === 'object') {
771
+ const candidates = ['video', 'videoUrl', 'download', 'url', 'image', 'imageUrl', 'src', 'link'];
772
+ for (const k of candidates) {
773
+ const got = pickFrom(obj[k]);
774
+ if (got) return got;
775
+ }
776
+ }
777
+ return null;
778
+ };
779
+ mediaUrl = pickFrom(data);
780
+ if (mediaUrl) return mediaUrl;
781
+ } catch (_) { }
782
+ break;
783
+ }
784
+
717
785
  default: {
718
786
  // Generic fallback for unknown platforms
719
787
  try {
@@ -729,18 +797,18 @@ async function tryFallbackDownload(url, maxRetries = 3) {
729
797
  break;
730
798
  }
731
799
  }
732
-
800
+
733
801
  // If we got a video URL, validate it
734
802
  if (videoUrl && videoUrl.includes("http")) {
735
803
  // Validate the URL is accessible
736
804
  try {
737
- const response = await axios.head(videoUrl, {
805
+ const response = await axios.head(videoUrl, {
738
806
  timeout: 10000,
739
807
  headers: {
740
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'
741
809
  }
742
810
  });
743
-
811
+
744
812
  if (response.status === 200) {
745
813
  return videoUrl;
746
814
  } else {
@@ -752,7 +820,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
752
820
  } else {
753
821
  throw new Error("No valid video URL obtained from any fallback method");
754
822
  }
755
-
823
+
756
824
  } catch (error) {
757
825
  if (attempt === maxRetries) {
758
826
  throw new Error(`Enhanced fallback method failed after ${maxRetries} attempts for ${platform}: ${error.message}`);
@@ -785,7 +853,7 @@ const MediaDownloader = async (url, options = {}) => {
785
853
  if (url.includes("threads.com")) {
786
854
  throw new Error("Threads links are not supported for download. Download videos from Instagram, X, TikTok, and Facebook");
787
855
  }
788
-
856
+ // Pinterest now supported
789
857
 
790
858
  const blacklisted = blacklistLink(url);
791
859
  if (blacklisted) {
@@ -799,9 +867,8 @@ const MediaDownloader = async (url, options = {}) => {
799
867
 
800
868
  // Verificar se é YouTube e lançar erro customizado
801
869
  const platform = getPlatformType(url);
802
- if (platform === "youtube") {
803
- throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook");
804
- }
870
+ // YouTube is now supported
871
+
805
872
 
806
873
  await cleanupTempFiles(); // Clean up previous temp files
807
874
 
@@ -813,21 +880,21 @@ const MediaDownloader = async (url, options = {}) => {
813
880
  downloadedFilePath = await downloadSmartVideo(url, config);
814
881
  } catch (error) {
815
882
  const platform = getPlatformType(url);
816
-
817
- // 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
818
885
  if (platform === "youtube") {
819
886
  throw new Error(`YouTube download failed: ${error.message}`);
820
887
  }
821
-
888
+
822
889
  try {
823
890
  const fallbackUrl = await tryFallbackDownload(url);
824
-
891
+
825
892
  // Validate fallback URL
826
893
  const isValid = await validateVideoUrl(fallbackUrl, platform);
827
894
  if (!isValid) {
828
895
  throw new Error("Fallback URL validation failed");
829
896
  }
830
-
897
+
831
898
  downloadedFilePath = await downloadDirectVideo(fallbackUrl, config);
832
899
  } catch (fallbackError) {
833
900
  throw new Error(`Failed to download video with both methods: ${fallbackError.message}`);
@@ -950,15 +1017,15 @@ async function downloadSmartVideo(url, config) {
950
1017
  throw new Error("No video URLs found in Facebook response");
951
1018
  }
952
1019
  videoUrl = data.Normal_video || data.HD;
953
-
1020
+
954
1021
  // Validate the URL is accessible
955
- const response = await axios.head(videoUrl, {
1022
+ const response = await axios.head(videoUrl, {
956
1023
  timeout: 10000,
957
1024
  headers: {
958
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'
959
1026
  }
960
1027
  }).catch(() => null);
961
-
1028
+
962
1029
  if (!response || response.status !== 200) {
963
1030
  throw new Error("Facebook video URL is not accessible");
964
1031
  }
@@ -979,17 +1046,63 @@ async function downloadSmartVideo(url, config) {
979
1046
  }
980
1047
  break;
981
1048
  }
1049
+ case "pinterest": {
1050
+ try {
1051
+ // Supports both pin links and search queries
1052
+ const data = await pinterest(url);
1053
+ // data may be array or object depending on query or pin
1054
+ // Try to find best url (image or video)
1055
+ let directUrl = null;
1056
+ const pickFrom = (obj) => {
1057
+ if (!obj) return null;
1058
+ if (typeof obj === 'string' && obj.startsWith('http')) return obj;
1059
+ if (Array.isArray(obj)) {
1060
+ for (const it of obj) {
1061
+ const got = pickFrom(it);
1062
+ if (got) return got;
1063
+ }
1064
+ } else if (typeof obj === 'object') {
1065
+ const candidates = ['video', 'videoUrl', 'download', 'url', 'image', 'imageUrl', 'src', 'link'];
1066
+ for (const k of candidates) {
1067
+ const got = pickFrom(obj[k]);
1068
+ if (got) return got;
1069
+ }
1070
+ }
1071
+ return null;
1072
+ };
1073
+ directUrl = pickFrom(data);
1074
+ if (!directUrl) throw new Error("No media URL found in Pinterest response");
1075
+
1076
+ // HEAD validate
1077
+ const head = await axios.head(directUrl, { timeout: 10000, maxRedirects: 5 }).catch(() => null);
1078
+ if (!head || (head.status < 200 || head.status >= 400)) {
1079
+ throw new Error("Pinterest media URL not accessible");
1080
+ }
1081
+
1082
+ // If it's an image, download with generic file helper; if video, continue normal flow
1083
+ const ct = (head.headers && head.headers['content-type']) || '';
1084
+ if (ct.startsWith('image/')) {
1085
+ const filePath = await downloadGenericFile(directUrl);
1086
+ return filePath;
1087
+ } else {
1088
+ videoUrl = directUrl;
1089
+ }
1090
+ } catch (error) {
1091
+ throw new Error(`Pinterest download failed: ${error.message}`);
1092
+ }
1093
+ break;
1094
+ }
982
1095
  case "twitter": {
983
1096
  try {
984
1097
  // Validate and clean the Twitter URL
985
1098
  const cleanUrl = validateTwitterUrl(url);
986
1099
  const data = await TwitterDL(cleanUrl, {});
987
-
1100
+
988
1101
  if (data && data.result) {
989
1102
  // Try multiple possible response structures
990
- if (data.result.media && data.result.media[0] &&
991
- data.result.media[0].videos && data.result.media[0].videos[0] &&
992
- 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) {
993
1106
  videoUrl = data.result.media[0].videos[0].url;
994
1107
  } else if (data.result.video && data.result.video[0] && data.result.video[0].url) {
995
1108
  videoUrl = data.result.video[0].url;
@@ -1000,8 +1113,8 @@ async function downloadSmartVideo(url, config) {
1000
1113
  } else {
1001
1114
  // Search for any video URL in the response
1002
1115
  const findVideoUrl = (obj) => {
1003
- if (typeof obj === 'string' && obj.includes('http') &&
1004
- (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'))) {
1005
1118
  return obj;
1006
1119
  }
1007
1120
  if (typeof obj === 'object' && obj !== null) {
@@ -1012,7 +1125,7 @@ async function downloadSmartVideo(url, config) {
1012
1125
  }
1013
1126
  return null;
1014
1127
  };
1015
-
1128
+
1016
1129
  const foundUrl = findVideoUrl(data.result);
1017
1130
  if (foundUrl) {
1018
1131
  videoUrl = foundUrl;
@@ -1028,6 +1141,56 @@ async function downloadSmartVideo(url, config) {
1028
1141
  }
1029
1142
  break;
1030
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
+ }
1031
1194
  default:
1032
1195
  throw new Error("Platform not supported or invalid link.");
1033
1196
  }
@@ -1035,21 +1198,21 @@ async function downloadSmartVideo(url, config) {
1035
1198
  if (!videoUrl || !videoUrl.includes("http")) {
1036
1199
  throw new Error("Returned video URL is invalid or unavailable.");
1037
1200
  }
1038
-
1201
+
1039
1202
  // Download the video with better error handling
1040
- const response = await axios({
1041
- url: videoUrl,
1042
- method: "GET",
1203
+ const response = await axios({
1204
+ url: videoUrl,
1205
+ method: "GET",
1043
1206
  responseType: "stream",
1044
1207
  timeout: 30000,
1045
1208
  headers: {
1046
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'
1047
1210
  }
1048
1211
  });
1049
-
1212
+
1050
1213
  // Create minimal unique file name in output dir
1051
- let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
1052
-
1214
+ let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
1215
+
1053
1216
  const videoWriter = fs.createWriteStream(fileName);
1054
1217
  response.data.pipe(videoWriter);
1055
1218
 
@@ -1074,19 +1237,19 @@ async function downloadSmartVideo(url, config) {
1074
1237
  // Function for direct video download
1075
1238
  async function downloadDirectVideo(url, config) {
1076
1239
  try {
1077
- const response = await axios({
1078
- url: url,
1079
- method: "GET",
1240
+ const response = await axios({
1241
+ url: url,
1242
+ method: "GET",
1080
1243
  responseType: "stream",
1081
1244
  timeout: 30000,
1082
1245
  headers: {
1083
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'
1084
1247
  }
1085
1248
  });
1086
-
1249
+
1087
1250
  // Create minimal unique file name in output dir
1088
- let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.mp4`);
1089
-
1251
+ let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.mp4`);
1252
+
1090
1253
  const videoWriter = fs.createWriteStream(fileName);
1091
1254
  response.data.pipe(videoWriter);
1092
1255
 
@@ -1134,7 +1297,7 @@ async function downloadGenericFile(url, preferredExt = null) {
1134
1297
  else ext = 'bin';
1135
1298
  }
1136
1299
 
1137
- 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}`);
1138
1301
  const writer = fs.createWriteStream(fileName);
1139
1302
  response.data.pipe(writer);
1140
1303
 
@@ -1149,7 +1312,7 @@ async function downloadGenericFile(url, preferredExt = null) {
1149
1312
 
1150
1313
  // Function to rotate video
1151
1314
  async function rotateVideo(fileName, rotation) {
1152
- 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`);
1153
1316
  let angle;
1154
1317
  switch (rotation.toLowerCase()) {
1155
1318
  case "left": angle = "transpose=2"; break;
@@ -1200,8 +1363,8 @@ async function cleanupTempFiles() {
1200
1363
  try {
1201
1364
  if (!fs.existsSync(TEMP_DIR)) return;
1202
1365
  const files = fs.readdirSync(TEMP_DIR);
1203
- const tempFiles = files.filter(file =>
1204
- /^temp_video.*\.mp4$/.test(file) ||
1366
+ const tempFiles = files.filter(file =>
1367
+ /^temp_video.*\.mp4$/.test(file) ||
1205
1368
  /_audio\.mp3$/.test(file) ||
1206
1369
  /_rotated\.mp4$/.test(file) ||
1207
1370
  /_cropped\.mp4$/.test(file) ||
@@ -1233,9 +1396,9 @@ async function cleanupOutputFiles() {
1233
1396
  if (now - st.mtimeMs > maxAgeMs) {
1234
1397
  await safeUnlinkWithRetry(full);
1235
1398
  }
1236
- } catch (_) {}
1399
+ } catch (_) { }
1237
1400
  }
1238
- } catch (_) {}
1401
+ } catch (_) { }
1239
1402
  }
1240
1403
 
1241
1404
  // Schedule periodic cleanup (every 10 minutes)
@@ -1249,7 +1412,7 @@ cleanupOutputFiles();
1249
1412
  // Function to auto-crop video
1250
1413
  async function autoCrop(fileName) {
1251
1414
  const inputPath = fileName;
1252
- 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`);
1253
1416
 
1254
1417
  return new Promise((resolve, reject) => {
1255
1418
  let cropValues = null;
@@ -1257,13 +1420,13 @@ async function autoCrop(fileName) {
1257
1420
  .outputOptions('-vf', 'cropdetect=24:16:0')
1258
1421
  .outputFormat('null')
1259
1422
  .output('-')
1260
- .on('stderr', function(stderrLine) {
1423
+ .on('stderr', function (stderrLine) {
1261
1424
  const cropMatch = stderrLine.match(/crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/);
1262
1425
  if (cropMatch) {
1263
1426
  cropValues = `crop=${cropMatch[1]}:${cropMatch[2]}:${cropMatch[3]}:${cropMatch[4]}`;
1264
1427
  }
1265
1428
  })
1266
- .on('end', function() {
1429
+ .on('end', function () {
1267
1430
  if (!cropValues) {
1268
1431
  resolve(inputPath);
1269
1432
  return;
@@ -1296,7 +1459,7 @@ async function checkAndCompressVideo(filePath, limitSizeMB, platform = null) {
1296
1459
  return filePath;
1297
1460
  }
1298
1461
 
1299
- 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`);
1300
1463
 
1301
1464
  return new Promise((resolve, reject) => {
1302
1465
  ffmpeg(filePath)
@@ -1331,17 +1494,17 @@ async function convertVideoFormat(inputPath, targetFormat) {
1331
1494
  const supported = ["mp4", "mov", "webm", "mkv"];
1332
1495
  if (!supported.includes(fmt)) return inputPath;
1333
1496
 
1334
- 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}`);
1335
1498
 
1336
1499
  const ff = ffmpeg(inputPath);
1337
1500
  switch (fmt) {
1338
1501
  case "mp4":
1339
1502
  case "mov":
1340
1503
  case "mkv":
1341
- 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");
1342
1505
  break;
1343
1506
  case "webm":
1344
- 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");
1345
1508
  break;
1346
1509
  default:
1347
1510
  return inputPath;
@@ -1378,8 +1541,10 @@ function getFileName(url) {
1378
1541
  // Function to unshorten URLs
1379
1542
  async function unshortenUrl(url) {
1380
1543
  try {
1544
+ // Special handling for Facebook and Pinterest short-links
1381
1545
  if (
1382
- url.includes('facebook.com') || url.includes('fb.watch') || url.includes('fb.com')
1546
+ url.includes('facebook.com') || url.includes('fb.watch') || url.includes('fb.com') ||
1547
+ url.includes('pi1231231n.it') || url.includes('pint123123erest.com')
1383
1548
  ) {
1384
1549
  const response = await axios.get(url, {
1385
1550
  maxRedirects: 10,
@@ -1396,14 +1561,14 @@ async function unshortenUrl(url) {
1396
1561
  return status >= 200 && status < 400; // Accept redirects
1397
1562
  }
1398
1563
  });
1399
-
1564
+
1400
1565
  // Get the final URL after all redirects
1401
1566
  const finalUrl = response.request.res.responseUrl || response.config.url;
1402
1567
  return finalUrl;
1403
1568
  }
1404
-
1569
+
1405
1570
  // For other URLs, use the original method
1406
- const response = await axios.head(url, {
1571
+ const response = await axios.head(url, {
1407
1572
  maxRedirects: 10,
1408
1573
  timeout: 10000,
1409
1574
  headers: {
@@ -1434,7 +1599,7 @@ const AudioDownloader = async (url, options = {}) => {
1434
1599
 
1435
1600
  // Verificar se o link está na lista de plataformas suportadas
1436
1601
  if (!isVideoLink(url)) {
1437
- throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook");
1602
+ throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook, and YouTube");
1438
1603
  }
1439
1604
 
1440
1605
  await cleanupTempFiles(); // Clean up previous temp files
@@ -1472,10 +1637,10 @@ const AudioDownloader = async (url, options = {}) => {
1472
1637
  if (downloadedFilePath) {
1473
1638
  // Extrair áudio em mp3
1474
1639
  audioFilePath = await extractAudioMp3(downloadedFilePath);
1475
-
1640
+
1476
1641
  // Remove o arquivo de vídeo temporário após extrair o áudio
1477
1642
  await safeUnlinkWithRetry(downloadedFilePath);
1478
-
1643
+
1479
1644
  const result = await uploadToGoFileIfNeeded(audioFilePath);
1480
1645
  return result;
1481
1646
  } else {
@@ -1491,7 +1656,7 @@ const AudioDownloader = async (url, options = {}) => {
1491
1656
  // Função para extrair áudio em mp3 usando ffmpeg
1492
1657
  async function extractAudioMp3(videoPath) {
1493
1658
  return new Promise((resolve, reject) => {
1494
- 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`);
1495
1660
  ffmpeg(videoPath)
1496
1661
  .noVideo()
1497
1662
  .audioCodec('libmp3lame')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frostpv",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "downloads",
5
5
  "main": "index.js",
6
6
  "scripts": {