frostpv 1.0.13 → 1.0.15
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.
- package/index.js +42 -127
- package/package.json +5 -5
package/index.js
CHANGED
|
@@ -9,9 +9,9 @@ const { TwitterDL } = require("twitter-downloader");
|
|
|
9
9
|
const btch = require("btch-downloader");
|
|
10
10
|
const btchOld = require("btch-downloader-old");
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
const Tiktok = require("@tobyg74/tiktok-api-dl");
|
|
14
|
-
const
|
|
14
|
+
const ab = require("ab-downloader");
|
|
15
15
|
const ffmpegPath = require('ffmpeg-ffprobe-static').ffmpegPath;
|
|
16
16
|
const { v4: uuidv4 } = require('uuid');
|
|
17
17
|
|
|
@@ -19,13 +19,10 @@ const pathToFfmpeg = require("ffmpeg-ffprobe-static");
|
|
|
19
19
|
const ffmpeg = require("fluent-ffmpeg");
|
|
20
20
|
ffmpeg.setFfmpegPath(pathToFfmpeg.ffmpegPath);
|
|
21
21
|
|
|
22
|
-
// Output directory is the caller's working directory (keeps files next to the running app)
|
|
23
22
|
const OUTPUT_DIR = process.cwd();
|
|
24
23
|
try { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); } catch (_) { }
|
|
25
|
-
// Keep an OS temp directory for any future transient needs
|
|
26
24
|
const TEMP_DIR = path.join(os.tmpdir(), "downloader-dl-bot");
|
|
27
25
|
try { fs.mkdirSync(TEMP_DIR, { recursive: true }); } catch (_) { }
|
|
28
|
-
// TTL (minutes) for files in OUTPUT_DIR (audio/video) before they are pruned
|
|
29
26
|
const OUTPUT_RETENTION_MIN = Number(process.env.OUTPUT_RETENTION_MIN || 5);
|
|
30
27
|
|
|
31
28
|
const GOFILE_API = "Vs4e2PL8n65ExPz6wgDlqSY8kcEBRrzN";
|
|
@@ -47,6 +44,7 @@ const videoPlatforms = [
|
|
|
47
44
|
"https://www.fb.watch",
|
|
48
45
|
"https://fb.com",
|
|
49
46
|
"https://www.fb.com",
|
|
47
|
+
"https://web.facebook.com",
|
|
50
48
|
"https://www.mediafire.com",
|
|
51
49
|
"https://mediafire.com",
|
|
52
50
|
"https://www.capcut.com",
|
|
@@ -84,7 +82,7 @@ const blacklistLink = (link) => {
|
|
|
84
82
|
|
|
85
83
|
const defaultConfig = {
|
|
86
84
|
autocrop: false,
|
|
87
|
-
limitSizeMB: null,
|
|
85
|
+
limitSizeMB: null,
|
|
88
86
|
rotation: null,
|
|
89
87
|
outputFormat: null,
|
|
90
88
|
};
|
|
@@ -264,102 +262,6 @@ function getYoutubeCookiesPath() {
|
|
|
264
262
|
return null;
|
|
265
263
|
}
|
|
266
264
|
|
|
267
|
-
// Função utilitária para obter duração do vídeo/áudio em segundos
|
|
268
|
-
async function getYoutubeDurationSeconds(url) {
|
|
269
|
-
try {
|
|
270
|
-
const ytdlp = new YtDlp({ ffmpegPath });
|
|
271
|
-
const info = await ytdlp.getInfoAsync(url);
|
|
272
|
-
if (info && info.duration) return info.duration;
|
|
273
|
-
return null;
|
|
274
|
-
} catch (e) {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Função para baixar áudio do YouTube
|
|
280
|
-
async function downloadYoutubeAudio(url, outputPath) {
|
|
281
|
-
// Verificar duração máxima de 15 minutos
|
|
282
|
-
const duration = await getYoutubeDurationSeconds(url);
|
|
283
|
-
if (duration && duration > 900) {
|
|
284
|
-
throw new Error('The audio is longer than 15 minutes. Test limit: 15 minutes. This feature is still in beta.');
|
|
285
|
-
}
|
|
286
|
-
return new Promise(async (resolve, reject) => {
|
|
287
|
-
try {
|
|
288
|
-
const ytdlp = new YtDlp({ ffmpegPath });
|
|
289
|
-
const cookiesPath = getYoutubeCookiesPath();
|
|
290
|
-
|
|
291
|
-
// Garantir que o nome do arquivo tenha a extensão correta
|
|
292
|
-
const baseName = outputPath.replace(/\.[^/.]+$/, ""); // Remove extensão se existir
|
|
293
|
-
|
|
294
|
-
// Primeiro, tentar com a API de opções
|
|
295
|
-
try {
|
|
296
|
-
const options = {
|
|
297
|
-
format: {
|
|
298
|
-
filter: 'audioonly',
|
|
299
|
-
type: 'mp3',
|
|
300
|
-
quality: 'highestaudio'
|
|
301
|
-
},
|
|
302
|
-
output: outputPath
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
if (cookiesPath) {
|
|
306
|
-
options.cookies = cookiesPath;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Remover logs detalhados para download de áudio do YouTube
|
|
310
|
-
const result = await ytdlp.downloadAsync(url, options);
|
|
311
|
-
// Verificar se o arquivo foi criado
|
|
312
|
-
const actualFileName = baseName + '.mp3';
|
|
313
|
-
if (fs.existsSync(actualFileName)) {
|
|
314
|
-
resolve(actualFileName);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Tentar encontrar o arquivo com extensão diferente
|
|
319
|
-
const files = fs.readdirSync('./');
|
|
320
|
-
const downloadedFile = files.find(file => file.startsWith(baseName.split('/').pop()));
|
|
321
|
-
if (downloadedFile) {
|
|
322
|
-
resolve(downloadedFile);
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
} catch (apiError) {
|
|
326
|
-
console.log('API method failed, trying direct args method:', apiError.message);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Fallback: usar argumentos diretos para máxima qualidade de áudio
|
|
330
|
-
const args = [
|
|
331
|
-
url,
|
|
332
|
-
'-f', 'bestaudio[ext=mp3]/bestaudio',
|
|
333
|
-
'-o', outputPath
|
|
334
|
-
];
|
|
335
|
-
|
|
336
|
-
if (cookiesPath) {
|
|
337
|
-
args.push('--cookies', cookiesPath);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Remover logs detalhados para download de áudio do YouTube
|
|
341
|
-
const result = await ytdlp.execAsync(args);
|
|
342
|
-
// Verificar se o arquivo foi criado
|
|
343
|
-
const actualFileName = baseName + '.mp3';
|
|
344
|
-
if (fs.existsSync(actualFileName)) {
|
|
345
|
-
resolve(actualFileName);
|
|
346
|
-
} else {
|
|
347
|
-
// Tentar encontrar o arquivo com extensão diferente
|
|
348
|
-
const files = fs.readdirSync('./');
|
|
349
|
-
const downloadedFile = files.find(file => file.startsWith(baseName.split('/').pop()));
|
|
350
|
-
if (downloadedFile) {
|
|
351
|
-
resolve(downloadedFile);
|
|
352
|
-
} else {
|
|
353
|
-
reject(new Error('YouTube audio download completed but file not found'));
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
} catch (error) {
|
|
357
|
-
console.error('YouTube audio download error:', error);
|
|
358
|
-
reject(new Error('This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok and Facebook'));
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
|
|
363
265
|
// Enhanced function to get platform type from URL
|
|
364
266
|
function getPlatformType(url) {
|
|
365
267
|
const lowerUrl = url.toLowerCase();
|
|
@@ -374,7 +276,6 @@ function getPlatformType(url) {
|
|
|
374
276
|
return "unknown";
|
|
375
277
|
}
|
|
376
278
|
|
|
377
|
-
// Enhanced fallback download function with platform-specific methods
|
|
378
279
|
async function tryFallbackDownload(url, maxRetries = 3) {
|
|
379
280
|
const platform = getPlatformType(url);
|
|
380
281
|
|
|
@@ -382,10 +283,8 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
382
283
|
try {
|
|
383
284
|
let videoUrl = null;
|
|
384
285
|
|
|
385
|
-
// Platform-specific fallback methods
|
|
386
286
|
switch (platform) {
|
|
387
287
|
case "instagram": {
|
|
388
|
-
// Try multiple Instagram methods
|
|
389
288
|
const methods = [
|
|
390
289
|
async () => {
|
|
391
290
|
const data = await igdl(url);
|
|
@@ -412,7 +311,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
412
311
|
throw new Error("No valid URL in igdl response with custom headers");
|
|
413
312
|
},
|
|
414
313
|
async () => {
|
|
415
|
-
// Fallback: try using the old version of btch-downloader
|
|
416
314
|
try {
|
|
417
315
|
const { igdl: igdlOld } = btchOld;
|
|
418
316
|
if (typeof igdlOld === 'function') {
|
|
@@ -421,10 +319,17 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
421
319
|
return data[0].url;
|
|
422
320
|
}
|
|
423
321
|
}
|
|
424
|
-
throw new Error("Old
|
|
322
|
+
throw new Error("Old downloader not available or failed");
|
|
425
323
|
} catch (oldError) {
|
|
426
|
-
throw new Error(`Old
|
|
324
|
+
throw new Error(`Old downloader fallback failed: ${oldError.message}`);
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
async () => {
|
|
328
|
+
const data = await ab.igdl(url);
|
|
329
|
+
if (data && Array.isArray(data) && data[0] && data[0].url) {
|
|
330
|
+
return data[0].url;
|
|
427
331
|
}
|
|
332
|
+
throw new Error("No valid URL");
|
|
428
333
|
}
|
|
429
334
|
];
|
|
430
335
|
|
|
@@ -440,7 +345,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
440
345
|
}
|
|
441
346
|
|
|
442
347
|
case "tiktok": {
|
|
443
|
-
|
|
348
|
+
|
|
444
349
|
const methods = [
|
|
445
350
|
async () => {
|
|
446
351
|
const data = await ttdl(url);
|
|
@@ -471,6 +376,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
471
376
|
}
|
|
472
377
|
}
|
|
473
378
|
throw new Error("No valid video in Tiktok.Downloader v2 response");
|
|
379
|
+
},
|
|
380
|
+
async () => {
|
|
381
|
+
const data = await ab.ttdl(url);
|
|
382
|
+
if (data && data.video && data.video[0]) {
|
|
383
|
+
return data.video[0];
|
|
384
|
+
}
|
|
385
|
+
throw new Error("No valid video");
|
|
474
386
|
}
|
|
475
387
|
];
|
|
476
388
|
|
|
@@ -506,6 +418,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
506
418
|
return data.Normal_video || data.HD;
|
|
507
419
|
}
|
|
508
420
|
throw new Error("No valid video in fbdown response with custom headers");
|
|
421
|
+
},
|
|
422
|
+
async () => {
|
|
423
|
+
const data = await ab.fbdown(url);
|
|
424
|
+
if (data && (data.Normal_video || data.HD)) {
|
|
425
|
+
return data.Normal_video || data.HD;
|
|
426
|
+
}
|
|
427
|
+
throw new Error("No valid video");
|
|
509
428
|
}
|
|
510
429
|
];
|
|
511
430
|
|
|
@@ -652,7 +571,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
652
571
|
}
|
|
653
572
|
},
|
|
654
573
|
async () => {
|
|
655
|
-
// Fallback: try using the old version of btch-downloader for Twitter/X
|
|
656
574
|
try {
|
|
657
575
|
const cleanUrl = validateTwitterUrl(url);
|
|
658
576
|
// Try to find any Twitter-related function in the old version
|
|
@@ -686,10 +604,20 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
686
604
|
}
|
|
687
605
|
}
|
|
688
606
|
}
|
|
689
|
-
throw new Error("No Twitter functions found
|
|
607
|
+
throw new Error("No Twitter functions found");
|
|
690
608
|
} catch (oldError) {
|
|
691
|
-
throw new Error(`
|
|
609
|
+
throw new Error(`Twitter fallback failed: ${oldError.message}`);
|
|
692
610
|
}
|
|
611
|
+
},
|
|
612
|
+
async () => {
|
|
613
|
+
const data = await ab.twitter(url);
|
|
614
|
+
if (data && data.result && data.result.url) {
|
|
615
|
+
return data.result.url;
|
|
616
|
+
}
|
|
617
|
+
if (data && Array.isArray(data) && data[0] && data[0].url) {
|
|
618
|
+
return data[0].url;
|
|
619
|
+
}
|
|
620
|
+
throw new Error("No valid video");
|
|
693
621
|
}
|
|
694
622
|
];
|
|
695
623
|
|
|
@@ -705,12 +633,10 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
705
633
|
}
|
|
706
634
|
|
|
707
635
|
case "youtube": {
|
|
708
|
-
// YouTube doesn't need fallback - it has its own specific method
|
|
709
636
|
throw new Error("YouTube downloads should use the primary method, not fallback");
|
|
710
637
|
}
|
|
711
638
|
|
|
712
639
|
case "mediafire": {
|
|
713
|
-
// Try MediaFire method
|
|
714
640
|
try {
|
|
715
641
|
const data = await mediafire(url);
|
|
716
642
|
if (data && data.url) {
|
|
@@ -719,7 +645,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
719
645
|
throw new Error("No valid URL in mediafire response");
|
|
720
646
|
}
|
|
721
647
|
} catch (methodError) {
|
|
722
|
-
// Continue to next attempt
|
|
723
648
|
}
|
|
724
649
|
break;
|
|
725
650
|
}
|
|
@@ -751,7 +676,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
751
676
|
}
|
|
752
677
|
|
|
753
678
|
default: {
|
|
754
|
-
// Generic fallback for unknown platforms
|
|
755
679
|
try {
|
|
756
680
|
const data = await igdl(url);
|
|
757
681
|
if (data && Array.isArray(data) && data[0] && data[0].url) {
|
|
@@ -760,15 +684,12 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
760
684
|
throw new Error("No valid URL in generic igdl response");
|
|
761
685
|
}
|
|
762
686
|
} catch (methodError) {
|
|
763
|
-
// Continue to next attempt
|
|
764
687
|
}
|
|
765
688
|
break;
|
|
766
689
|
}
|
|
767
690
|
}
|
|
768
691
|
|
|
769
|
-
// If we got a video URL, validate it
|
|
770
692
|
if (videoUrl && videoUrl.includes("http")) {
|
|
771
|
-
// Validate the URL is accessible
|
|
772
693
|
try {
|
|
773
694
|
const response = await axios.head(videoUrl, {
|
|
774
695
|
timeout: 10000,
|
|
@@ -942,7 +863,6 @@ async function downloadSmartVideo(url, config) {
|
|
|
942
863
|
}
|
|
943
864
|
videoUrl = data.video[0];
|
|
944
865
|
} catch (error) {
|
|
945
|
-
// Fallback: @tobyg74/tiktok-api-dl
|
|
946
866
|
try {
|
|
947
867
|
const result = await Tiktok.Downloader(url, { version: "v1" });
|
|
948
868
|
if (result.status === "success" && result.result) {
|
|
@@ -1017,10 +937,7 @@ async function downloadSmartVideo(url, config) {
|
|
|
1017
937
|
}
|
|
1018
938
|
case "pinterest": {
|
|
1019
939
|
try {
|
|
1020
|
-
// Supports both pin links and search queries
|
|
1021
940
|
const data = await pinterest(url);
|
|
1022
|
-
// data may be array or object depending on query or pin
|
|
1023
|
-
// Try to find best url (image or video)
|
|
1024
941
|
let directUrl = null;
|
|
1025
942
|
const pickFrom = (obj) => {
|
|
1026
943
|
if (!obj) return null;
|
|
@@ -1081,7 +998,6 @@ async function downloadSmartVideo(url, config) {
|
|
|
1081
998
|
if (highQuality) {
|
|
1082
999
|
videoUrl = highQuality.url;
|
|
1083
1000
|
} else {
|
|
1084
|
-
// Fallback: pega o primeiro que tiver url
|
|
1085
1001
|
const anyVideo = data.url.find(v => v.url);
|
|
1086
1002
|
videoUrl = anyVideo ? anyVideo.url : data.url[0];
|
|
1087
1003
|
}
|
|
@@ -1092,7 +1008,6 @@ async function downloadSmartVideo(url, config) {
|
|
|
1092
1008
|
}
|
|
1093
1009
|
|
|
1094
1010
|
if (!videoUrl) {
|
|
1095
|
-
// Fallback para estrutura antiga ou diferente
|
|
1096
1011
|
if (Array.isArray(data) && data[0] && data[0].url) {
|
|
1097
1012
|
videoUrl = data[0].url;
|
|
1098
1013
|
} else if (data && data.HD) {
|
|
@@ -1101,7 +1016,7 @@ async function downloadSmartVideo(url, config) {
|
|
|
1101
1016
|
}
|
|
1102
1017
|
|
|
1103
1018
|
if (!videoUrl) {
|
|
1104
|
-
throw new Error("No video URL found in
|
|
1019
|
+
throw new Error("No video URL found in downloader twitter response");
|
|
1105
1020
|
}
|
|
1106
1021
|
} catch (error) {
|
|
1107
1022
|
throw new Error(`Twitter download failed: ${error.message}`);
|
|
@@ -1527,7 +1442,7 @@ const AudioDownloader = async (url, options = {}) => {
|
|
|
1527
1442
|
try {
|
|
1528
1443
|
let platform = getPlatformType(url);
|
|
1529
1444
|
if (platform === "y124outube") {
|
|
1530
|
-
|
|
1445
|
+
|
|
1531
1446
|
let fileName = "temp_audio.mp3";
|
|
1532
1447
|
let count = 1;
|
|
1533
1448
|
while (fs.existsSync(fileName)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frostpv",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "downloads",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"author": "Delta",
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"dependencies": {
|
|
15
|
+
"@tobyg74/tiktok-api-dl": "^1.3.7",
|
|
16
|
+
"ab-downloader": "^1.0.1",
|
|
15
17
|
"axios": "1.12.0",
|
|
16
18
|
"btch-downloader": "6.0.25",
|
|
17
19
|
"btch-downloader-old": "npm:btch-downloader@4.0.15",
|
|
@@ -21,8 +23,6 @@
|
|
|
21
23
|
"fs": "^0.0.1-security",
|
|
22
24
|
"path": "^0.12.7",
|
|
23
25
|
"twitter-downloader": "^1.1.8",
|
|
24
|
-
"uuid": "^11.1.0"
|
|
25
|
-
"@tobyg74/tiktok-api-dl": "^1.3.7",
|
|
26
|
-
"ytdlp-nodejs": "2.3.4"
|
|
26
|
+
"uuid": "^11.1.0"
|
|
27
27
|
}
|
|
28
|
-
}
|
|
28
|
+
}
|