gerdur 2.1.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 +42 -0
- package/README.md +101 -17
- package/dist/package.json +2 -2
- package/dist/src/gerdur.js +120 -13
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +17 -1
- package/dist/src/lib/search.d.ts +39 -0
- package/dist/src/lib/search.js +67 -0
- package/dist/src/lib/session.d.ts +25 -3
- package/dist/src/lib/session.js +47 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.0 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`gerdur-core@^2.2.0`** — the browse / discovery surface is re-exported:
|
|
8
|
+
`getGenres`, `getChart`, `getChartTracks`, `getGenreArtists`,
|
|
9
|
+
`getEditorialList` / `getEditorialReleases` / `getEditorialSelection` /
|
|
10
|
+
`getEditorialCharts`, `getArtistTopTracks`, `getRelatedArtists`,
|
|
11
|
+
`getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`,
|
|
12
|
+
`getTrackByISRC`, `getAlbumByUPC`, plus their types.
|
|
13
|
+
- **`Session` browse methods**: `genres`, `chart`, `chartTracks`,
|
|
14
|
+
`editorialSections`, `artistTopTracks`, `relatedArtists`, `artistAlbums`,
|
|
15
|
+
`artistRadio`, `trackByISRC`, `albumByUPC`.
|
|
16
|
+
- **CLI `isrc:` / `upc:` prefixes** — `gerdur -u isrc:GBDUW0000059` downloads the
|
|
17
|
+
exact track for an ISRC; `gerdur -u upc:0724384960650` downloads the album for
|
|
18
|
+
a barcode. Both work in `--headless` mode and from the interactive prompt.
|
|
19
|
+
|
|
20
|
+
## 2.2.0 - 2026-08-31
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **CLI search.** New flags — `--search`, `--artist`, `--album`, `--track`,
|
|
25
|
+
`--label`, `--bpm-min` / `--bpm-max`, `--dur-min` / `--dur-max`,
|
|
26
|
+
`--search-limit` — are composed into a Deezer advanced-search query. The
|
|
27
|
+
matches are listed and you tick which to download; `--headless` grabs every
|
|
28
|
+
match up to `--search-limit`. Hits are resolved to full tracks before
|
|
29
|
+
downloading, so quality fallback / tagging / `.lrc` sidecars all work.
|
|
30
|
+
- **`search:` prefix** at the interactive prompt (and `-u 'search:…'`) runs the
|
|
31
|
+
same advanced track search, e.g. `search:artist:"daft punk" bpm_min:120`.
|
|
32
|
+
- When Deezer's advanced operators return nothing (they are unreliable), the CLI
|
|
33
|
+
automatically retries the same words as a plain free-text query.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **`Session.searchAdvanced`** now falls back to a plain free-text query when the
|
|
38
|
+
advanced operators match nothing (opt out with `{fallback: false}`), and
|
|
39
|
+
accepts `fallback` in its options.
|
|
40
|
+
- The interactive prompt now reads "Enter a URL, a search term, or
|
|
41
|
+
`search:<advanced query>`".
|
|
42
|
+
- Search / filter helpers extracted to `src/lib/search.ts`
|
|
43
|
+
(`advancedFiltersFromFlags`, `searchAdvancedTracks`, `plainTextQuery`).
|
|
44
|
+
|
|
3
45
|
## 2.1.0 - 2026-08-31
|
|
4
46
|
|
|
5
47
|
### Added
|
package/README.md
CHANGED
|
@@ -134,14 +134,69 @@ All options are optional. You can suppress prompts via providing `quality` and `
|
|
|
134
134
|
| `--config-file` | `-conf` | Config location. Example: `gerdur -conf my-gerdur.config.json` | Specify custom location to config file |
|
|
135
135
|
| `--update` | `-U` | *Nothing* | Download new update (binary only) |
|
|
136
136
|
| `--help` | `-h` | *Nothing* | Shows the CLI help |
|
|
137
|
+
| `--search` | *None* | free-text query | Search tracks and pick what to download. Combine with the filters below. |
|
|
138
|
+
| `--artist` | *None* | artist name | Search filter — restrict results to this artist |
|
|
139
|
+
| `--album` | *None* | album title | Search filter — restrict results to this album |
|
|
140
|
+
| `--track` | *None* | track title | Search filter — restrict results to this title |
|
|
141
|
+
| `--label` | *None* | record label | Search filter — restrict results to this label |
|
|
142
|
+
| `--bpm-min` / `--bpm-max` | *None* | number | Search filter — tempo range (beats per minute) |
|
|
143
|
+
| `--dur-min` / `--dur-max` | *None* | number (seconds) | Search filter — track duration range |
|
|
144
|
+
| `--search-limit` | *None* | number (default 50) | How many search results to fetch |
|
|
145
|
+
|
|
146
|
+
## Search
|
|
147
|
+
|
|
148
|
+
### From the interactive prompt
|
|
149
|
+
|
|
150
|
+
When `gerdur` asks for a URL, a search term, or a prefixed query:
|
|
151
|
+
|
|
152
|
+
| Input | Does |
|
|
153
|
+
| :--- | :--- |
|
|
154
|
+
| `Harder Better Faster Stronger` | plain track search — pick tracks to download |
|
|
155
|
+
| `artist:daft punk` | artist search — pick an artist, then its discography |
|
|
156
|
+
| `album:discovery` | album search — pick an album |
|
|
157
|
+
| `playlist:deep focus` | playlist search — pick a playlist |
|
|
158
|
+
| `search:artist:"daft punk" bpm_min:120` | advanced track search (see operators below) |
|
|
159
|
+
| `isrc:GBDUW0000059` | download the exact track for an ISRC |
|
|
160
|
+
| `upc:0724384960650` | download the album for a UPC / EAN barcode |
|
|
161
|
+
|
|
162
|
+
`isrc:` and `upc:` also work in `--headless` mode (`-u isrc:…` / `-u upc:…`).
|
|
163
|
+
|
|
164
|
+
### From flags (works headless)
|
|
165
|
+
|
|
166
|
+
The `--search` / `--artist` / `--album` / `--track` / `--label` / `--bpm-min` /
|
|
167
|
+
`--bpm-max` / `--dur-min` / `--dur-max` flags are composed into one Deezer
|
|
168
|
+
advanced-search query, the matches are listed, and — interactively — you pick
|
|
169
|
+
which to download. In `--headless` mode every match (up to `--search-limit`) is
|
|
170
|
+
downloaded.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# interactive: search, then tick the tracks you want
|
|
174
|
+
gerdur --artist "Daft Punk" --track "Around the World"
|
|
175
|
+
|
|
176
|
+
# headless: grab the first 5 matches as FLAC
|
|
177
|
+
gerdur -d -q flac --search "get lucky" --artist "Daft Punk" --search-limit 5
|
|
178
|
+
|
|
179
|
+
# tempo / duration windows
|
|
180
|
+
gerdur --artist "Justice" --bpm-min 120 --bpm-max 130 --dur-min 180
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Search hits are resolved to full tracks (via `getTrackInfo`) before downloading,
|
|
184
|
+
so quality fallback, tagging and `.lrc` sidecars all work as normal.
|
|
137
185
|
|
|
138
|
-
|
|
186
|
+
### Advanced query operators
|
|
139
187
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
|
188
|
+
Usable inside `--search`, after `search:`, or via `buildAdvancedQuery` in code.
|
|
189
|
+
Deezer applies them as ranking hints (not hard filters), and they bite reliably
|
|
190
|
+
only on **track** search:
|
|
191
|
+
|
|
192
|
+
| Operator | Example |
|
|
193
|
+
| :--- | :--- |
|
|
194
|
+
| `artist:` | `artist:"daft punk"` |
|
|
195
|
+
| `album:` | `album:"discovery"` |
|
|
196
|
+
| `track:` | `track:"one more time"` |
|
|
197
|
+
| `label:` | `label:"Virgin"` |
|
|
198
|
+
| `dur_min:` / `dur_max:` | `dur_min:200` (seconds) |
|
|
199
|
+
| `bpm_min:` / `bpm_max:` | `bpm_min:120` |
|
|
145
200
|
|
|
146
201
|
## Programmatic API
|
|
147
202
|
|
|
@@ -184,13 +239,36 @@ const hits = await session.searchAdvanced(
|
|
|
184
239
|
const suggestions = await session.suggest('daft'); // autocomplete
|
|
185
240
|
```
|
|
186
241
|
|
|
187
|
-
Session methods:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
242
|
+
Session methods:
|
|
243
|
+
|
|
244
|
+
- **resolve / search** — `parseUrl`, `search`, `searchAdvanced`, `suggest`
|
|
245
|
+
- **browse** — `genres`, `chart`, `chartTracks`, `editorialSections`,
|
|
246
|
+
`artistTopTracks`, `relatedArtists`, `artistAlbums`, `artistRadio`,
|
|
247
|
+
`trackByISRC`, `albumByUPC`
|
|
248
|
+
- **user / download** — `getUser`, `getTrackBuffer`, `downloadTrack`,
|
|
249
|
+
`downloadTracks`, `downloadUrl`
|
|
191
250
|
|
|
192
|
-
|
|
193
|
-
`
|
|
251
|
+
Every download call is silent; `downloadTracks` / `downloadUrl` accept
|
|
252
|
+
`concurrency` and an `onProgress` callback and return one `{path, written} | null`
|
|
253
|
+
per track.
|
|
254
|
+
|
|
255
|
+
`searchAdvanced(filters, options?)` builds a Deezer advanced-search query from
|
|
256
|
+
`{query?, artist?, album?, track?, label?, durMin?, durMax?, bpmMin?, bpmMax?}`
|
|
257
|
+
and takes `{order?, strict?, limit?, index?, fallback?}`. Deezer's operators are
|
|
258
|
+
unreliable, so an empty result is retried as a plain free-text query unless
|
|
259
|
+
`fallback: false`. It returns public-API track objects — fetch a hit's gw track
|
|
260
|
+
with `getTrackInfo(id)` before downloading it.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// Browse: this week's electro chart, download the top 5
|
|
264
|
+
const {tracks} = await session.chart(106, 5); // 106 = "Dance" genre
|
|
265
|
+
const full = await Promise.all(tracks.data.map((t) => getTrackInfo(String(t.id))));
|
|
266
|
+
await session.downloadTracks(full, 'flac');
|
|
267
|
+
|
|
268
|
+
// Find & grab an exact recording by barcode
|
|
269
|
+
const t = await session.trackByISRC('GBDUW0000059');
|
|
270
|
+
await session.downloadTrack(await getTrackInfo(String(t.id)), '320');
|
|
271
|
+
```
|
|
194
272
|
|
|
195
273
|
### Low-level: primitives
|
|
196
274
|
|
|
@@ -214,11 +292,17 @@ const {path, written} = (await downloadTrackToFile(track, 'flac', {output: '{ART
|
|
|
214
292
|
So you don't need `gerdur-core` as a second dependency (call after `initDeezerApi`
|
|
215
293
|
or `createSession`):
|
|
216
294
|
|
|
217
|
-
`parseInfo`, `
|
|
218
|
-
`
|
|
219
|
-
`
|
|
220
|
-
`
|
|
221
|
-
`
|
|
295
|
+
- **Query** — `parseInfo`, `getUser`, `getTrackInfo`, `getAlbumInfo`,
|
|
296
|
+
`getAlbumTracks`, `getPlaylistInfo`, `getPlaylistTracks`, `getArtistInfo`,
|
|
297
|
+
`getDiscography`, `getLyrics`
|
|
298
|
+
- **Search** — `searchMusic`, `searchPublicApi`, `searchTracks`, `searchAlbums`,
|
|
299
|
+
`searchArtists`, `searchPlaylists`, `buildAdvancedQuery`, `suggest`
|
|
300
|
+
- **Browse** — `getGenres`, `getChart`, `getChartTracks`, `getGenreArtists`,
|
|
301
|
+
`getEditorialList`, `getEditorialReleases`, `getEditorialSelection`,
|
|
302
|
+
`getEditorialCharts`, `getArtistTopTracks`, `getRelatedArtists`,
|
|
303
|
+
`getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`,
|
|
304
|
+
`getTrackByISRC`, `getAlbumByUPC`
|
|
305
|
+
- **Download** — `getTrackDownloadUrl`, `resolveDownloadUrls`, `GeoBlocked`
|
|
222
306
|
|
|
223
307
|
### Auth & config helpers
|
|
224
308
|
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "The crucial streaming module for dabox systems.",
|
|
5
5
|
"author": "Christian",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"adm-zip": "^0.5.16",
|
|
49
49
|
"chalk": "^4.1.2",
|
|
50
50
|
"commander": "^9.5.0",
|
|
51
|
-
"gerdur-core": "^2.
|
|
51
|
+
"gerdur-core": "^2.2.0",
|
|
52
52
|
"dot-prop": "^6.0.1",
|
|
53
53
|
"got": "^11.8.6",
|
|
54
54
|
"gradient-string": "^2.0.2",
|
package/dist/src/gerdur.js
CHANGED
|
@@ -22,6 +22,7 @@ const update_check_1 = __importDefault(require("./lib/update-check"));
|
|
|
22
22
|
const auto_updater_1 = __importDefault(require("./lib/auto-updater"));
|
|
23
23
|
const arl_setup_1 = require("./lib/arl-setup");
|
|
24
24
|
const util_1 = require("./lib/util");
|
|
25
|
+
const search_1 = require("./lib/search");
|
|
25
26
|
const package_json_1 = __importDefault(require("../package.json"));
|
|
26
27
|
// App info
|
|
27
28
|
console.log((0, gradient_string_1.default)('red', 'yellow', 'orange')(` ♥ gerdur - ${package_json_1.default.version} ♥ `) +
|
|
@@ -46,7 +47,17 @@ const cmd = new commander_1.Command()
|
|
|
46
47
|
.option('-d, --headless', 'Run in headless mode for scripting automation', false)
|
|
47
48
|
.option('-conf, --config-file <file>', 'Custom location to your config file', 'gerdur.config.json')
|
|
48
49
|
.option('-rfp, --resolve-full-path', 'Use absolute path for playlists')
|
|
49
|
-
.option('-cp, --create-playlist', 'Force create a playlist file for non playlists')
|
|
50
|
+
.option('-cp, --create-playlist', 'Force create a playlist file for non playlists')
|
|
51
|
+
.option('--search <query>', 'Search tracks by free text (combine with the filters below)')
|
|
52
|
+
.option('--artist <name>', 'Search filter: artist name')
|
|
53
|
+
.option('--album <name>', 'Search filter: album title')
|
|
54
|
+
.option('--track <name>', 'Search filter: track title')
|
|
55
|
+
.option('--label <name>', 'Search filter: record label')
|
|
56
|
+
.option('--bpm-min <n>', 'Search filter: minimum BPM')
|
|
57
|
+
.option('--bpm-max <n>', 'Search filter: maximum BPM')
|
|
58
|
+
.option('--dur-min <seconds>', 'Search filter: minimum duration')
|
|
59
|
+
.option('--dur-max <seconds>', 'Search filter: maximum duration')
|
|
60
|
+
.option('--search-limit <n>', 'Max search results to fetch', '50');
|
|
50
61
|
if (process.pkg) {
|
|
51
62
|
cmd.option('-U, --update', 'Update this program to latest version');
|
|
52
63
|
}
|
|
@@ -59,14 +70,23 @@ if (cmd.args[0] && cmd.args[0].toLowerCase() === 'setup') {
|
|
|
59
70
|
if (!options.url && cmd.args[0]) {
|
|
60
71
|
options.url = cmd.args[0];
|
|
61
72
|
}
|
|
73
|
+
const toFiniteNumber = (value) => {
|
|
74
|
+
if (value === undefined || value === null || value === '') {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const n = Number(value);
|
|
78
|
+
return Number.isFinite(n) ? n : undefined;
|
|
79
|
+
};
|
|
80
|
+
const advancedFilters = (0, search_1.advancedFiltersFromFlags)(options);
|
|
81
|
+
let advancedFiltersConsumed = false;
|
|
62
82
|
if (options.headless && !options.quality) {
|
|
63
83
|
console.error(signale_1.default.error('Missing parameters --quality'));
|
|
64
84
|
console.error(signale_1.default.note('Quality must be provided with headless mode'));
|
|
65
85
|
process.exit(1);
|
|
66
86
|
}
|
|
67
|
-
if (options.headless && !options.url && !options.inputFile) {
|
|
87
|
+
if (options.headless && !options.url && !options.inputFile && !advancedFilters) {
|
|
68
88
|
console.error(signale_1.default.error('Missing parameters --url'));
|
|
69
|
-
console.error(signale_1.default.note('
|
|
89
|
+
console.error(signale_1.default.note('Provide --url, --input-file, or a --search / --artist / … filter with headless mode'));
|
|
70
90
|
process.exit(1);
|
|
71
91
|
}
|
|
72
92
|
const conf = new config_1.default(options.configFile);
|
|
@@ -79,8 +99,62 @@ const onCancel = () => {
|
|
|
79
99
|
console.info(signale_1.default.note('Aborted!'));
|
|
80
100
|
process.exit();
|
|
81
101
|
};
|
|
102
|
+
const stampVersion = (t) => {
|
|
103
|
+
if (t.VERSION && !t.SNG_TITLE.includes(t.VERSION)) {
|
|
104
|
+
t.SNG_TITLE += ' ' + t.VERSION;
|
|
105
|
+
}
|
|
106
|
+
return t;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Run an advanced (public-REST) track search, let the user pick, and hydrate the
|
|
110
|
+
* picks into download-ready gw tracks via `getTrackInfo`. Returns `null` when
|
|
111
|
+
* nothing was matched or selected.
|
|
112
|
+
*/
|
|
113
|
+
const resolveAdvancedSearch = async (filters) => {
|
|
114
|
+
const limit = toFiniteNumber(options.searchLimit) ?? 50;
|
|
115
|
+
const { data: results, query, usedFallback } = await (0, search_1.searchAdvancedTracks)(filters, { limit });
|
|
116
|
+
if (!query) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
console.log(signale_1.default.info(`Searching Deezer for: ${chalk_1.default.cyan(query)}`));
|
|
120
|
+
if (usedFallback) {
|
|
121
|
+
console.log(signale_1.default.note('No matches for the operators — retried as a plain-text search.'));
|
|
122
|
+
}
|
|
123
|
+
if (!results.length) {
|
|
124
|
+
console.log(signale_1.default.warn('No tracks matched that search.'));
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
let picks = results;
|
|
128
|
+
if (!options.headless) {
|
|
129
|
+
const choice = await (0, prompts_1.default)([
|
|
130
|
+
{
|
|
131
|
+
type: 'multiselect',
|
|
132
|
+
name: 'items',
|
|
133
|
+
message: `Select tracks to download. ${results.length} matched.`,
|
|
134
|
+
choices: results.map((r) => ({
|
|
135
|
+
title: `${r.title} — ${r.artist?.name ?? 'Unknown'}`,
|
|
136
|
+
value: r,
|
|
137
|
+
description: `Album: ${r.album?.title ?? 'Unknown'} · ${(0, util_1.formatSecondsReadable)(r.duration)}${r.explicit_lyrics ? ' · explicit' : ''}`,
|
|
138
|
+
})),
|
|
139
|
+
},
|
|
140
|
+
], { onCancel });
|
|
141
|
+
picks = choice.items ?? [];
|
|
142
|
+
}
|
|
143
|
+
if (!picks.length) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
console.log(signale_1.default.info(`Fetching track data for ${picks.length} ${picks.length === 1 ? 'track' : 'tracks'}…`));
|
|
147
|
+
const hydrated = await Promise.all(picks.map((r) => (0, gerdur_core_1.getTrackInfo)(String(r.id)).catch(() => null)));
|
|
148
|
+
const tracks = hydrated.filter((t) => Boolean(t && t.SNG_ID)).map(stampVersion);
|
|
149
|
+
if (!tracks.length) {
|
|
150
|
+
console.log(signale_1.default.warn('None of the selected tracks could be resolved.'));
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return { info: { type: 'track', id: query }, linktype: 'track', linkinfo: {}, tracks };
|
|
154
|
+
};
|
|
82
155
|
const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
83
156
|
try {
|
|
157
|
+
url = url ?? '';
|
|
84
158
|
if (!options.quality) {
|
|
85
159
|
const { musicQuality } = await (0, prompts_1.default)([
|
|
86
160
|
{
|
|
@@ -97,19 +171,57 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
97
171
|
], { onCancel });
|
|
98
172
|
options.quality = musicQuality;
|
|
99
173
|
}
|
|
100
|
-
|
|
174
|
+
let searchData = null;
|
|
175
|
+
// `--search` / `--artist` / `--bpm-min` … flags: structured search, no URL needed.
|
|
176
|
+
// Consumed once — the interactive re-prompt loop must not re-run it.
|
|
177
|
+
if (!url && advancedFilters && !advancedFiltersConsumed) {
|
|
178
|
+
advancedFiltersConsumed = true;
|
|
179
|
+
searchData = await resolveAdvancedSearch(advancedFilters);
|
|
180
|
+
if (!searchData) {
|
|
181
|
+
if (options.headless) {
|
|
182
|
+
throw new Error('No tracks matched the search filters.');
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (!searchData && !url) {
|
|
101
188
|
const { query } = await (0, prompts_1.default)([
|
|
102
189
|
{
|
|
103
190
|
type: 'text',
|
|
104
191
|
name: 'query',
|
|
105
|
-
message: 'Enter URL or search:',
|
|
192
|
+
message: 'Enter a URL, a search term, or a `search:` / `isrc:` / `upc:` query:',
|
|
106
193
|
validate: (value) => (value ? true : false),
|
|
107
194
|
},
|
|
108
195
|
], { onCancel });
|
|
109
196
|
url = query;
|
|
110
197
|
}
|
|
111
|
-
|
|
112
|
-
if (!url.
|
|
198
|
+
// `search:` prefix — advanced track search from the prompt or `-u search:…`.
|
|
199
|
+
if (!searchData && url && url.startsWith('search:')) {
|
|
200
|
+
searchData = await resolveAdvancedSearch({ query: url.slice('search:'.length).trim() });
|
|
201
|
+
if (!searchData) {
|
|
202
|
+
throw new Error('No tracks matched that search.');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// `isrc:` / `upc:` — resolve a barcode to a Deezer track / album (works headless).
|
|
206
|
+
if (!searchData && url && url.startsWith('isrc:')) {
|
|
207
|
+
const code = url.slice('isrc:'.length).trim();
|
|
208
|
+
const found = await (0, gerdur_core_1.getTrackByISRC)(code).catch(() => null);
|
|
209
|
+
if (!found) {
|
|
210
|
+
throw new Error(`No Deezer track for ISRC ${code}`);
|
|
211
|
+
}
|
|
212
|
+
const track = await (0, gerdur_core_1.getTrackInfo)(String(found.id));
|
|
213
|
+
searchData = { info: { type: 'track', id: code }, linktype: 'track', linkinfo: {}, tracks: [stampVersion(track)] };
|
|
214
|
+
}
|
|
215
|
+
if (!searchData && url && url.startsWith('upc:')) {
|
|
216
|
+
const code = url.slice('upc:'.length).trim();
|
|
217
|
+
const found = await (0, gerdur_core_1.getAlbumByUPC)(code).catch(() => null);
|
|
218
|
+
if (!found) {
|
|
219
|
+
throw new Error(`No Deezer album for UPC ${code}`);
|
|
220
|
+
}
|
|
221
|
+
console.log(signale_1.default.info(`UPC ${code} → ${found.title} (${found.nb_tracks} tracks)`));
|
|
222
|
+
url = `https://www.deezer.com/album/${found.id}`;
|
|
223
|
+
}
|
|
224
|
+
if (!searchData && !url.match(urlRegex)) {
|
|
113
225
|
if (options.headless) {
|
|
114
226
|
throw new Error('Please provide a valid URL. Unknown URL: ' + url);
|
|
115
227
|
}
|
|
@@ -168,12 +280,7 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
168
280
|
info: { type: 'track', id: url },
|
|
169
281
|
linktype: 'track',
|
|
170
282
|
linkinfo: {},
|
|
171
|
-
tracks: TRACK.data.map(
|
|
172
|
-
if (t.VERSION && !t.SNG_TITLE.includes(t.VERSION)) {
|
|
173
|
-
t.SNG_TITLE += ' ' + t.VERSION;
|
|
174
|
-
}
|
|
175
|
-
return t;
|
|
176
|
-
}),
|
|
283
|
+
tracks: TRACK.data.map(stampVersion),
|
|
177
284
|
};
|
|
178
285
|
}
|
|
179
286
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -59,6 +59,6 @@ export type { SessionOptions, DownloadTracksOptions, SearchType } from './lib/se
|
|
|
59
59
|
export { getTrackBuffer, getTaggedTrack, downloadTrackToFile } from './lib/api-download';
|
|
60
60
|
export type { Quality, GetTrackBufferOptions, DownloadTrackOptions, DownloadResult } from './lib/api-download';
|
|
61
61
|
export { default as Config, globalConfigPath, resolveConfigFile } from './lib/config';
|
|
62
|
-
export { initDeezerApi, parseInfo, searchMusic, searchPublicApi, searchTracks, searchAlbums, searchArtists, searchPlaylists, buildAdvancedQuery, suggest, getUser, getTrackInfo, getAlbumInfo, getAlbumTracks, getPlaylistInfo, getPlaylistTracks, getArtistInfo, getDiscography, getLyrics, getTrackDownloadUrl, resolveDownloadUrls, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, } from 'gerdur-core';
|
|
62
|
+
export { initDeezerApi, parseInfo, searchMusic, searchPublicApi, searchTracks, searchAlbums, searchArtists, searchPlaylists, buildAdvancedQuery, suggest, getGenres, getChart, getChartTracks, getGenreArtists, getEditorialList, getEditorialReleases, getEditorialSelection, getEditorialCharts, getArtistTopTracks, getRelatedArtists, getArtistAlbums, getArtistPlaylists, getArtistRadioTracks, getTrackByISRC, getAlbumByUPC, getUser, getTrackInfo, getAlbumInfo, getAlbumTracks, getPlaylistInfo, getPlaylistTracks, getArtistInfo, getDiscography, getLyrics, getTrackDownloadUrl, resolveDownloadUrls, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, } from 'gerdur-core';
|
|
63
63
|
export type { AddTrackTagsOptions, TaggedTrack, TrackTagModel, RichAlbum, ResolvedUrl, NormalizedContributors, } from 'gerdur-core';
|
|
64
|
-
export type { advancedSearchFilters, searchOrder, searchEntity, publicApiSearchOptions, publicApiSearchResponse, searchResultTrack, searchResultAlbum, searchResultArtist, searchResultPlaylist, suggestResult, } from 'gerdur-core/types';
|
|
64
|
+
export type { advancedSearchFilters, searchOrder, searchEntity, publicApiSearchOptions, publicApiSearchResponse, searchResultTrack, searchResultAlbum, searchResultArtist, searchResultPlaylist, suggestResult, publicApiList, chartType, chartTrack, chartAlbum, chartArtist, chartPlaylist, chartPodcast, genreType, editorialType, artistAlbumResult, } from 'gerdur-core/types';
|
package/dist/src/index.js
CHANGED
|
@@ -33,7 +33,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
33
33
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
34
34
|
};
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.normalizeContributors = exports.getRichAlbum = exports.addTrackTags = exports.resolveDownloadUrls = exports.getTrackDownloadUrl = exports.getLyrics = exports.getDiscography = exports.getArtistInfo = exports.getPlaylistTracks = exports.getPlaylistInfo = exports.getAlbumTracks = exports.getAlbumInfo = exports.getTrackInfo = exports.getUser = exports.getAlbumByUPC = exports.getTrackByISRC = exports.getArtistRadioTracks = exports.getArtistPlaylists = exports.getArtistAlbums = exports.getRelatedArtists = exports.getArtistTopTracks = exports.getEditorialCharts = exports.getEditorialSelection = exports.getEditorialReleases = exports.getEditorialList = exports.getGenreArtists = exports.getChartTracks = exports.getChart = exports.getGenres = exports.suggest = exports.buildAdvancedQuery = exports.searchPlaylists = exports.searchArtists = exports.searchAlbums = exports.searchTracks = exports.searchPublicApi = exports.searchMusic = exports.parseInfo = exports.initDeezerApi = exports.resolveConfigFile = exports.globalConfigPath = exports.Config = exports.downloadTrackToFile = exports.getTaggedTrack = exports.getTrackBuffer = exports.Session = exports.createSession = exports.getArl = exports.LoginError = exports.loginWithEmail = void 0;
|
|
37
|
+
exports.GeoBlocked = exports.toLrc = void 0;
|
|
37
38
|
// ─── Authentication ────────────────────────────────────────────────────────
|
|
38
39
|
const email_login_1 = require("./lib/email-login");
|
|
39
40
|
Object.defineProperty(exports, "loginWithEmail", { enumerable: true, get: function () { return email_login_1.loginWithEmail; } });
|
|
@@ -96,6 +97,21 @@ Object.defineProperty(exports, "searchArtists", { enumerable: true, get: functio
|
|
|
96
97
|
Object.defineProperty(exports, "searchPlaylists", { enumerable: true, get: function () { return gerdur_core_1.searchPlaylists; } });
|
|
97
98
|
Object.defineProperty(exports, "buildAdvancedQuery", { enumerable: true, get: function () { return gerdur_core_1.buildAdvancedQuery; } });
|
|
98
99
|
Object.defineProperty(exports, "suggest", { enumerable: true, get: function () { return gerdur_core_1.suggest; } });
|
|
100
|
+
Object.defineProperty(exports, "getGenres", { enumerable: true, get: function () { return gerdur_core_1.getGenres; } });
|
|
101
|
+
Object.defineProperty(exports, "getChart", { enumerable: true, get: function () { return gerdur_core_1.getChart; } });
|
|
102
|
+
Object.defineProperty(exports, "getChartTracks", { enumerable: true, get: function () { return gerdur_core_1.getChartTracks; } });
|
|
103
|
+
Object.defineProperty(exports, "getGenreArtists", { enumerable: true, get: function () { return gerdur_core_1.getGenreArtists; } });
|
|
104
|
+
Object.defineProperty(exports, "getEditorialList", { enumerable: true, get: function () { return gerdur_core_1.getEditorialList; } });
|
|
105
|
+
Object.defineProperty(exports, "getEditorialReleases", { enumerable: true, get: function () { return gerdur_core_1.getEditorialReleases; } });
|
|
106
|
+
Object.defineProperty(exports, "getEditorialSelection", { enumerable: true, get: function () { return gerdur_core_1.getEditorialSelection; } });
|
|
107
|
+
Object.defineProperty(exports, "getEditorialCharts", { enumerable: true, get: function () { return gerdur_core_1.getEditorialCharts; } });
|
|
108
|
+
Object.defineProperty(exports, "getArtistTopTracks", { enumerable: true, get: function () { return gerdur_core_1.getArtistTopTracks; } });
|
|
109
|
+
Object.defineProperty(exports, "getRelatedArtists", { enumerable: true, get: function () { return gerdur_core_1.getRelatedArtists; } });
|
|
110
|
+
Object.defineProperty(exports, "getArtistAlbums", { enumerable: true, get: function () { return gerdur_core_1.getArtistAlbums; } });
|
|
111
|
+
Object.defineProperty(exports, "getArtistPlaylists", { enumerable: true, get: function () { return gerdur_core_1.getArtistPlaylists; } });
|
|
112
|
+
Object.defineProperty(exports, "getArtistRadioTracks", { enumerable: true, get: function () { return gerdur_core_1.getArtistRadioTracks; } });
|
|
113
|
+
Object.defineProperty(exports, "getTrackByISRC", { enumerable: true, get: function () { return gerdur_core_1.getTrackByISRC; } });
|
|
114
|
+
Object.defineProperty(exports, "getAlbumByUPC", { enumerable: true, get: function () { return gerdur_core_1.getAlbumByUPC; } });
|
|
99
115
|
Object.defineProperty(exports, "getUser", { enumerable: true, get: function () { return gerdur_core_1.getUser; } });
|
|
100
116
|
Object.defineProperty(exports, "getTrackInfo", { enumerable: true, get: function () { return gerdur_core_1.getTrackInfo; } });
|
|
101
117
|
Object.defineProperty(exports, "getAlbumInfo", { enumerable: true, get: function () { return gerdur_core_1.getAlbumInfo; } });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { advancedSearchFilters, publicApiSearchOptions, publicApiSearchResponse, searchResultTrack } from 'gerdur-core/types';
|
|
2
|
+
/** The raw `--search` / `--artist` / `--bpm-min` … values as `commander` parses them (strings). */
|
|
3
|
+
export interface SearchFlags {
|
|
4
|
+
search?: string;
|
|
5
|
+
artist?: string;
|
|
6
|
+
album?: string;
|
|
7
|
+
track?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
bpmMin?: string | number;
|
|
10
|
+
bpmMax?: string | number;
|
|
11
|
+
durMin?: string | number;
|
|
12
|
+
durMax?: string | number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Fold the CLI search flags into a {@link advancedSearchFilters} bag for
|
|
16
|
+
* `buildAdvancedQuery` / `searchTracks`. Returns `null` when none were supplied,
|
|
17
|
+
* so the caller can tell "no search requested" from "empty search".
|
|
18
|
+
*/
|
|
19
|
+
export declare const advancedFiltersFromFlags: (flags: SearchFlags) => advancedSearchFilters | null;
|
|
20
|
+
/** The words in a filter bag, for a free-text retry when the operators come back empty. */
|
|
21
|
+
export declare const plainTextQuery: (filters: advancedSearchFilters) => string;
|
|
22
|
+
export interface AdvancedSearchOptions extends Omit<publicApiSearchOptions, 'type'> {
|
|
23
|
+
/**
|
|
24
|
+
* Deezer's advanced operators are unreliable. When they return nothing, retry
|
|
25
|
+
* with the same words as a plain free-text query. Default `true`.
|
|
26
|
+
*/
|
|
27
|
+
fallback?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface AdvancedSearchResult extends publicApiSearchResponse<searchResultTrack> {
|
|
30
|
+
/** the advanced-operator query that was built */
|
|
31
|
+
query: string;
|
|
32
|
+
/** whether the free-text fallback was used */
|
|
33
|
+
usedFallback: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `buildAdvancedQuery` + `searchTracks`, with an automatic free-text retry when
|
|
37
|
+
* the operators match nothing (unless `fallback: false`).
|
|
38
|
+
*/
|
|
39
|
+
export declare const searchAdvancedTracks: (filters: advancedSearchFilters, options?: AdvancedSearchOptions) => Promise<AdvancedSearchResult>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchAdvancedTracks = exports.plainTextQuery = exports.advancedFiltersFromFlags = void 0;
|
|
4
|
+
const gerdur_core_1 = require("gerdur-core");
|
|
5
|
+
const toFiniteNumber = (value) => {
|
|
6
|
+
if (value === undefined || value === null || value === '') {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const n = Number(value);
|
|
10
|
+
return Number.isFinite(n) ? n : undefined;
|
|
11
|
+
};
|
|
12
|
+
const trimmed = (value) => {
|
|
13
|
+
if (typeof value !== 'string') {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const s = value.trim();
|
|
17
|
+
return s ? s : undefined;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Fold the CLI search flags into a {@link advancedSearchFilters} bag for
|
|
21
|
+
* `buildAdvancedQuery` / `searchTracks`. Returns `null` when none were supplied,
|
|
22
|
+
* so the caller can tell "no search requested" from "empty search".
|
|
23
|
+
*/
|
|
24
|
+
const advancedFiltersFromFlags = (flags) => {
|
|
25
|
+
const filters = {
|
|
26
|
+
query: trimmed(flags.search),
|
|
27
|
+
artist: trimmed(flags.artist),
|
|
28
|
+
album: trimmed(flags.album),
|
|
29
|
+
track: trimmed(flags.track),
|
|
30
|
+
label: trimmed(flags.label),
|
|
31
|
+
durMin: toFiniteNumber(flags.durMin),
|
|
32
|
+
durMax: toFiniteNumber(flags.durMax),
|
|
33
|
+
bpmMin: toFiniteNumber(flags.bpmMin),
|
|
34
|
+
bpmMax: toFiniteNumber(flags.bpmMax),
|
|
35
|
+
};
|
|
36
|
+
return Object.values(filters).some((v) => v !== undefined) ? filters : null;
|
|
37
|
+
};
|
|
38
|
+
exports.advancedFiltersFromFlags = advancedFiltersFromFlags;
|
|
39
|
+
/** The words in a filter bag, for a free-text retry when the operators come back empty. */
|
|
40
|
+
const plainTextQuery = (filters) => [filters.query, filters.artist, filters.album, filters.track, filters.label]
|
|
41
|
+
.filter((v) => Boolean(v))
|
|
42
|
+
.join(' ')
|
|
43
|
+
.trim();
|
|
44
|
+
exports.plainTextQuery = plainTextQuery;
|
|
45
|
+
/**
|
|
46
|
+
* `buildAdvancedQuery` + `searchTracks`, with an automatic free-text retry when
|
|
47
|
+
* the operators match nothing (unless `fallback: false`).
|
|
48
|
+
*/
|
|
49
|
+
const searchAdvancedTracks = async (filters, options = {}) => {
|
|
50
|
+
const { fallback = true, ...searchOptions } = options;
|
|
51
|
+
const query = (0, gerdur_core_1.buildAdvancedQuery)(filters);
|
|
52
|
+
// `query` already contains every text field, so an empty query means nothing to search.
|
|
53
|
+
if (!query) {
|
|
54
|
+
return { data: [], total: 0, query, usedFallback: false };
|
|
55
|
+
}
|
|
56
|
+
let response = await (0, gerdur_core_1.searchTracks)(query, searchOptions);
|
|
57
|
+
let usedFallback = false;
|
|
58
|
+
if (!response.data.length && fallback) {
|
|
59
|
+
const plain = (0, exports.plainTextQuery)(filters);
|
|
60
|
+
if (plain && plain !== query) {
|
|
61
|
+
response = await (0, gerdur_core_1.searchTracks)(plain, searchOptions);
|
|
62
|
+
usedFallback = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { ...response, query, usedFallback };
|
|
66
|
+
};
|
|
67
|
+
exports.searchAdvancedTracks = searchAdvancedTracks;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { parseInfo, searchMusic } from 'gerdur-core';
|
|
3
|
+
import type { AdvancedSearchOptions } from './search';
|
|
3
4
|
import type { Quality, DownloadTrackOptions, DownloadResult } from './api-download';
|
|
4
|
-
import type { trackType, userType, advancedSearchFilters,
|
|
5
|
+
import type { trackType, userType, advancedSearchFilters, publicApiSearchResponse, searchResultTrack, suggestResult, chartType, genreType, editorialType, artistAlbumResult, searchResultArtist, publicApiList, trackTypePublicApi, albumTypePublicApi } from 'gerdur-core/types';
|
|
5
6
|
/** Search result categories accepted by {@link Session.search}. */
|
|
6
7
|
export type SearchType = 'ALBUM' | 'ARTIST' | 'TRACK' | 'PLAYLIST' | 'RADIO' | 'SHOW' | 'USER' | 'LIVESTREAM' | 'CHANNEL';
|
|
7
8
|
export interface SessionOptions {
|
|
@@ -58,11 +59,32 @@ export declare class Session {
|
|
|
58
59
|
* public-API track objects (with `isrc`, `preview`); to download a result,
|
|
59
60
|
* fetch its gw track first with the re-exported `getTrackInfo(id)`.
|
|
60
61
|
*
|
|
61
|
-
* Deezer
|
|
62
|
+
* Deezer's advanced operators are unreliable, so an empty result is retried as
|
|
63
|
+
* a plain free-text query unless `options.fallback` is `false`.
|
|
62
64
|
*/
|
|
63
|
-
searchAdvanced(filters: advancedSearchFilters, options?:
|
|
65
|
+
searchAdvanced(filters: advancedSearchFilters, options?: AdvancedSearchOptions): Promise<publicApiSearchResponse<searchResultTrack>>;
|
|
64
66
|
/** `deezer.suggest` autocomplete — for "as you type" UIs. `nb` caps items per type. */
|
|
65
67
|
suggest(query: string, nb?: number): Promise<suggestResult>;
|
|
68
|
+
/** Deezer's genre list (`id` `0` = "All"). */
|
|
69
|
+
genres(): Promise<publicApiList<genreType>>;
|
|
70
|
+
/** The five ranked lists for a genre (`0` = all): `{tracks, albums, artists, playlists, podcasts}`. */
|
|
71
|
+
chart(genreId?: number | string, limit?: number): Promise<chartType>;
|
|
72
|
+
/** Just the track chart for a genre — a ready-to-download list. */
|
|
73
|
+
chartTracks(genreId?: number | string, limit?: number, index?: number): Promise<publicApiList<searchResultTrack>>;
|
|
74
|
+
/** Deezer's editorial sections. */
|
|
75
|
+
editorialSections(): Promise<publicApiList<editorialType>>;
|
|
76
|
+
/** An artist's most popular tracks (public-API shape). */
|
|
77
|
+
artistTopTracks(artistId: number | string, limit?: number): Promise<publicApiList<searchResultTrack>>;
|
|
78
|
+
/** Artists Deezer considers related / similar. */
|
|
79
|
+
relatedArtists(artistId: number | string, limit?: number): Promise<publicApiList<searchResultArtist>>;
|
|
80
|
+
/** An artist's discography (public-API album shape). */
|
|
81
|
+
artistAlbums(artistId: number | string, limit?: number, index?: number): Promise<publicApiList<artistAlbumResult>>;
|
|
82
|
+
/** A ready-made radio (track list) seeded from an artist. */
|
|
83
|
+
artistRadio(artistId: number | string): Promise<publicApiList<searchResultTrack>>;
|
|
84
|
+
/** Resolve an ISRC to the public-API track. Pass `.id` to `getTrackInfo` to download. */
|
|
85
|
+
trackByISRC(isrc: string): Promise<trackTypePublicApi>;
|
|
86
|
+
/** Resolve a UPC/EAN barcode to the public-API album (with its `tracks`). */
|
|
87
|
+
albumByUPC(upc: string): Promise<albumTypePublicApi>;
|
|
66
88
|
/** Return a fully tagged audio Buffer for a track (nothing written to disk). */
|
|
67
89
|
getTrackBuffer(track: trackType, quality?: Quality, options?: {}): Promise<Buffer | null>;
|
|
68
90
|
/** Download a single track to disk. */
|
package/dist/src/lib/session.js
CHANGED
|
@@ -8,6 +8,7 @@ const p_queue_1 = __importDefault(require("p-queue"));
|
|
|
8
8
|
const gerdur_core_1 = require("gerdur-core");
|
|
9
9
|
const email_login_1 = require("./email-login");
|
|
10
10
|
const api_download_1 = require("./api-download");
|
|
11
|
+
const search_1 = require("./search");
|
|
11
12
|
/**
|
|
12
13
|
* An authenticated Deezer session exposing high-level query and download
|
|
13
14
|
* helpers. Create one with {@link createSession}. All methods are silent (no
|
|
@@ -63,15 +64,58 @@ class Session {
|
|
|
63
64
|
* public-API track objects (with `isrc`, `preview`); to download a result,
|
|
64
65
|
* fetch its gw track first with the re-exported `getTrackInfo(id)`.
|
|
65
66
|
*
|
|
66
|
-
* Deezer
|
|
67
|
+
* Deezer's advanced operators are unreliable, so an empty result is retried as
|
|
68
|
+
* a plain free-text query unless `options.fallback` is `false`.
|
|
67
69
|
*/
|
|
68
|
-
searchAdvanced(filters, options = {}) {
|
|
69
|
-
|
|
70
|
+
async searchAdvanced(filters, options = {}) {
|
|
71
|
+
const { query: _query, usedFallback: _usedFallback, ...response } = await (0, search_1.searchAdvancedTracks)(filters, options);
|
|
72
|
+
return response;
|
|
70
73
|
}
|
|
71
74
|
/** `deezer.suggest` autocomplete — for "as you type" UIs. `nb` caps items per type. */
|
|
72
75
|
suggest(query, nb) {
|
|
73
76
|
return (0, gerdur_core_1.suggest)(query, nb);
|
|
74
77
|
}
|
|
78
|
+
// ─── Browse & discovery (public REST) ──────────────────────────────────────
|
|
79
|
+
/** Deezer's genre list (`id` `0` = "All"). */
|
|
80
|
+
genres() {
|
|
81
|
+
return (0, gerdur_core_1.getGenres)();
|
|
82
|
+
}
|
|
83
|
+
/** The five ranked lists for a genre (`0` = all): `{tracks, albums, artists, playlists, podcasts}`. */
|
|
84
|
+
chart(genreId = 0, limit = 10) {
|
|
85
|
+
return (0, gerdur_core_1.getChart)(genreId, limit);
|
|
86
|
+
}
|
|
87
|
+
/** Just the track chart for a genre — a ready-to-download list. */
|
|
88
|
+
chartTracks(genreId = 0, limit = 100, index = 0) {
|
|
89
|
+
return (0, gerdur_core_1.getChartTracks)(genreId, limit, index);
|
|
90
|
+
}
|
|
91
|
+
/** Deezer's editorial sections. */
|
|
92
|
+
editorialSections() {
|
|
93
|
+
return (0, gerdur_core_1.getEditorialList)();
|
|
94
|
+
}
|
|
95
|
+
/** An artist's most popular tracks (public-API shape). */
|
|
96
|
+
artistTopTracks(artistId, limit = 50) {
|
|
97
|
+
return (0, gerdur_core_1.getArtistTopTracks)(artistId, limit);
|
|
98
|
+
}
|
|
99
|
+
/** Artists Deezer considers related / similar. */
|
|
100
|
+
relatedArtists(artistId, limit = 20) {
|
|
101
|
+
return (0, gerdur_core_1.getRelatedArtists)(artistId, limit);
|
|
102
|
+
}
|
|
103
|
+
/** An artist's discography (public-API album shape). */
|
|
104
|
+
artistAlbums(artistId, limit = 50, index = 0) {
|
|
105
|
+
return (0, gerdur_core_1.getArtistAlbums)(artistId, limit, index);
|
|
106
|
+
}
|
|
107
|
+
/** A ready-made radio (track list) seeded from an artist. */
|
|
108
|
+
artistRadio(artistId) {
|
|
109
|
+
return (0, gerdur_core_1.getArtistRadioTracks)(artistId);
|
|
110
|
+
}
|
|
111
|
+
/** Resolve an ISRC to the public-API track. Pass `.id` to `getTrackInfo` to download. */
|
|
112
|
+
trackByISRC(isrc) {
|
|
113
|
+
return (0, gerdur_core_1.getTrackByISRC)(isrc);
|
|
114
|
+
}
|
|
115
|
+
/** Resolve a UPC/EAN barcode to the public-API album (with its `tracks`). */
|
|
116
|
+
albumByUPC(upc) {
|
|
117
|
+
return (0, gerdur_core_1.getAlbumByUPC)(upc);
|
|
118
|
+
}
|
|
75
119
|
/** Return a fully tagged audio Buffer for a track (nothing written to disk). */
|
|
76
120
|
getTrackBuffer(track, quality = '320', options = {}) {
|
|
77
121
|
return (0, api_download_1.getTrackBuffer)(track, quality, options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "The crucial streaming module for dabox systems.",
|
|
5
5
|
"author": "Christian",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"adm-zip": "^0.5.16",
|
|
49
49
|
"chalk": "^4.1.2",
|
|
50
50
|
"commander": "^9.5.0",
|
|
51
|
-
"gerdur-core": "^2.
|
|
51
|
+
"gerdur-core": "^2.2.0",
|
|
52
52
|
"dot-prop": "^6.0.1",
|
|
53
53
|
"got": "^11.8.6",
|
|
54
54
|
"gradient-string": "^2.0.2",
|