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.
Files changed (3) hide show
  1. package/README +80 -0
  2. package/index.js +133 -0
  3. 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
+ }