frostpv 1.0.1 → 1.0.3
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 +124 -11
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -58,7 +58,10 @@ const videoPlatforms = [
|
|
|
58
58
|
"https://twitter.com",
|
|
59
59
|
"https://www.twitter.com",
|
|
60
60
|
"https://vm.tiktok.com/",
|
|
61
|
-
"https://vt.tiktok.com/"
|
|
61
|
+
"https://vt.tiktok.com/",
|
|
62
|
+
"https://www.pinterest.com",
|
|
63
|
+
"https://pinterest.com",
|
|
64
|
+
"https://pin.it"
|
|
62
65
|
];
|
|
63
66
|
|
|
64
67
|
// Blacklist links
|
|
@@ -364,6 +367,7 @@ function getPlatformType(url) {
|
|
|
364
367
|
if (lowerUrl.includes("facebook.com") || lowerUrl.includes("fb.watch") || lowerUrl.includes("fb.com")) return "facebook";
|
|
365
368
|
if (lowerUrl.includes("mediafire.com")) return "mediafire";
|
|
366
369
|
if (lowerUrl.includes("x.com") || lowerUrl.includes("twitter.com")) return "twitter";
|
|
370
|
+
if (lowerUrl.includes("pinterest.com") || lowerUrl.includes("pin.it")) return "pinterest";
|
|
367
371
|
if (lowerUrl.includes("youtube.com") || lowerUrl.includes("you112t12u.be") || lowerUrl.includes("m.y11outu314be.com")) return "youtu1354be";
|
|
368
372
|
|
|
369
373
|
return "unknown";
|
|
@@ -718,6 +722,32 @@ async function tryFallbackDownload(url, maxRetries = 3) {
|
|
|
718
722
|
}
|
|
719
723
|
break;
|
|
720
724
|
}
|
|
725
|
+
case "pinterest": {
|
|
726
|
+
try {
|
|
727
|
+
const data = await pinterest(url);
|
|
728
|
+
let mediaUrl = null;
|
|
729
|
+
const pickFrom = (obj) => {
|
|
730
|
+
if (!obj) return null;
|
|
731
|
+
if (typeof obj === 'string' && obj.startsWith('http')) return obj;
|
|
732
|
+
if (Array.isArray(obj)) {
|
|
733
|
+
for (const it of obj) {
|
|
734
|
+
const got = pickFrom(it);
|
|
735
|
+
if (got) return got;
|
|
736
|
+
}
|
|
737
|
+
} else if (typeof obj === 'object') {
|
|
738
|
+
const candidates = ['video', 'videoUrl', 'download', 'url', 'image', 'imageUrl', 'src', 'link'];
|
|
739
|
+
for (const k of candidates) {
|
|
740
|
+
const got = pickFrom(obj[k]);
|
|
741
|
+
if (got) return got;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return null;
|
|
745
|
+
};
|
|
746
|
+
mediaUrl = pickFrom(data);
|
|
747
|
+
if (mediaUrl) return mediaUrl;
|
|
748
|
+
} catch (_) {}
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
721
751
|
|
|
722
752
|
default: {
|
|
723
753
|
// Generic fallback for unknown platforms
|
|
@@ -790,12 +820,7 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
790
820
|
if (url.includes("threads.com")) {
|
|
791
821
|
throw new Error("Threads links are not supported for download. Download videos from Instagram, X, TikTok, and Facebook");
|
|
792
822
|
}
|
|
793
|
-
|
|
794
|
-
throw new Error("Pinterest links are not supported for download. Download videos from Instagram, X, TikTok, and Facebook");
|
|
795
|
-
}
|
|
796
|
-
if (url.includes("pin.it")) {
|
|
797
|
-
throw new Error("Pinterest links are not supported for download. Download videos from Instagram, X, TikTok, and Facebook");
|
|
798
|
-
}
|
|
823
|
+
// Pinterest now supported
|
|
799
824
|
|
|
800
825
|
const blacklisted = blacklistLink(url);
|
|
801
826
|
if (blacklisted) {
|
|
@@ -804,13 +829,13 @@ const MediaDownloader = async (url, options = {}) => {
|
|
|
804
829
|
|
|
805
830
|
// Verificar se o link está na lista de plataformas suportadas
|
|
806
831
|
if (!isVideoLink(url)) {
|
|
807
|
-
throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok and
|
|
832
|
+
throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook, and Pinterest");
|
|
808
833
|
}
|
|
809
834
|
|
|
810
835
|
// Verificar se é YouTube e lançar erro customizado
|
|
811
836
|
const platform = getPlatformType(url);
|
|
812
837
|
if (platform === "youtube") {
|
|
813
|
-
throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok and
|
|
838
|
+
throw new Error("This URL is not from a supported platform. Supported platforms: Instagram, X(Twitter), TikTok, Facebook, and Pinterest");
|
|
814
839
|
}
|
|
815
840
|
|
|
816
841
|
await cleanupTempFiles(); // Clean up previous temp files
|
|
@@ -989,6 +1014,52 @@ async function downloadSmartVideo(url, config) {
|
|
|
989
1014
|
}
|
|
990
1015
|
break;
|
|
991
1016
|
}
|
|
1017
|
+
case "pinterest": {
|
|
1018
|
+
try {
|
|
1019
|
+
// Supports both pin links and search queries
|
|
1020
|
+
const data = await pinterest(url);
|
|
1021
|
+
// data may be array or object depending on query or pin
|
|
1022
|
+
// Try to find best url (image or video)
|
|
1023
|
+
let directUrl = null;
|
|
1024
|
+
const pickFrom = (obj) => {
|
|
1025
|
+
if (!obj) return null;
|
|
1026
|
+
if (typeof obj === 'string' && obj.startsWith('http')) return obj;
|
|
1027
|
+
if (Array.isArray(obj)) {
|
|
1028
|
+
for (const it of obj) {
|
|
1029
|
+
const got = pickFrom(it);
|
|
1030
|
+
if (got) return got;
|
|
1031
|
+
}
|
|
1032
|
+
} else if (typeof obj === 'object') {
|
|
1033
|
+
const candidates = ['video', 'videoUrl', 'download', 'url', 'image', 'imageUrl', 'src', 'link'];
|
|
1034
|
+
for (const k of candidates) {
|
|
1035
|
+
const got = pickFrom(obj[k]);
|
|
1036
|
+
if (got) return got;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return null;
|
|
1040
|
+
};
|
|
1041
|
+
directUrl = pickFrom(data);
|
|
1042
|
+
if (!directUrl) throw new Error("No media URL found in Pinterest response");
|
|
1043
|
+
|
|
1044
|
+
// HEAD validate
|
|
1045
|
+
const head = await axios.head(directUrl, { timeout: 10000, maxRedirects: 5 }).catch(() => null);
|
|
1046
|
+
if (!head || (head.status < 200 || head.status >= 400)) {
|
|
1047
|
+
throw new Error("Pinterest media URL not accessible");
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// If it's an image, download with generic file helper; if video, continue normal flow
|
|
1051
|
+
const ct = (head.headers && head.headers['content-type']) || '';
|
|
1052
|
+
if (ct.startsWith('image/')) {
|
|
1053
|
+
const filePath = await downloadGenericFile(directUrl);
|
|
1054
|
+
return filePath;
|
|
1055
|
+
} else {
|
|
1056
|
+
videoUrl = directUrl;
|
|
1057
|
+
}
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
throw new Error(`Pinterest download failed: ${error.message}`);
|
|
1060
|
+
}
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
992
1063
|
case "twitter": {
|
|
993
1064
|
try {
|
|
994
1065
|
// Validate and clean the Twitter URL
|
|
@@ -1118,6 +1189,45 @@ async function downloadDirectVideo(url, config) {
|
|
|
1118
1189
|
}
|
|
1119
1190
|
}
|
|
1120
1191
|
|
|
1192
|
+
// Generic file (image/video) downloader
|
|
1193
|
+
async function downloadGenericFile(url, preferredExt = null) {
|
|
1194
|
+
try {
|
|
1195
|
+
const response = await axios({
|
|
1196
|
+
url: url,
|
|
1197
|
+
method: "GET",
|
|
1198
|
+
responseType: "stream",
|
|
1199
|
+
timeout: 30000,
|
|
1200
|
+
headers: {
|
|
1201
|
+
'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'
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
// Try to infer extension from content-type
|
|
1206
|
+
const ct = (response.headers && response.headers['content-type']) || '';
|
|
1207
|
+
let ext = preferredExt;
|
|
1208
|
+
if (!ext) {
|
|
1209
|
+
if (ct.includes('image/jpeg')) ext = 'jpg';
|
|
1210
|
+
else if (ct.includes('image/png')) ext = 'png';
|
|
1211
|
+
else if (ct.includes('image/webp')) ext = 'webp';
|
|
1212
|
+
else if (ct.includes('image/gif')) ext = 'gif';
|
|
1213
|
+
else if (ct.includes('video/mp4')) ext = 'mp4';
|
|
1214
|
+
else if (ct.includes('video/webm')) ext = 'webm';
|
|
1215
|
+
else ext = 'bin';
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
const fileName = path.join(OUTPUT_DIR, `v_${uuidv4().slice(0,4)}.${ext}`);
|
|
1219
|
+
const writer = fs.createWriteStream(fileName);
|
|
1220
|
+
response.data.pipe(writer);
|
|
1221
|
+
|
|
1222
|
+
return await new Promise((resolve, reject) => {
|
|
1223
|
+
writer.on('finish', () => resolve(fileName));
|
|
1224
|
+
writer.on('error', (err) => reject(new Error(`Error saving file: ${err.message}`)));
|
|
1225
|
+
});
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
throw new Error(`Failed to download file: ${error.message}`);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1121
1231
|
// Function to rotate video
|
|
1122
1232
|
async function rotateVideo(fileName, rotation) {
|
|
1123
1233
|
const outputPath = path.join(OUTPUT_DIR, `vr_${uuidv4().slice(0,4)}.mp4`);
|
|
@@ -1349,8 +1459,11 @@ function getFileName(url) {
|
|
|
1349
1459
|
// Function to unshorten URLs
|
|
1350
1460
|
async function unshortenUrl(url) {
|
|
1351
1461
|
try {
|
|
1352
|
-
// Special handling for Facebook
|
|
1353
|
-
if (
|
|
1462
|
+
// Special handling for Facebook and Pinterest short-links
|
|
1463
|
+
if (
|
|
1464
|
+
url.includes('facebook.com') || url.includes('fb.watch') || url.includes('fb.com') ||
|
|
1465
|
+
url.includes('pin.it') || url.includes('pinterest.com')
|
|
1466
|
+
) {
|
|
1354
1467
|
const response = await axios.get(url, {
|
|
1355
1468
|
maxRedirects: 10,
|
|
1356
1469
|
timeout: 10000,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frostpv",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "downloads",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"axios": "1.12.0",
|
|
16
|
-
"btch-downloader": "
|
|
16
|
+
"btch-downloader": "5.0.14",
|
|
17
17
|
"btch-downloader-old": "npm:btch-downloader@4.0.15",
|
|
18
18
|
"express": "^5.1.0",
|
|
19
19
|
"ffmpeg-ffprobe-static": "^6.1.1-rc.5",
|