frostpv 1.0.14 → 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 +41 -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";
|
|
@@ -85,7 +82,7 @@ const blacklistLink = (link) => {
|
|
|
85
82
|
|
|
86
83
|
const defaultConfig = {
|
|
87
84
|
autocrop: false,
|
|
88
|
-
limitSizeMB: null,
|
|
85
|
+
limitSizeMB: null,
|
|
89
86
|
rotation: null,
|
|
90
87
|
outputFormat: null,
|
|
91
88
|
};
|
|
@@ -265,102 +262,6 @@ function getYoutubeCookiesPath() {
|
|
|
265
262
|
return null;
|
|
266
263
|
}
|
|
267
264
|
|
|
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
265
|
// Enhanced function to get platform type from URL
|
|
365
266
|
function getPlatformType(url) {
|
|
366
267
|
const lowerUrl = url.toLowerCase();
|
|
@@ -375,7 +276,6 @@ function getPlatformType(url) {
|
|
|
375
276
|
return "unknown";
|
|
376
277
|
}
|
|
377
278
|
|
|
378
|
-
// Enhanced fallback download function with platform-specific methods
|
|
379
279
|
async function tryFallbackDownload(url, maxRetries = 3) {
|
|
380
280
|
const platform = getPlatformType(url);
|
|
381
281
|
|
|
@@ -383,10 +283,8 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
383
283
|
try {
|
|
384
284
|
let videoUrl = null;
|
|
385
285
|
|
|
386
|
-
// Platform-specific fallback methods
|
|
387
286
|
switch (platform) {
|
|
388
287
|
case "instagram": {
|
|
389
|
-
// Try multiple Instagram methods
|
|
390
288
|
const methods = [
|
|
391
289
|
async () => {
|
|
392
290
|
const data = await igdl(url);
|
|
@@ -413,7 +311,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
413
311
|
throw new Error("No valid URL in igdl response with custom headers");
|
|
414
312
|
},
|
|
415
313
|
async () => {
|
|
416
|
-
// Fallback: try using the old version of btch-downloader
|
|
417
314
|
try {
|
|
418
315
|
const { igdl: igdlOld } = btchOld;
|
|
419
316
|
if (typeof igdlOld === 'function') {
|
|
@@ -422,10 +319,17 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
422
319
|
return data[0].url;
|
|
423
320
|
}
|
|
424
321
|
}
|
|
425
|
-
throw new Error("Old
|
|
322
|
+
throw new Error("Old downloader not available or failed");
|
|
426
323
|
} catch (oldError) {
|
|
427
|
-
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;
|
|
428
331
|
}
|
|
332
|
+
throw new Error("No valid URL");
|
|
429
333
|
}
|
|
430
334
|
];
|
|
431
335
|
|
|
@@ -441,7 +345,7 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
441
345
|
}
|
|
442
346
|
|
|
443
347
|
case "tiktok": {
|
|
444
|
-
|
|
348
|
+
|
|
445
349
|
const methods = [
|
|
446
350
|
async () => {
|
|
447
351
|
const data = await ttdl(url);
|
|
@@ -472,6 +376,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
472
376
|
}
|
|
473
377
|
}
|
|
474
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");
|
|
475
386
|
}
|
|
476
387
|
];
|
|
477
388
|
|
|
@@ -507,6 +418,13 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
507
418
|
return data.Normal_video || data.HD;
|
|
508
419
|
}
|
|
509
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");
|
|
510
428
|
}
|
|
511
429
|
];
|
|
512
430
|
|
|
@@ -653,7 +571,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
653
571
|
}
|
|
654
572
|
},
|
|
655
573
|
async () => {
|
|
656
|
-
// Fallback: try using the old version of btch-downloader for Twitter/X
|
|
657
574
|
try {
|
|
658
575
|
const cleanUrl = validateTwitterUrl(url);
|
|
659
576
|
// Try to find any Twitter-related function in the old version
|
|
@@ -687,10 +604,20 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
687
604
|
}
|
|
688
605
|
}
|
|
689
606
|
}
|
|
690
|
-
throw new Error("No Twitter functions found
|
|
607
|
+
throw new Error("No Twitter functions found");
|
|
691
608
|
} catch (oldError) {
|
|
692
|
-
throw new Error(`
|
|
609
|
+
throw new Error(`Twitter fallback failed: ${oldError.message}`);
|
|
693
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");
|
|
694
621
|
}
|
|
695
622
|
];
|
|
696
623
|
|
|
@@ -706,12 +633,10 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
706
633
|
}
|
|
707
634
|
|
|
708
635
|
case "youtube": {
|
|
709
|
-
// YouTube doesn't need fallback - it has its own specific method
|
|
710
636
|
throw new Error("YouTube downloads should use the primary method, not fallback");
|
|
711
637
|
}
|
|
712
638
|
|
|
713
639
|
case "mediafire": {
|
|
714
|
-
// Try MediaFire method
|
|
715
640
|
try {
|
|
716
641
|
const data = await mediafire(url);
|
|
717
642
|
if (data && data.url) {
|
|
@@ -720,7 +645,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
720
645
|
throw new Error("No valid URL in mediafire response");
|
|
721
646
|
}
|
|
722
647
|
} catch (methodError) {
|
|
723
|
-
// Continue to next attempt
|
|
724
648
|
}
|
|
725
649
|
break;
|
|
726
650
|
}
|
|
@@ -752,7 +676,6 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
752
676
|
}
|
|
753
677
|
|
|
754
678
|
default: {
|
|
755
|
-
// Generic fallback for unknown platforms
|
|
756
679
|
try {
|
|
757
680
|
const data = await igdl(url);
|
|
758
681
|
if (data && Array.isArray(data) && data[0] && data[0].url) {
|
|
@@ -761,15 +684,12 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
761
684
|
throw new Error("No valid URL in generic igdl response");
|
|
762
685
|
}
|
|
763
686
|
} catch (methodError) {
|
|
764
|
-
// Continue to next attempt
|
|
765
687
|
}
|
|
766
688
|
break;
|
|
767
689
|
}
|
|
768
690
|
}
|
|
769
691
|
|
|
770
|
-
// If we got a video URL, validate it
|
|
771
692
|
if (videoUrl && videoUrl.includes("http")) {
|
|
772
|
-
// Validate the URL is accessible
|
|
773
693
|
try {
|
|
774
694
|
const response = await axios.head(videoUrl, {
|
|
775
695
|
timeout: 10000,
|
|
@@ -943,7 +863,6 @@ async function downloadSmartVideo(url, config) {
|
|
|
943
863
|
}
|
|
944
864
|
videoUrl = data.video[0];
|
|
945
865
|
} catch (error) {
|
|
946
|
-
// Fallback: @tobyg74/tiktok-api-dl
|
|
947
866
|
try {
|
|
948
867
|
const result = await Tiktok.Downloader(url, { version: "v1" });
|
|
949
868
|
if (result.status === "success" && result.result) {
|
|
@@ -1018,10 +937,7 @@ async function downloadSmartVideo(url, config) {
|
|
|
1018
937
|
}
|
|
1019
938
|
case "pinterest": {
|
|
1020
939
|
try {
|
|
1021
|
-
// Supports both pin links and search queries
|
|
1022
940
|
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
941
|
let directUrl = null;
|
|
1026
942
|
const pickFrom = (obj) => {
|
|
1027
943
|
if (!obj) return null;
|
|
@@ -1082,7 +998,6 @@ async function downloadSmartVideo(url, config) {
|
|
|
1082
998
|
if (highQuality) {
|
|
1083
999
|
videoUrl = highQuality.url;
|
|
1084
1000
|
} else {
|
|
1085
|
-
// Fallback: pega o primeiro que tiver url
|
|
1086
1001
|
const anyVideo = data.url.find(v => v.url);
|
|
1087
1002
|
videoUrl = anyVideo ? anyVideo.url : data.url[0];
|
|
1088
1003
|
}
|
|
@@ -1093,7 +1008,6 @@ async function downloadSmartVideo(url, config) {
|
|
|
1093
1008
|
}
|
|
1094
1009
|
|
|
1095
1010
|
if (!videoUrl) {
|
|
1096
|
-
// Fallback para estrutura antiga ou diferente
|
|
1097
1011
|
if (Array.isArray(data) && data[0] && data[0].url) {
|
|
1098
1012
|
videoUrl = data[0].url;
|
|
1099
1013
|
} else if (data && data.HD) {
|
|
@@ -1102,7 +1016,7 @@ async function downloadSmartVideo(url, config) {
|
|
|
1102
1016
|
}
|
|
1103
1017
|
|
|
1104
1018
|
if (!videoUrl) {
|
|
1105
|
-
throw new Error("No video URL found in
|
|
1019
|
+
throw new Error("No video URL found in downloader twitter response");
|
|
1106
1020
|
}
|
|
1107
1021
|
} catch (error) {
|
|
1108
1022
|
throw new Error(`Twitter download failed: ${error.message}`);
|
|
@@ -1528,7 +1442,7 @@ const AudioDownloader = async (url, options = {}) => {
|
|
|
1528
1442
|
try {
|
|
1529
1443
|
let platform = getPlatformType(url);
|
|
1530
1444
|
if (platform === "y124outube") {
|
|
1531
|
-
|
|
1445
|
+
|
|
1532
1446
|
let fileName = "temp_audio.mp3";
|
|
1533
1447
|
let count = 1;
|
|
1534
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
|
+
}
|