@khang07/zing-mp3-api 1.0.1 → 1.1.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.md +344 -191
- package/dist/cjs/index.cjs +55 -23
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +55 -23
- package/dist/esm/index.js.map +1 -1
- package/dist/types/index.d.ts +25 -35
- package/dist/types/types/fetch/response.d.ts +22 -0
- package/dist/types/types/fetch/uri.d.ts +46 -0
- package/dist/{.types → types}/types/index.d.ts +1 -2
- package/package.json +19 -4
- package/dist/.types/index.d.ts +0 -23
- package/dist/.types/types/fetch.d.ts +0 -24
- package/dist/types/tsdoc-metadata.json +0 -11
- /package/dist/{.types → types}/types/cookie.d.ts +0 -0
- /package/dist/{.types → types}/utils/cookies.d.ts +0 -0
- /package/dist/{.types → types}/utils/encrypt.d.ts +0 -0
- /package/dist/{.types → types}/utils/lapse.d.ts +0 -0
package/README.md
CHANGED
|
@@ -1,101 +1,85 @@
|
|
|
1
1
|
# @khang07/zing-mp3-api
|
|
2
2
|
|
|
3
|
-
Node.js library
|
|
3
|
+
A lightweight Node.js library for working with Zing MP3 streams.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This package focuses on a small, practical API:
|
|
6
|
+
- fetch a music stream by song ID
|
|
7
|
+
- fetch a video stream by video ID
|
|
8
|
+
- search songs by keyword
|
|
9
|
+
- expose a reusable cookie jar utility
|
|
10
|
+
- expose the signature generator used by the API
|
|
11
|
+
- wrap library failures with a dedicated `Lapse` error class
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
-
|
|
13
|
+
The package ships with:
|
|
14
|
+
- ESM build
|
|
15
|
+
- CommonJS build
|
|
16
|
+
- TypeScript declarations
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
## Features
|
|
11
19
|
|
|
12
|
-
|
|
20
|
+
- Stream music as a Node.js `Readable`
|
|
21
|
+
- Stream video as a Node.js `Readable`
|
|
22
|
+
- Sync-like helpers that return a stream immediately and pipe later
|
|
23
|
+
- Built-in cookie handling for Zing requests
|
|
24
|
+
- Typed response for `search()`
|
|
25
|
+
- Small public surface area
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
- Có thể tự tạo `new Client()` để chỉnh tốc độ stream/buffer
|
|
16
|
-
- Trả về `Readable` stream để pipe thẳng ra file, HTTP response, Discord voice pipeline, hoặc xử lý tiếp
|
|
17
|
-
- Có cookie jar nội bộ để giữ cookie giữa các request
|
|
18
|
-
- Có chữ ký request nội bộ cho API đang dùng
|
|
19
|
-
- Hỗ trợ ESM, CJS, TypeScript
|
|
20
|
-
|
|
21
|
-
## Cài đặt
|
|
27
|
+
## Installation
|
|
22
28
|
|
|
23
29
|
```bash
|
|
24
30
|
npm install @khang07/zing-mp3-api
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
bun add @khang07/zing-mp3-api
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Yêu cầu môi trường
|
|
34
|
-
|
|
35
|
-
Package này chạy cho **Node.js**, không dành cho browser vì dùng:
|
|
33
|
+
## Requirements
|
|
36
34
|
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- `axios` với Node adapter
|
|
40
|
-
- `m3u8stream`
|
|
35
|
+
- Node.js
|
|
36
|
+
- A runtime that supports Node streams
|
|
41
37
|
|
|
42
|
-
##
|
|
38
|
+
## Package exports
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
Main package:
|
|
45
41
|
|
|
46
42
|
```ts
|
|
47
|
-
import client from "@khang07/zing-mp3-api";
|
|
48
|
-
import { createWriteStream } from "node:fs";
|
|
49
|
-
|
|
50
|
-
const writer = createWriteStream("music.mp3");
|
|
51
|
-
const stream = await client.music("MUSIC_ID");
|
|
52
|
-
|
|
53
|
-
stream.pipe(writer);
|
|
43
|
+
import client, { ZingClient } from "@khang07/zing-mp3-api";
|
|
54
44
|
```
|
|
55
45
|
|
|
56
|
-
|
|
46
|
+
Subpath exports:
|
|
57
47
|
|
|
58
48
|
```ts
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
|
|
62
|
-
const musicWriter = createWriteStream("music.mp3");
|
|
63
|
-
client.musicSyncLike("MUSIC_ID").pipe(musicWriter);
|
|
64
|
-
|
|
65
|
-
const customClient = new Client({
|
|
66
|
-
maxRate: [256 * 1024, 64 * 1024]
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const videoWriter = createWriteStream("video.bin");
|
|
70
|
-
const videoStream = await customClient.video("VIDEO_ID");
|
|
71
|
-
videoStream.pipe(videoWriter);
|
|
49
|
+
import { Cookies } from "@khang07/zing-mp3-api/utils/cookies";
|
|
50
|
+
import { createSignature } from "@khang07/zing-mp3-api/utils/encrypt";
|
|
51
|
+
import { Lapse } from "@khang07/zing-mp3-api/utils/lapse";
|
|
72
52
|
```
|
|
73
53
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```js
|
|
77
|
-
const { client, Client } = require("@khang07/zing-mp3-api");
|
|
78
|
-
const { createWriteStream } = require("node:fs");
|
|
54
|
+
## Quick start
|
|
79
55
|
|
|
80
|
-
|
|
81
|
-
client.musicSyncLike("MUSIC_ID").pipe(writer);
|
|
82
|
-
```
|
|
56
|
+
### Use the default client
|
|
83
57
|
|
|
84
|
-
|
|
58
|
+
```ts
|
|
59
|
+
import fs from "node:fs";
|
|
60
|
+
import client from "@khang07/zing-mp3-api";
|
|
85
61
|
|
|
86
|
-
|
|
62
|
+
const stream = await client.music("Z7ACBFEF");
|
|
63
|
+
stream.pipe(fs.createWriteStream("music.mp3"));
|
|
64
|
+
```
|
|
87
65
|
|
|
88
|
-
|
|
66
|
+
### Create your own client
|
|
89
67
|
|
|
90
68
|
```ts
|
|
91
|
-
import {
|
|
69
|
+
import { ZingClient } from "@khang07/zing-mp3-api";
|
|
92
70
|
|
|
93
|
-
const client = new
|
|
71
|
+
const client = new ZingClient({
|
|
94
72
|
maxRate: [100 * 1024, 16 * 1024]
|
|
95
73
|
});
|
|
96
74
|
```
|
|
97
75
|
|
|
98
|
-
|
|
76
|
+
## API
|
|
77
|
+
|
|
78
|
+
### `new ZingClient(options?)`
|
|
79
|
+
|
|
80
|
+
Create a new client instance.
|
|
81
|
+
|
|
82
|
+
#### Parameters
|
|
99
83
|
|
|
100
84
|
```ts
|
|
101
85
|
interface ClientOptions {
|
|
@@ -103,221 +87,390 @@ interface ClientOptions {
|
|
|
103
87
|
}
|
|
104
88
|
```
|
|
105
89
|
|
|
106
|
-
|
|
90
|
+
#### Notes
|
|
107
91
|
|
|
108
|
-
- `
|
|
109
|
-
- `
|
|
92
|
+
- `maxRate[0]`: download rate limit passed to Axios
|
|
93
|
+
- `maxRate[1]`: `highWaterMark` used by `musicSyncLike()` and `videoSyncLike()`
|
|
110
94
|
|
|
111
|
-
|
|
95
|
+
### `client.music(musicID)`
|
|
96
|
+
|
|
97
|
+
Fetches a music stream by song ID.
|
|
98
|
+
|
|
99
|
+
#### Signature
|
|
112
100
|
|
|
113
101
|
```ts
|
|
114
|
-
|
|
102
|
+
music(musicID: string): Promise<Readable>
|
|
115
103
|
```
|
|
116
104
|
|
|
117
|
-
|
|
105
|
+
#### Returns
|
|
118
106
|
|
|
119
|
-
|
|
107
|
+
A `Promise<Readable>` for the audio stream.
|
|
120
108
|
|
|
121
|
-
|
|
109
|
+
#### Example
|
|
122
110
|
|
|
123
111
|
```ts
|
|
112
|
+
import fs from "node:fs";
|
|
124
113
|
import client from "@khang07/zing-mp3-api";
|
|
125
|
-
import { createWriteStream } from "node:fs";
|
|
126
114
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
stream.pipe(output);
|
|
115
|
+
const music = await client.music("Z7ACBFEF");
|
|
116
|
+
music.pipe(fs.createWriteStream("track.mp3"));
|
|
131
117
|
```
|
|
132
118
|
|
|
133
|
-
|
|
119
|
+
#### Behavior
|
|
134
120
|
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
121
|
+
- validates the input ID
|
|
122
|
+
- initializes cookies if needed
|
|
123
|
+
- requests the Zing streaming endpoint
|
|
124
|
+
- retries a secondary endpoint for some VIP/PRI-style responses
|
|
125
|
+
- throws a `Lapse` when the song cannot be streamed
|
|
138
126
|
|
|
139
127
|
### `client.musicSyncLike(musicID)`
|
|
140
128
|
|
|
141
|
-
|
|
129
|
+
Returns a stream immediately and starts resolving the remote source in the background.
|
|
130
|
+
|
|
131
|
+
#### Signature
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
musicSyncLike(musicID: string): Readable
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Example
|
|
142
138
|
|
|
143
139
|
```ts
|
|
140
|
+
import fs from "node:fs";
|
|
144
141
|
import client from "@khang07/zing-mp3-api";
|
|
145
|
-
import { createWriteStream } from "node:fs";
|
|
146
142
|
|
|
147
|
-
client.musicSyncLike("
|
|
143
|
+
const music = client.musicSyncLike("Z7ACBFEF");
|
|
144
|
+
music.pipe(fs.createWriteStream("track.mp3"));
|
|
148
145
|
```
|
|
149
146
|
|
|
150
|
-
|
|
147
|
+
#### When to use
|
|
148
|
+
|
|
149
|
+
Use this when you want a stream object right away instead of awaiting `Promise<Readable>`.
|
|
150
|
+
|
|
151
|
+
### `client.video(videoID)`
|
|
152
|
+
|
|
153
|
+
Fetches a video stream by video ID.
|
|
151
154
|
|
|
152
|
-
|
|
155
|
+
#### Signature
|
|
153
156
|
|
|
154
157
|
```ts
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
video(videoID: string): Promise<Readable>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Returns
|
|
162
|
+
|
|
163
|
+
A `Promise<Readable>` for the video stream.
|
|
157
164
|
|
|
158
|
-
|
|
159
|
-
const stream = await client.video("ZZEEOZEC");
|
|
165
|
+
#### Example
|
|
160
166
|
|
|
161
|
-
|
|
167
|
+
```ts
|
|
168
|
+
import fs from "node:fs";
|
|
169
|
+
import client from "@khang07/zing-mp3-api";
|
|
170
|
+
|
|
171
|
+
const video = await client.video("ZO8I9ZZC");
|
|
172
|
+
video.pipe(fs.createWriteStream("video.ts"));
|
|
162
173
|
```
|
|
163
174
|
|
|
164
|
-
|
|
175
|
+
#### Behavior
|
|
176
|
+
|
|
177
|
+
- validates the input ID
|
|
178
|
+
- initializes cookies if needed
|
|
179
|
+
- requests the video endpoint
|
|
180
|
+
- reads the HLS `360p` stream URL from the response
|
|
181
|
+
- uses `m3u8stream` to produce the returned stream
|
|
165
182
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
- Library **không transcode**, **không remux**, **không ép sang MP4 thật**
|
|
170
|
-
- Vì vậy bạn nên xem dữ liệu trả về là **stream media thô từ HLS**, không nên mặc định coi nó luôn là file `.mp4` chuẩn
|
|
183
|
+
#### Important
|
|
184
|
+
|
|
185
|
+
The current implementation streams HLS media from the `360p` source. It does **not** remux or convert the output into MP4 by itself.
|
|
171
186
|
|
|
172
187
|
### `client.videoSyncLike(videoID)`
|
|
173
188
|
|
|
174
|
-
|
|
189
|
+
Returns a stream immediately and starts resolving the remote video source in the background.
|
|
190
|
+
|
|
191
|
+
#### Signature
|
|
175
192
|
|
|
176
193
|
```ts
|
|
194
|
+
videoSyncLike(videoID: string): Readable
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### Example
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import fs from "node:fs";
|
|
177
201
|
import client from "@khang07/zing-mp3-api";
|
|
178
|
-
import { createWriteStream } from "node:fs";
|
|
179
202
|
|
|
180
|
-
client.videoSyncLike("
|
|
203
|
+
const video = client.videoSyncLike("ZO8I9ZZC");
|
|
204
|
+
video.pipe(fs.createWriteStream("video.ts"));
|
|
181
205
|
```
|
|
182
206
|
|
|
183
|
-
|
|
207
|
+
### `client.search(keyword)`
|
|
184
208
|
|
|
185
|
-
|
|
209
|
+
Searches songs by keyword.
|
|
186
210
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
211
|
+
#### Signature
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
search(keyword: string): Promise<ResponseSearch[]>
|
|
215
|
+
```
|
|
192
216
|
|
|
193
|
-
|
|
217
|
+
#### Return type
|
|
194
218
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
219
|
+
```ts
|
|
220
|
+
interface ResponseSearch {
|
|
221
|
+
id: string;
|
|
222
|
+
name: string;
|
|
223
|
+
alias: string;
|
|
224
|
+
isOffical: boolean;
|
|
225
|
+
username: string;
|
|
226
|
+
artists: {
|
|
227
|
+
id: string;
|
|
228
|
+
name: string;
|
|
229
|
+
alias: string;
|
|
230
|
+
thumbnail: {
|
|
231
|
+
w240: string;
|
|
232
|
+
w360: string;
|
|
233
|
+
};
|
|
234
|
+
}[];
|
|
235
|
+
thumbnail: {
|
|
236
|
+
w94: string;
|
|
237
|
+
w240: string;
|
|
238
|
+
};
|
|
239
|
+
duration: number;
|
|
240
|
+
releaseDate: number;
|
|
241
|
+
}
|
|
242
|
+
```
|
|
203
243
|
|
|
204
|
-
|
|
244
|
+
#### Example
|
|
205
245
|
|
|
206
246
|
```ts
|
|
207
247
|
import client from "@khang07/zing-mp3-api";
|
|
208
|
-
import { createWriteStream } from "node:fs";
|
|
209
248
|
|
|
210
|
-
|
|
211
|
-
const stream = await client.music("ZZEEOZEC");
|
|
212
|
-
const writer = createWriteStream("music.mp3");
|
|
249
|
+
const results = await client.search("mở mắt");
|
|
213
250
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
251
|
+
for (const song of results) {
|
|
252
|
+
console.log(song.id, song.name, song.artists.map((artist) => artist.name).join(", "));
|
|
253
|
+
}
|
|
254
|
+
```
|
|
217
255
|
|
|
218
|
-
|
|
219
|
-
console.error("Write error:", error);
|
|
220
|
-
});
|
|
256
|
+
#### Important
|
|
221
257
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
258
|
+
The current implementation only maps the `songs` section from the search response. It does not currently return artists, playlists, or videos.
|
|
259
|
+
|
|
260
|
+
## Utility exports
|
|
261
|
+
|
|
262
|
+
### `Cookies`
|
|
263
|
+
|
|
264
|
+
A lightweight in-memory cookie jar.
|
|
265
|
+
|
|
266
|
+
#### Available methods
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
class Cookies {
|
|
270
|
+
setCookie(setCookie: string, requestUrl: string): void;
|
|
271
|
+
setCookies(setCookies: string[] | undefined, requestUrl: string): void;
|
|
272
|
+
getCookies(requestUrl: string): CookieRecord[];
|
|
273
|
+
getCookieHeader(requestUrl: string): string;
|
|
274
|
+
applyToHeaders(requestUrl: string, headers?: Record<string, string>): Record<string, string>;
|
|
275
|
+
deleteCookie(domain: string, path: string, name: string): void;
|
|
276
|
+
cleanup(): void;
|
|
277
|
+
toJSON(): CookieRecord[];
|
|
278
|
+
fromJSON(cookies: CookieRecord[]): void;
|
|
225
279
|
}
|
|
226
280
|
```
|
|
227
281
|
|
|
228
|
-
|
|
282
|
+
#### Example
|
|
229
283
|
|
|
230
284
|
```ts
|
|
231
|
-
import
|
|
232
|
-
import client from "@khang07/zing-mp3-api";
|
|
285
|
+
import { Cookies } from "@khang07/zing-mp3-api/utils/cookies";
|
|
233
286
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
const stream = await client.music("ZZEEOZEC");
|
|
287
|
+
const jar = new Cookies();
|
|
288
|
+
jar.setCookie("sessionid=abc123; Path=/; HttpOnly", "https://zingmp3.vn/");
|
|
237
289
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
290
|
+
const headers = jar.applyToHeaders("https://zingmp3.vn/api/v2/song/get/streaming");
|
|
291
|
+
console.log(headers.Cookie);
|
|
292
|
+
```
|
|
241
293
|
|
|
242
|
-
|
|
243
|
-
if (!res.headersSent)
|
|
244
|
-
res.writeHead(500);
|
|
294
|
+
### `createSignature(uri, params, secret)`
|
|
245
295
|
|
|
246
|
-
|
|
247
|
-
});
|
|
296
|
+
Creates the request signature used by the Zing endpoints.
|
|
248
297
|
|
|
249
|
-
|
|
250
|
-
} catch {
|
|
251
|
-
res.writeHead(500);
|
|
252
|
-
res.end("Failed to fetch music");
|
|
253
|
-
}
|
|
254
|
-
});
|
|
298
|
+
#### Signature
|
|
255
299
|
|
|
256
|
-
|
|
300
|
+
```ts
|
|
301
|
+
createSignature(uri: string, params: string, secret: string): string
|
|
257
302
|
```
|
|
258
303
|
|
|
259
|
-
|
|
304
|
+
#### Example
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { createSignature } from "@khang07/zing-mp3-api/utils/encrypt";
|
|
260
308
|
|
|
261
|
-
|
|
309
|
+
const sig = createSignature(
|
|
310
|
+
"/api/v2/song/get/streaming",
|
|
311
|
+
"ctime=1234567890id=Z7ACBFEFversion=1.6.34",
|
|
312
|
+
"2aa2d1c561e809b267f3638c4a307aab"
|
|
313
|
+
);
|
|
262
314
|
|
|
263
|
-
|
|
264
|
-
bun install
|
|
265
|
-
bun run build
|
|
315
|
+
console.log(sig);
|
|
266
316
|
```
|
|
267
317
|
|
|
268
|
-
|
|
318
|
+
### `Lapse`
|
|
269
319
|
|
|
270
|
-
|
|
271
|
-
- `dist/cjs` → bản CommonJS
|
|
272
|
-
- `dist/types` → type declarations gộp
|
|
320
|
+
A custom error class used by the library.
|
|
273
321
|
|
|
274
|
-
|
|
322
|
+
#### Signature
|
|
275
323
|
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
324
|
+
```ts
|
|
325
|
+
class Lapse extends Error {
|
|
326
|
+
code: string;
|
|
327
|
+
status?: number;
|
|
328
|
+
cause?: unknown;
|
|
329
|
+
}
|
|
281
330
|
```
|
|
282
331
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
332
|
+
#### Example
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
import client from "@khang07/zing-mp3-api";
|
|
336
|
+
import { Lapse } from "@khang07/zing-mp3-api/utils/lapse";
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
await client.music("");
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (error instanceof Lapse) {
|
|
342
|
+
console.error(error.name);
|
|
343
|
+
console.error(error.code);
|
|
344
|
+
console.error(error.message);
|
|
345
|
+
console.error(error.status);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
294
348
|
```
|
|
295
349
|
|
|
296
|
-
##
|
|
350
|
+
## Error codes
|
|
297
351
|
|
|
298
|
-
|
|
352
|
+
The current codebase throws these `Lapse.code` values:
|
|
299
353
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
354
|
+
| Code | Meaning |
|
|
355
|
+
|---|---|
|
|
356
|
+
| `ERROR_INVALID_ID` | `music()` or `video()` received an empty or invalid ID |
|
|
357
|
+
| `ERROR_INVALID_KEYWORD` | `search()` received an empty keyword |
|
|
358
|
+
| `ERROR_MUSIC_NOT_FOUND` | Music was not found from the primary endpoint |
|
|
359
|
+
| `ERROR_MUSIC_VIP_ONLY` | Music required access not available through the fallback flow |
|
|
360
|
+
| `ERROR_VIDEO_NOT_FOUND` | Video was not found |
|
|
361
|
+
| `ERROR_STREAM_URL_NOT_FOUND` | The API response did not contain a playable stream URL |
|
|
362
|
+
| `ERROR_STREAM_DOWNLOAD` | The download stream failed while reading data |
|
|
363
|
+
| `ERROR_MUSIC_FETCH` | A non-library failure occurred while fetching music |
|
|
364
|
+
| `ERROR_VIDEO_FETCH` | A non-library failure occurred while fetching video |
|
|
365
|
+
| `ERROR_SEARCH_FAILED` | Search endpoint returned an error response |
|
|
366
|
+
| `ERROR_SEARCH` | A non-library failure occurred while performing search |
|
|
304
367
|
|
|
305
|
-
##
|
|
368
|
+
## Demo
|
|
306
369
|
|
|
307
|
-
|
|
370
|
+
### Download a song to file
|
|
308
371
|
|
|
309
|
-
|
|
372
|
+
```ts
|
|
373
|
+
import fs from "node:fs";
|
|
374
|
+
import client from "@khang07/zing-mp3-api";
|
|
375
|
+
import { Lapse } from "@khang07/zing-mp3-api/utils/lapse";
|
|
310
376
|
|
|
311
|
-
|
|
312
|
-
|
|
377
|
+
async function main(): Promise<void> {
|
|
378
|
+
try {
|
|
379
|
+
const stream = await client.music("Z7ACBFEF");
|
|
380
|
+
stream.pipe(fs.createWriteStream("song.mp3"));
|
|
381
|
+
} catch (error) {
|
|
382
|
+
if (error instanceof Lapse)
|
|
383
|
+
console.error(error.code, error.message);
|
|
384
|
+
else
|
|
385
|
+
console.error(error);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
void main();
|
|
313
390
|
```
|
|
314
391
|
|
|
315
|
-
|
|
392
|
+
### Download a video stream to file
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
import fs from "node:fs";
|
|
396
|
+
import client from "@khang07/zing-mp3-api";
|
|
397
|
+
|
|
398
|
+
async function main(): Promise<void> {
|
|
399
|
+
const stream = await client.video("ZO8I9ZZC");
|
|
400
|
+
stream.pipe(fs.createWriteStream("video.ts"));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
void main();
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Search then download the first result
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import fs from "node:fs";
|
|
410
|
+
import client from "@khang07/zing-mp3-api";
|
|
411
|
+
import { Lapse } from "@khang07/zing-mp3-api/utils/lapse";
|
|
412
|
+
|
|
413
|
+
async function main(): Promise<void> {
|
|
414
|
+
try {
|
|
415
|
+
const results = await client.search("mở mắt");
|
|
416
|
+
const first = results[0];
|
|
417
|
+
|
|
418
|
+
if (!first)
|
|
419
|
+
throw new Error("No result found");
|
|
420
|
+
|
|
421
|
+
const stream = await client.music(first.id);
|
|
422
|
+
stream.pipe(fs.createWriteStream(first.alias + ".mp3"));
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (error instanceof Lapse)
|
|
425
|
+
console.error(error.code, error.message);
|
|
426
|
+
else
|
|
427
|
+
console.error(error);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
void main();
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## CommonJS example
|
|
316
435
|
|
|
317
436
|
```js
|
|
318
|
-
const
|
|
437
|
+
const fs = require("node:fs");
|
|
438
|
+
const zing = require("@khang07/zing-mp3-api");
|
|
439
|
+
|
|
440
|
+
async function main() {
|
|
441
|
+
const client = zing.default;
|
|
442
|
+
const stream = await client.music("Z7ACBFEF");
|
|
443
|
+
stream.pipe(fs.createWriteStream("track.mp3"));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
main();
|
|
319
447
|
```
|
|
320
448
|
|
|
449
|
+
## Build
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
npm run build
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Current scripts in the project:
|
|
456
|
+
|
|
457
|
+
```json
|
|
458
|
+
{
|
|
459
|
+
"build:esm": "tsc -p tsconfig.json",
|
|
460
|
+
"build:cjs": "rollup -c",
|
|
461
|
+
"build": "rm -rf dist && bun run build:esm && bun run build:cjs && rm -fr dist/esm/types dist/cjs/types",
|
|
462
|
+
"test": "mocha"
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Notes
|
|
467
|
+
|
|
468
|
+
- The default export is a preconfigured client instance.
|
|
469
|
+
- The named export is `ZingClient`.
|
|
470
|
+
- Search currently returns songs only.
|
|
471
|
+
- Video currently streams HLS `360p`.
|
|
472
|
+
- Errors are wrapped in `Lapse` for consistent handling.
|
|
473
|
+
|
|
321
474
|
## License
|
|
322
475
|
|
|
323
|
-
|
|
476
|
+
MIT
|