gerdur 2.7.0 → 2.8.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 +16 -0
- package/README.md +6 -3
- package/dist/package.json +2 -2
- package/dist/src/gerdur.js +39 -11
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +5 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.8.0 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`gerdur-core@^2.10.0`** — re-exports `refreshTrackTokens`, `getEpisode`,
|
|
8
|
+
`getShowEpisodes`.
|
|
9
|
+
- **CLI `artist-top:<id>`** — pick from an artist's most popular tracks.
|
|
10
|
+
- **CLI `episode:<id>`** — download a single podcast episode (plain MP3, no
|
|
11
|
+
`--quality` needed, saved to `Podcasts/`).
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Batch downloads (album / playlist / search selection) now **refresh stale
|
|
16
|
+
`TRACK_TOKEN`s** before resolving URLs — a long playlist no longer starts
|
|
17
|
+
failing partway through with opaque CDN 403s.
|
|
18
|
+
|
|
3
19
|
## 2.7.0 - 2026-08-31
|
|
4
20
|
|
|
5
21
|
### Added
|
package/README.md
CHANGED
|
@@ -163,6 +163,8 @@ When `gerdur` asks for a URL, a search term, or a prefixed query:
|
|
|
163
163
|
| `flow:2064440442` | pick tracks from another user's Flow |
|
|
164
164
|
| `radio:38305` | pick tracks from a radio's current playlist |
|
|
165
165
|
| `chart` / `chart:132` | pick tracks from this week's chart (optionally a genre) |
|
|
166
|
+
| `artist-top:27` | pick from an artist's most popular tracks |
|
|
167
|
+
| `episode:294961882` | download a single podcast episode (plain MP3, to `Podcasts/`) |
|
|
166
168
|
|
|
167
169
|
`isrc:` / `upc:` / `flow` / `radio:` also work in `--headless` mode (`-u flow`, …).
|
|
168
170
|
|
|
@@ -329,10 +331,11 @@ or `createSession`):
|
|
|
329
331
|
- **Flow / library** — `getUserFlow`, `getUserFavoriteTracks`,
|
|
330
332
|
`getUserFavoriteAlbums`, `getUserFavoriteArtists`, `getUserPlaylists`,
|
|
331
333
|
`getUserRadios`, `getUserChartTracks`, `getRadios`, `getRadioTracks`,
|
|
332
|
-
`getRadioGenres`
|
|
334
|
+
`getRadioGenres`, `getEpisode`, `getShowEpisodes`
|
|
333
335
|
- **Download** — `getTrackDownloadUrl`, `resolveDownloadUrls`,
|
|
334
|
-
`
|
|
335
|
-
`
|
|
336
|
+
`refreshTrackTokens`, `streamTrackDownload`, `downloadTrackBuffer`,
|
|
337
|
+
`createDecryptStream`, `getStream`, `getTrackPreview`, `downloadPreview`,
|
|
338
|
+
`formatName`, `toFormat`, `DEEZER_FORMATS`
|
|
336
339
|
- **Errors** — `DeezerError` (`code` / `keys` / `retryable`), `GeoBlocked`
|
|
337
340
|
- **Low-level sessions** — `createCoreSession(arl)` / `CoreSession` /
|
|
338
341
|
`defaultSession` from `gerdur-core`: an isolated client (its own `arl`, cache,
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.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.10.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
|
@@ -9,6 +9,7 @@ const fs_1 = require("fs");
|
|
|
9
9
|
const path_1 = require("path");
|
|
10
10
|
const commander_1 = require("commander");
|
|
11
11
|
const gradient_string_1 = __importDefault(require("gradient-string"));
|
|
12
|
+
const got_1 = __importDefault(require("got"));
|
|
12
13
|
const gerdur_core_1 = require("gerdur-core");
|
|
13
14
|
const prompts_1 = __importDefault(require("prompts"));
|
|
14
15
|
const log_update_1 = __importDefault(require("log-update"));
|
|
@@ -80,7 +81,8 @@ const toFiniteNumber = (value) => {
|
|
|
80
81
|
};
|
|
81
82
|
const advancedFilters = (0, search_1.advancedFiltersFromFlags)(options);
|
|
82
83
|
let advancedFiltersConsumed = false;
|
|
83
|
-
|
|
84
|
+
const isEpisodeUrl = String(options.url ?? '').startsWith('episode:');
|
|
85
|
+
if (options.headless && !options.quality && !options.preview && !isEpisodeUrl) {
|
|
84
86
|
console.error(signale_1.default.error('Missing parameters --quality'));
|
|
85
87
|
console.error(signale_1.default.note('Quality must be provided with headless mode'));
|
|
86
88
|
process.exit(1);
|
|
@@ -107,14 +109,9 @@ const stampVersion = (t) => {
|
|
|
107
109
|
return t;
|
|
108
110
|
};
|
|
109
111
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*/
|
|
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`.
|
|
112
|
+
* Given a list of public-API track objects (from search / flow / a radio / a
|
|
113
|
+
* chart), let the user pick (unless headless) and hydrate the picks into
|
|
114
|
+
* download-ready gw tracks via `getTrackInfo`.
|
|
118
115
|
*/
|
|
119
116
|
const pickAndHydrate = async (results, id) => {
|
|
120
117
|
if (!results.length) {
|
|
@@ -202,7 +199,7 @@ const downloadPreviews = async (tracks, info, template, totalTracks, trackNumber
|
|
|
202
199
|
const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
203
200
|
try {
|
|
204
201
|
url = url ?? '';
|
|
205
|
-
if (!options.quality && !options.preview) {
|
|
202
|
+
if (!options.quality && !options.preview && !url.startsWith('episode:')) {
|
|
206
203
|
const { musicQuality } = await (0, prompts_1.default)([
|
|
207
204
|
{
|
|
208
205
|
type: 'select',
|
|
@@ -236,7 +233,7 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
236
233
|
{
|
|
237
234
|
type: 'text',
|
|
238
235
|
name: 'query',
|
|
239
|
-
message: 'Enter a URL, a search term, or
|
|
236
|
+
message: 'Enter a URL, a search term, or a prefix (search: isrc: upc: flow radio: chart artist-top: episode:):',
|
|
240
237
|
validate: (value) => (value ? true : false),
|
|
241
238
|
},
|
|
242
239
|
], { onCancel });
|
|
@@ -298,6 +295,34 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
298
295
|
throw new Error('Chart returned no tracks.');
|
|
299
296
|
}
|
|
300
297
|
}
|
|
298
|
+
// `artist-top:<id>` — an artist's most popular tracks.
|
|
299
|
+
if (!searchData && url && url.startsWith('artist-top:')) {
|
|
300
|
+
const aid = url.slice('artist-top:'.length).trim();
|
|
301
|
+
console.log(signale_1.default.info(`Fetching top tracks for artist ${aid}…`));
|
|
302
|
+
const { data } = await (0, gerdur_core_1.getArtistTopTracks)(aid, toFiniteNumber(options.searchLimit) ?? 50);
|
|
303
|
+
searchData = await pickAndHydrate(data, `artist-top:${aid}`);
|
|
304
|
+
if (!searchData) {
|
|
305
|
+
throw new Error(`Artist ${aid} returned no top tracks.`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// `episode:<id>` — a podcast episode (a plain MP3: no licence, decryption or tagging).
|
|
309
|
+
if (!searchData && url && url.startsWith('episode:')) {
|
|
310
|
+
const eid = url.slice('episode:'.length).trim();
|
|
311
|
+
const episode = await (0, gerdur_core_1.getEpisode)(eid);
|
|
312
|
+
if (!episode.EPISODE_DIRECT_STREAM_URL) {
|
|
313
|
+
throw new Error(`Episode ${eid} has no direct stream.`);
|
|
314
|
+
}
|
|
315
|
+
const dir = options.output ? (0, path_1.dirname)(options.output) : 'Podcasts';
|
|
316
|
+
const dest = (0, path_1.join)(dir, (0, util_1.sanitizeFilename)(`${episode.SHOW_NAME} - ${episode.EPISODE_TITLE}`.slice(0, 180)) + '.mp3');
|
|
317
|
+
console.log(signale_1.default.info(`Downloading episode: ${episode.EPISODE_TITLE}`));
|
|
318
|
+
if (options.overwrite || !(0, fs_1.existsSync)(dest)) {
|
|
319
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
320
|
+
const { body } = await (0, got_1.default)(episode.EPISODE_DIRECT_STREAM_URL, { responseType: 'buffer' });
|
|
321
|
+
(0, fs_1.writeFileSync)(dest, body);
|
|
322
|
+
}
|
|
323
|
+
console.log(signale_1.default.success(`Saved ${dest}`));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
301
326
|
if (!searchData && !url.match(urlRegex)) {
|
|
302
327
|
if (options.headless) {
|
|
303
328
|
throw new Error('Please provide a valid URL. Unknown URL: ' + url);
|
|
@@ -423,6 +448,9 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
423
448
|
const prefetchedUrls = new Map();
|
|
424
449
|
if (data.tracks.length > 1 && !process.env.SIMULATE) {
|
|
425
450
|
try {
|
|
451
|
+
// TRACK_TOKENs expire ~1h — refresh any stale ones so a long download
|
|
452
|
+
// doesn't start failing partway through with opaque CDN 403s.
|
|
453
|
+
data.tracks = await (0, gerdur_core_1.refreshTrackTokens)(data.tracks);
|
|
426
454
|
const base = QUALITY_PREF[String(options.quality).toLowerCase()] ?? [3, 1];
|
|
427
455
|
const pref = fallbackQuality ? base : [base[0]];
|
|
428
456
|
const resolved = await (0, gerdur_core_1.resolveDownloadUrls)(data.tracks, pref);
|
package/dist/src/index.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export type { SessionUserData } from 'gerdur-core';
|
|
|
61
61
|
export { getTrackBuffer, getTaggedTrack, downloadTrackToFile } from './lib/api-download';
|
|
62
62
|
export type { Quality, GetTrackBufferOptions, DownloadTrackOptions, DownloadResult } from './lib/api-download';
|
|
63
63
|
export { default as Config, globalConfigPath, resolveConfigFile } from './lib/config';
|
|
64
|
-
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, downloadTrackBuffer, createDecryptStream, getStream, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, DeezerError, } from 'gerdur-core';
|
|
64
|
+
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, getEpisode, getShowEpisodes, refreshTrackTokens, getTrackByISRC, getAlbumByUPC, getTrackPreview, downloadPreview, formatName, toFormat, DEEZER_FORMATS, getUser, getTrackInfo, getAlbumInfo, getAlbumTracks, getPlaylistInfo, getPlaylistTracks, getArtistInfo, getDiscography, getLyrics, getTrackDownloadUrl, resolveDownloadUrls, streamTrackDownload, downloadTrackBuffer, createDecryptStream, getStream, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, DeezerError, } from 'gerdur-core';
|
|
65
65
|
export type { AddTrackTagsOptions, TaggedTrack, TrackTagModel, RichAlbum, ResolvedUrl, NormalizedContributors, } from 'gerdur-core';
|
|
66
66
|
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';
|
|
67
67
|
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.DeezerError = exports.GeoBlocked = exports.toLrc = exports.normalizeContributors = exports.getRichAlbum = exports.addTrackTags = exports.getStream = exports.createDecryptStream = exports.downloadTrackBuffer = 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 = exports.toFormat = exports.formatName = exports.downloadPreview = void 0;
|
|
36
|
+
exports.refreshTrackTokens = exports.getShowEpisodes = exports.getEpisode = 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.defaultSession = exports.CoreSession = exports.createCoreSession = 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.downloadTrackBuffer = 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 = exports.toFormat = exports.formatName = exports.downloadPreview = exports.getTrackPreview = exports.getAlbumByUPC = exports.getTrackByISRC = 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; } });
|
|
@@ -127,6 +127,9 @@ Object.defineProperty(exports, "getUserChartTracks", { enumerable: true, get: fu
|
|
|
127
127
|
Object.defineProperty(exports, "getRadios", { enumerable: true, get: function () { return gerdur_core_2.getRadios; } });
|
|
128
128
|
Object.defineProperty(exports, "getRadioTracks", { enumerable: true, get: function () { return gerdur_core_2.getRadioTracks; } });
|
|
129
129
|
Object.defineProperty(exports, "getRadioGenres", { enumerable: true, get: function () { return gerdur_core_2.getRadioGenres; } });
|
|
130
|
+
Object.defineProperty(exports, "getEpisode", { enumerable: true, get: function () { return gerdur_core_2.getEpisode; } });
|
|
131
|
+
Object.defineProperty(exports, "getShowEpisodes", { enumerable: true, get: function () { return gerdur_core_2.getShowEpisodes; } });
|
|
132
|
+
Object.defineProperty(exports, "refreshTrackTokens", { enumerable: true, get: function () { return gerdur_core_2.refreshTrackTokens; } });
|
|
130
133
|
Object.defineProperty(exports, "getTrackByISRC", { enumerable: true, get: function () { return gerdur_core_2.getTrackByISRC; } });
|
|
131
134
|
Object.defineProperty(exports, "getAlbumByUPC", { enumerable: true, get: function () { return gerdur_core_2.getAlbumByUPC; } });
|
|
132
135
|
Object.defineProperty(exports, "getTrackPreview", { enumerable: true, get: function () { return gerdur_core_2.getTrackPreview; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.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.10.0",
|
|
52
52
|
"dot-prop": "^6.0.1",
|
|
53
53
|
"got": "^11.8.6",
|
|
54
54
|
"gradient-string": "^2.0.2",
|