downlynpm 1.0.0
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/README +80 -0
- package/index.js +133 -0
- package/package.json +25 -0
package/README
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# downly
|
|
2
|
+
|
|
3
|
+
Node.js SDK for [downly.web.id](https://downly.web.id) — Social media video & audio downloader.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install downly
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
const { download, detectPlatform } = require('downly');
|
|
15
|
+
|
|
16
|
+
// Download
|
|
17
|
+
const result = await download('https://www.tiktok.com/@khaby.lame/video/123');
|
|
18
|
+
|
|
19
|
+
console.log(result.downloadUrl); // Direct download URL (expires in 1 hour)
|
|
20
|
+
console.log(result.filename); // 'tiktok-video.mp4'
|
|
21
|
+
console.log(result.thumbnail); // Thumbnail URL or null
|
|
22
|
+
console.log(result.platform); // 'tiktok'
|
|
23
|
+
console.log(result.expiresAt); // '2026-01-01T00:00:00+00:00'
|
|
24
|
+
console.log(result.rateLimit); // { remaining: 1, resetAt: '...' }
|
|
25
|
+
|
|
26
|
+
// Detect platform without downloading
|
|
27
|
+
const platform = detectPlatform('https://youtu.be/dQw4w9WgXcQ');
|
|
28
|
+
console.log(platform); // 'youtube'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Supported Platforms
|
|
32
|
+
|
|
33
|
+
| Platform | URL Example |
|
|
34
|
+
|----------|-------------|
|
|
35
|
+
| TikTok | `tiktok.com/@user/video/...` |
|
|
36
|
+
| YouTube | `youtube.com/watch?v=...`, `youtu.be/...`, `youtube.com/shorts/...` |
|
|
37
|
+
| Twitter / X | `twitter.com/...`, `x.com/...` |
|
|
38
|
+
| Facebook | `facebook.com/...`, `fb.watch/...` |
|
|
39
|
+
| Spotify | `open.spotify.com/track/...` |
|
|
40
|
+
| Instagram | `instagram.com/reel/...`, `instagram.com/p/...` |
|
|
41
|
+
|
|
42
|
+
## Error Handling
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
const { download, DownlyError } = require('downly');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const result = await download('https://www.tiktok.com/@user/video/123');
|
|
49
|
+
console.log(result.downloadUrl);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (err instanceof DownlyError) {
|
|
52
|
+
console.error(err.message); // Error message
|
|
53
|
+
console.error(err.code); // 'RATE_LIMITED' | 'DOWNLOAD_FAILED' | 'NETWORK_ERROR' | 'TIMEOUT' | 'INVALID_URL'
|
|
54
|
+
console.error(err.platform); // 'tiktok' | 'youtube' | etc.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Error Codes
|
|
60
|
+
|
|
61
|
+
| Code | Description |
|
|
62
|
+
|------|-------------|
|
|
63
|
+
| `INVALID_URL` | URL kosong atau format tidak valid |
|
|
64
|
+
| `DOWNLOAD_FAILED` | Platform tidak support atau konten private |
|
|
65
|
+
| `RATE_LIMITED` | Melebihi limit 2 request/menit |
|
|
66
|
+
| `NETWORK_ERROR` | Gagal konek ke server |
|
|
67
|
+
| `TIMEOUT` | Request timeout (default 30s) |
|
|
68
|
+
|
|
69
|
+
## Notes
|
|
70
|
+
|
|
71
|
+
- Download URL expire dalam **1 jam**
|
|
72
|
+
- Rate limit: **2 request/menit** per IP
|
|
73
|
+
- File disimpan di server downly.web.id, bukan di device lo
|
|
74
|
+
- No API key needed
|
|
75
|
+
|
|
76
|
+
## Links
|
|
77
|
+
|
|
78
|
+
- Website: [downly.web.id](https://downly.web.id)
|
|
79
|
+
- Author: [zatzd](https://github.com/zatzd)
|
|
80
|
+
- Support: [saweria.co/ZATT](https://saweria.co/ZATT)
|
package/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const http = require('http');
|
|
5
|
+
|
|
6
|
+
const BASE_URL = 'https://downly.web.id/api.php';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Downly SDK - Node.js
|
|
10
|
+
* Social media downloader wrapper for downly.web.id
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class DownlyError extends Error {
|
|
14
|
+
constructor(message, code, platform) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'DownlyError';
|
|
17
|
+
this.code = code || 'UNKNOWN_ERROR';
|
|
18
|
+
this.platform = platform || null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Make a POST request
|
|
24
|
+
* @param {string} url
|
|
25
|
+
* @param {object} body
|
|
26
|
+
* @returns {Promise<object>}
|
|
27
|
+
*/
|
|
28
|
+
function post(url, body) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const payload = JSON.stringify(body);
|
|
31
|
+
const parsed = new URL(url);
|
|
32
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
33
|
+
|
|
34
|
+
const options = {
|
|
35
|
+
hostname: parsed.hostname,
|
|
36
|
+
port : parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
37
|
+
path : parsed.pathname,
|
|
38
|
+
method : 'POST',
|
|
39
|
+
headers : {
|
|
40
|
+
'Content-Type' : 'application/json',
|
|
41
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
42
|
+
'User-Agent' : 'downly-node-sdk/1.0.0',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const req = lib.request(options, (res) => {
|
|
47
|
+
let data = '';
|
|
48
|
+
res.on('data', chunk => data += chunk);
|
|
49
|
+
res.on('end', () => {
|
|
50
|
+
try {
|
|
51
|
+
resolve({ status: res.statusCode, body: JSON.parse(data) });
|
|
52
|
+
} catch {
|
|
53
|
+
reject(new DownlyError('Invalid JSON response', 'PARSE_ERROR'));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
req.on('error', err => reject(new DownlyError(err.message, 'NETWORK_ERROR')));
|
|
59
|
+
req.setTimeout(30000, () => {
|
|
60
|
+
req.destroy();
|
|
61
|
+
reject(new DownlyError('Request timeout', 'TIMEOUT'));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
req.write(payload);
|
|
65
|
+
req.end();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Download media from social media URL
|
|
71
|
+
* @param {string} url - Social media post URL
|
|
72
|
+
* @returns {Promise<DownlyResult>}
|
|
73
|
+
*
|
|
74
|
+
* @typedef {Object} DownlyResult
|
|
75
|
+
* @property {string} status - 'success'
|
|
76
|
+
* @property {string} downloadUrl - Direct download URL (hosted on downly.web.id)
|
|
77
|
+
* @property {string} filename - Suggested filename
|
|
78
|
+
* @property {string|null} thumbnail - Thumbnail URL
|
|
79
|
+
* @property {string} platform - Detected platform
|
|
80
|
+
* @property {string} expiresAt - ISO 8601 expiry time (1 hour)
|
|
81
|
+
* @property {object} rateLimit - { remaining, resetAt }
|
|
82
|
+
*/
|
|
83
|
+
async function download(url) {
|
|
84
|
+
if (!url || typeof url !== 'string') {
|
|
85
|
+
throw new DownlyError('URL is required', 'INVALID_URL');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try { new URL(url); } catch {
|
|
89
|
+
throw new DownlyError('Invalid URL format', 'INVALID_URL');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const res = await post(BASE_URL, { url });
|
|
93
|
+
|
|
94
|
+
if (res.status === 429) {
|
|
95
|
+
throw new DownlyError(
|
|
96
|
+
res.body.error || 'Rate limit exceeded',
|
|
97
|
+
'RATE_LIMITED',
|
|
98
|
+
res.body.platform
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (res.status !== 200 || res.body.error) {
|
|
103
|
+
throw new DownlyError(
|
|
104
|
+
res.body.error || 'Download failed',
|
|
105
|
+
'DOWNLOAD_FAILED',
|
|
106
|
+
res.body.platform
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return res.body;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Detect platform from URL without downloading
|
|
115
|
+
* @param {string} url
|
|
116
|
+
* @returns {string}
|
|
117
|
+
*/
|
|
118
|
+
function detectPlatform(url) {
|
|
119
|
+
try {
|
|
120
|
+
const host = new URL(url).hostname.replace(/^(www\.|m\.)/, '');
|
|
121
|
+
if (host.includes('instagram.com')) return 'instagram';
|
|
122
|
+
if (host.includes('tiktok.com')) return 'tiktok';
|
|
123
|
+
if (host.includes('twitter.com') || host.includes('x.com')) return 'twitter';
|
|
124
|
+
if (host.includes('youtube.com') || host.includes('youtu.be')) return 'youtube';
|
|
125
|
+
if (host.includes('facebook.com') || host.includes('fb.watch')) return 'facebook';
|
|
126
|
+
if (host.includes('spotify.com')) return 'spotify';
|
|
127
|
+
return 'unknown';
|
|
128
|
+
} catch {
|
|
129
|
+
return 'unknown';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = { download, detectPlatform, DownlyError };
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "downlynpm",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Node.js SDK for downly.web.id — social media downloader",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"downloader",
|
|
8
|
+
"tiktok",
|
|
9
|
+
"instagram",
|
|
10
|
+
"youtube",
|
|
11
|
+
"twitter",
|
|
12
|
+
"facebook",
|
|
13
|
+
"spotify"
|
|
14
|
+
],
|
|
15
|
+
"author": "zatzd",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/zatzd/downly"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://downly.web.id",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=14.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|