gerdur-core 2.9.0 → 2.10.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.10.0 - 2026-08-31
4
+
5
+ Leftovers — batch token refresh + podcast episodes. Additive.
6
+
7
+ ### Added
8
+
9
+ - **`refreshTrackTokens(tracks, {graceSeconds?, session?})`** — one
10
+ `song.getListData` request refreshes every `TRACK_TOKEN` that has expired (or
11
+ is about to). Run a long playlist through this before `resolveDownloadUrls` so
12
+ it doesn't die on stale tokens at track 40. Also `session.refreshTrackTokens`.
13
+ - **`getEpisode(episodeId)`** (`episode.getData`) and **`getShowEpisodes(showId,
14
+ nb?, start?)`** — podcast episodes; `EPISODE_DIRECT_STREAM_URL` is a plain MP3
15
+ (no licence, no decryption).
16
+
3
17
  ## 2.9.0 - 2026-08-31
4
18
 
5
19
  Phase 3.2 (cont.) — downloads are session-aware. Additive.
package/README.md CHANGED
@@ -321,6 +321,13 @@ const {data: loved} = await getUserFavoriteTracks(me.USER_ID);
321
321
  const {data: eighties} = await getRadioTracks(38305); // "The '80s"
322
322
  ```
323
323
 
324
+ ### Podcasts
325
+
326
+ | Method | |
327
+ | :--- | :--- |
328
+ | `getShowEpisodes(showId, nb = 25, start = 0)` | a page of a show's episodes, newest first |
329
+ | `getEpisode(episodeId)` | one episode — `EPISODE_DIRECT_STREAM_URL` is a plain MP3, no licence / decryption |
330
+
324
331
  ### `.getTrackDownloadUrl(track, quality);`
325
332
 
326
333
  | Parameters | Required | Type | Description |
@@ -331,6 +338,19 @@ const {data: eighties} = await getRadioTracks(38305); // "The '80s"
331
338
  Resolves `{trackUrl, isEncrypted, fileSize}`. `isEncrypted` now comes from the
332
339
  media API's `cipher` field (authoritative) rather than a URL guess.
333
340
 
341
+ ### `.refreshTrackTokens(tracks, options?);`
342
+
343
+ `TRACK_TOKEN`s live ~1 hour, so a token fetched at the start of a long playlist
344
+ download is dead by track 40 (surfacing as an opaque CDN 403). Run the selection
345
+ through this first — **one** `song.getListData` request refreshes every token
346
+ that has expired (or expires within `options.graceSeconds`, default 300).
347
+ Tracks with a still-valid token come back untouched. Also `session.refreshTrackTokens(tracks, graceSeconds?)`.
348
+
349
+ ```js
350
+ const fresh = await refreshTrackTokens(playlist.tracks);
351
+ const urls = await resolveDownloadUrls(fresh, [9, 3, 1]);
352
+ ```
353
+
334
354
  ### Formats
335
355
 
336
356
  Deezer's `get_url` understands more than `1 / 3 / 9`. `DEEZER_FORMATS` lists them
@@ -4,3 +4,4 @@ export * from './search';
4
4
  export * from './browse';
5
5
  export * from './preview';
6
6
  export * from './user';
7
+ export * from './podcast';
package/dist/api/index.js CHANGED
@@ -20,3 +20,4 @@ __exportStar(require("./search"), exports);
20
20
  __exportStar(require("./browse"), exports);
21
21
  __exportStar(require("./preview"), exports);
22
22
  __exportStar(require("./user"), exports);
23
+ __exportStar(require("./podcast"), exports);
@@ -0,0 +1,16 @@
1
+ import type { publicApiList, showEpisodeType } from '../types';
2
+ /**
3
+ * One podcast episode (`episode.getData`). Carries `EPISODE_DIRECT_STREAM_URL`
4
+ * (a plain MP3 — no licence, no decryption) plus `MD5_ORIGIN` / `FILESIZE_MP3_*`
5
+ * / `TRACK_TOKEN` for the licensed stream.
6
+ */
7
+ export declare const getEpisode: (episodeId: string) => Promise<showEpisodeType>;
8
+ /**
9
+ * A page of a show's episodes, newest first — a thin view over `getShowInfo`'s
10
+ * `EPISODES` block.
11
+ *
12
+ * @param showId `SHOW_ID`
13
+ * @param nb page size (default 25)
14
+ * @param start offset
15
+ */
16
+ export declare const getShowEpisodes: (showId: string, nb?: number, start?: number) => Promise<publicApiList<showEpisodeType>>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getShowEpisodes = exports.getEpisode = void 0;
4
+ const request_1 = require("./request");
5
+ const api_1 = require("./api");
6
+ /**
7
+ * One podcast episode (`episode.getData`). Carries `EPISODE_DIRECT_STREAM_URL`
8
+ * (a plain MP3 — no licence, no decryption) plus `MD5_ORIGIN` / `FILESIZE_MP3_*`
9
+ * / `TRACK_TOKEN` for the licensed stream.
10
+ */
11
+ const getEpisode = (episodeId) => (0, request_1.request)({ episode_id: episodeId }, 'episode.getData');
12
+ exports.getEpisode = getEpisode;
13
+ /**
14
+ * A page of a show's episodes, newest first — a thin view over `getShowInfo`'s
15
+ * `EPISODES` block.
16
+ *
17
+ * @param showId `SHOW_ID`
18
+ * @param nb page size (default 25)
19
+ * @param start offset
20
+ */
21
+ const getShowEpisodes = async (showId, nb = 25, start = 0) => {
22
+ const { EPISODES } = await (0, api_1.getShowInfo)(showId, nb, start);
23
+ return { data: EPISODES.data, total: EPISODES.total };
24
+ };
25
+ exports.getShowEpisodes = getShowEpisodes;
@@ -65,3 +65,21 @@ export interface ResolvedUrl {
65
65
  * @param session which account to resolve for — defaults to the process default
66
66
  */
67
67
  export declare const resolveDownloadUrls: (tracks: trackType[], qualities?: Quality[], session?: Session) => Promise<(ResolvedUrl | null)[]>;
68
+ /**
69
+ * Batch-refresh `TRACK_TOKEN`s that have (or are about to) expire — one
70
+ * `song.getListData` request for the lot, instead of a `getTrackInfo` per track.
71
+ * `TRACK_TOKEN`s live ~1 hour, so a token fetched at the start of a long
72
+ * playlist download is dead by track 40 and surfaces as an opaque CDN 403; run
73
+ * the selection through this first.
74
+ *
75
+ * Returns the input tracks with fresh `TRACK_TOKEN` / `TRACK_TOKEN_EXPIRE`
76
+ * merged in; tracks whose token is still comfortably valid are returned as-is.
77
+ *
78
+ * @param tracks from `getTrackInfo` / `getAlbumTracks` / `parseInfo`
79
+ * @param options `graceSeconds` — treat a token expiring within this window as
80
+ * stale (default 300); `session` — which account (default: process default)
81
+ */
82
+ export declare const refreshTrackTokens: (tracks: trackType[], options?: {
83
+ graceSeconds?: number;
84
+ session?: Session;
85
+ }) => Promise<trackType[]>;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.resolveDownloadUrls = exports.getTrackDownloadUrl = exports.formatName = exports.toFormat = exports.DEEZER_FORMATS = exports.ExpiredTrackToken = exports.GeoBlocked = exports.WrongLicense = void 0;
6
+ exports.refreshTrackTokens = exports.resolveDownloadUrls = exports.getTrackDownloadUrl = exports.formatName = exports.toFormat = exports.DEEZER_FORMATS = exports.ExpiredTrackToken = exports.GeoBlocked = exports.WrongLicense = void 0;
7
7
  const delay_1 = __importDefault(require("delay"));
8
8
  const decrypt_1 = require("../lib/decrypt");
9
9
  const errors_1 = require("../lib/errors");
@@ -265,3 +265,35 @@ const resolveDownloadUrls = async (tracks, qualities = [9, 3, 1], session = (0,
265
265
  });
266
266
  };
267
267
  exports.resolveDownloadUrls = resolveDownloadUrls;
268
+ /**
269
+ * Batch-refresh `TRACK_TOKEN`s that have (or are about to) expire — one
270
+ * `song.getListData` request for the lot, instead of a `getTrackInfo` per track.
271
+ * `TRACK_TOKEN`s live ~1 hour, so a token fetched at the start of a long
272
+ * playlist download is dead by track 40 and surfaces as an opaque CDN 403; run
273
+ * the selection through this first.
274
+ *
275
+ * Returns the input tracks with fresh `TRACK_TOKEN` / `TRACK_TOKEN_EXPIRE`
276
+ * merged in; tracks whose token is still comfortably valid are returned as-is.
277
+ *
278
+ * @param tracks from `getTrackInfo` / `getAlbumTracks` / `parseInfo`
279
+ * @param options `graceSeconds` — treat a token expiring within this window as
280
+ * stale (default 300); `session` — which account (default: process default)
281
+ */
282
+ const refreshTrackTokens = async (tracks, options = {}) => {
283
+ var _a, _b;
284
+ const session = (_a = options.session) !== null && _a !== void 0 ? _a : (0, session_1.defaultSession)();
285
+ const graceMs = ((_b = options.graceSeconds) !== null && _b !== void 0 ? _b : 300) * 1000;
286
+ const now = Date.now();
287
+ const staleIds = new Set(tracks
288
+ .filter((t) => !t.TRACK_TOKEN || !t.TRACK_TOKEN_EXPIRE || t.TRACK_TOKEN_EXPIRE * 1000 - now < graceMs)
289
+ .map((t) => t.SNG_ID));
290
+ if (!staleIds.size)
291
+ return tracks;
292
+ const { data } = await session.gw({ sng_ids: [...staleIds] }, 'song.getListData');
293
+ const fresh = new Map(data.map((d) => [d.SNG_ID, d]));
294
+ return tracks.map((track) => {
295
+ const f = fresh.get(track.SNG_ID);
296
+ return f ? { ...track, TRACK_TOKEN: f.TRACK_TOKEN, TRACK_TOKEN_EXPIRE: Number(f.TRACK_TOKEN_EXPIRE) } : track;
297
+ });
298
+ };
299
+ exports.refreshTrackTokens = refreshTrackTokens;
@@ -16,5 +16,7 @@ declare module './session' {
16
16
  streamTrack(track: trackType, quality: number, options?: Omit<StreamTrackOptions, 'session'>): Promise<TrackStream>;
17
17
  /** Download + decrypt a track fully into a `Buffer`, as this account. */
18
18
  getTrackBuffer(track: trackType, quality: number, options?: Omit<StreamTrackOptions, 'session' | 'resumeFrom'>): Promise<Buffer | null>;
19
+ /** Batch-refresh the `TRACK_TOKEN`s on these tracks (as this account) before a long download. */
20
+ refreshTrackTokens(tracks: trackType[], graceSeconds?: number): Promise<trackType[]>;
19
21
  }
20
22
  }
@@ -21,3 +21,6 @@ session_1.Session.prototype.streamTrack = function (track, quality, options = {}
21
21
  session_1.Session.prototype.getTrackBuffer = function (track, quality, options = {}) {
22
22
  return (0, stream_download_1.downloadTrackBuffer)(track, quality, { ...options, session: this });
23
23
  };
24
+ session_1.Session.prototype.refreshTrackTokens = function (tracks, graceSeconds) {
25
+ return (0, get_url_1.refreshTrackTokens)(tracks, { graceSeconds, session: this });
26
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gerdur-core",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "Core module for gerdur.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",