gerdur-core 2.5.0 → 2.7.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,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.7.0 - 2026-08-31
4
+
5
+ Phase 3.2 — session lifecycle. The scattered module-level state
6
+ (`request.ts`: `arl` / `sid` / `api_token`; `get-url.ts`: `license_token` /
7
+ `country` / streaming rights) is consolidated into one **`Session`** object.
8
+ Backwards compatible — `initDeezerApi` and every free function are unchanged.
9
+
10
+ ### Added
11
+
12
+ - **`Session`** — owns one account's `arl`, HTTP client (`sid` / `api_token`),
13
+ `license_token` / `country` / `canStreamLossless` / `canStreamHq`, the
14
+ bounded-retry request loop, and token refresh (`init`, `refreshApiToken`,
15
+ `loadUserData`).
16
+ - **`createSession(arl?)`** — an isolated session you hold and inspect, so
17
+ multiple accounts can be used concurrently.
18
+ - **`defaultSession()`** — the process-wide session `initDeezerApi` and the free
19
+ functions run against. `setDefaultSession(session)` swaps it.
20
+ - `SessionUserData` type; `DEFAULT_ARL` constant.
21
+
22
+ ### Changed
23
+
24
+ - **Proactive `license_token` refresh** — `loadUserData()` caches the account's
25
+ media-API credentials for 25 min and force-refreshes on a media 403, instead of
26
+ only re-fetching after a failed download. Fewer opaque CDN 403s on long
27
+ playlists.
28
+ - `RETRY_POLICY` now lives in `lib/session.ts` (still exported, same shape).
29
+
30
+ ## 2.6.0 - 2026-08-31
31
+
32
+ Phase 2.4 round 2 — Flow, radios, and a user's library. All public REST, all
33
+ memoised, all additive.
34
+
35
+ ### Added
36
+
37
+ - **Flow / library** (take a `userId`): `getUserFlow`, `getUserFavoriteTracks`
38
+ (with `time_add`), `getUserFavoriteAlbums`, `getUserFavoriteArtists`,
39
+ `getUserPlaylists`, `getUserRadios`, `getUserChartTracks`.
40
+ - **Radios**: `getRadios`, `getRadioTracks(radioId)`, `getRadioGenres`.
41
+ - New types: `userFavoriteTrack`, `userFavoriteAlbum`, `userFavoriteArtist`,
42
+ `userPlaylistResult`, `radioResult`, `radioGenre`.
43
+
44
+ ### Internal
45
+
46
+ - Tests run under `ts-node/register/transpile-only` (build still type-checks) —
47
+ the growing live-API suite was hitting the ts-node worker heap limit.
48
+ - Live-API tests skip cleanly on Deezer's `code: 4` "Quota limit exceeded"
49
+ instead of failing when the suite bursts the public API.
50
+
3
51
  ## 2.5.0 - 2026-08-31
4
52
 
5
53
  Phase 3.1 — streaming download primitives. Additive.
package/README.md CHANGED
@@ -97,6 +97,32 @@ Retries are bounded — `RETRY_POLICY` (exported) sets per-class attempt caps an
97
97
  `DeezerError` instead of spinning. `GeoBlocked`, `WrongLicense` and
98
98
  `ExpiredTrackToken` are still thrown as their own types from the download path.
99
99
 
100
+ ### Sessions
101
+
102
+ A **`Session`** owns one account's state — the `arl`, the HTTP client (`sid` /
103
+ `api_token`), and the account's `license_token` / `country` / streaming rights,
104
+ plus the bounded-retry loop and token refresh. This state used to be
105
+ module-level globals; bundling it means multiple accounts can coexist.
106
+
107
+ - **`initDeezerApi(arl)`** — (re)authenticate the **default** session. Every free
108
+ function (`getTrackInfo`, `searchMusic`, `getTrackDownloadUrl`, …) runs against
109
+ it. Unchanged: same signature, returns the gateway `SESSION` id.
110
+ - **`createSession(arl?)`** — an **isolated** session you hold and inspect:
111
+ `session.arl`, `session.sid`, and — after `await session.loadUserData()` —
112
+ `session.country`, `session.licenseToken`, `session.canStreamLossless`,
113
+ `session.canStreamHq`. `loadUserData()` caches for 25 min and refreshes on a
114
+ media-API 403.
115
+ - **`defaultSession()`** — the `Session` the free functions use.
116
+
117
+ ```js
118
+ await initDeezerApi(arl); // default session, as before
119
+ const track = await getTrackInfo('3135556');
120
+
121
+ const s = await createSession(otherArl); // a second account, isolated
122
+ await s.loadUserData();
123
+ console.log(s.country, s.canStreamLossless);
124
+ ```
125
+
100
126
  ### `.initDeezerApi(arl_cookie);`
101
127
 
102
128
  > It is recommended that you first init the app with this method using your arl cookie.
@@ -244,6 +270,31 @@ const track = await getTrackByISRC('USUM71311296'); // "Get Lucky"
244
270
  pass the `id` to `getTrackInfo` / `getAlbumTracks` (or use the converter's
245
271
  `isrc2deezer` / `upc2deezer`, which hydrate a gw track for you).
246
272
 
273
+ ### Flow, radios & a user's library
274
+
275
+ Public-profile data — pass a `userId` (`getUser().USER_ID`, a profile URL, or
276
+ `parseInfo`). A private library is only visible to that user's own session.
277
+
278
+ | Method | Returns |
279
+ | :--- | :--- |
280
+ | `getUserFlow(userId, limit = 40)` | **Flow** — the endless personalised mix, as tracks. |
281
+ | `getUserFavoriteTracks(userId, limit?, index?)` | loved tracks, newest first (each with `time_add`). |
282
+ | `getUserFavoriteAlbums(userId, limit?, index?)` | favourite albums. |
283
+ | `getUserFavoriteArtists(userId, limit?, index?)` | favourite artists. |
284
+ | `getUserPlaylists(userId, limit?, index?)` | the user's own + followed playlists. |
285
+ | `getUserRadios(userId)` | radios the user favourited. |
286
+ | `getUserChartTracks(userId, limit?)` | the user's personal top tracks. |
287
+ | `getRadios()` | Deezer's curated radio list. |
288
+ | `getRadioTracks(radioId)` | a radio's current tracklist — a ready-to-play source. |
289
+ | `getRadioGenres()` | radios grouped by genre. |
290
+
291
+ ```js
292
+ const me = await getUser();
293
+ const {data: flow} = await getUserFlow(me.USER_ID); // your Flow
294
+ const {data: loved} = await getUserFavoriteTracks(me.USER_ID);
295
+ const {data: eighties} = await getRadioTracks(38305); // "The '80s"
296
+ ```
297
+
247
298
  ### `.getTrackDownloadUrl(track, quality);`
248
299
 
249
300
  | Parameters | Required | Type | Description |
@@ -3,3 +3,4 @@ export * from './request';
3
3
  export * from './search';
4
4
  export * from './browse';
5
5
  export * from './preview';
6
+ export * from './user';
package/dist/api/index.js CHANGED
@@ -19,3 +19,4 @@ __exportStar(require("./request"), exports);
19
19
  __exportStar(require("./search"), exports);
20
20
  __exportStar(require("./browse"), exports);
21
21
  __exportStar(require("./preview"), exports);
22
+ __exportStar(require("./user"), exports);
@@ -0,0 +1,24 @@
1
+ import type { publicApiList, radioGenre, radioResult, searchResultTrack, userFavoriteAlbum, userFavoriteArtist, userFavoriteTrack, userPlaylistResult } from '../types';
2
+ /**
3
+ * Deezer **Flow** — the endless personalised mix — for a user. Returns
4
+ * public-API track objects; `getTrackInfo(id)` to make one downloadable.
5
+ */
6
+ export declare const getUserFlow: (userId: number | string, limit?: number) => Promise<publicApiList<searchResultTrack>>;
7
+ /** A user's favourite (loved) tracks, newest first; each carries `time_add`. */
8
+ export declare const getUserFavoriteTracks: (userId: number | string, limit?: number, index?: number) => Promise<publicApiList<userFavoriteTrack>>;
9
+ /** A user's favourite albums. */
10
+ export declare const getUserFavoriteAlbums: (userId: number | string, limit?: number, index?: number) => Promise<publicApiList<userFavoriteAlbum>>;
11
+ /** A user's favourite artists. */
12
+ export declare const getUserFavoriteArtists: (userId: number | string, limit?: number, index?: number) => Promise<publicApiList<userFavoriteArtist>>;
13
+ /** A user's own + followed playlists. */
14
+ export declare const getUserPlaylists: (userId: number | string, limit?: number, index?: number) => Promise<publicApiList<userPlaylistResult>>;
15
+ /** The radios a user has favourited. */
16
+ export declare const getUserRadios: (userId: number | string) => Promise<publicApiList<radioResult>>;
17
+ /** A user's personal track chart (their most-played). */
18
+ export declare const getUserChartTracks: (userId: number | string, limit?: number) => Promise<publicApiList<searchResultTrack>>;
19
+ /** Deezer's curated radio list. */
20
+ export declare const getRadios: () => Promise<publicApiList<radioResult>>;
21
+ /** A radio's current track list — a ready-to-play (public-API) source. */
22
+ export declare const getRadioTracks: (radioId: number | string) => Promise<publicApiList<searchResultTrack>>;
23
+ /** Radios grouped by genre. */
24
+ export declare const getRadioGenres: () => Promise<publicApiList<radioGenre>>;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRadioGenres = exports.getRadioTracks = exports.getRadios = exports.getUserChartTracks = exports.getUserRadios = exports.getUserPlaylists = exports.getUserFavoriteArtists = exports.getUserFavoriteAlbums = exports.getUserFavoriteTracks = exports.getUserFlow = void 0;
4
+ const request_1 = require("./request");
5
+ const withParams = (slug, params) => {
6
+ const search = new URLSearchParams();
7
+ for (const [key, value] of Object.entries(params)) {
8
+ if (value !== undefined && value !== '') {
9
+ search.set(key, String(value));
10
+ }
11
+ }
12
+ const qs = search.toString();
13
+ return qs ? `${slug}?${qs}` : slug;
14
+ };
15
+ // ─── Flow & a user's library (public REST) ───────────────────────────────────
16
+ //
17
+ // These take an explicit `userId` (from `getUser().USER_ID`, a profile URL, or
18
+ // `parseInfo`). They read **public** profile data — a user whose library is
19
+ // private only exposes it to their own authenticated session. All memoised.
20
+ /**
21
+ * Deezer **Flow** — the endless personalised mix — for a user. Returns
22
+ * public-API track objects; `getTrackInfo(id)` to make one downloadable.
23
+ */
24
+ const getUserFlow = (userId, limit = 40) => (0, request_1.requestPublicApi)(withParams(`/user/${userId}/flow`, { limit }));
25
+ exports.getUserFlow = getUserFlow;
26
+ /** A user's favourite (loved) tracks, newest first; each carries `time_add`. */
27
+ const getUserFavoriteTracks = (userId, limit = 100, index = 0) => (0, request_1.requestPublicApi)(withParams(`/user/${userId}/tracks`, { limit, index }));
28
+ exports.getUserFavoriteTracks = getUserFavoriteTracks;
29
+ /** A user's favourite albums. */
30
+ const getUserFavoriteAlbums = (userId, limit = 50, index = 0) => (0, request_1.requestPublicApi)(withParams(`/user/${userId}/albums`, { limit, index }));
31
+ exports.getUserFavoriteAlbums = getUserFavoriteAlbums;
32
+ /** A user's favourite artists. */
33
+ const getUserFavoriteArtists = (userId, limit = 50, index = 0) => (0, request_1.requestPublicApi)(withParams(`/user/${userId}/artists`, { limit, index }));
34
+ exports.getUserFavoriteArtists = getUserFavoriteArtists;
35
+ /** A user's own + followed playlists. */
36
+ const getUserPlaylists = (userId, limit = 50, index = 0) => (0, request_1.requestPublicApi)(withParams(`/user/${userId}/playlists`, { limit, index }));
37
+ exports.getUserPlaylists = getUserPlaylists;
38
+ /** The radios a user has favourited. */
39
+ const getUserRadios = (userId) => (0, request_1.requestPublicApi)(`/user/${userId}/radios`);
40
+ exports.getUserRadios = getUserRadios;
41
+ /** A user's personal track chart (their most-played). */
42
+ const getUserChartTracks = (userId, limit = 50) => (0, request_1.requestPublicApi)(withParams(`/user/${userId}/charts/tracks`, { limit }));
43
+ exports.getUserChartTracks = getUserChartTracks;
44
+ // ─── Radios (public REST) ────────────────────────────────────────────────────
45
+ /** Deezer's curated radio list. */
46
+ const getRadios = () => (0, request_1.requestPublicApi)('/radio');
47
+ exports.getRadios = getRadios;
48
+ /** A radio's current track list — a ready-to-play (public-API) source. */
49
+ const getRadioTracks = (radioId) => (0, request_1.requestPublicApi)(`/radio/${radioId}/tracks`);
50
+ exports.getRadioTracks = getRadioTracks;
51
+ /** Radios grouped by genre. */
52
+ const getRadioGenres = () => (0, request_1.requestPublicApi)('/radio/genres');
53
+ exports.getRadioGenres = getRadioGenres;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { initDeezerApi, RETRY_POLICY } from './lib/request';
1
+ export { initDeezerApi, createSession, defaultSession, Session, RETRY_POLICY, DEFAULT_ARL } from './lib/session';
2
+ export type { SessionUserData } from './lib/session';
2
3
  export { DeezerError } from './lib/errors';
3
4
  export type { DeezerErrorPayload } from './lib/errors';
4
5
  export * from './api';
package/dist/index.js CHANGED
@@ -14,10 +14,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.getStream = exports.getText = exports.getJson = exports.getBuffer = exports.httpsAgent = exports.httpAgent = exports.DeezerError = exports.RETRY_POLICY = exports.initDeezerApi = void 0;
18
- var request_1 = require("./lib/request");
19
- Object.defineProperty(exports, "initDeezerApi", { enumerable: true, get: function () { return request_1.initDeezerApi; } });
20
- Object.defineProperty(exports, "RETRY_POLICY", { enumerable: true, get: function () { return request_1.RETRY_POLICY; } });
17
+ exports.getStream = exports.getText = exports.getJson = exports.getBuffer = exports.httpsAgent = exports.httpAgent = exports.DeezerError = exports.DEFAULT_ARL = exports.RETRY_POLICY = exports.Session = exports.defaultSession = exports.createSession = exports.initDeezerApi = void 0;
18
+ var session_1 = require("./lib/session");
19
+ Object.defineProperty(exports, "initDeezerApi", { enumerable: true, get: function () { return session_1.initDeezerApi; } });
20
+ Object.defineProperty(exports, "createSession", { enumerable: true, get: function () { return session_1.createSession; } });
21
+ Object.defineProperty(exports, "defaultSession", { enumerable: true, get: function () { return session_1.defaultSession; } });
22
+ Object.defineProperty(exports, "Session", { enumerable: true, get: function () { return session_1.Session; } });
23
+ Object.defineProperty(exports, "RETRY_POLICY", { enumerable: true, get: function () { return session_1.RETRY_POLICY; } });
24
+ Object.defineProperty(exports, "DEFAULT_ARL", { enumerable: true, get: function () { return session_1.DEFAULT_ARL; } });
21
25
  var errors_1 = require("./lib/errors");
22
26
  Object.defineProperty(exports, "DeezerError", { enumerable: true, get: function () { return errors_1.DeezerError; } });
23
27
  __exportStar(require("./api"), exports);
@@ -9,6 +9,7 @@ const decrypt_1 = require("../lib/decrypt");
9
9
  const errors_1 = require("../lib/errors");
10
10
  const http_1 = require("../lib/http");
11
11
  const request_1 = __importDefault(require("../lib/request"));
12
+ const session_1 = require("../lib/session");
12
13
  class WrongLicense extends Error {
13
14
  constructor(format) {
14
15
  super();
@@ -37,7 +38,6 @@ class ExpiredTrackToken extends Error {
37
38
  }
38
39
  }
39
40
  exports.ExpiredTrackToken = ExpiredTrackToken;
40
- let user_data = null;
41
41
  /**
42
42
  * Every audio format Deezer's `get_url` understands, best → worst. `FLAC`,
43
43
  * `MP3_320` and `MP3_128` are the classic `9 / 3 / 1` qualities; the rest are
@@ -83,22 +83,6 @@ const getTrackFileSize = (track, quality) => {
83
83
  const key = FORMAT_FILESIZE_KEY[(0, exports.toFormat)(quality)];
84
84
  return key ? Number(track[key]) || 0 : 0;
85
85
  };
86
- const dzAuthenticate = async () => {
87
- const { data } = await request_1.default.get('https://www.deezer.com/ajax/gw-light.php', {
88
- params: {
89
- method: 'deezer.getUserData',
90
- api_version: '1.0',
91
- api_token: 'null',
92
- },
93
- });
94
- user_data = {
95
- license_token: data.results.USER.OPTIONS.license_token,
96
- can_stream_lossless: data.results.USER.OPTIONS.web_lossless || data.results.USER.OPTIONS.mobile_loseless,
97
- can_stream_hq: data.results.USER.OPTIONS.web_hq || data.results.USER.OPTIONS.mobile_hq,
98
- country: data.results.COUNTRY,
99
- };
100
- return user_data;
101
- };
102
86
  const MEDIA_MAX_RETRIES = 3;
103
87
  /**
104
88
  * Quality code (`1 | 3 | 9`) or format string → the `format` string the media
@@ -110,10 +94,10 @@ exports.formatName = formatName;
110
94
  /** POST media.deezer.com/v1/get_url with re-auth + exponential-backoff retry on 403/429/5xx. */
111
95
  const mediaGetUrl = async (track_tokens, formats, attempt = 0) => {
112
96
  var _a;
113
- const user = user_data ? user_data : await dzAuthenticate();
97
+ const user = await (0, session_1.defaultSession)().loadUserData();
114
98
  try {
115
99
  const { data } = await request_1.default.post('https://media.deezer.com/v1/get_url', {
116
- license_token: user.license_token,
100
+ license_token: user.licenseToken,
117
101
  media: [{ type: 'FULL', formats }],
118
102
  track_tokens,
119
103
  });
@@ -122,7 +106,8 @@ const mediaGetUrl = async (track_tokens, formats, attempt = 0) => {
122
106
  catch (err) {
123
107
  const status = err instanceof http_1.HttpStatusError ? err.statusCode : 0;
124
108
  if ((status === 403 || status === 429 || status >= 500) && attempt < MEDIA_MAX_RETRIES) {
125
- user_data = null;
109
+ // a stale license token is a common cause — force a refresh before the retry
110
+ await (0, session_1.defaultSession)().loadUserData(true);
126
111
  await (0, delay_1.default)(500 * 2 ** attempt + Math.floor(Math.random() * 250));
127
112
  return mediaGetUrl(track_tokens, formats, attempt + 1);
128
113
  }
@@ -148,8 +133,8 @@ const parseMediaEntry = (entry, token, country) => {
148
133
  /** Whether a track fetched with the given cipher needs Blowfish stripe decryption. */
149
134
  const cipherIsEncrypted = (cipher, url) => cipher ? cipher !== 'NONE' : url.includes('/mobile/') || url.includes('/media/');
150
135
  const getTrackUrlFromServer = async (track_token, format) => {
151
- const user = user_data ? user_data : await dzAuthenticate();
152
- if ((format === 'FLAC' && !user.can_stream_lossless) || (format === 'MP3_320' && !user.can_stream_hq)) {
136
+ const user = await (0, session_1.defaultSession)().loadUserData();
137
+ if ((format === 'FLAC' && !user.canStreamLossless) || (format === 'MP3_320' && !user.canStreamHq)) {
153
138
  throw new WrongLicense(format);
154
139
  }
155
140
  const { data, country } = await mediaGetUrl([track_token], [{ format, cipher: 'BF_CBC_STRIPE' }]);
@@ -1,31 +1,17 @@
1
1
  import type { HttpQuery, HttpResponse } from './http';
2
+ export { initDeezerApi, createSession, defaultSession, setDefaultSession, Session, RETRY_POLICY } from './session';
3
+ export type { SessionUserData } from './session';
2
4
  type DeezerRequestConfig = {
3
5
  headers?: Record<string, string>;
4
6
  params?: HttpQuery;
5
7
  };
6
8
  /**
7
- * Bounded-retry policy for the gateway. Every retry class has its own attempt
8
- * cap **and** there is a wall-clock deadline, so a persistently failing endpoint
9
- * can no longer spin forever (the old code had no cap on `code === 4`,
10
- * `NEED_API_AUTH_REQUIRED` or `GATEWAY_ERROR`).
9
+ * A thin proxy over the **default** {@link Session} kept so the api / get-url
10
+ * layers can keep calling `client.get` / `client.post` unchanged. Each call runs
11
+ * through the session's bounded-retry loop.
11
12
  */
12
- export declare const RETRY_POLICY: {
13
- /** total attempts for the transient `code === 4` class */
14
- code4Attempts: number;
15
- /** re-inits allowed for `NEED_API_AUTH_REQUIRED` */
16
- authReinits: number;
17
- /** token refreshes allowed for `GATEWAY_ERROR` / `VALID_TOKEN_REQUIRED` */
18
- tokenRefreshes: number;
19
- /** base backoff (ms) — grows exponentially, full-jittered */
20
- baseMs: number;
21
- /** cap on a single backoff wait */
22
- maxDelayMs: number;
23
- /** overall wall-clock budget from the first attempt */
24
- deadlineMs: number;
25
- };
26
- export declare const initDeezerApi: (arl: string) => Promise<string>;
27
13
  declare const _default: {
28
- defaults: {
14
+ readonly defaults: {
29
15
  baseURL?: string | undefined;
30
16
  headers: import("./http").HttpHeaders;
31
17
  maxRedirects: number;
@@ -1,123 +1,23 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.initDeezerApi = exports.RETRY_POLICY = void 0;
7
- const delay_1 = __importDefault(require("delay"));
8
- const http_1 = require("./http");
9
- const errors_1 = require("./errors");
10
- let user_arl = 'c973964816688562722418b5200c1515dffaad15a42643ebf87cc72824a54612ec51c2ad42d566743f9e424c774e98ccae7737770acff59251328e6cd598c7bcac38ca269adf78bfb88ec5bbad6cd800db3c0b88b2af645bb22b99e71de26416';
3
+ exports.RETRY_POLICY = exports.Session = exports.setDefaultSession = exports.defaultSession = exports.createSession = exports.initDeezerApi = void 0;
4
+ const session_1 = require("./session");
5
+ var session_2 = require("./session");
6
+ Object.defineProperty(exports, "initDeezerApi", { enumerable: true, get: function () { return session_2.initDeezerApi; } });
7
+ Object.defineProperty(exports, "createSession", { enumerable: true, get: function () { return session_2.createSession; } });
8
+ Object.defineProperty(exports, "defaultSession", { enumerable: true, get: function () { return session_2.defaultSession; } });
9
+ Object.defineProperty(exports, "setDefaultSession", { enumerable: true, get: function () { return session_2.setDefaultSession; } });
10
+ Object.defineProperty(exports, "Session", { enumerable: true, get: function () { return session_2.Session; } });
11
+ Object.defineProperty(exports, "RETRY_POLICY", { enumerable: true, get: function () { return session_2.RETRY_POLICY; } });
11
12
  /**
12
- * Bounded-retry policy for the gateway. Every retry class has its own attempt
13
- * cap **and** there is a wall-clock deadline, so a persistently failing endpoint
14
- * can no longer spin forever (the old code had no cap on `code === 4`,
15
- * `NEED_API_AUTH_REQUIRED` or `GATEWAY_ERROR`).
13
+ * A thin proxy over the **default** {@link Session} kept so the api / get-url
14
+ * layers can keep calling `client.get` / `client.post` unchanged. Each call runs
15
+ * through the session's bounded-retry loop.
16
16
  */
17
- exports.RETRY_POLICY = {
18
- /** total attempts for the transient `code === 4` class */
19
- code4Attempts: 6,
20
- /** re-inits allowed for `NEED_API_AUTH_REQUIRED` */
21
- authReinits: 3,
22
- /** token refreshes allowed for `GATEWAY_ERROR` / `VALID_TOKEN_REQUIRED` */
23
- tokenRefreshes: 15,
24
- /** base backoff (ms) — grows exponentially, full-jittered */
25
- baseMs: 800,
26
- /** cap on a single backoff wait */
27
- maxDelayMs: 8000,
28
- /** overall wall-clock budget from the first attempt */
29
- deadlineMs: 30000,
30
- };
31
- /** Exponential backoff (ms) with full jitter on the top half of the window. */
32
- const backoffDelay = (attempt) => {
33
- const windowMs = Math.min(exports.RETRY_POLICY.baseMs * 2 ** attempt, exports.RETRY_POLICY.maxDelayMs);
34
- return windowMs / 2 + Math.random() * (windowMs / 2);
35
- };
36
- const instance = new http_1.HttpClient({
37
- baseURL: 'https://api.deezer.com/1.0',
38
- timeout: 15000,
39
- headers: {
40
- Accept: '*/*',
41
- 'Accept-Encoding': 'gzip, deflate',
42
- 'Accept-Language': 'en-US',
43
- 'Cache-Control': 'no-cache',
44
- 'Content-Type': 'application/json; charset=UTF-8',
45
- 'User-Agent': 'Deezer/8.32.0.2 (iOS; 14.4; Mobile; en; iPhone10_5)',
46
- },
47
- params: {
48
- version: '8.32.0',
49
- api_key: 'ZAIVAHCEISOHWAICUQUEXAEPICENGUAFAEZAIPHAELEEVAHPHUCUFONGUAPASUAY',
50
- output: 3,
51
- input: 3,
52
- buildId: 'ios12_universal',
53
- screenHeight: '480',
54
- screenWidth: '320',
55
- lang: 'en',
56
- },
57
- });
58
- const getApiToken = async () => {
59
- const { data } = await instance.get('https://www.deezer.com/ajax/gw-light.php', {
60
- params: {
61
- method: 'deezer.getUserData',
62
- api_version: '1.0',
63
- api_token: 'null',
64
- },
65
- });
66
- instance.defaults.params.sid = data.results.SESSION_ID;
67
- instance.defaults.params.api_token = data.results.checkForm;
68
- return data.results.checkForm;
69
- };
70
- const initDeezerApi = async (arl) => {
71
- if (arl.length !== 192) {
72
- throw new Error(`Invalid arl. Length should be 192 characters. You have provided ${arl.length} characters.`);
73
- }
74
- user_arl = arl;
75
- const { data } = await instance.get('https://www.deezer.com/ajax/gw-light.php', {
76
- params: { method: 'deezer.ping', api_version: '1.0', api_token: '' },
77
- headers: { cookie: 'arl=' + arl },
78
- });
79
- instance.defaults.params.sid = data.results.SESSION;
80
- return data.results.SESSION;
81
- };
82
- exports.initDeezerApi = initDeezerApi;
83
- const requestWithRetry = async (method, url, body, config = {}) => {
84
- var _a;
85
- const startedAt = Date.now();
86
- let authReinits = 0;
87
- let tokenRefreshes = 0;
88
- let code4Attempts = 0;
89
- // eslint-disable-next-line no-constant-condition
90
- while (true) {
91
- const response = method === 'POST' ? await instance.post(url, body, config) : await instance.get(url, config);
92
- const error = (_a = response.data) === null || _a === void 0 ? void 0 : _a.error;
93
- if (!error || Object.keys(error).length === 0) {
94
- return response;
95
- }
96
- const overDeadline = Date.now() - startedAt > exports.RETRY_POLICY.deadlineMs;
97
- if (error.NEED_API_AUTH_REQUIRED && authReinits < exports.RETRY_POLICY.authReinits && !overDeadline) {
98
- authReinits += 1;
99
- await (0, exports.initDeezerApi)(user_arl);
100
- continue;
101
- }
102
- if ((error.GATEWAY_ERROR || error.VALID_TOKEN_REQUIRED) &&
103
- tokenRefreshes < exports.RETRY_POLICY.tokenRefreshes &&
104
- !overDeadline) {
105
- tokenRefreshes += 1;
106
- await getApiToken();
107
- await (0, delay_1.default)(backoffDelay(tokenRefreshes - 1));
108
- continue;
109
- }
110
- if (error.code === 4 && code4Attempts < exports.RETRY_POLICY.code4Attempts && !overDeadline) {
111
- await (0, delay_1.default)(backoffDelay(code4Attempts));
112
- code4Attempts += 1;
113
- continue;
114
- }
115
- // Unhandled error, or a retry class ran out of attempts / hit the deadline.
116
- throw new errors_1.DeezerError(error);
117
- }
118
- };
119
17
  exports.default = {
120
- defaults: instance.defaults,
121
- get: async (url, config = {}) => await requestWithRetry('GET', url, undefined, config),
122
- post: async (url, body, config = {}) => await requestWithRetry('POST', url, body, config),
18
+ get defaults() {
19
+ return (0, session_1.defaultSession)().http.defaults;
20
+ },
21
+ get: (url, config = {}) => (0, session_1.defaultSession)().request('GET', url, undefined, config),
22
+ post: (url, body, config = {}) => (0, session_1.defaultSession)().request('POST', url, body, config),
123
23
  };
@@ -0,0 +1,112 @@
1
+ import { HttpClient } from './http';
2
+ import type { HttpQuery, HttpResponse } from './http';
3
+ /**
4
+ * The bundled default `arl`. It is shared, rate-limited and expiring — good for
5
+ * unauthenticated endpoints, useless for downloads. `initDeezerApi` /
6
+ * `createSession` replace it.
7
+ */
8
+ export declare const DEFAULT_ARL = "c973964816688562722418b5200c1515dffaad15a42643ebf87cc72824a54612ec51c2ad42d566743f9e424c774e98ccae7737770acff59251328e6cd598c7bcac38ca269adf78bfb88ec5bbad6cd800db3c0b88b2af645bb22b99e71de26416";
9
+ /**
10
+ * Bounded-retry policy for the gateway. Every retry class has its own attempt
11
+ * cap **and** there is a wall-clock deadline, so a persistently failing endpoint
12
+ * can no longer spin forever.
13
+ */
14
+ export declare const RETRY_POLICY: {
15
+ /** total attempts for the transient `code === 4` class */
16
+ code4Attempts: number;
17
+ /** re-inits allowed for `NEED_API_AUTH_REQUIRED` */
18
+ authReinits: number;
19
+ /** token refreshes allowed for `GATEWAY_ERROR` / `VALID_TOKEN_REQUIRED` */
20
+ tokenRefreshes: number;
21
+ /** base backoff (ms) — grows exponentially, full-jittered */
22
+ baseMs: number;
23
+ /** cap on a single backoff wait */
24
+ maxDelayMs: number;
25
+ /** overall wall-clock budget from the first attempt */
26
+ deadlineMs: number;
27
+ };
28
+ type SessionRequestConfig = {
29
+ headers?: Record<string, string>;
30
+ params?: HttpQuery;
31
+ };
32
+ /** The account-scoped bits of `deezer.getUserData` a download needs. */
33
+ export interface SessionUserData {
34
+ /** the `license_token` the media API's `get_url` requires */
35
+ licenseToken: string;
36
+ /** ISO country the account resolves to (drives geo-blocking) */
37
+ country: string;
38
+ /** account may stream FLAC */
39
+ canStreamLossless: boolean;
40
+ /** account may stream 320 kbps */
41
+ canStreamHq: boolean;
42
+ offerId?: number;
43
+ }
44
+ /**
45
+ * One Deezer session — owns the `arl`, the HTTP client (and its `sid` /
46
+ * `api_token`), and the account's `license_token` / `country` / streaming
47
+ * rights, plus the bounded-retry request loop and token refresh.
48
+ *
49
+ * The state used to be spread across module-level variables in `request.ts` and
50
+ * `get-url.ts`; a `Session` bundles it so multiple accounts can coexist. The
51
+ * free functions (`getTrackInfo`, `request`, …) run against a process-wide
52
+ * default session; `createSession(arl)` gives you an isolated one.
53
+ */
54
+ export declare class Session {
55
+ /** the arl cookie in use */
56
+ arl: string;
57
+ /** the underlying HTTP client — its `defaults.params` carry `sid` / `api_token` */
58
+ readonly http: HttpClient;
59
+ private userData;
60
+ private userDataAt;
61
+ constructor(arl?: string);
62
+ /** current gateway session id */
63
+ get sid(): string | undefined;
64
+ /** current CSRF-ish api token */
65
+ get apiToken(): string | undefined;
66
+ /** ISO country the account resolves to, once `loadUserData` has run */
67
+ get country(): string | undefined;
68
+ /** the media-API `license_token`, once `loadUserData` has run */
69
+ get licenseToken(): string | undefined;
70
+ get canStreamLossless(): boolean;
71
+ get canStreamHq(): boolean;
72
+ /**
73
+ * Ping `gw-light.php` for a fresh `SESSION` id. Pass an `arl` to (re)authenticate
74
+ * this session as that account — it must be exactly 192 characters.
75
+ * Returns the new `SESSION`.
76
+ */
77
+ init(arl?: string): Promise<string>;
78
+ /** Refresh `sid` + `api_token` from `deezer.getUserData`. Returns the api token. */
79
+ refreshApiToken(): Promise<string>;
80
+ /**
81
+ * The account's `license_token` / `country` / streaming rights, from
82
+ * `deezer.getUserData`. Cached for {@link USER_DATA_TTL_MS}; pass `force` (or
83
+ * see a media-API 403) to refresh. Also refreshes `sid` / `api_token`.
84
+ */
85
+ loadUserData(force?: boolean): Promise<SessionUserData>;
86
+ /** Drop the cached user data so the next `loadUserData` re-fetches. */
87
+ invalidateUserData(): void;
88
+ /**
89
+ * The bounded-retry gateway request loop. Handles `NEED_API_AUTH_REQUIRED`
90
+ * (re-init), `GATEWAY_ERROR` / `VALID_TOKEN_REQUIRED` (token refresh) and
91
+ * `code === 4` (transient), each with its own attempt cap, full-jittered
92
+ * backoff, and a wall-clock deadline. Throws `DeezerError` on exhaustion.
93
+ */
94
+ request<T>(method: 'GET' | 'POST', url: string, body?: unknown, config?: SessionRequestConfig): Promise<HttpResponse<T>>;
95
+ }
96
+ /** The process-wide default session every free function runs against. */
97
+ export declare const defaultSession: () => Session;
98
+ /**
99
+ * (Re)authenticate the **default** session. Kept for backwards compatibility —
100
+ * `createSession` is the way to hold an isolated session. `arl` must be exactly
101
+ * 192 characters. Returns the gateway `SESSION` id.
102
+ */
103
+ export declare const initDeezerApi: (arl: string) => Promise<string>;
104
+ /**
105
+ * Create an **isolated** Deezer session — its own `arl`, `sid`, tokens and
106
+ * `license_token`, so multiple accounts can be used concurrently. Without an
107
+ * `arl` it runs on the bundled (shared, rate-limited) default.
108
+ */
109
+ export declare const createSession: (arl?: string) => Promise<Session>;
110
+ /** Replace the default session (used by tests). */
111
+ export declare const setDefaultSession: (session: Session) => void;
112
+ export {};
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.setDefaultSession = exports.createSession = exports.initDeezerApi = exports.defaultSession = exports.Session = exports.RETRY_POLICY = exports.DEFAULT_ARL = void 0;
7
+ const delay_1 = __importDefault(require("delay"));
8
+ const errors_1 = require("./errors");
9
+ const http_1 = require("./http");
10
+ /**
11
+ * The bundled default `arl`. It is shared, rate-limited and expiring — good for
12
+ * unauthenticated endpoints, useless for downloads. `initDeezerApi` /
13
+ * `createSession` replace it.
14
+ */
15
+ exports.DEFAULT_ARL = 'c973964816688562722418b5200c1515dffaad15a42643ebf87cc72824a54612ec51c2ad42d566743f9e424c774e98ccae7737770acff59251328e6cd598c7bcac38ca269adf78bfb88ec5bbad6cd800db3c0b88b2af645bb22b99e71de26416';
16
+ /**
17
+ * Bounded-retry policy for the gateway. Every retry class has its own attempt
18
+ * cap **and** there is a wall-clock deadline, so a persistently failing endpoint
19
+ * can no longer spin forever.
20
+ */
21
+ exports.RETRY_POLICY = {
22
+ /** total attempts for the transient `code === 4` class */
23
+ code4Attempts: 6,
24
+ /** re-inits allowed for `NEED_API_AUTH_REQUIRED` */
25
+ authReinits: 3,
26
+ /** token refreshes allowed for `GATEWAY_ERROR` / `VALID_TOKEN_REQUIRED` */
27
+ tokenRefreshes: 15,
28
+ /** base backoff (ms) — grows exponentially, full-jittered */
29
+ baseMs: 800,
30
+ /** cap on a single backoff wait */
31
+ maxDelayMs: 8000,
32
+ /** overall wall-clock budget from the first attempt */
33
+ deadlineMs: 30000,
34
+ };
35
+ /** Exponential backoff (ms) with full jitter on the top half of the window. */
36
+ const backoffDelay = (attempt) => {
37
+ const windowMs = Math.min(exports.RETRY_POLICY.baseMs * 2 ** attempt, exports.RETRY_POLICY.maxDelayMs);
38
+ return windowMs / 2 + Math.random() * (windowMs / 2);
39
+ };
40
+ /** How long a loaded `deezer.getUserData` payload is trusted before a refresh. */
41
+ const USER_DATA_TTL_MS = 25 * 60 * 1000;
42
+ /**
43
+ * One Deezer session — owns the `arl`, the HTTP client (and its `sid` /
44
+ * `api_token`), and the account's `license_token` / `country` / streaming
45
+ * rights, plus the bounded-retry request loop and token refresh.
46
+ *
47
+ * The state used to be spread across module-level variables in `request.ts` and
48
+ * `get-url.ts`; a `Session` bundles it so multiple accounts can coexist. The
49
+ * free functions (`getTrackInfo`, `request`, …) run against a process-wide
50
+ * default session; `createSession(arl)` gives you an isolated one.
51
+ */
52
+ class Session {
53
+ constructor(arl) {
54
+ this.userData = null;
55
+ this.userDataAt = 0;
56
+ this.arl = arl !== null && arl !== void 0 ? arl : exports.DEFAULT_ARL;
57
+ this.http = new http_1.HttpClient({
58
+ baseURL: 'https://api.deezer.com/1.0',
59
+ timeout: 15000,
60
+ headers: {
61
+ Accept: '*/*',
62
+ 'Accept-Encoding': 'gzip, deflate',
63
+ 'Accept-Language': 'en-US',
64
+ 'Cache-Control': 'no-cache',
65
+ 'Content-Type': 'application/json; charset=UTF-8',
66
+ 'User-Agent': 'Deezer/8.32.0.2 (iOS; 14.4; Mobile; en; iPhone10_5)',
67
+ },
68
+ params: {
69
+ version: '8.32.0',
70
+ api_key: 'ZAIVAHCEISOHWAICUQUEXAEPICENGUAFAEZAIPHAELEEVAHPHUCUFONGUAPASUAY',
71
+ output: 3,
72
+ input: 3,
73
+ buildId: 'ios12_universal',
74
+ screenHeight: '480',
75
+ screenWidth: '320',
76
+ lang: 'en',
77
+ },
78
+ });
79
+ }
80
+ /** current gateway session id */
81
+ get sid() {
82
+ return this.http.defaults.params.sid;
83
+ }
84
+ /** current CSRF-ish api token */
85
+ get apiToken() {
86
+ return this.http.defaults.params.api_token;
87
+ }
88
+ /** ISO country the account resolves to, once `loadUserData` has run */
89
+ get country() {
90
+ var _a;
91
+ return (_a = this.userData) === null || _a === void 0 ? void 0 : _a.country;
92
+ }
93
+ /** the media-API `license_token`, once `loadUserData` has run */
94
+ get licenseToken() {
95
+ var _a;
96
+ return (_a = this.userData) === null || _a === void 0 ? void 0 : _a.licenseToken;
97
+ }
98
+ get canStreamLossless() {
99
+ var _a, _b;
100
+ return (_b = (_a = this.userData) === null || _a === void 0 ? void 0 : _a.canStreamLossless) !== null && _b !== void 0 ? _b : false;
101
+ }
102
+ get canStreamHq() {
103
+ var _a, _b;
104
+ return (_b = (_a = this.userData) === null || _a === void 0 ? void 0 : _a.canStreamHq) !== null && _b !== void 0 ? _b : false;
105
+ }
106
+ /**
107
+ * Ping `gw-light.php` for a fresh `SESSION` id. Pass an `arl` to (re)authenticate
108
+ * this session as that account — it must be exactly 192 characters.
109
+ * Returns the new `SESSION`.
110
+ */
111
+ async init(arl) {
112
+ if (arl !== undefined) {
113
+ if (arl.length !== 192) {
114
+ throw new Error(`Invalid arl. Length should be 192 characters. You have provided ${arl.length} characters.`);
115
+ }
116
+ this.arl = arl;
117
+ this.userData = null;
118
+ }
119
+ const { data } = await this.http.get('https://www.deezer.com/ajax/gw-light.php', {
120
+ params: { method: 'deezer.ping', api_version: '1.0', api_token: '' },
121
+ headers: { cookie: 'arl=' + this.arl },
122
+ });
123
+ this.http.defaults.params.sid = data.results.SESSION;
124
+ return data.results.SESSION;
125
+ }
126
+ /** Refresh `sid` + `api_token` from `deezer.getUserData`. Returns the api token. */
127
+ async refreshApiToken() {
128
+ const { data } = await this.http.get('https://www.deezer.com/ajax/gw-light.php', {
129
+ params: { method: 'deezer.getUserData', api_version: '1.0', api_token: 'null' },
130
+ });
131
+ this.http.defaults.params.sid = data.results.SESSION_ID;
132
+ this.http.defaults.params.api_token = data.results.checkForm;
133
+ return data.results.checkForm;
134
+ }
135
+ /**
136
+ * The account's `license_token` / `country` / streaming rights, from
137
+ * `deezer.getUserData`. Cached for {@link USER_DATA_TTL_MS}; pass `force` (or
138
+ * see a media-API 403) to refresh. Also refreshes `sid` / `api_token`.
139
+ */
140
+ async loadUserData(force = false) {
141
+ var _a, _b, _c, _d, _e, _f, _g;
142
+ if (this.userData && !force && Date.now() - this.userDataAt < USER_DATA_TTL_MS) {
143
+ return this.userData;
144
+ }
145
+ const { data } = await this.http.get('https://www.deezer.com/ajax/gw-light.php', {
146
+ params: { method: 'deezer.getUserData', api_version: '1.0', api_token: 'null' },
147
+ });
148
+ const options = (_c = (_b = (_a = data.results) === null || _a === void 0 ? void 0 : _a.USER) === null || _b === void 0 ? void 0 : _b.OPTIONS) !== null && _c !== void 0 ? _c : {};
149
+ this.userData = {
150
+ licenseToken: options.license_token,
151
+ country: (_d = data.results) === null || _d === void 0 ? void 0 : _d.COUNTRY,
152
+ canStreamLossless: Boolean(options.web_lossless || options.mobile_loseless),
153
+ canStreamHq: Boolean(options.web_hq || options.mobile_hq),
154
+ offerId: (_e = data.results) === null || _e === void 0 ? void 0 : _e.OFFER_ID,
155
+ };
156
+ this.userDataAt = Date.now();
157
+ if ((_f = data.results) === null || _f === void 0 ? void 0 : _f.checkForm) {
158
+ this.http.defaults.params.api_token = data.results.checkForm;
159
+ }
160
+ if ((_g = data.results) === null || _g === void 0 ? void 0 : _g.SESSION_ID) {
161
+ this.http.defaults.params.sid = data.results.SESSION_ID;
162
+ }
163
+ return this.userData;
164
+ }
165
+ /** Drop the cached user data so the next `loadUserData` re-fetches. */
166
+ invalidateUserData() {
167
+ this.userData = null;
168
+ }
169
+ /**
170
+ * The bounded-retry gateway request loop. Handles `NEED_API_AUTH_REQUIRED`
171
+ * (re-init), `GATEWAY_ERROR` / `VALID_TOKEN_REQUIRED` (token refresh) and
172
+ * `code === 4` (transient), each with its own attempt cap, full-jittered
173
+ * backoff, and a wall-clock deadline. Throws `DeezerError` on exhaustion.
174
+ */
175
+ async request(method, url, body, config = {}) {
176
+ var _a;
177
+ const startedAt = Date.now();
178
+ let authReinits = 0;
179
+ let tokenRefreshes = 0;
180
+ let code4Attempts = 0;
181
+ // eslint-disable-next-line no-constant-condition
182
+ while (true) {
183
+ const response = method === 'POST' ? await this.http.post(url, body, config) : await this.http.get(url, config);
184
+ const error = (_a = response.data) === null || _a === void 0 ? void 0 : _a.error;
185
+ if (!error || Object.keys(error).length === 0) {
186
+ return response;
187
+ }
188
+ const overDeadline = Date.now() - startedAt > exports.RETRY_POLICY.deadlineMs;
189
+ if (error.NEED_API_AUTH_REQUIRED && authReinits < exports.RETRY_POLICY.authReinits && !overDeadline) {
190
+ authReinits += 1;
191
+ await this.init();
192
+ continue;
193
+ }
194
+ if ((error.GATEWAY_ERROR || error.VALID_TOKEN_REQUIRED) &&
195
+ tokenRefreshes < exports.RETRY_POLICY.tokenRefreshes &&
196
+ !overDeadline) {
197
+ tokenRefreshes += 1;
198
+ await this.refreshApiToken();
199
+ await (0, delay_1.default)(backoffDelay(tokenRefreshes - 1));
200
+ continue;
201
+ }
202
+ if (error.code === 4 && code4Attempts < exports.RETRY_POLICY.code4Attempts && !overDeadline) {
203
+ await (0, delay_1.default)(backoffDelay(code4Attempts));
204
+ code4Attempts += 1;
205
+ continue;
206
+ }
207
+ throw new errors_1.DeezerError(error);
208
+ }
209
+ }
210
+ }
211
+ exports.Session = Session;
212
+ let _default = new Session();
213
+ /** The process-wide default session every free function runs against. */
214
+ const defaultSession = () => _default;
215
+ exports.defaultSession = defaultSession;
216
+ /**
217
+ * (Re)authenticate the **default** session. Kept for backwards compatibility —
218
+ * `createSession` is the way to hold an isolated session. `arl` must be exactly
219
+ * 192 characters. Returns the gateway `SESSION` id.
220
+ */
221
+ const initDeezerApi = (arl) => _default.init(arl);
222
+ exports.initDeezerApi = initDeezerApi;
223
+ /**
224
+ * Create an **isolated** Deezer session — its own `arl`, `sid`, tokens and
225
+ * `license_token`, so multiple accounts can be used concurrently. Without an
226
+ * `arl` it runs on the bundled (shared, rate-limited) default.
227
+ */
228
+ const createSession = async (arl) => {
229
+ const session = new Session(arl);
230
+ await session.init();
231
+ return session;
232
+ };
233
+ exports.createSession = createSession;
234
+ /** Replace the default session (used by tests). */
235
+ const setDefaultSession = (session) => {
236
+ _default = session;
237
+ };
238
+ exports.setDefaultSession = setDefaultSession;
@@ -1,3 +1,4 @@
1
+ import type { searchResultTrack, searchResultArtist } from './search';
1
2
  export interface userType {
2
3
  USER_ID: string;
3
4
  EMAIL: string;
@@ -14,3 +15,82 @@ export interface userType {
14
15
  PHONE?: string;
15
16
  __TYPE__: 'user';
16
17
  }
18
+ /** A favourite track from `/user/{id}/tracks` — public-API shape, plus `time_add`. */
19
+ export interface userFavoriteTrack extends searchResultTrack {
20
+ /** unix timestamp the track was added to favourites */
21
+ time_add?: number;
22
+ }
23
+ export interface userFavoriteAlbum {
24
+ id: number;
25
+ title: string;
26
+ link: string;
27
+ cover: string;
28
+ cover_small?: string;
29
+ cover_medium?: string;
30
+ cover_big?: string;
31
+ cover_xl?: string;
32
+ md5_image: string;
33
+ nb_tracks: number;
34
+ release_date: string;
35
+ record_type: string;
36
+ available: boolean;
37
+ tracklist: string;
38
+ explicit_lyrics: boolean;
39
+ time_add?: number;
40
+ artist: searchResultArtist | {
41
+ id: number;
42
+ name: string;
43
+ tracklist?: string;
44
+ type: 'artist';
45
+ };
46
+ type: 'album';
47
+ }
48
+ export interface userFavoriteArtist extends searchResultArtist {
49
+ time_add?: number;
50
+ }
51
+ export interface userPlaylistResult {
52
+ id: number;
53
+ title: string;
54
+ duration: number;
55
+ public: boolean;
56
+ is_loved_track: boolean;
57
+ collaborative: boolean;
58
+ nb_tracks: number;
59
+ fans: number;
60
+ link: string;
61
+ picture: string;
62
+ picture_small?: string;
63
+ picture_medium?: string;
64
+ picture_big?: string;
65
+ picture_xl?: string;
66
+ checksum: string;
67
+ tracklist: string;
68
+ creation_date?: string;
69
+ time_add?: number;
70
+ time_mod?: number;
71
+ creator?: {
72
+ id: number;
73
+ name: string;
74
+ tracklist?: string;
75
+ type: 'user';
76
+ };
77
+ type: 'playlist';
78
+ }
79
+ export interface radioResult {
80
+ id: number;
81
+ title: string;
82
+ description?: string;
83
+ picture: string;
84
+ picture_small?: string;
85
+ picture_medium?: string;
86
+ picture_big?: string;
87
+ picture_xl?: string;
88
+ md5_image?: string;
89
+ tracklist: string;
90
+ type: 'radio';
91
+ }
92
+ export interface radioGenre {
93
+ id: number;
94
+ title: string;
95
+ radios: radioResult[];
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gerdur-core",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Core module for gerdur.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -61,7 +61,7 @@
61
61
  "!__tests__/helpers.ts"
62
62
  ],
63
63
  "require": [
64
- "ts-node/register"
64
+ "ts-node/register/transpile-only"
65
65
  ],
66
66
  "timeout": "2m",
67
67
  "verbose": true