frostpv 1.0.14 → 1.0.16

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 +290 -170
  2. package/package.json +6 -5
package/index.js CHANGED
@@ -4,14 +4,15 @@ 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, twitter } = require("btch-downloader");
7
+ const { igdl, ttdl, fbdown, mediafire, capcut, youtube, gdrive, pinterest, twitter } = 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");
11
+ const ytdl = require("@distube/ytdl-core");
12
+
11
13
 
12
- // Fallback libs
13
14
  const Tiktok = require("@tobyg74/tiktok-api-dl");
14
- const { YtDlp } = require('ytdlp-nodejs');
15
+ const ab = require("ab-downloader");
15
16
  const ffmpegPath = require('ffmpeg-ffprobe-static').ffmpegPath;
16
17
  const { v4: uuidv4 } = require('uuid');
17
18
 
@@ -19,13 +20,10 @@ const pathToFfmpeg = require("ffmpeg-ffprobe-static");
19
20
  const ffmpeg = require("fluent-ffmpeg");
20
21
  ffmpeg.setFfmpegPath(pathToFfmpeg.ffmpegPath);
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
24
  try { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); } catch (_) { }
25
- // Keep an OS temp directory for any future transient needs
26
25
  const TEMP_DIR = path.join(os.tmpdir(), "downloader-dl-bot");
27
26
  try { fs.mkdirSync(TEMP_DIR, { recursive: true }); } catch (_) { }
28
- // TTL (minutes) for files in OUTPUT_DIR (audio/video) before they are pruned
29
27
  const OUTPUT_RETENTION_MIN = Number(process.env.OUTPUT_RETENTION_MIN || 5);
30
28
 
31
29
  const GOFILE_API = "Vs4e2PL8n65ExPz6wgDlqSY8kcEBRrzN";
@@ -46,6 +44,10 @@ const videoPlatforms = [
46
44
  "https://fb.watch",
47
45
  "https://www.fb.watch",
48
46
  "https://fb.com",
47
+ "https://www.youtube.com",
48
+ "https://youtube.com",
49
+ "https://www.youtu.be",
50
+ "https://youtu.be",
49
51
  "https://www.fb.com",
50
52
  "https://web.facebook.com",
51
53
  "https://www.mediafire.com",
@@ -85,8 +87,9 @@ const blacklistLink = (link) => {
85
87
 
86
88
  const defaultConfig = {
87
89
  autocrop: false,
88
- limitSizeMB: null, // For compression, not for GoFile limit
90
+ limitSizeMB: null,
89
91
  rotation: null,
92
+ YTBmaxduration: 30,
90
93
  outputFormat: null,
91
94
  };
92
95
 
@@ -265,102 +268,6 @@ function getYoutubeCookiesPath() {
265
268
  return null;
266
269
  }
267
270
 
268
- // Função utilitária para obter duração do vídeo/áudio em segundos
269
- async function getYoutubeDurationSeconds(url) {
270
- try {
271
- const ytdlp = new YtDlp({ ffmpegPath });
272
- const info = await ytdlp.getInfoAsync(url);
273
- if (info && info.duration) return info.duration;
274
- return null;
275
- } catch (e) {
276
- return null;
277
- }
278
- }
279
-
280
- // Função para baixar áudio do YouTube
281
- async function downloadYoutubeAudio(url, outputPath) {
282
- // Verificar duração máxima de 15 minutos
283
- const duration = await getYoutubeDurationSeconds(url);
284
- if (duration && duration > 900) {
285
- throw new Error('The audio is longer than 15 minutes. Test limit: 15 minutes. This feature is still in beta.');
286
- }
287
- return new Promise(async (resolve, reject) => {
288
- try {
289
- const ytdlp = new YtDlp({ ffmpegPath });
290
- const cookiesPath = getYoutubeCookiesPath();
291
-
292
- // Garantir que o nome do arquivo tenha a extensão correta
293
- const baseName = outputPath.replace(/\.[^/.]+$/, ""); // Remove extensão se existir
294
-
295
- // Primeiro, tentar com a API de opções
296
- try {
297
- const options = {
298
- format: {
299
- filter: 'audioonly',
300
- type: 'mp3',
301
- quality: 'highestaudio'
302
- },
303
- output: outputPath
304
- };
305
-
306
- if (cookiesPath) {
307
- options.cookies = cookiesPath;
308
- }
309
-
310
- // Remover logs detalhados para download de áudio do YouTube
311
- const result = await ytdlp.downloadAsync(url, options);
312
- // Verificar se o arquivo foi criado
313
- const actualFileName = baseName + '.mp3';
314
- if (fs.existsSync(actualFileName)) {
315
- resolve(actualFileName);
316
- return;
317
- }
318
-
319
- // Tentar encontrar o arquivo com extensão diferente
320
- const files = fs.readdirSync('./');
321
- const downloadedFile = files.find(file => file.startsWith(baseName.split('/').pop()));
322
- if (downloadedFile) {
323
- resolve(downloadedFile);
324
- return;
325
- }
326
- } catch (apiError) {
327
- console.log('API method failed, trying direct args method:', apiError.message);
328
- }
329
-
330
- // Fallback: usar argumentos diretos para máxima qualidade de áudio
331
- const args = [
332
- url,
333
- '-f', 'bestaudio[ext=mp3]/bestaudio',
334
- '-o', outputPath
335
- ];
336
-
337
- if (cookiesPath) {
338
- args.push('--cookies', cookiesPath);
339
- }
340
-
341
- // Remover logs detalhados para download de áudio do YouTube
342
- const result = await ytdlp.execAsync(args);
343
- // Verificar se o arquivo foi criado
344
- const actualFileName = baseName + '.mp3';
345
- if (fs.existsSync(actualFileName)) {
346
- resolve(actualFileName);
347
- } else {
348
- // Tentar encontrar o arquivo com extensão diferente
349
- const files = fs.readdirSync('./');
350
- const downloadedFile = files.find(file => file.startsWith(baseName.split('/').pop()));
351
- if (downloadedFile) {
352
- resolve(downloadedFile);
353
- } else {
354
- reject(new Error('YouTube audio download completed but file not found'));
355
- }
356
- }
357
- } catch (error) {
358
- console.error('YouTube audio download error:', error);
359
- reject(new Error('This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok and Facebook'));
360
- }
361
- });
362
- }
363
-
364
271
  // Enhanced function to get platform type from URL
365
272
  function getPlatformType(url) {
366
273
  const lowerUrl = url.toLowerCase();
@@ -370,12 +277,11 @@ function getPlatformType(url) {
370
277
  if (lowerUrl.includes("facebook.com") || lowerUrl.includes("fb.watch") || lowerUrl.includes("fb.com")) return "facebook";
371
278
  if (lowerUrl.includes("mediafire.com")) return "mediafire";
372
279
  if (lowerUrl.includes("x.com") || lowerUrl.includes("twitter.com")) return "twitter";
373
- if (lowerUrl.includes("youtube.com") || lowerUrl.includes("you112t12u.be") || lowerUrl.includes("m.y11outu314be.com")) return "youtu1354be";
280
+ if (lowerUrl.includes("youtube.com") || lowerUrl.includes("youtu.be") || lowerUrl.includes("www.youtube.com")) return "youtube";
374
281
 
375
282
  return "unknown";
376
283
  }
377
284
 
378
- // Enhanced fallback download function with platform-specific methods
379
285
  async function tryFallbackDownload(url, maxRetries = 3) {
380
286
  const platform = getPlatformType(url);
381
287
 
@@ -383,10 +289,8 @@ async function tryFallbackDownload(url, maxRetries = 3) {
383
289
  try {
384
290
  let videoUrl = null;
385
291
 
386
- // Platform-specific fallback methods
387
292
  switch (platform) {
388
293
  case "instagram": {
389
- // Try multiple Instagram methods
390
294
  const methods = [
391
295
  async () => {
392
296
  const data = await igdl(url);
@@ -413,7 +317,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
413
317
  throw new Error("No valid URL in igdl response with custom headers");
414
318
  },
415
319
  async () => {
416
- // Fallback: try using the old version of btch-downloader
417
320
  try {
418
321
  const { igdl: igdlOld } = btchOld;
419
322
  if (typeof igdlOld === 'function') {
@@ -422,10 +325,17 @@ async function tryFallbackDownload(url, maxRetries = 3) {
422
325
  return data[0].url;
423
326
  }
424
327
  }
425
- throw new Error("Old btch-downloader igdl not available or failed");
328
+ throw new Error("Old downloader not available or failed");
426
329
  } catch (oldError) {
427
- throw new Error(`Old btch-downloader fallback failed: ${oldError.message}`);
330
+ throw new Error(`Old downloader fallback failed: ${oldError.message}`);
428
331
  }
332
+ },
333
+ async () => {
334
+ const data = await ab.igdl(url);
335
+ if (data && Array.isArray(data) && data[0] && data[0].url) {
336
+ return data[0].url;
337
+ }
338
+ throw new Error("No valid URL");
429
339
  }
430
340
  ];
431
341
 
@@ -441,7 +351,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
441
351
  }
442
352
 
443
353
  case "tiktok": {
444
- // Try multiple TikTok methods
354
+
445
355
  const methods = [
446
356
  async () => {
447
357
  const data = await ttdl(url);
@@ -472,6 +382,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
472
382
  }
473
383
  }
474
384
  throw new Error("No valid video in Tiktok.Downloader v2 response");
385
+ },
386
+ async () => {
387
+ const data = await ab.ttdl(url);
388
+ if (data && data.video && data.video[0]) {
389
+ return data.video[0];
390
+ }
391
+ throw new Error("No valid video");
475
392
  }
476
393
  ];
477
394
 
@@ -507,6 +424,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
507
424
  return data.Normal_video || data.HD;
508
425
  }
509
426
  throw new Error("No valid video in fbdown response with custom headers");
427
+ },
428
+ async () => {
429
+ const data = await ab.fbdown(url);
430
+ if (data && (data.Normal_video || data.HD)) {
431
+ return data.Normal_video || data.HD;
432
+ }
433
+ throw new Error("No valid video");
510
434
  }
511
435
  ];
512
436
 
@@ -653,7 +577,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
653
577
  }
654
578
  },
655
579
  async () => {
656
- // Fallback: try using the old version of btch-downloader for Twitter/X
657
580
  try {
658
581
  const cleanUrl = validateTwitterUrl(url);
659
582
  // Try to find any Twitter-related function in the old version
@@ -687,10 +610,20 @@ async function tryFallbackDownload(url, maxRetries = 3) {
687
610
  }
688
611
  }
689
612
  }
690
- throw new Error("No Twitter functions found in old btch-downloader");
613
+ throw new Error("No Twitter functions found");
691
614
  } catch (oldError) {
692
- throw new Error(`Old btch-downloader Twitter fallback failed: ${oldError.message}`);
615
+ throw new Error(`Twitter fallback failed: ${oldError.message}`);
693
616
  }
617
+ },
618
+ async () => {
619
+ const data = await ab.twitter(url);
620
+ if (data && data.result && data.result.url) {
621
+ return data.result.url;
622
+ }
623
+ if (data && Array.isArray(data) && data[0] && data[0].url) {
624
+ return data[0].url;
625
+ }
626
+ throw new Error("No valid video");
694
627
  }
695
628
  ];
696
629
 
@@ -706,12 +639,10 @@ async function tryFallbackDownload(url, maxRetries = 3) {
706
639
  }
707
640
 
708
641
  case "youtube": {
709
- // YouTube doesn't need fallback - it has its own specific method
710
642
  throw new Error("YouTube downloads should use the primary method, not fallback");
711
643
  }
712
644
 
713
645
  case "mediafire": {
714
- // Try MediaFire method
715
646
  try {
716
647
  const data = await mediafire(url);
717
648
  if (data && data.url) {
@@ -720,7 +651,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
720
651
  throw new Error("No valid URL in mediafire response");
721
652
  }
722
653
  } catch (methodError) {
723
- // Continue to next attempt
724
654
  }
725
655
  break;
726
656
  }
@@ -752,7 +682,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
752
682
  }
753
683
 
754
684
  default: {
755
- // Generic fallback for unknown platforms
756
685
  try {
757
686
  const data = await igdl(url);
758
687
  if (data && Array.isArray(data) && data[0] && data[0].url) {
@@ -761,15 +690,12 @@ async function tryFallbackDownload(url, maxRetries = 3) {
761
690
  throw new Error("No valid URL in generic igdl response");
762
691
  }
763
692
  } catch (methodError) {
764
- // Continue to next attempt
765
693
  }
766
694
  break;
767
695
  }
768
696
  }
769
697
 
770
- // If we got a video URL, validate it
771
698
  if (videoUrl && videoUrl.includes("http")) {
772
- // Validate the URL is accessible
773
699
  try {
774
700
  const response = await axios.head(videoUrl, {
775
701
  timeout: 10000,
@@ -829,58 +755,80 @@ const MediaDownloader = async (url, options = {}) => {
829
755
  throw new Error(`URL not supported: ${blacklisted.reason}`);
830
756
  }
831
757
 
832
- // Verificar se o link está na lista de plataformas suportadas
833
- if (!isVideoLink(url)) {
834
- throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook");
835
- }
758
+ await deleteTempVideos(); // Clean up previous temp files
836
759
 
837
- // Verificar se é YouTube e lançar erro customizado
838
- const platform = getPlatformType(url);
839
- if (platform === "youtube") {
840
- throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook");
760
+ if (url.includes('youtube') || url.includes('youtu.be')) {
761
+ let cookies = options.YTBcookies || (options.YTBcookie ? [options.YTBcookie] : []);
762
+
763
+ // Automatically load external cookies if none provided
764
+ if (cookies.length === 0) {
765
+ cookies = loadExternalCookies();
766
+ }
767
+
768
+ if (cookies.length === 0) {
769
+ throw new Error("YouTube download requires a cookie. Please Provide a valid cookie in 'cookies.json' or options.");
770
+ }
771
+
772
+ // Determine if audio-only is requested
773
+ const isAudioOnly = options.outputFormat === 'mp3' || options.downloadAudio === true || config.downloadAudio === true;
774
+
775
+ const videofile = await downloadYoutubeVideo(url, config, cookies, config.YTBmaxduration, isAudioOnly);
776
+ if (videofile) {
777
+ const result = await uploadToGoFileIfNeeded(videofile);
778
+ return result;
779
+ } else {
780
+ throw new Error("URL not supported. Please provide a video URL from a valid platform.");
781
+ }
841
782
  }
842
783
 
843
- await cleanupTempFiles(); // Clean up previous temp files
784
+ else if (url.includes("http")) {
785
+ const blacklisted = blacklistLink(url);
786
+ if (blacklisted) {
787
+ throw new Error(`URL not supported: ${blacklisted.reason}`);
788
+ }
844
789
 
845
- let downloadedFilePath = null;
790
+ // Verificar se o link está na lista de plataformas suportadas
791
+ if (!isVideoLink(url)) {
792
+ throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook");
793
+ }
794
+
795
+ let downloadedFilePath = null;
846
796
 
847
- try {
848
- // Try primary download method
849
797
  try {
850
- downloadedFilePath = await downloadSmartVideo(url, config);
851
- } catch (error) {
852
- const platform = getPlatformType(url);
798
+ // Try primary download method
799
+ try {
800
+ downloadedFilePath = await downloadSmartVideo(url, config);
801
+ } catch (error) {
802
+ const platform = getPlatformType(url);
853
803
 
854
- // YouTube doesn't use fallback method - it has its own specific implementation
855
- if (platform === "youtube") {
856
- throw new Error(`YouTube download failed: ${error.message}`);
857
- }
804
+ try {
805
+ const fallbackUrl = await tryFallbackDownload(url);
858
806
 
859
- try {
860
- const fallbackUrl = await tryFallbackDownload(url);
807
+ // Validate fallback URL
808
+ const isValid = await validateVideoUrl(fallbackUrl, platform);
809
+ if (!isValid) {
810
+ throw new Error("Fallback URL validation failed");
811
+ }
861
812
 
862
- // Validate fallback URL
863
- const isValid = await validateVideoUrl(fallbackUrl, platform);
864
- if (!isValid) {
865
- throw new Error("Fallback URL validation failed");
813
+ downloadedFilePath = await downloadDirectVideo(fallbackUrl, config);
814
+ } catch (fallbackError) {
815
+ throw new Error(`Failed to download video with both methods: ${fallbackError.message}`);
866
816
  }
867
-
868
- downloadedFilePath = await downloadDirectVideo(fallbackUrl, config);
869
- } catch (fallbackError) {
870
- throw new Error(`Failed to download video with both methods: ${fallbackError.message}`);
871
817
  }
872
- }
873
818
 
874
- if (downloadedFilePath) {
875
- const result = await uploadToGoFileIfNeeded(downloadedFilePath);
876
- return result;
877
- } else {
878
- throw new Error("Failed to obtain downloaded file path.");
819
+ if (downloadedFilePath) {
820
+ const result = await uploadToGoFileIfNeeded(downloadedFilePath);
821
+ return result;
822
+ } else {
823
+ throw new Error("Failed to obtain downloaded file path.");
824
+ }
825
+ } catch (error) {
826
+ // Clean up any remaining temp files on error
827
+ await cleanupTempFiles();
828
+ throw new Error(`Error in downloader videos: ${error.message}`);
879
829
  }
880
- } catch (error) {
881
- // Clean up any remaining temp files on error
882
- await cleanupTempFiles();
883
- throw new Error(`Error in downloader videos: ${error.message}`);
830
+ } else {
831
+ throw new Error("Please specify a video URL from Instagram, facebook or TikTok...");
884
832
  }
885
833
  };
886
834
 
@@ -943,7 +891,6 @@ async function downloadSmartVideo(url, config) {
943
891
  }
944
892
  videoUrl = data.video[0];
945
893
  } catch (error) {
946
- // Fallback: @tobyg74/tiktok-api-dl
947
894
  try {
948
895
  const result = await Tiktok.Downloader(url, { version: "v1" });
949
896
  if (result.status === "success" && result.result) {
@@ -1018,10 +965,7 @@ async function downloadSmartVideo(url, config) {
1018
965
  }
1019
966
  case "pinterest": {
1020
967
  try {
1021
- // Supports both pin links and search queries
1022
968
  const data = await pinterest(url);
1023
- // data may be array or object depending on query or pin
1024
- // Try to find best url (image or video)
1025
969
  let directUrl = null;
1026
970
  const pickFrom = (obj) => {
1027
971
  if (!obj) return null;
@@ -1082,7 +1026,6 @@ async function downloadSmartVideo(url, config) {
1082
1026
  if (highQuality) {
1083
1027
  videoUrl = highQuality.url;
1084
1028
  } else {
1085
- // Fallback: pega o primeiro que tiver url
1086
1029
  const anyVideo = data.url.find(v => v.url);
1087
1030
  videoUrl = anyVideo ? anyVideo.url : data.url[0];
1088
1031
  }
@@ -1093,7 +1036,6 @@ async function downloadSmartVideo(url, config) {
1093
1036
  }
1094
1037
 
1095
1038
  if (!videoUrl) {
1096
- // Fallback para estrutura antiga ou diferente
1097
1039
  if (Array.isArray(data) && data[0] && data[0].url) {
1098
1040
  videoUrl = data[0].url;
1099
1041
  } else if (data && data.HD) {
@@ -1102,7 +1044,7 @@ async function downloadSmartVideo(url, config) {
1102
1044
  }
1103
1045
 
1104
1046
  if (!videoUrl) {
1105
- throw new Error("No video URL found in btch-downloader twitter response");
1047
+ throw new Error("No video URL found in downloader twitter response");
1106
1048
  }
1107
1049
  } catch (error) {
1108
1050
  throw new Error(`Twitter download failed: ${error.message}`);
@@ -1255,9 +1197,187 @@ async function rotateVideo(fileName, rotation) {
1255
1197
 
1256
1198
  // Function to extract URL from string
1257
1199
  function extractUrlFromString(text) {
1258
- const urlRegex = /(https?:\/\/[^\s]+)/;
1200
+ const urlRegex = /(https?:\/\/[^\s]+)/g;
1259
1201
  const match = text.match(urlRegex);
1260
- return match ? match[0] : null;
1202
+ return match ? match[0] : text;
1203
+ }
1204
+
1205
+ async function downloadYoutubeVideo(url, config, cookies, YTBmaxduration, isAudioOnly = false) {
1206
+ let lastError = null;
1207
+
1208
+ for (let i = 0; i < Math.min(cookies.length, 6); i++) {
1209
+ const currentCookie = cookies[i];
1210
+ try {
1211
+ const parsedCookies = parseCookies(currentCookie);
1212
+
1213
+ if (parsedCookies.length === 0) {
1214
+ continue;
1215
+ }
1216
+
1217
+ const agent = ytdl.createAgent(parsedCookies);
1218
+ const info = await ytdl.getInfo(url, { agent });
1219
+
1220
+ const durationSeconds = parseInt(info.videoDetails.lengthSeconds, 10);
1221
+ if (durationSeconds > YTBmaxduration) {
1222
+ throw new Error(`❌ The video is longer than ${YTBmaxduration} seconds. Aborting.`);
1223
+ }
1224
+
1225
+ let bestFormat;
1226
+ if (isAudioOnly) {
1227
+ // Filter for audio-only formats
1228
+ const audioFormats = info.formats.filter(f => !f.hasVideo && f.hasAudio);
1229
+ bestFormat = audioFormats.sort((a, b) => (b.audioBitrate || 0) - (a.audioBitrate || 0))[0];
1230
+
1231
+ if (!bestFormat) {
1232
+ // Fallback: any format with audio
1233
+ bestFormat = info.formats.filter(f => f.hasAudio).sort((a, b) => (b.audioBitrate || 0) - (a.audioBitrate || 0))[0];
1234
+ }
1235
+ if (!bestFormat) {
1236
+ throw new Error('❌ No suitable audio format found.');
1237
+ }
1238
+ } else {
1239
+ let formats = info.formats.filter(format => {
1240
+ return format.contentLength && parseInt(format.contentLength) <= 10 * 1024 * 1024 && // ≤ 10 MB
1241
+ format.hasAudio && format.hasVideo;
1242
+ });
1243
+
1244
+ if (formats.length === 0) {
1245
+ formats = info.formats.filter(format => {
1246
+ return format.hasAudio && format.hasVideo;
1247
+ });
1248
+ }
1249
+
1250
+ if (formats.length === 0) {
1251
+ throw new Error('❌ No suitable video format found.');
1252
+ }
1253
+
1254
+ bestFormat = formats.sort((a, b) => b.height - a.height)[0];
1255
+ }
1256
+
1257
+ const ext = isAudioOnly ? 'mp3' : 'mp4';
1258
+ // Create minimal unique file name in output dir
1259
+ let fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0, 4)}.${ext}`);
1260
+
1261
+ const videoStream = ytdl(url, {
1262
+ format: bestFormat,
1263
+ agent
1264
+ });
1265
+
1266
+ const videoWriter = fs.createWriteStream(fileName);
1267
+ videoStream.pipe(videoWriter);
1268
+
1269
+ return await new Promise((resolve, reject) => {
1270
+ videoWriter.on('finish', async () => {
1271
+ try {
1272
+ let finalFilePath = fileName;
1273
+ if (!isAudioOnly) {
1274
+ finalFilePath = await processDownloadedFile(fileName, config, "youtube");
1275
+ }
1276
+ resolve(finalFilePath);
1277
+ } catch (error) {
1278
+ reject(error);
1279
+ }
1280
+ });
1281
+ videoWriter.on('error', (error) => reject(error));
1282
+ });
1283
+
1284
+ } catch (error) {
1285
+ console.error(`Attempt with cookie ${i + 1} failed: ${error.message}`);
1286
+ lastError = error;
1287
+ // If the error is about duration, don't retry with other cookies
1288
+ if (error.message.includes('longer than')) {
1289
+ throw error;
1290
+ }
1291
+ continue; // Try next cookie
1292
+ }
1293
+ }
1294
+
1295
+ throw new Error(`An error occurred while downloading the YouTube video (all ${Math.min(cookies.length, 10)} cookies failed): ${lastError?.message}`);
1296
+ }
1297
+
1298
+ /**
1299
+ * Automatically loads cookies from cookies.json if present
1300
+ * @returns {Array} Array of cookie sets (one or more accounts)
1301
+ */
1302
+ function loadExternalCookies() {
1303
+ const cookiesPath = path.join(process.cwd(), 'cookies.json');
1304
+ if (!fs.existsSync(cookiesPath)) return [];
1305
+
1306
+ try {
1307
+ const fileContent = fs.readFileSync(cookiesPath, 'utf8');
1308
+ const parsed = JSON.parse(fileContent);
1309
+ if (!Array.isArray(parsed)) return [];
1310
+
1311
+ // If it's an array of objects, it's ONE account.
1312
+ // If it's an array of arrays, it's MULTIPLE accounts.
1313
+ if (parsed.length > 0) {
1314
+ if (Array.isArray(parsed[0])) {
1315
+ // Multiple accounts: return them as strings for the rotation logic
1316
+ return parsed.map(acc => JSON.stringify(acc));
1317
+ } else {
1318
+ // Single account: return the whole JSON as a string
1319
+ return [fileContent];
1320
+ }
1321
+ }
1322
+ } catch (e) {
1323
+ console.error("Error loading external cookies:", e.message);
1324
+ }
1325
+ return [];
1326
+ }
1327
+
1328
+ /**
1329
+ * Parses cookies in various formats (JSON, Netscape, or Header String)
1330
+ * @param {string} cookieInput
1331
+ * @returns {Array} Array of cookie objects
1332
+ */
1333
+ function parseCookies(cookieInput) {
1334
+ if (!cookieInput) return [];
1335
+
1336
+ const trimmedInput = cookieInput.trim();
1337
+
1338
+ // 1. Try JSON (EditThisCookie export)
1339
+ try {
1340
+ const parsed = JSON.parse(trimmedInput);
1341
+ if (Array.isArray(parsed)) return parsed;
1342
+ if (typeof parsed === 'object') return [parsed];
1343
+ } catch (e) { }
1344
+
1345
+ // 2. Try Netscape format (tab-separated)
1346
+ if (trimmedInput.includes('\t')) {
1347
+ const lines = trimmedInput.split(/\r?\n/);
1348
+ const cookies = [];
1349
+ for (const line of lines) {
1350
+ if (!line.trim() || line.startsWith('#')) continue;
1351
+ const parts = line.split('\t');
1352
+ if (parts.length >= 7) {
1353
+ cookies.push({
1354
+ domain: parts[0],
1355
+ path: parts[2],
1356
+ secure: parts[3].toUpperCase() === 'TRUE',
1357
+ expirationDate: parseInt(parts[4]),
1358
+ name: parts[5],
1359
+ value: parts[6]
1360
+ });
1361
+ }
1362
+ }
1363
+ if (cookies.length > 0) return cookies;
1364
+ }
1365
+
1366
+ // 3. Fallback: Cookie header string (name=value; name2=value2)
1367
+ return trimmedInput.split(';').map(v => v.split('=')).reduce((acc, v) => {
1368
+ if (v.length >= 2) {
1369
+ const name = v[0].trim();
1370
+ const value = v.slice(1).join('=').trim();
1371
+ if (name && value) {
1372
+ acc.push({
1373
+ name: name,
1374
+ value: value,
1375
+ domain: '.youtube.com'
1376
+ });
1377
+ }
1378
+ }
1379
+ return acc;
1380
+ }, []);
1261
1381
  }
1262
1382
 
1263
1383
  // Function to delete temporary videos
@@ -1528,7 +1648,7 @@ const AudioDownloader = async (url, options = {}) => {
1528
1648
  try {
1529
1649
  let platform = getPlatformType(url);
1530
1650
  if (platform === "y124outube") {
1531
- // Baixar áudio do YouTube usando ytdlp-nodejs
1651
+
1532
1652
  let fileName = "temp_audio.mp3";
1533
1653
  let count = 1;
1534
1654
  while (fs.existsSync(fileName)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frostpv",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "downloads",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -12,6 +12,9 @@
12
12
  "author": "Delta",
13
13
  "license": "ISC",
14
14
  "dependencies": {
15
+ "@distube/ytdl-core": "^4.16.12",
16
+ "@tobyg74/tiktok-api-dl": "^1.3.7",
17
+ "ab-downloader": "^1.0.1",
15
18
  "axios": "1.12.0",
16
19
  "btch-downloader": "6.0.25",
17
20
  "btch-downloader-old": "npm:btch-downloader@4.0.15",
@@ -21,8 +24,6 @@
21
24
  "fs": "^0.0.1-security",
22
25
  "path": "^0.12.7",
23
26
  "twitter-downloader": "^1.1.8",
24
- "uuid": "^11.1.0",
25
- "@tobyg74/tiktok-api-dl": "^1.3.7",
26
- "ytdlp-nodejs": "2.3.4"
27
+ "uuid": "^11.1.0"
27
28
  }
28
- }
29
+ }