@khang07/zing-mp3-api 1.1.0 → 1.3.4

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 CHANGED
@@ -1,476 +1,442 @@
1
- # @khang07/zing-mp3-api
2
-
3
- A lightweight Node.js library for working with Zing MP3 streams.
4
-
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
12
-
13
- The package ships with:
14
- - ESM build
15
- - CommonJS build
16
- - TypeScript declarations
17
-
18
- ## Features
19
-
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
26
-
27
- ## Installation
28
-
29
- ```bash
30
- npm install @khang07/zing-mp3-api
31
- ```
32
-
33
- ## Requirements
34
-
35
- - Node.js
36
- - A runtime that supports Node streams
37
-
38
- ## Package exports
39
-
40
- Main package:
41
-
42
- ```ts
43
- import client, { ZingClient } from "@khang07/zing-mp3-api";
44
- ```
45
-
46
- Subpath exports:
47
-
48
- ```ts
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";
52
- ```
53
-
54
- ## Quick start
55
-
56
- ### Use the default client
57
-
58
- ```ts
59
- import fs from "node:fs";
60
- import client from "@khang07/zing-mp3-api";
61
-
62
- const stream = await client.music("Z7ACBFEF");
63
- stream.pipe(fs.createWriteStream("music.mp3"));
64
- ```
65
-
66
- ### Create your own client
67
-
68
- ```ts
69
- import { ZingClient } from "@khang07/zing-mp3-api";
70
-
71
- const client = new ZingClient({
72
- maxRate: [100 * 1024, 16 * 1024]
73
- });
74
- ```
75
-
76
- ## API
77
-
78
- ### `new ZingClient(options?)`
79
-
80
- Create a new client instance.
81
-
82
- #### Parameters
83
-
84
- ```ts
85
- interface ClientOptions {
86
- maxRate?: [download?: number, highWaterMark?: number];
87
- }
88
- ```
89
-
90
- #### Notes
91
-
92
- - `maxRate[0]`: download rate limit passed to Axios
93
- - `maxRate[1]`: `highWaterMark` used by `musicSyncLike()` and `videoSyncLike()`
94
-
95
- ### `client.music(musicID)`
96
-
97
- Fetches a music stream by song ID.
98
-
99
- #### Signature
100
-
101
- ```ts
102
- music(musicID: string): Promise<Readable>
103
- ```
104
-
105
- #### Returns
106
-
107
- A `Promise<Readable>` for the audio stream.
108
-
109
- #### Example
110
-
111
- ```ts
112
- import fs from "node:fs";
113
- import client from "@khang07/zing-mp3-api";
114
-
115
- const music = await client.music("Z7ACBFEF");
116
- music.pipe(fs.createWriteStream("track.mp3"));
117
- ```
118
-
119
- #### Behavior
120
-
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
126
-
127
- ### `client.musicSyncLike(musicID)`
128
-
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
138
-
139
- ```ts
140
- import fs from "node:fs";
141
- import client from "@khang07/zing-mp3-api";
142
-
143
- const music = client.musicSyncLike("Z7ACBFEF");
144
- music.pipe(fs.createWriteStream("track.mp3"));
145
- ```
146
-
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.
154
-
155
- #### Signature
156
-
157
- ```ts
158
- video(videoID: string): Promise<Readable>
159
- ```
160
-
161
- #### Returns
162
-
163
- A `Promise<Readable>` for the video stream.
164
-
165
- #### Example
166
-
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"));
173
- ```
174
-
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
182
-
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.
186
-
187
- ### `client.videoSyncLike(videoID)`
188
-
189
- Returns a stream immediately and starts resolving the remote video source in the background.
190
-
191
- #### Signature
192
-
193
- ```ts
194
- videoSyncLike(videoID: string): Readable
195
- ```
196
-
197
- #### Example
198
-
199
- ```ts
200
- import fs from "node:fs";
201
- import client from "@khang07/zing-mp3-api";
202
-
203
- const video = client.videoSyncLike("ZO8I9ZZC");
204
- video.pipe(fs.createWriteStream("video.ts"));
205
- ```
206
-
207
- ### `client.search(keyword)`
208
-
209
- Searches songs by keyword.
210
-
211
- #### Signature
212
-
213
- ```ts
214
- search(keyword: string): Promise<ResponseSearch[]>
215
- ```
216
-
217
- #### Return type
218
-
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
- ```
243
-
244
- #### Example
245
-
246
- ```ts
247
- import client from "@khang07/zing-mp3-api";
248
-
249
- const results = await client.search("mở mắt");
250
-
251
- for (const song of results) {
252
- console.log(song.id, song.name, song.artists.map((artist) => artist.name).join(", "));
253
- }
254
- ```
255
-
256
- #### Important
257
-
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;
279
- }
280
- ```
281
-
282
- #### Example
283
-
284
- ```ts
285
- import { Cookies } from "@khang07/zing-mp3-api/utils/cookies";
286
-
287
- const jar = new Cookies();
288
- jar.setCookie("sessionid=abc123; Path=/; HttpOnly", "https://zingmp3.vn/");
289
-
290
- const headers = jar.applyToHeaders("https://zingmp3.vn/api/v2/song/get/streaming");
291
- console.log(headers.Cookie);
292
- ```
293
-
294
- ### `createSignature(uri, params, secret)`
295
-
296
- Creates the request signature used by the Zing endpoints.
297
-
298
- #### Signature
299
-
300
- ```ts
301
- createSignature(uri: string, params: string, secret: string): string
302
- ```
303
-
304
- #### Example
305
-
306
- ```ts
307
- import { createSignature } from "@khang07/zing-mp3-api/utils/encrypt";
308
-
309
- const sig = createSignature(
310
- "/api/v2/song/get/streaming",
311
- "ctime=1234567890id=Z7ACBFEFversion=1.6.34",
312
- "2aa2d1c561e809b267f3638c4a307aab"
313
- );
314
-
315
- console.log(sig);
316
- ```
317
-
318
- ### `Lapse`
319
-
320
- A custom error class used by the library.
321
-
322
- #### Signature
323
-
324
- ```ts
325
- class Lapse extends Error {
326
- code: string;
327
- status?: number;
328
- cause?: unknown;
329
- }
330
- ```
331
-
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
- }
348
- ```
349
-
350
- ## Error codes
351
-
352
- The current codebase throws these `Lapse.code` values:
353
-
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 |
367
-
368
- ## Demo
369
-
370
- ### Download a song to file
371
-
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";
376
-
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();
390
- ```
391
-
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
435
-
436
- ```js
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();
447
- ```
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
-
474
- ## License
475
-
476
- MIT
1
+ # `@khang07/zing-mp3-api`
2
+
3
+ The basic APIs provide the features of ZingMp3.
4
+
5
+ ## Features
6
+
7
+ * Search songs, videos, playlists, and artists
8
+ * Fetch playlist, artist, and song details
9
+ * Get readable streams for music and video
10
+ * Accept raw resource tokens or `URL` values for resource-based methods
11
+ * Provide a cookie jar utility through a public subpath export
12
+ * Ship ESM and CommonJS entry points
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @khang07/zing-mp3-api
18
+ ```
19
+
20
+ ## Public Exports
21
+
22
+ ### Root export
23
+
24
+ #### Runtime exports
25
+
26
+ * `default`: a pre-created `Client` instance
27
+ * `Client`: the client class
28
+
29
+ #### Type exports
30
+
31
+ * `ClientOptions`
32
+ * `PlayList`
33
+ * `Artist`
34
+ * `Media`
35
+ * `SearhMedia`
36
+ * `SearchPlayList`
37
+ * `Cookies` *(type-only export from the root entry)*
38
+
39
+ ### Public subpath exports
40
+
41
+ ```ts
42
+ import { Cookies } from "@khang07/zing-mp3-api/utils/cookies";
43
+ import { createSignature } from "@khang07/zing-mp3-api/utils/encrypt";
44
+ import { Lapse } from "@khang07/zing-mp3-api/utils/lapse";
45
+ ```
46
+
47
+ ## Import
48
+
49
+ ### ESM
50
+
51
+ ```ts
52
+ import client, { Client } from "@khang07/zing-mp3-api";
53
+ ```
54
+
55
+ ### CommonJS
56
+
57
+ ```js
58
+ const zing = require("@khang07/zing-mp3-api");
59
+
60
+ const client = zing.default;
61
+ const { Client } = zing;
62
+ ```
63
+
64
+ ## Basic Usage
65
+
66
+ ### Use the default client
67
+
68
+ ```ts
69
+ import client from "@khang07/zing-mp3-api";
70
+
71
+ const items = await client.searchMusic("Do For Love Bray");
72
+ console.log(items[0]);
73
+ ```
74
+
75
+ ### Create a client
76
+
77
+ ```ts
78
+ import { Client } from "@khang07/zing-mp3-api";
79
+ import { Cookies } from "@khang07/zing-mp3-api/utils/cookies";
80
+
81
+ const client = new Client({
82
+ maxLoad: 16 * 1024,
83
+ maxHighWaterMark: 16 * 1024,
84
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
85
+ jar: new Cookies()
86
+ });
87
+ ```
88
+
89
+ ### Fetch a playlist from a URL
90
+
91
+ ```ts
92
+ import client from "@khang07/zing-mp3-api";
93
+
94
+ const playlist = await client.playlist("https://zingmp3.vn/album/example/ZWZB9WAB.html");
95
+ console.log(playlist.name);
96
+ console.log(playlist.mediaCount);
97
+ ```
98
+
99
+ ### Fetch artist details
100
+
101
+ ```ts
102
+ import client from "@khang07/zing-mp3-api";
103
+
104
+ const artist = await client.artist("https://zingmp3.vn/Obito");
105
+ console.log(artist.name);
106
+ console.log(artist.followCount);
107
+ ```
108
+
109
+ ### Download a music stream
110
+
111
+ ```ts
112
+ import { createWriteStream } from "node:fs";
113
+ import client from "@khang07/zing-mp3-api";
114
+
115
+ const items = await client.searchMusic("Do For Love Bray");
116
+ const stream = await client.music(items[0].id);
117
+
118
+ stream.pipe(createWriteStream("music.mp3"));
119
+ ```
120
+
121
+ ### Download a video stream
122
+
123
+ ```ts
124
+ import { createWriteStream } from "node:fs";
125
+ import client from "@khang07/zing-mp3-api";
126
+
127
+ const items = await client.searchVideo("Do For Love Bray");
128
+ const stream = await client.video(items[0].id);
129
+
130
+ stream.pipe(createWriteStream("video.ts"));
131
+ ```
132
+
133
+ ### Use the immediate stream-returning methods
134
+
135
+ ```ts
136
+ import { createWriteStream } from "node:fs";
137
+ import client from "@khang07/zing-mp3-api";
138
+
139
+ const stream = client.musicSync("ZWZB9WAB");
140
+ stream.pipe(createWriteStream("music.mp3"));
141
+ ```
142
+
143
+ ### Handle library errors
144
+
145
+ ```ts
146
+ import client from "@khang07/zing-mp3-api";
147
+ import { Lapse } from "@khang07/zing-mp3-api/utils/lapse";
148
+
149
+ try {
150
+ await client.playlist("");
151
+ } catch (error) {
152
+ if (error instanceof Lapse) {
153
+ console.error(error.name);
154
+ console.error(error.code);
155
+ console.error(error.status);
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## API Reference
161
+
162
+ ## `new Client(options?)`
163
+
164
+ Creates a new client instance.
165
+
166
+ ### `ClientOptions`
167
+
168
+ | Field | Type | Default |
169
+ | ------------------ | --------- | -------------------------------------------------------------------------------------------------------------------- |
170
+ | `maxLoad` | `number` | `1024 * 1024` |
171
+ | `maxHighWaterMark` | `number` | `16 * 1024` |
172
+ | `userAgent` | `string` | `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3` |
173
+ | `jar` | `Cookies` | `new Cookies()` |
174
+
175
+ `maxLoad` is passed to Axios as `maxRate`.
176
+
177
+ `maxHighWaterMark` is used as the `highWaterMark` when `musicSync()` and `videoSync()` create a `PassThrough` stream.
178
+
179
+ ## `Client.getIDFromURL(url)`
180
+
181
+ ```ts
182
+ static getIDFromURL(url: string): string
183
+ ```
184
+
185
+ Extracts a resource token from a ZingMp3 URL.
186
+
187
+ It throws `ERROR_INVALID_URL` when the input is not a non-empty string or when no token can be extracted.
188
+
189
+ ## Instance Methods
190
+
191
+ | Method | Returns | Description |
192
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
193
+ | `video(videoID)` | `Promise<Readable>` | Fetches a video stream. Accepts a raw token, URL string, or `URL`. |
194
+ | `videoSync(videoID)` | `Readable` | Returns a `PassThrough` immediately and pipes the fetched video stream into it. |
195
+ | `music(musicID)` | `Promise<Readable>` | Fetches a music stream. Accepts a raw token, URL string, or `URL`. |
196
+ | `musicSync(musicID)` | `Readable` | Returns a `PassThrough` immediately and pipes the fetched music stream into it. |
197
+ | `playlist(playlistID)` | `Promise<PlayList>` | Fetches playlist details. Accepts a raw token, URL string, or `URL`. |
198
+ | `artist(aliasID)` | `Promise<Artist>` | Fetches artist details. Accepts an alias token, URL string, or `URL`. |
199
+ | `mediaDetails(mediaID)` | `Promise<Media>` | Fetches song details. Accepts a raw token, URL string, or `URL`. |
200
+ | `searchMusic(query)` | `Promise<SearhMedia[]>` | Searches songs. |
201
+ | `searchVideo(query)` | `Promise<SearhMedia[]>` | Searches videos. |
202
+ | `searchList(query)` | `Promise<SearchPlayList[]>` | Searches playlists. |
203
+ | `searchArtist(query)` | `Promise<SearchArtist[]>` | Searches artists. |
204
+
205
+ ### Method notes
206
+
207
+ * `video()` selects `720p` first, then falls back to `360p`.
208
+ * `music()` selects `320` first when available and not equal to `"VIP"`, then falls back to `128`.
209
+ * When the music API returns `err === -1150`, `music()` retries the extra music endpoint up to 4 times and throws `ERROR_MUSIC_VIP_ONLY` if it still cannot resolve a playable URL.
210
+ * `searchMusic()`, `searchVideo()`, `searchList()`, and `searchArtist()` always request `page: 1` and `count: 20`.
211
+
212
+ ## Public Types
213
+
214
+ ### `Artist`
215
+
216
+ ```ts
217
+ interface Artist {
218
+ alias: string;
219
+ birthday: string;
220
+ biography: string;
221
+ followCount: number;
222
+ name: string;
223
+ national: string;
224
+ realname: string;
225
+ thumbnail: {
226
+ w240: string;
227
+ w600: string;
228
+ };
229
+ }
230
+ ```
231
+
232
+ ### `Media`
233
+
234
+ ```ts
235
+ interface Media {
236
+ id: string;
237
+ name: string;
238
+ alias: string;
239
+ isOffical: boolean;
240
+ username: string;
241
+ artists: Array<{
242
+ alias: string;
243
+ followCount?: number;
244
+ name: string;
245
+ thumbnail: {
246
+ w240: string;
247
+ w600: string;
248
+ };
249
+ }>;
250
+ isWorldWide: boolean;
251
+ thumbnail: {
252
+ w94: string;
253
+ w240: string;
254
+ };
255
+ duration: number;
256
+ isPrivate: boolean;
257
+ releaseDate: number;
258
+ album: {
259
+ id: string;
260
+ name: string;
261
+ isOffical: boolean;
262
+ releaseDate: string;
263
+ releasedAt: number;
264
+ artists: Array<{
265
+ alias: string;
266
+ followCount?: number;
267
+ name: string;
268
+ thumbnail: {
269
+ w240: string;
270
+ w600: string;
271
+ };
272
+ }>;
273
+ thumbnail: {
274
+ w165: string;
275
+ };
276
+ };
277
+ hasLyric: boolean;
278
+ }
279
+ ```
280
+
281
+ ### `SearhMedia`
282
+
283
+ ```ts
284
+ type SearchMedia = Omit<Media, 'album' | 'isPrivate' | 'releaseDate'>;
285
+ ```
286
+
287
+ ### `PlayList`
288
+
289
+ ```ts
290
+ interface PlayList {
291
+ id: string;
292
+ name: string;
293
+ alias: string;
294
+ artists: Array<{
295
+ alias: string;
296
+ followCount?: number;
297
+ name: string;
298
+ thumbnail: {
299
+ w240: string;
300
+ w600: string;
301
+ };
302
+ }>;
303
+ description: string;
304
+ duration: number;
305
+ isOffical: boolean;
306
+ isPrivate: boolean;
307
+ isSingle: boolean;
308
+ likeCount: number;
309
+ listenCount: number;
310
+ media: Media[];
311
+ mediaCount: number;
312
+ releaseDate: string;
313
+ releasedAt: number;
314
+ thumbnail: {
315
+ w165: string;
316
+ w320: string;
317
+ };
318
+ updatedAt: number;
319
+ }
320
+ ```
321
+
322
+ ### `SearchPlayList`
323
+
324
+ ```ts
325
+ type SearchPlayList = Omit<
326
+ PlayList,
327
+ "updatedAt" | "mediaCount" | "listenCount" | "likeCount" | "duration" | "description" | "media"
328
+ >;
329
+ ```
330
+
331
+ ## `Cookies`
332
+
333
+ Import from `@khang07/zing-mp3-api/utils/cookies`.
334
+
335
+ ### Methods
336
+
337
+ | Method | Returns |
338
+ | -------------------------------------- | ------------------------ |
339
+ | `setCookie(setCookie, requestUrl)` | `void` |
340
+ | `setCookies(setCookies, requestUrl)` | `void` |
341
+ | `getCookies(requestUrl)` | `CookieRecord[]` |
342
+ | `getCookieHeader(requestUrl)` | `string` |
343
+ | `applyToHeaders(requestUrl, headers?)` | `Record<string, string>` |
344
+ | `deleteCookie(domain, path, name)` | `void` |
345
+ | `cleanup()` | `void` |
346
+ | `toJSON()` | `CookieRecord[]` |
347
+ | `fromJSON(cookies)` | `void` |
348
+
349
+ ### Cookie record shape
350
+
351
+ ```ts
352
+ interface CookieRecord {
353
+ name: string;
354
+ value: string;
355
+ domain: string;
356
+ path: string;
357
+ expiresAt?: number;
358
+ secure: boolean;
359
+ httpOnly: boolean;
360
+ sameSite?: "Strict" | "Lax" | "None";
361
+ hostOnly: boolean;
362
+ }
363
+ ```
364
+
365
+ ### Example
366
+
367
+ ```ts
368
+ import { Cookies } from "@khang07/zing-mp3-api/utils/cookies";
369
+
370
+ const jar = new Cookies();
371
+ jar.setCookie("sid=abc; Path=/; HttpOnly", "https://zingmp3.vn/");
372
+
373
+ console.log(jar.getCookieHeader("https://zingmp3.vn/"));
374
+ ```
375
+
376
+ ## `createSignature(uri, params, secret)`
377
+
378
+ Import from `@khang07/zing-mp3-api/utils/encrypt`.
379
+
380
+ ```ts
381
+ function createSignature(uri: string, params: string, secret: string): string
382
+ ```
383
+
384
+ Generates the request signature used by the client.
385
+
386
+ ## `Lapse`
387
+
388
+ Import from `@khang07/zing-mp3-api/utils/lapse`.
389
+
390
+ ```ts
391
+ class Lapse extends Error {
392
+ code: string;
393
+ status?: number;
394
+ cause?: unknown;
395
+ }
396
+ ```
397
+
398
+ ### Constructor
399
+
400
+ ```ts
401
+ new Lapse(message: string, code: string, status?: number, cause?: unknown)
402
+ ```
403
+
404
+ ### Properties
405
+
406
+ * `name`: always set to `"ZING_MP3_ERROR"`
407
+ * `message`: inherited from `Error`
408
+ * `code`: library error code
409
+ * `status`: optional status or API error value
410
+ * `cause`: optional original error or response payload
411
+
412
+ ## Error Codes
413
+
414
+ | Code | Used by |
415
+ | ---------------------------- | ------------------------------------------------------------------ |
416
+ | `ERROR_INVALID_URL` | `Client.getIDFromURL()` |
417
+ | `ERROR_INVALID_ID` | `video()`, `music()`, `playlist()`, `artist()`, `mediaDetails()` |
418
+ | `ERROR_INVALID_QUERY` | `searchMusic()`, `searchVideo()`, `searchList()`, `searchArtist()` |
419
+ | `ERROR_VIDEO_NOT_FOUND` | `video()` |
420
+ | `ERROR_VIDEO_FETCH` | `video()`, `videoSync()` |
421
+ | `ERROR_MUSIC_VIP_ONLY` | `music()` |
422
+ | `ERROR_MUSIC_FETCH` | `music()`, `musicSync()` |
423
+ | `ERROR_PLAYLIST_NOT_FOUND` | `playlist()` |
424
+ | `ERROR_PLAYLIST_FETCH` | `playlist()` |
425
+ | `ERROR_ARTIST_NOT_FOUND` | `artist()` |
426
+ | `ERROR_ARTIST_FETCH` | `artist()` |
427
+ | `ERROR_MEDIA_NOT_FOUND` | `mediaDetails()` |
428
+ | `ERROR_MEDIA_FETCH` | `mediaDetails()` |
429
+ | `ERROR_SEARCH_FAILED` | `searchMusic()`, `searchVideo()`, `searchList()`, `searchArtist()` |
430
+ | `ERROR_SEARCH_FETCH` | `searchMusic()`, `searchVideo()`, `searchList()`, `searchArtist()` |
431
+ | `ERROR_STREAM_URL_NOT_FOUND` | `video()`, `music()` |
432
+ | `ERROR_STREAM_DOWNLOAD` | `video()`, `videoSync()`, `music()`, `musicSync()` |
433
+
434
+ ## Notes
435
+
436
+ * The root entry exports `Cookies` as a type only. To construct a cookie jar, import `Cookies` from `@khang07/zing-mp3-api/utils/cookies`.
437
+ * The root entry exports the type name `SearhMedia` exactly as written in source.
438
+ * `searchArtist()` is a public method, but its `SearchArtist` type is not re-exported from the root entry.
439
+ * No test, example, or demo files were included in the provided source bundle.
440
+
441
+ ## License
442
+ [MIT](https://github.com/GiaKhang1810/zing-mp3-api?tab=MIT-1-ov-file)