gerdur-core 2.2.0 → 2.3.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/CHANGELOG.md +27 -0
- package/README.md +31 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/preview.d.ts +21 -0
- package/dist/api/preview.js +37 -0
- package/dist/lib/get-url.d.ts +28 -9
- package/dist/lib/get-url.js +68 -42
- package/dist/types/tracks.d.ts +12 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.0 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
Phase 2.1 — all the formats, and previews. Additive; the `1 / 3 / 9` path is
|
|
6
|
+
unchanged.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`getTrackPreview(track)` / `downloadPreview(track)`** — the 30-second preview
|
|
11
|
+
clip. A plain MP3: no licence, no `arl`, no Blowfish. Accepts a gw track (reads
|
|
12
|
+
`MEDIA`, no extra request), a track id, or a number.
|
|
13
|
+
- **`DEEZER_FORMATS`** — every format `get_url` understands, best → worst
|
|
14
|
+
(`FLAC`, `MP3_320`, `MP3_256`, `MP3_128`, `MP3_64`, `AAC_64`, `MP4_RA3/2/1`).
|
|
15
|
+
- **`resolveDownloadUrls(tracks, qualities)`** now accepts format strings in the
|
|
16
|
+
preference list (`['FLAC', 'MP3_320', 'AAC_64']`), not just `1 | 3 | 9`. Each
|
|
17
|
+
`ResolvedUrl` gains `cipher` (`'BF_CBC_STRIPE'` | `'NONE'`).
|
|
18
|
+
- **`toFormat(quality)`** — normalise a number or format string to the `get_url`
|
|
19
|
+
format string. `formatName` now also accepts format strings (identity).
|
|
20
|
+
- `Quality` / `DeezerFormat` exported types.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `isEncrypted` (from `getTrackDownloadUrl` and `resolveDownloadUrls`) is now
|
|
25
|
+
taken from the media API's `cipher` field instead of guessing from the URL
|
|
26
|
+
path — authoritative, and correct for `cipher: NONE` content.
|
|
27
|
+
- `trackType.FILESIZE_*` fields are typed `string` (several were mistyped as the
|
|
28
|
+
literal `'0'`); added `FILESIZE_MP3_MISC` / `FILESIZE_MHM1_RA*`.
|
|
29
|
+
|
|
3
30
|
## 2.2.0 - 2026-08-31
|
|
4
31
|
|
|
5
32
|
Phase 2.4 — browse & discovery. All additive, all on the public REST API (no
|
package/README.md
CHANGED
|
@@ -238,6 +238,37 @@ pass the `id` to `getTrackInfo` / `getAlbumTracks` (or use the converter's
|
|
|
238
238
|
| `track` | Yes | `string` | track object |
|
|
239
239
|
| `quality` | Yes | `1, 3 or 9` | 1 = 128kbps, 3 = 320kbps, 9 = flac |
|
|
240
240
|
|
|
241
|
+
Resolves `{trackUrl, isEncrypted, fileSize}`. `isEncrypted` now comes from the
|
|
242
|
+
media API's `cipher` field (authoritative) rather than a URL guess.
|
|
243
|
+
|
|
244
|
+
### Formats
|
|
245
|
+
|
|
246
|
+
Deezer's `get_url` understands more than `1 / 3 / 9`. `DEEZER_FORMATS` lists them
|
|
247
|
+
best → worst: `FLAC`, `MP3_320`, `MP3_256`, `MP3_128`, `MP3_64`, `AAC_64`,
|
|
248
|
+
`MP4_RA3`, `MP4_RA2`, `MP4_RA1` (the last four are the HE-AAC ladder some
|
|
249
|
+
accounts / regions expose).
|
|
250
|
+
|
|
251
|
+
- **`resolveDownloadUrls(tracks, qualities)`** — `qualities` entries may be the
|
|
252
|
+
`1 | 3 | 9` shorthand **or** any format string, e.g.
|
|
253
|
+
`resolveDownloadUrls(tracks, ['FLAC', 'MP3_320', 'AAC_64'])`. Deezer returns
|
|
254
|
+
the best each track is licensed for. Each result now also carries `format` and
|
|
255
|
+
`cipher` (`'BF_CBC_STRIPE'` or `'NONE'`).
|
|
256
|
+
- **`formatName(quality)`** / **`toFormat(quality)`** — normalise a number or
|
|
257
|
+
format string to the `get_url` format string.
|
|
258
|
+
|
|
259
|
+
### `.getTrackPreview(track)` / `.downloadPreview(track)`
|
|
260
|
+
|
|
261
|
+
The 30-second preview clip — a plain MP3, **no licence, no `arl`, no
|
|
262
|
+
encryption**. `track` may be a gw track object (reads its `MEDIA`, no extra
|
|
263
|
+
request), a track id, or a number.
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
const {url} = await getTrackPreview('3135556'); // {url, duration: 30}
|
|
267
|
+
const clip = await downloadPreview('3135556'); // Buffer (ID3-tagged MP3)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Useful for "audition before download" and for CI that shouldn't pull full tracks.
|
|
271
|
+
|
|
241
272
|
### `.decryptDownload(data, song_id);`
|
|
242
273
|
|
|
243
274
|
| Parameters | Required | Type | Description |
|
package/dist/api/index.d.ts
CHANGED
package/dist/api/index.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { trackType } from '../types';
|
|
3
|
+
export interface TrackPreview {
|
|
4
|
+
/** direct URL to a ~30 s MP3 clip — no licence, no encryption, no `arl` */
|
|
5
|
+
url: string;
|
|
6
|
+
/** clip length in seconds (Deezer previews are 30 s) */
|
|
7
|
+
duration: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The 30-second preview clip for a track. Deezer exposes it two ways:
|
|
11
|
+
* `song.getData` carries it in `MEDIA` (`{TYPE: 'preview', HREF}`), and the
|
|
12
|
+
* public `/track/` endpoint carries it as `preview`. Pass a gw `track` object
|
|
13
|
+
* (uses `MEDIA`, no extra request) or a track id (one public-API lookup).
|
|
14
|
+
*
|
|
15
|
+
* The clip is a plain MP3 — never Blowfish-encrypted — so it needs no
|
|
16
|
+
* `decryptDownload` and is safe to use in tests and "audition before download"
|
|
17
|
+
* flows.
|
|
18
|
+
*/
|
|
19
|
+
export declare const getTrackPreview: (track: trackType | string | number) => Promise<TrackPreview | null>;
|
|
20
|
+
/** Fetch the 30-second preview clip as a `Buffer` (plain MP3, no decryption needed). */
|
|
21
|
+
export declare const downloadPreview: (track: trackType | string | number) => Promise<Buffer | null>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.downloadPreview = exports.getTrackPreview = void 0;
|
|
4
|
+
const http_1 = require("../lib/http");
|
|
5
|
+
const api_1 = require("./api");
|
|
6
|
+
/**
|
|
7
|
+
* The 30-second preview clip for a track. Deezer exposes it two ways:
|
|
8
|
+
* `song.getData` carries it in `MEDIA` (`{TYPE: 'preview', HREF}`), and the
|
|
9
|
+
* public `/track/` endpoint carries it as `preview`. Pass a gw `track` object
|
|
10
|
+
* (uses `MEDIA`, no extra request) or a track id (one public-API lookup).
|
|
11
|
+
*
|
|
12
|
+
* The clip is a plain MP3 — never Blowfish-encrypted — so it needs no
|
|
13
|
+
* `decryptDownload` and is safe to use in tests and "audition before download"
|
|
14
|
+
* flows.
|
|
15
|
+
*/
|
|
16
|
+
const getTrackPreview = async (track) => {
|
|
17
|
+
var _a;
|
|
18
|
+
if (typeof track === 'object') {
|
|
19
|
+
const media = (_a = track.MEDIA) === null || _a === void 0 ? void 0 : _a[0];
|
|
20
|
+
if ((media === null || media === void 0 ? void 0 : media.HREF) && (!media.TYPE || media.TYPE === 'preview')) {
|
|
21
|
+
return { url: media.HREF, duration: 30 };
|
|
22
|
+
}
|
|
23
|
+
const pub = await (0, api_1.getTrackInfoPublicApi)(track.SNG_ID);
|
|
24
|
+
return pub.preview ? { url: pub.preview, duration: 30 } : null;
|
|
25
|
+
}
|
|
26
|
+
const pub = await (0, api_1.getTrackInfoPublicApi)(String(track));
|
|
27
|
+
return pub.preview ? { url: pub.preview, duration: 30 } : null;
|
|
28
|
+
};
|
|
29
|
+
exports.getTrackPreview = getTrackPreview;
|
|
30
|
+
/** Fetch the 30-second preview clip as a `Buffer` (plain MP3, no decryption needed). */
|
|
31
|
+
const downloadPreview = async (track) => {
|
|
32
|
+
const preview = await (0, exports.getTrackPreview)(track);
|
|
33
|
+
if (!preview)
|
|
34
|
+
return null;
|
|
35
|
+
return (0, http_1.getBuffer)(preview.url);
|
|
36
|
+
};
|
|
37
|
+
exports.downloadPreview = downloadPreview;
|
package/dist/lib/get-url.d.ts
CHANGED
|
@@ -13,8 +13,24 @@ export declare class ExpiredTrackToken extends Error {
|
|
|
13
13
|
readonly sngId: string;
|
|
14
14
|
constructor(sngId: string);
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Every audio format Deezer's `get_url` understands, best → worst. `FLAC`,
|
|
18
|
+
* `MP3_320` and `MP3_128` are the classic `9 / 3 / 1` qualities; the rest are
|
|
19
|
+
* additional tiers (`MP3_256`, `MP3_64`, the `AAC_64` / `MP4_RA*` HE-AAC ladder)
|
|
20
|
+
* that some accounts and regions expose.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEEZER_FORMATS: readonly ["FLAC", "MP3_320", "MP3_256", "MP3_128", "MP3_64", "AAC_64", "MP4_RA3", "MP4_RA2", "MP4_RA1"];
|
|
23
|
+
export type DeezerFormat = (typeof DEEZER_FORMATS)[number];
|
|
24
|
+
/** A quality request: the numeric `1 | 3 | 9` shorthand, or any format string. */
|
|
25
|
+
export type Quality = number | DeezerFormat | string;
|
|
26
|
+
/** Normalise a {@link Quality} (number shorthand or format string) to a format string. */
|
|
27
|
+
export declare const toFormat: (quality: Quality) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Quality code (`1 | 3 | 9`) or format string → the `format` string the media
|
|
30
|
+
* API expects. Format strings pass straight through, so this doubles as a
|
|
31
|
+
* validator-free identity for `'AAC_64'`, `'MP3_256'`, …
|
|
32
|
+
*/
|
|
33
|
+
export declare const formatName: (quality: Quality) => string;
|
|
18
34
|
/**
|
|
19
35
|
* @param track Track info json returned from `getTrackInfo`
|
|
20
36
|
* @param quality 1 = 128kbps, 3 = 320kbps and 9 = flac (around 1411kbps)
|
|
@@ -28,18 +44,21 @@ export interface ResolvedUrl {
|
|
|
28
44
|
trackUrl: string;
|
|
29
45
|
isEncrypted: boolean;
|
|
30
46
|
fileSize: number;
|
|
31
|
-
/** the format Deezer actually returned, e.g. `'FLAC'` */
|
|
47
|
+
/** the format Deezer actually returned, e.g. `'FLAC'`, `'MP3_128'`, `'AAC_64'` */
|
|
32
48
|
format: string;
|
|
49
|
+
/** the cipher Deezer applied — `'BF_CBC_STRIPE'` (stripe-encrypted) or `'NONE'` */
|
|
50
|
+
cipher: string;
|
|
33
51
|
}
|
|
34
52
|
/**
|
|
35
53
|
* Resolve download URLs for many tracks in a **single** `get_url` request.
|
|
36
54
|
*
|
|
37
|
-
* `qualities` is an ordered preference list
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
55
|
+
* `qualities` is an ordered preference list: Deezer returns the best each track
|
|
56
|
+
* is licensed for, so there is no per-quality retry. Entries may be the numeric
|
|
57
|
+
* `1 | 3 | 9` shorthand **or** any format string from {@link DEEZER_FORMATS}
|
|
58
|
+
* (e.g. `['FLAC', 'MP3_320', 'AAC_64']`). The result has one entry per input
|
|
59
|
+
* track, in order; `null` for a track that is geo-blocked, unavailable, or errored.
|
|
41
60
|
*
|
|
42
61
|
* @param tracks from `getTrackInfo` / `parseInfo` (needs `TRACK_TOKEN`, `SNG_ID`, `FILESIZE_*`)
|
|
43
|
-
* @param qualities preference order — 9
|
|
62
|
+
* @param qualities preference order — default `[9, 3, 1]` (FLAC → MP3 320 → MP3 128)
|
|
44
63
|
*/
|
|
45
|
-
export declare const resolveDownloadUrls: (tracks: trackType[], qualities?:
|
|
64
|
+
export declare const resolveDownloadUrls: (tracks: trackType[], qualities?: Quality[]) => Promise<(ResolvedUrl | null)[]>;
|
package/dist/lib/get-url.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.resolveDownloadUrls = exports.getTrackDownloadUrl = exports.formatName = exports.ExpiredTrackToken = exports.GeoBlocked = exports.WrongLicense = void 0;
|
|
6
|
+
exports.resolveDownloadUrls = exports.getTrackDownloadUrl = exports.formatName = exports.toFormat = exports.DEEZER_FORMATS = exports.ExpiredTrackToken = exports.GeoBlocked = exports.WrongLicense = void 0;
|
|
7
7
|
const delay_1 = __importDefault(require("delay"));
|
|
8
8
|
const decrypt_1 = require("../lib/decrypt");
|
|
9
9
|
const http_1 = require("../lib/http");
|
|
@@ -37,17 +37,50 @@ class ExpiredTrackToken extends Error {
|
|
|
37
37
|
}
|
|
38
38
|
exports.ExpiredTrackToken = ExpiredTrackToken;
|
|
39
39
|
let user_data = null;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Every audio format Deezer's `get_url` understands, best → worst. `FLAC`,
|
|
42
|
+
* `MP3_320` and `MP3_128` are the classic `9 / 3 / 1` qualities; the rest are
|
|
43
|
+
* additional tiers (`MP3_256`, `MP3_64`, the `AAC_64` / `MP4_RA*` HE-AAC ladder)
|
|
44
|
+
* that some accounts and regions expose.
|
|
45
|
+
*/
|
|
46
|
+
exports.DEEZER_FORMATS = [
|
|
47
|
+
'FLAC',
|
|
48
|
+
'MP3_320',
|
|
49
|
+
'MP3_256',
|
|
50
|
+
'MP3_128',
|
|
51
|
+
'MP3_64',
|
|
52
|
+
'AAC_64',
|
|
53
|
+
'MP4_RA3',
|
|
54
|
+
'MP4_RA2',
|
|
55
|
+
'MP4_RA1',
|
|
56
|
+
];
|
|
57
|
+
const FORMAT_FILESIZE_KEY = {
|
|
58
|
+
FLAC: 'FILESIZE_FLAC',
|
|
59
|
+
MP3_320: 'FILESIZE_MP3_320',
|
|
60
|
+
MP3_256: 'FILESIZE_MP3_256',
|
|
61
|
+
MP3_128: 'FILESIZE_MP3_128',
|
|
62
|
+
MP3_64: 'FILESIZE_MP3_64',
|
|
63
|
+
MP3_MISC: 'FILESIZE_MP3_MISC',
|
|
64
|
+
AAC_64: 'FILESIZE_AAC_64',
|
|
65
|
+
MP4_RA1: 'FILESIZE_MP4_RA1',
|
|
66
|
+
MP4_RA2: 'FILESIZE_MP4_RA2',
|
|
67
|
+
MP4_RA3: 'FILESIZE_MP4_RA3',
|
|
68
|
+
};
|
|
69
|
+
const NUMERIC_FORMAT = { 9: 'FLAC', 3: 'MP3_320', 1: 'MP3_128' };
|
|
70
|
+
/** Normalise a {@link Quality} (number shorthand or format string) to a format string. */
|
|
71
|
+
const toFormat = (quality) => {
|
|
72
|
+
if (typeof quality === 'number') {
|
|
73
|
+
const f = NUMERIC_FORMAT[quality];
|
|
74
|
+
if (!f)
|
|
75
|
+
throw new Error(`Unknown quality ${quality}`);
|
|
76
|
+
return f;
|
|
50
77
|
}
|
|
78
|
+
return quality;
|
|
79
|
+
};
|
|
80
|
+
exports.toFormat = toFormat;
|
|
81
|
+
const getTrackFileSize = (track, quality) => {
|
|
82
|
+
const key = FORMAT_FILESIZE_KEY[(0, exports.toFormat)(quality)];
|
|
83
|
+
return key ? Number(track[key]) || 0 : 0;
|
|
51
84
|
};
|
|
52
85
|
const dzAuthenticate = async () => {
|
|
53
86
|
const { data } = await request_1.default.get('https://www.deezer.com/ajax/gw-light.php', {
|
|
@@ -66,19 +99,12 @@ const dzAuthenticate = async () => {
|
|
|
66
99
|
return user_data;
|
|
67
100
|
};
|
|
68
101
|
const MEDIA_MAX_RETRIES = 3;
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return 'MP3_320';
|
|
76
|
-
case 1:
|
|
77
|
-
return 'MP3_128';
|
|
78
|
-
default:
|
|
79
|
-
throw new Error(`Unknown quality ${quality}`);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
102
|
+
/**
|
|
103
|
+
* Quality code (`1 | 3 | 9`) or format string → the `format` string the media
|
|
104
|
+
* API expects. Format strings pass straight through, so this doubles as a
|
|
105
|
+
* validator-free identity for `'AAC_64'`, `'MP3_256'`, …
|
|
106
|
+
*/
|
|
107
|
+
const formatName = (quality) => (0, exports.toFormat)(quality);
|
|
82
108
|
exports.formatName = formatName;
|
|
83
109
|
/** POST media.deezer.com/v1/get_url with re-auth + exponential-backoff retry on 403/429/5xx. */
|
|
84
110
|
const mediaGetUrl = async (track_tokens, formats, attempt = 0) => {
|
|
@@ -104,7 +130,7 @@ const mediaGetUrl = async (track_tokens, formats, attempt = 0) => {
|
|
|
104
130
|
};
|
|
105
131
|
/** Parse one `data[i]` entry from a get_url response into a source URL (or null / throw). */
|
|
106
132
|
const parseMediaEntry = (entry, token, country) => {
|
|
107
|
-
var _a, _b;
|
|
133
|
+
var _a, _b, _c, _d;
|
|
108
134
|
if (entry === null || entry === void 0 ? void 0 : entry.errors) {
|
|
109
135
|
const { code } = entry.errors[0];
|
|
110
136
|
if (code === 2002)
|
|
@@ -116,10 +142,11 @@ const parseMediaEntry = (entry, token, country) => {
|
|
|
116
142
|
const media = (_a = entry === null || entry === void 0 ? void 0 : entry.media) === null || _a === void 0 ? void 0 : _a[0];
|
|
117
143
|
if (!((_b = media === null || media === void 0 ? void 0 : media.sources) === null || _b === void 0 ? void 0 : _b.length))
|
|
118
144
|
return null;
|
|
119
|
-
return { url: media.sources[0].url, format: media.format };
|
|
145
|
+
return { url: media.sources[0].url, format: media.format, cipher: (_d = (_c = media.cipher) === null || _c === void 0 ? void 0 : _c.type) !== null && _d !== void 0 ? _d : 'BF_CBC_STRIPE' };
|
|
120
146
|
};
|
|
147
|
+
/** Whether a track fetched with the given cipher needs Blowfish stripe decryption. */
|
|
148
|
+
const cipherIsEncrypted = (cipher, url) => cipher ? cipher !== 'NONE' : url.includes('/mobile/') || url.includes('/media/');
|
|
121
149
|
const getTrackUrlFromServer = async (track_token, format) => {
|
|
122
|
-
var _a, _b;
|
|
123
150
|
const user = user_data ? user_data : await dzAuthenticate();
|
|
124
151
|
if ((format === 'FLAC' && !user.can_stream_lossless) || (format === 'MP3_320' && !user.can_stream_hq)) {
|
|
125
152
|
throw new WrongLicense(format);
|
|
@@ -127,7 +154,7 @@ const getTrackUrlFromServer = async (track_token, format) => {
|
|
|
127
154
|
const { data, country } = await mediaGetUrl([track_token], [{ format, cipher: 'BF_CBC_STRIPE' }]);
|
|
128
155
|
if (!data.length)
|
|
129
156
|
return null;
|
|
130
|
-
return
|
|
157
|
+
return parseMediaEntry(data[0], track_token, country);
|
|
131
158
|
};
|
|
132
159
|
/**
|
|
133
160
|
* @param track Track info json returned from `getTrackInfo`
|
|
@@ -148,11 +175,11 @@ const getTrackDownloadUrl = async (track, quality) => {
|
|
|
148
175
|
else {
|
|
149
176
|
// Get URL with the official API
|
|
150
177
|
try {
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
178
|
+
const resolved = await getTrackUrlFromServer(track.TRACK_TOKEN, format);
|
|
179
|
+
if (resolved) {
|
|
153
180
|
return {
|
|
154
|
-
trackUrl: url,
|
|
155
|
-
isEncrypted:
|
|
181
|
+
trackUrl: resolved.url,
|
|
182
|
+
isEncrypted: cipherIsEncrypted(resolved.cipher, resolved.url),
|
|
156
183
|
fileSize: getTrackFileSize(track, quality),
|
|
157
184
|
};
|
|
158
185
|
}
|
|
@@ -213,17 +240,17 @@ const testUrl = async (url) => {
|
|
|
213
240
|
return 0;
|
|
214
241
|
}
|
|
215
242
|
};
|
|
216
|
-
const QUALITY_OF_FORMAT = { FLAC: 9, MP3_320: 3, MP3_128: 1, MP3_256: 3, MP3_64: 1 };
|
|
217
243
|
/**
|
|
218
244
|
* Resolve download URLs for many tracks in a **single** `get_url` request.
|
|
219
245
|
*
|
|
220
|
-
* `qualities` is an ordered preference list
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
246
|
+
* `qualities` is an ordered preference list: Deezer returns the best each track
|
|
247
|
+
* is licensed for, so there is no per-quality retry. Entries may be the numeric
|
|
248
|
+
* `1 | 3 | 9` shorthand **or** any format string from {@link DEEZER_FORMATS}
|
|
249
|
+
* (e.g. `['FLAC', 'MP3_320', 'AAC_64']`). The result has one entry per input
|
|
250
|
+
* track, in order; `null` for a track that is geo-blocked, unavailable, or errored.
|
|
224
251
|
*
|
|
225
252
|
* @param tracks from `getTrackInfo` / `parseInfo` (needs `TRACK_TOKEN`, `SNG_ID`, `FILESIZE_*`)
|
|
226
|
-
* @param qualities preference order — 9
|
|
253
|
+
* @param qualities preference order — default `[9, 3, 1]` (FLAC → MP3 320 → MP3 128)
|
|
227
254
|
*/
|
|
228
255
|
const resolveDownloadUrls = async (tracks, qualities = [9, 3, 1]) => {
|
|
229
256
|
if (!tracks.length)
|
|
@@ -231,7 +258,6 @@ const resolveDownloadUrls = async (tracks, qualities = [9, 3, 1]) => {
|
|
|
231
258
|
const formats = qualities.map((q) => ({ format: (0, exports.formatName)(q), cipher: 'BF_CBC_STRIPE' }));
|
|
232
259
|
const { data, country } = await mediaGetUrl(tracks.map((t) => t.TRACK_TOKEN), formats);
|
|
233
260
|
return tracks.map((track, i) => {
|
|
234
|
-
var _a;
|
|
235
261
|
let parsed;
|
|
236
262
|
try {
|
|
237
263
|
parsed = parseMediaEntry(data[i], track.TRACK_TOKEN, country);
|
|
@@ -242,12 +268,12 @@ const resolveDownloadUrls = async (tracks, qualities = [9, 3, 1]) => {
|
|
|
242
268
|
}
|
|
243
269
|
if (!parsed)
|
|
244
270
|
return null;
|
|
245
|
-
const q = (_a = QUALITY_OF_FORMAT[parsed.format]) !== null && _a !== void 0 ? _a : qualities[0];
|
|
246
271
|
return {
|
|
247
272
|
trackUrl: parsed.url,
|
|
248
|
-
isEncrypted: parsed.
|
|
249
|
-
fileSize: getTrackFileSize(track,
|
|
273
|
+
isEncrypted: cipherIsEncrypted(parsed.cipher, parsed.url),
|
|
274
|
+
fileSize: getTrackFileSize(track, parsed.format),
|
|
250
275
|
format: parsed.format,
|
|
276
|
+
cipher: parsed.cipher,
|
|
251
277
|
};
|
|
252
278
|
});
|
|
253
279
|
};
|
package/dist/types/tracks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { artistType } from './artist';
|
|
2
2
|
interface mediaType {
|
|
3
|
-
TYPE: 'preview';
|
|
3
|
+
TYPE: 'preview' | string;
|
|
4
4
|
HREF: string;
|
|
5
5
|
}
|
|
6
6
|
export interface lyricsSync {
|
|
@@ -59,15 +59,19 @@ interface songType {
|
|
|
59
59
|
URL_REWRITING: string;
|
|
60
60
|
VERSION?: string;
|
|
61
61
|
MD5_ORIGIN?: string;
|
|
62
|
-
FILESIZE_AAC_64:
|
|
62
|
+
FILESIZE_AAC_64: string;
|
|
63
63
|
FILESIZE_MP3_64: string;
|
|
64
64
|
FILESIZE_MP3_128: string;
|
|
65
|
-
FILESIZE_MP3_256:
|
|
66
|
-
FILESIZE_MP3_320:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
FILESIZE_MP3_256: string;
|
|
66
|
+
FILESIZE_MP3_320: string;
|
|
67
|
+
FILESIZE_MP3_MISC?: string;
|
|
68
|
+
FILESIZE_MP4_RA1: string;
|
|
69
|
+
FILESIZE_MP4_RA2: string;
|
|
70
|
+
FILESIZE_MP4_RA3: string;
|
|
71
|
+
FILESIZE_MHM1_RA1?: string;
|
|
72
|
+
FILESIZE_MHM1_RA2?: string;
|
|
73
|
+
FILESIZE_MHM1_RA3?: string;
|
|
74
|
+
FILESIZE_FLAC: string;
|
|
71
75
|
FILESIZE: string;
|
|
72
76
|
GAIN: string;
|
|
73
77
|
MEDIA_VERSION: string;
|