gerdur 2.4.0 → 2.6.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 +34 -0
- package/README.md +21 -5
- package/dist/package.json +2 -2
- package/dist/src/gerdur.js +42 -14
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +16 -2
- package/dist/src/lib/decrypt.d.ts +7 -2
- package/dist/src/lib/decrypt.js +15 -2
- package/dist/src/lib/download-track.js +4 -1
- package/dist/src/lib/session.d.ts +28 -2
- package/dist/src/lib/session.js +55 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.6.0 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`gerdur-core@^2.6.0`** — re-exports the Flow / radios / library surface:
|
|
8
|
+
`getUserFlow`, `getUserFavoriteTracks` / `Albums` / `Artists`,
|
|
9
|
+
`getUserPlaylists`, `getUserRadios`, `getUserChartTracks`, `getRadios`,
|
|
10
|
+
`getRadioTracks`, `getRadioGenres` + their types.
|
|
11
|
+
- **`Session` methods**: `flow`, `favoriteTracks`, `favoriteAlbums`,
|
|
12
|
+
`favoriteArtists`, `playlists`, `userRadios`, `radios`, `radioTracks` — the
|
|
13
|
+
`userId` argument defaults to the logged-in user.
|
|
14
|
+
- **CLI `flow` / `flow:<userId>` / `radio:<id>`** — pick tracks from your (or
|
|
15
|
+
another user's) Flow, or a radio's current playlist, and download them.
|
|
16
|
+
Headless-friendly.
|
|
17
|
+
|
|
18
|
+
## 2.5.0 - 2026-08-31
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **`gerdur-core@^2.5.0`.** Track decryption in the download pipeline is now
|
|
23
|
+
**streamed** to a temp file (`decryptFileToFile`) instead of read fully into
|
|
24
|
+
memory twice — peak memory during decrypt drops to ~one 2048-byte stripe.
|
|
25
|
+
- Gateway failures now surface as `DeezerError` (re-exported) with `code` /
|
|
26
|
+
`keys` / `retryable`, and gateway retries are bounded (no more infinite spin
|
|
27
|
+
on a persistently failing endpoint).
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Re-exports: `streamTrackDownload`, `createDecryptStream`, `getStream`,
|
|
32
|
+
`DeezerError` + the `StreamTrackOptions` / `TrackStream` / `StreamResponse` types.
|
|
33
|
+
- **`Session.streamTrack(track, quality?, options?)`** — download a track as a
|
|
34
|
+
constant-memory stream of decrypted audio (`{stream, size, startedAt}`),
|
|
35
|
+
with `onProgress` and `resumeFrom`.
|
|
36
|
+
|
|
3
37
|
## 2.4.0 - 2026-08-31
|
|
4
38
|
|
|
5
39
|
### Added
|
package/README.md
CHANGED
|
@@ -159,8 +159,11 @@ When `gerdur` asks for a URL, a search term, or a prefixed query:
|
|
|
159
159
|
| `search:artist:"daft punk" bpm_min:120` | advanced track search (see operators below) |
|
|
160
160
|
| `isrc:GBDUW0000059` | download the exact track for an ISRC |
|
|
161
161
|
| `upc:0724384960650` | download the album for a UPC / EAN barcode |
|
|
162
|
+
| `flow` | pick tracks from your Deezer **Flow** |
|
|
163
|
+
| `flow:2064440442` | pick tracks from another user's Flow |
|
|
164
|
+
| `radio:38305` | pick tracks from a radio's current playlist |
|
|
162
165
|
|
|
163
|
-
`isrc:`
|
|
166
|
+
`isrc:` / `upc:` / `flow` / `radio:` also work in `--headless` mode (`-u flow`, …).
|
|
164
167
|
|
|
165
168
|
### From flags (works headless)
|
|
166
169
|
|
|
@@ -258,13 +261,20 @@ Session methods:
|
|
|
258
261
|
- **browse** — `genres`, `chart`, `chartTracks`, `editorialSections`,
|
|
259
262
|
`artistTopTracks`, `relatedArtists`, `artistAlbums`, `artistRadio`,
|
|
260
263
|
`trackByISRC`, `albumByUPC`
|
|
261
|
-
- **
|
|
262
|
-
`
|
|
264
|
+
- **flow / library** — `flow`, `favoriteTracks`, `favoriteAlbums`,
|
|
265
|
+
`favoriteArtists`, `playlists`, `userRadios`, `radios`, `radioTracks`
|
|
266
|
+
(the `userId` arg defaults to the logged-in user)
|
|
267
|
+
- **user / download** — `getUser`, `getTrackBuffer`, `streamTrack`,
|
|
268
|
+
`downloadTrack`, `downloadTracks`, `downloadUrl`, `trackPreview`, `downloadPreview`
|
|
263
269
|
|
|
264
270
|
Every download call is silent; `downloadTracks` / `downloadUrl` accept
|
|
265
271
|
`concurrency` and an `onProgress` callback and return one `{path, written} | null`
|
|
266
272
|
per track.
|
|
267
273
|
|
|
274
|
+
`streamTrack(track, quality?, options?)` returns `{stream, size, startedAt,
|
|
275
|
+
isEncrypted}` — decrypted audio at constant memory, with `onProgress(got, total)`
|
|
276
|
+
and `resumeFrom` (bytes). Pipe `stream` to a file or your own tag muxer.
|
|
277
|
+
|
|
268
278
|
`searchAdvanced(filters, options?)` builds a Deezer advanced-search query from
|
|
269
279
|
`{query?, artist?, album?, track?, label?, durMin?, durMax?, bpmMin?, bpmMax?}`
|
|
270
280
|
and takes `{order?, strict?, limit?, index?, fallback?}`. Deezer's operators are
|
|
@@ -315,8 +325,14 @@ or `createSession`):
|
|
|
315
325
|
`getEditorialCharts`, `getArtistTopTracks`, `getRelatedArtists`,
|
|
316
326
|
`getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`,
|
|
317
327
|
`getTrackByISRC`, `getAlbumByUPC`
|
|
318
|
-
- **
|
|
319
|
-
`
|
|
328
|
+
- **Flow / library** — `getUserFlow`, `getUserFavoriteTracks`,
|
|
329
|
+
`getUserFavoriteAlbums`, `getUserFavoriteArtists`, `getUserPlaylists`,
|
|
330
|
+
`getUserRadios`, `getUserChartTracks`, `getRadios`, `getRadioTracks`,
|
|
331
|
+
`getRadioGenres`
|
|
332
|
+
- **Download** — `getTrackDownloadUrl`, `resolveDownloadUrls`,
|
|
333
|
+
`streamTrackDownload`, `createDecryptStream`, `getStream`, `getTrackPreview`,
|
|
334
|
+
`downloadPreview`, `formatName`, `toFormat`, `DEEZER_FORMATS`
|
|
335
|
+
- **Errors** — `DeezerError` (`code` / `keys` / `retryable`), `GeoBlocked`
|
|
320
336
|
|
|
321
337
|
### Auth & config helpers
|
|
322
338
|
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.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.6.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
|
@@ -111,18 +111,14 @@ const stampVersion = (t) => {
|
|
|
111
111
|
* picks into download-ready gw tracks via `getTrackInfo`. Returns `null` when
|
|
112
112
|
* nothing was matched or selected.
|
|
113
113
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
console.log(signale_1.default.info(`Searching Deezer for: ${chalk_1.default.cyan(query)}`));
|
|
121
|
-
if (usedFallback) {
|
|
122
|
-
console.log(signale_1.default.note('No matches for the operators — retried as a plain-text search.'));
|
|
123
|
-
}
|
|
114
|
+
/**
|
|
115
|
+
* Given a list of public-API track objects (from search / flow / a radio), let
|
|
116
|
+
* the user pick (unless headless) and hydrate the picks into download-ready gw
|
|
117
|
+
* tracks via `getTrackInfo`.
|
|
118
|
+
*/
|
|
119
|
+
const pickAndHydrate = async (results, id) => {
|
|
124
120
|
if (!results.length) {
|
|
125
|
-
console.log(signale_1.default.warn('
|
|
121
|
+
console.log(signale_1.default.warn('Nothing to download.'));
|
|
126
122
|
return null;
|
|
127
123
|
}
|
|
128
124
|
let picks = results;
|
|
@@ -131,7 +127,7 @@ const resolveAdvancedSearch = async (filters) => {
|
|
|
131
127
|
{
|
|
132
128
|
type: 'multiselect',
|
|
133
129
|
name: 'items',
|
|
134
|
-
message: `Select tracks to download. ${results.length}
|
|
130
|
+
message: `Select tracks to download. ${results.length} available.`,
|
|
135
131
|
choices: results.map((r) => ({
|
|
136
132
|
title: `${r.title} — ${r.artist?.name ?? 'Unknown'}`,
|
|
137
133
|
value: r,
|
|
@@ -151,7 +147,19 @@ const resolveAdvancedSearch = async (filters) => {
|
|
|
151
147
|
console.log(signale_1.default.warn('None of the selected tracks could be resolved.'));
|
|
152
148
|
return null;
|
|
153
149
|
}
|
|
154
|
-
return { info: { type: 'track', id
|
|
150
|
+
return { info: { type: 'track', id }, linktype: 'track', linkinfo: {}, tracks };
|
|
151
|
+
};
|
|
152
|
+
const resolveAdvancedSearch = async (filters) => {
|
|
153
|
+
const limit = toFiniteNumber(options.searchLimit) ?? 50;
|
|
154
|
+
const { data: results, query, usedFallback } = await (0, search_1.searchAdvancedTracks)(filters, { limit });
|
|
155
|
+
if (!query) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
console.log(signale_1.default.info(`Searching Deezer for: ${chalk_1.default.cyan(query)}`));
|
|
159
|
+
if (usedFallback) {
|
|
160
|
+
console.log(signale_1.default.note('No matches for the operators — retried as a plain-text search.'));
|
|
161
|
+
}
|
|
162
|
+
return pickAndHydrate(results, query);
|
|
155
163
|
};
|
|
156
164
|
/**
|
|
157
165
|
* `--preview`: fetch the 30-second clip for each selected track and write it as
|
|
@@ -228,7 +236,7 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
228
236
|
{
|
|
229
237
|
type: 'text',
|
|
230
238
|
name: 'query',
|
|
231
|
-
message: 'Enter a URL, a search term, or
|
|
239
|
+
message: 'Enter a URL, a search term, or `search:` / `isrc:` / `upc:` / `flow` / `radio:<id>`:',
|
|
232
240
|
validate: (value) => (value ? true : false),
|
|
233
241
|
},
|
|
234
242
|
], { onCancel });
|
|
@@ -260,6 +268,26 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
260
268
|
console.log(signale_1.default.info(`UPC ${code} → ${found.title} (${found.nb_tracks} tracks)`));
|
|
261
269
|
url = `https://www.deezer.com/album/${found.id}`;
|
|
262
270
|
}
|
|
271
|
+
// `flow:` / `flow:<userId>` — download from Deezer Flow.
|
|
272
|
+
if (!searchData && url && (url === 'flow' || url.startsWith('flow:'))) {
|
|
273
|
+
const uid = url.includes(':') ? url.slice('flow:'.length).trim() : (await (0, gerdur_core_1.getUser)()).USER_ID;
|
|
274
|
+
console.log(signale_1.default.info(`Fetching Flow for user ${uid}…`));
|
|
275
|
+
const { data } = await (0, gerdur_core_1.getUserFlow)(uid);
|
|
276
|
+
searchData = await pickAndHydrate(data, `flow:${uid}`);
|
|
277
|
+
if (!searchData) {
|
|
278
|
+
throw new Error('Flow returned no tracks (is the profile public?).');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// `radio:<id>` — download a radio's current tracklist.
|
|
282
|
+
if (!searchData && url && url.startsWith('radio:')) {
|
|
283
|
+
const rid = url.slice('radio:'.length).trim();
|
|
284
|
+
console.log(signale_1.default.info(`Fetching radio ${rid}…`));
|
|
285
|
+
const { data } = await (0, gerdur_core_1.getRadioTracks)(rid);
|
|
286
|
+
searchData = await pickAndHydrate(data, `radio:${rid}`);
|
|
287
|
+
if (!searchData) {
|
|
288
|
+
throw new Error(`Radio ${rid} returned no tracks.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
263
291
|
if (!searchData && !url.match(urlRegex)) {
|
|
264
292
|
if (options.headless) {
|
|
265
293
|
throw new Error('Please provide a valid URL. Unknown URL: ' + url);
|
package/dist/src/index.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ 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, getGenres, getChart, getChartTracks, getGenreArtists, getEditorialList, getEditorialReleases, getEditorialSelection, getEditorialCharts, getArtistTopTracks, getRelatedArtists, getArtistAlbums, getArtistPlaylists, getArtistRadioTracks, getTrackByISRC, getAlbumByUPC, getTrackPreview, downloadPreview, formatName, toFormat, DEEZER_FORMATS, 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, getUserFlow, getUserFavoriteTracks, getUserFavoriteAlbums, getUserFavoriteArtists, getUserPlaylists, getUserRadios, getUserChartTracks, getRadios, getRadioTracks, getRadioGenres, getTrackByISRC, getAlbumByUPC, getTrackPreview, downloadPreview, formatName, toFormat, DEEZER_FORMATS, getUser, getTrackInfo, getAlbumInfo, getAlbumTracks, getPlaylistInfo, getPlaylistTracks, getArtistInfo, getDiscography, getLyrics, getTrackDownloadUrl, resolveDownloadUrls, streamTrackDownload, createDecryptStream, getStream, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, DeezerError, } 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, publicApiList, chartType, chartTrack, chartAlbum, chartArtist, chartPlaylist, chartPodcast, genreType, editorialType, artistAlbumResult, } from 'gerdur-core/types';
|
|
65
|
-
export type { Quality as MediaFormat, DeezerFormat, TrackPreview } from 'gerdur-core';
|
|
64
|
+
export type { advancedSearchFilters, searchOrder, searchEntity, publicApiSearchOptions, publicApiSearchResponse, searchResultTrack, searchResultAlbum, searchResultArtist, searchResultPlaylist, suggestResult, publicApiList, chartType, chartTrack, chartAlbum, chartArtist, chartPlaylist, chartPodcast, genreType, editorialType, artistAlbumResult, userFavoriteTrack, userFavoriteAlbum, userFavoriteArtist, userPlaylistResult, radioResult, radioGenre, } from 'gerdur-core/types';
|
|
65
|
+
export type { Quality as MediaFormat, DeezerFormat, TrackPreview, StreamTrackOptions, TrackStream, StreamResponse, DeezerErrorPayload, } from 'gerdur-core';
|
package/dist/src/index.js
CHANGED
|
@@ -33,8 +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.
|
|
37
|
-
exports.GeoBlocked = exports.toLrc = exports.normalizeContributors = exports.getRichAlbum = exports.addTrackTags = exports.resolveDownloadUrls = exports.getTrackDownloadUrl = void 0;
|
|
36
|
+
exports.toFormat = exports.formatName = exports.downloadPreview = exports.getTrackPreview = exports.getAlbumByUPC = exports.getTrackByISRC = exports.getRadioGenres = exports.getRadioTracks = exports.getRadios = exports.getUserChartTracks = exports.getUserRadios = exports.getUserPlaylists = exports.getUserFavoriteArtists = exports.getUserFavoriteAlbums = exports.getUserFavoriteTracks = exports.getUserFlow = 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.DeezerError = exports.GeoBlocked = exports.toLrc = exports.normalizeContributors = exports.getRichAlbum = exports.addTrackTags = exports.getStream = exports.createDecryptStream = exports.streamTrackDownload = exports.resolveDownloadUrls = exports.getTrackDownloadUrl = exports.getLyrics = exports.getDiscography = exports.getArtistInfo = exports.getPlaylistTracks = exports.getPlaylistInfo = exports.getAlbumTracks = exports.getAlbumInfo = exports.getTrackInfo = exports.getUser = exports.DEEZER_FORMATS = void 0;
|
|
38
38
|
// ─── Authentication ────────────────────────────────────────────────────────
|
|
39
39
|
const email_login_1 = require("./lib/email-login");
|
|
40
40
|
Object.defineProperty(exports, "loginWithEmail", { enumerable: true, get: function () { return email_login_1.loginWithEmail; } });
|
|
@@ -110,6 +110,16 @@ Object.defineProperty(exports, "getRelatedArtists", { enumerable: true, get: fun
|
|
|
110
110
|
Object.defineProperty(exports, "getArtistAlbums", { enumerable: true, get: function () { return gerdur_core_1.getArtistAlbums; } });
|
|
111
111
|
Object.defineProperty(exports, "getArtistPlaylists", { enumerable: true, get: function () { return gerdur_core_1.getArtistPlaylists; } });
|
|
112
112
|
Object.defineProperty(exports, "getArtistRadioTracks", { enumerable: true, get: function () { return gerdur_core_1.getArtistRadioTracks; } });
|
|
113
|
+
Object.defineProperty(exports, "getUserFlow", { enumerable: true, get: function () { return gerdur_core_1.getUserFlow; } });
|
|
114
|
+
Object.defineProperty(exports, "getUserFavoriteTracks", { enumerable: true, get: function () { return gerdur_core_1.getUserFavoriteTracks; } });
|
|
115
|
+
Object.defineProperty(exports, "getUserFavoriteAlbums", { enumerable: true, get: function () { return gerdur_core_1.getUserFavoriteAlbums; } });
|
|
116
|
+
Object.defineProperty(exports, "getUserFavoriteArtists", { enumerable: true, get: function () { return gerdur_core_1.getUserFavoriteArtists; } });
|
|
117
|
+
Object.defineProperty(exports, "getUserPlaylists", { enumerable: true, get: function () { return gerdur_core_1.getUserPlaylists; } });
|
|
118
|
+
Object.defineProperty(exports, "getUserRadios", { enumerable: true, get: function () { return gerdur_core_1.getUserRadios; } });
|
|
119
|
+
Object.defineProperty(exports, "getUserChartTracks", { enumerable: true, get: function () { return gerdur_core_1.getUserChartTracks; } });
|
|
120
|
+
Object.defineProperty(exports, "getRadios", { enumerable: true, get: function () { return gerdur_core_1.getRadios; } });
|
|
121
|
+
Object.defineProperty(exports, "getRadioTracks", { enumerable: true, get: function () { return gerdur_core_1.getRadioTracks; } });
|
|
122
|
+
Object.defineProperty(exports, "getRadioGenres", { enumerable: true, get: function () { return gerdur_core_1.getRadioGenres; } });
|
|
113
123
|
Object.defineProperty(exports, "getTrackByISRC", { enumerable: true, get: function () { return gerdur_core_1.getTrackByISRC; } });
|
|
114
124
|
Object.defineProperty(exports, "getAlbumByUPC", { enumerable: true, get: function () { return gerdur_core_1.getAlbumByUPC; } });
|
|
115
125
|
Object.defineProperty(exports, "getTrackPreview", { enumerable: true, get: function () { return gerdur_core_1.getTrackPreview; } });
|
|
@@ -128,8 +138,12 @@ Object.defineProperty(exports, "getDiscography", { enumerable: true, get: functi
|
|
|
128
138
|
Object.defineProperty(exports, "getLyrics", { enumerable: true, get: function () { return gerdur_core_1.getLyrics; } });
|
|
129
139
|
Object.defineProperty(exports, "getTrackDownloadUrl", { enumerable: true, get: function () { return gerdur_core_1.getTrackDownloadUrl; } });
|
|
130
140
|
Object.defineProperty(exports, "resolveDownloadUrls", { enumerable: true, get: function () { return gerdur_core_1.resolveDownloadUrls; } });
|
|
141
|
+
Object.defineProperty(exports, "streamTrackDownload", { enumerable: true, get: function () { return gerdur_core_1.streamTrackDownload; } });
|
|
142
|
+
Object.defineProperty(exports, "createDecryptStream", { enumerable: true, get: function () { return gerdur_core_1.createDecryptStream; } });
|
|
143
|
+
Object.defineProperty(exports, "getStream", { enumerable: true, get: function () { return gerdur_core_1.getStream; } });
|
|
131
144
|
Object.defineProperty(exports, "addTrackTags", { enumerable: true, get: function () { return gerdur_core_1.addTrackTags; } });
|
|
132
145
|
Object.defineProperty(exports, "getRichAlbum", { enumerable: true, get: function () { return gerdur_core_1.getRichAlbum; } });
|
|
133
146
|
Object.defineProperty(exports, "normalizeContributors", { enumerable: true, get: function () { return gerdur_core_1.normalizeContributors; } });
|
|
134
147
|
Object.defineProperty(exports, "toLrc", { enumerable: true, get: function () { return gerdur_core_1.toLrc; } });
|
|
135
148
|
Object.defineProperty(exports, "GeoBlocked", { enumerable: true, get: function () { return gerdur_core_1.GeoBlocked; } });
|
|
149
|
+
Object.defineProperty(exports, "DeezerError", { enumerable: true, get: function () { return gerdur_core_1.DeezerError; } });
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
export { decryptDownload, TrackDecryptStream } from 'gerdur-core';
|
|
3
|
-
/** Decrypt a track that was streamed to a temp file. */
|
|
2
|
+
export { decryptDownload, createDecryptStream, TrackDecryptStream } from 'gerdur-core';
|
|
3
|
+
/** Decrypt a track that was streamed to a temp file (buffered — reads the file into memory). */
|
|
4
4
|
export declare const decryptDownloadFile: (tmpfile: string, trackId: string) => Buffer;
|
|
5
|
+
/**
|
|
6
|
+
* Decrypt `src` → `dest` as a stream — peak memory is ~one 2048-byte stripe,
|
|
7
|
+
* not the whole file twice. Use this over `decryptDownloadFile` for large FLACs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const decryptFileToFile: (src: string, dest: string, trackId: string) => Promise<void>;
|
package/dist/src/lib/decrypt.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.decryptDownloadFile = exports.TrackDecryptStream = exports.decryptDownload = void 0;
|
|
6
|
+
exports.decryptFileToFile = exports.decryptDownloadFile = exports.TrackDecryptStream = exports.createDecryptStream = exports.decryptDownload = void 0;
|
|
4
7
|
/**
|
|
5
8
|
* Track decryption. As of gerdur-core@1.0.2 the core `decryptDownload` is a
|
|
6
9
|
* correct, dependency-free Blowfish that works on every Node (it no longer
|
|
7
10
|
* touches OpenSSL's removed `bf-cbc`), at ~290 MiB/s — so there is nothing left
|
|
8
11
|
* to wrap or fall back to here.
|
|
9
12
|
*/
|
|
13
|
+
const stream_1 = __importDefault(require("stream"));
|
|
14
|
+
const util_1 = require("util");
|
|
10
15
|
const fs_1 = require("fs");
|
|
11
16
|
const gerdur_core_1 = require("gerdur-core");
|
|
12
17
|
var gerdur_core_2 = require("gerdur-core");
|
|
13
18
|
Object.defineProperty(exports, "decryptDownload", { enumerable: true, get: function () { return gerdur_core_2.decryptDownload; } });
|
|
19
|
+
Object.defineProperty(exports, "createDecryptStream", { enumerable: true, get: function () { return gerdur_core_2.createDecryptStream; } });
|
|
14
20
|
Object.defineProperty(exports, "TrackDecryptStream", { enumerable: true, get: function () { return gerdur_core_2.TrackDecryptStream; } });
|
|
15
|
-
|
|
21
|
+
const pipeline = (0, util_1.promisify)(stream_1.default.pipeline);
|
|
22
|
+
/** Decrypt a track that was streamed to a temp file (buffered — reads the file into memory). */
|
|
16
23
|
const decryptDownloadFile = (tmpfile, trackId) => (0, gerdur_core_1.decryptDownload)((0, fs_1.readFileSync)(tmpfile), trackId);
|
|
17
24
|
exports.decryptDownloadFile = decryptDownloadFile;
|
|
25
|
+
/**
|
|
26
|
+
* Decrypt `src` → `dest` as a stream — peak memory is ~one 2048-byte stripe,
|
|
27
|
+
* not the whole file twice. Use this over `decryptDownloadFile` for large FLACs.
|
|
28
|
+
*/
|
|
29
|
+
const decryptFileToFile = (src, dest, trackId) => pipeline((0, fs_1.createReadStream)(src), (0, gerdur_core_1.createDecryptStream)(trackId), (0, fs_1.createWriteStream)(dest));
|
|
30
|
+
exports.decryptFileToFile = decryptFileToFile;
|
|
@@ -145,7 +145,10 @@ const downloadTrack = async ({ track, quality, info, coverSizes, path, totalTrac
|
|
|
145
145
|
let outFile;
|
|
146
146
|
if (trackData.isEncrypted) {
|
|
147
147
|
(0, log_update_1.default)(signale_1.default.pending('Decrypting ' + track.SNG_TITLE + ' by ' + track.ART_NAME));
|
|
148
|
-
|
|
148
|
+
const decfile = tmpfile + '.dec';
|
|
149
|
+
await (0, decrypt_1.decryptFileToFile)(tmpfile, decfile, track.SNG_ID); // streamed — ~one stripe in memory
|
|
150
|
+
outFile = (0, fs_1.readFileSync)(decfile);
|
|
151
|
+
(0, fs_1.unlinkSync)(decfile);
|
|
149
152
|
}
|
|
150
153
|
else {
|
|
151
154
|
outFile = (0, fs_1.readFileSync)(tmpfile);
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { parseInfo, searchMusic } from 'gerdur-core';
|
|
3
3
|
import type { AdvancedSearchOptions } from './search';
|
|
4
4
|
import type { Quality, DownloadTrackOptions, DownloadResult } from './api-download';
|
|
5
|
-
import type { trackType, userType, advancedSearchFilters, publicApiSearchResponse, searchResultTrack, suggestResult, chartType, genreType, editorialType, artistAlbumResult, searchResultArtist, publicApiList, trackTypePublicApi, albumTypePublicApi } from 'gerdur-core/types';
|
|
6
|
-
import type { TrackPreview } from 'gerdur-core';
|
|
5
|
+
import type { trackType, userType, advancedSearchFilters, publicApiSearchResponse, searchResultTrack, suggestResult, chartType, genreType, editorialType, artistAlbumResult, searchResultArtist, publicApiList, trackTypePublicApi, albumTypePublicApi, userFavoriteTrack, userFavoriteAlbum, userFavoriteArtist, userPlaylistResult, radioResult } from 'gerdur-core/types';
|
|
6
|
+
import type { TrackPreview, StreamTrackOptions, TrackStream } from 'gerdur-core';
|
|
7
7
|
/** Search result categories accepted by {@link Session.search}. */
|
|
8
8
|
export type SearchType = 'ALBUM' | 'ARTIST' | 'TRACK' | 'PLAYLIST' | 'RADIO' | 'SHOW' | 'USER' | 'LIVESTREAM' | 'CHANNEL';
|
|
9
9
|
export interface SessionOptions {
|
|
@@ -39,7 +39,10 @@ export declare class Session {
|
|
|
39
39
|
/** The arl in use for this session. */
|
|
40
40
|
readonly arl: string;
|
|
41
41
|
private concurrency;
|
|
42
|
+
private userId?;
|
|
42
43
|
private constructor();
|
|
44
|
+
/** The logged-in user's numeric id (cached). Used by the no-arg `flow` / `favorite*` methods. */
|
|
45
|
+
private resolveUserId;
|
|
43
46
|
/**
|
|
44
47
|
* Log in and return a ready-to-use session. Accepts either an `arl` or
|
|
45
48
|
* `email`+`password` (which are exchanged for an arl via Deezer's OAuth flow).
|
|
@@ -86,12 +89,35 @@ export declare class Session {
|
|
|
86
89
|
trackByISRC(isrc: string): Promise<trackTypePublicApi>;
|
|
87
90
|
/** Resolve a UPC/EAN barcode to the public-API album (with its `tracks`). */
|
|
88
91
|
albumByUPC(upc: string): Promise<albumTypePublicApi>;
|
|
92
|
+
/** Deezer **Flow** — the endless personalised mix, as tracks. */
|
|
93
|
+
flow(userId?: string | number, limit?: number): Promise<publicApiList<searchResultTrack>>;
|
|
94
|
+
/** A user's favourite (loved) tracks, newest first (each with `time_add`). */
|
|
95
|
+
favoriteTracks(userId?: string | number, limit?: number): Promise<publicApiList<userFavoriteTrack>>;
|
|
96
|
+
/** A user's favourite albums. */
|
|
97
|
+
favoriteAlbums(userId?: string | number, limit?: number): Promise<publicApiList<userFavoriteAlbum>>;
|
|
98
|
+
/** A user's favourite artists. */
|
|
99
|
+
favoriteArtists(userId?: string | number, limit?: number): Promise<publicApiList<userFavoriteArtist>>;
|
|
100
|
+
/** A user's own + followed playlists. */
|
|
101
|
+
playlists(userId?: string | number, limit?: number): Promise<publicApiList<userPlaylistResult>>;
|
|
102
|
+
/** The radios a user has favourited. */
|
|
103
|
+
userRadios(userId?: string | number): Promise<publicApiList<radioResult>>;
|
|
104
|
+
/** Deezer's curated radio list. */
|
|
105
|
+
radios(): Promise<publicApiList<radioResult>>;
|
|
106
|
+
/** A radio's current tracklist — a ready-to-play (public-API) source. */
|
|
107
|
+
radioTracks(radioId: string | number): Promise<publicApiList<searchResultTrack>>;
|
|
89
108
|
/** The 30-second preview clip URL for a track (plain MP3 — no licence, no decryption). */
|
|
90
109
|
trackPreview(track: trackType | string | number): Promise<TrackPreview | null>;
|
|
91
110
|
/** Fetch a track's 30-second preview clip as a `Buffer` (plain MP3). */
|
|
92
111
|
downloadPreview(track: trackType | string | number): Promise<Buffer | null>;
|
|
93
112
|
/** Return a fully tagged audio Buffer for a track (nothing written to disk). */
|
|
94
113
|
getTrackBuffer(track: trackType, quality?: Quality, options?: {}): Promise<Buffer | null>;
|
|
114
|
+
/**
|
|
115
|
+
* Download a track as a **stream** of decrypted audio — constant memory,
|
|
116
|
+
* regardless of file size or concurrency. `{stream, size, startedAt,
|
|
117
|
+
* isEncrypted}`; pipe `stream` to a file or your own tag muxer.
|
|
118
|
+
* `options.onProgress(received, total)`, `options.resumeFrom` (bytes).
|
|
119
|
+
*/
|
|
120
|
+
streamTrack(track: trackType, quality?: 1 | 3 | 9, options?: StreamTrackOptions): Promise<TrackStream>;
|
|
95
121
|
/** Download a single track to disk. */
|
|
96
122
|
downloadTrack(track: trackType, quality?: Quality, options?: DownloadTrackOptions): Promise<DownloadResult | null>;
|
|
97
123
|
/**
|
package/dist/src/lib/session.js
CHANGED
|
@@ -18,10 +18,21 @@ class Session {
|
|
|
18
18
|
/** The arl in use for this session. */
|
|
19
19
|
arl;
|
|
20
20
|
concurrency;
|
|
21
|
+
userId;
|
|
21
22
|
constructor(arl, concurrency) {
|
|
22
23
|
this.arl = arl;
|
|
23
24
|
this.concurrency = concurrency;
|
|
24
25
|
}
|
|
26
|
+
/** The logged-in user's numeric id (cached). Used by the no-arg `flow` / `favorite*` methods. */
|
|
27
|
+
async resolveUserId(explicit) {
|
|
28
|
+
if (explicit !== undefined) {
|
|
29
|
+
return String(explicit);
|
|
30
|
+
}
|
|
31
|
+
if (!this.userId) {
|
|
32
|
+
this.userId = (await (0, gerdur_core_1.getUser)()).USER_ID;
|
|
33
|
+
}
|
|
34
|
+
return this.userId;
|
|
35
|
+
}
|
|
25
36
|
/**
|
|
26
37
|
* Log in and return a ready-to-use session. Accepts either an `arl` or
|
|
27
38
|
* `email`+`password` (which are exchanged for an arl via Deezer's OAuth flow).
|
|
@@ -116,6 +127,41 @@ class Session {
|
|
|
116
127
|
albumByUPC(upc) {
|
|
117
128
|
return (0, gerdur_core_1.getAlbumByUPC)(upc);
|
|
118
129
|
}
|
|
130
|
+
// ─── Flow, radios & library (public REST) ──────────────────────────────────
|
|
131
|
+
// `userId` defaults to the logged-in user (via `getUser`); their profile must
|
|
132
|
+
// be public for these to see it.
|
|
133
|
+
/** Deezer **Flow** — the endless personalised mix, as tracks. */
|
|
134
|
+
async flow(userId, limit) {
|
|
135
|
+
return (0, gerdur_core_1.getUserFlow)(await this.resolveUserId(userId), limit);
|
|
136
|
+
}
|
|
137
|
+
/** A user's favourite (loved) tracks, newest first (each with `time_add`). */
|
|
138
|
+
async favoriteTracks(userId, limit) {
|
|
139
|
+
return (0, gerdur_core_1.getUserFavoriteTracks)(await this.resolveUserId(userId), limit);
|
|
140
|
+
}
|
|
141
|
+
/** A user's favourite albums. */
|
|
142
|
+
async favoriteAlbums(userId, limit) {
|
|
143
|
+
return (0, gerdur_core_1.getUserFavoriteAlbums)(await this.resolveUserId(userId), limit);
|
|
144
|
+
}
|
|
145
|
+
/** A user's favourite artists. */
|
|
146
|
+
async favoriteArtists(userId, limit) {
|
|
147
|
+
return (0, gerdur_core_1.getUserFavoriteArtists)(await this.resolveUserId(userId), limit);
|
|
148
|
+
}
|
|
149
|
+
/** A user's own + followed playlists. */
|
|
150
|
+
async playlists(userId, limit) {
|
|
151
|
+
return (0, gerdur_core_1.getUserPlaylists)(await this.resolveUserId(userId), limit);
|
|
152
|
+
}
|
|
153
|
+
/** The radios a user has favourited. */
|
|
154
|
+
async userRadios(userId) {
|
|
155
|
+
return (0, gerdur_core_1.getUserRadios)(await this.resolveUserId(userId));
|
|
156
|
+
}
|
|
157
|
+
/** Deezer's curated radio list. */
|
|
158
|
+
radios() {
|
|
159
|
+
return (0, gerdur_core_1.getRadios)();
|
|
160
|
+
}
|
|
161
|
+
/** A radio's current tracklist — a ready-to-play (public-API) source. */
|
|
162
|
+
radioTracks(radioId) {
|
|
163
|
+
return (0, gerdur_core_1.getRadioTracks)(radioId);
|
|
164
|
+
}
|
|
119
165
|
/** The 30-second preview clip URL for a track (plain MP3 — no licence, no decryption). */
|
|
120
166
|
trackPreview(track) {
|
|
121
167
|
return (0, gerdur_core_1.getTrackPreview)(track);
|
|
@@ -128,6 +174,15 @@ class Session {
|
|
|
128
174
|
getTrackBuffer(track, quality = '320', options = {}) {
|
|
129
175
|
return (0, api_download_1.getTrackBuffer)(track, quality, options);
|
|
130
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Download a track as a **stream** of decrypted audio — constant memory,
|
|
179
|
+
* regardless of file size or concurrency. `{stream, size, startedAt,
|
|
180
|
+
* isEncrypted}`; pipe `stream` to a file or your own tag muxer.
|
|
181
|
+
* `options.onProgress(received, total)`, `options.resumeFrom` (bytes).
|
|
182
|
+
*/
|
|
183
|
+
streamTrack(track, quality = 3, options = {}) {
|
|
184
|
+
return (0, gerdur_core_1.streamTrackDownload)(track, quality, options);
|
|
185
|
+
}
|
|
131
186
|
/** Download a single track to disk. */
|
|
132
187
|
downloadTrack(track, quality = '320', options = {}) {
|
|
133
188
|
return (0, api_download_1.downloadTrackToFile)(track, quality, options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.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.6.0",
|
|
52
52
|
"dot-prop": "^6.0.1",
|
|
53
53
|
"got": "^11.8.6",
|
|
54
54
|
"gradient-string": "^2.0.2",
|