gerdur-core 2.0.0 → 2.2.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,56 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.2.0 - 2026-08-31
4
+
5
+ Phase 2.4 — browse & discovery. All additive, all on the public REST API (no
6
+ `arl` needed), all memoised.
7
+
8
+ ### Added
9
+
10
+ - **Charts / editorial**: `getGenres`, `getChart(genreId, limit)` (the five
11
+ ranked lists), `getChartTracks`, `getGenreArtists`, `getEditorialList`,
12
+ `getEditorialReleases`, `getEditorialSelection`, `getEditorialCharts`.
13
+ - **Artist discovery**: `getArtistTopTracks`, `getRelatedArtists`,
14
+ `getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`.
15
+ - **ISRC / UPC resolution**: `getTrackByISRC(isrc)` and `getAlbumByUPC(upc)` —
16
+ raw public-API track/album (with `bpm`, `gain`, `preview`, embedded `tracks`).
17
+ Complements the converter's `isrc2deezer` / `upc2deezer`, which hydrate a gw
18
+ track instead.
19
+ - New exported types: `chartType`, `chartTrack`/`chartAlbum`/`chartArtist`/
20
+ `chartPlaylist`/`chartPodcast`, `genreType`, `editorialType`,
21
+ `artistAlbumResult`, `publicApiList<T>`.
22
+
23
+ ## 2.1.0 - 2026-08-31
24
+
25
+ Phase 2.5 — search, properly. All additive.
26
+
27
+ ### Added
28
+
29
+ - **`searchPublicApi(query, options?)`** — search the public REST API
30
+ (`api.deezer.com/search`) rather than the internal `pageSearch` gateway.
31
+ Returns clean public-API objects (`isrc`, `preview`, `rank`, numeric ids) and
32
+ takes `type` (`track` | `album` | `artist` | `playlist` | `user` | `radio` |
33
+ `podcast`), `order`, `strict`, and `limit` / `index` paging. No auth required.
34
+ - **`searchTracks` / `searchAlbums` / `searchArtists` / `searchPlaylists`** —
35
+ thin typed wrappers over `searchPublicApi` with the entity fixed.
36
+ - **`buildAdvancedQuery(filters)`** — pure helper that composes Deezer's advanced
37
+ operators into one query string: `{artist, album, track, label}` →
38
+ `artist:"…"`, `{durMin, durMax, bpmMin, bpmMax}` → `dur_min:NNN` / `bpm_min:NNN`,
39
+ free-text `query` first. Reliable on the track index only (Deezer's
40
+ `/search/album` and `/search/artist` ignore the operators).
41
+ - **`suggest(query, nb?)`** — the `deezer.suggest` autocomplete endpoint, for
42
+ "as you type" UIs; cheaper than a full `searchMusic`.
43
+ - New exported types: `advancedSearchFilters`, `searchOrder`, `searchEntity`,
44
+ `publicApiSearchOptions`, `publicApiSearchResponse<T>`, `searchResultTrack`,
45
+ `searchResultAlbum`, `searchResultArtist`, `searchResultPlaylist`,
46
+ `suggestResult`.
47
+
48
+ ### Fixed
49
+
50
+ - **`getPlaylistChannel`** returned `MISSING_PARAMETER_PAGE` — the nested
51
+ `gateway_input` was serialised as `[object Object]`. It is now JSON-stringified
52
+ before the request, so channel pages resolve again.
53
+
3
54
  ## 2.0.0 - 2026-08-31
4
55
 
5
56
  Metadata overhaul — extract everything Deezer actually exposes for a track.
package/README.md CHANGED
@@ -162,6 +162,75 @@ All method returns `Object` or throws `Error`. Make sure to catch error on your
162
162
  | `types` | No | `array` | ['TRACK'] | array of search types |
163
163
  | `limit` | No | `number` | 15 | maximum item to fetch per types |
164
164
 
165
+ ### `.searchPublicApi(query, options?)` — and `.searchTracks` / `.searchAlbums` / `.searchArtists` / `.searchPlaylists`
166
+
167
+ Hits the **public** REST API (`api.deezer.com/search`) instead of the internal
168
+ `pageSearch` gateway. Returns clean public-API objects (`isrc`, `preview`,
169
+ `rank`, numeric ids), accepts the advanced query operators, an `order`, and
170
+ `limit` / `index` paging. No auth required.
171
+
172
+ | Parameters | Required | Type | Description |
173
+ | ---------------- | :------: | --------- | --------------------------------------------------------------------------- |
174
+ | `query` | Yes | `string` | plain text, or the output of `buildAdvancedQuery` |
175
+ | `options.type` | No | `string` | `'track'` (default), `'album'`, `'artist'`, `'playlist'`, `'user'`, `'radio'` |
176
+ | `options.order` | No | `string` | `RANKING`, `TRACK_ASC`, `RATING_DESC`, `DURATION_DESC`, … |
177
+ | `options.strict` | No | `boolean` | send Deezer's `strict=on` (disables the fuzzy fallback) |
178
+ | `options.limit` | No | `number` | page size (Deezer caps near 100) |
179
+ | `options.index` | No | `number` | offset into the result set |
180
+
181
+ ```js
182
+ const {data} = await searchTracks(buildAdvancedQuery({artist: 'daft punk', durMin: 200}), {limit: 25});
183
+ ```
184
+
185
+ ### `.buildAdvancedQuery(filters)`
186
+
187
+ Pure helper — composes Deezer's advanced operators into one query string.
188
+ `{artist, album, track, label}` become `artist:"…"`; `{durMin, durMax, bpmMin, bpmMax}`
189
+ become `dur_min:NNN` / `bpm_min:NNN`; a free-text `query` is emitted first.
190
+ Reliable only on the **track** index — `/search/album` and `/search/artist`
191
+ ignore the operators, so pass a plain string there.
192
+
193
+ ### `.suggest(query, nb?)`
194
+
195
+ `deezer.suggest` autocomplete — cheaper and faster than `searchMusic`, for
196
+ "as you type" UIs. `nb` (default 5) caps items per type. Needs an initialised
197
+ session (`initDeezerApi`).
198
+
199
+ ### Browse & discovery
200
+
201
+ Public REST endpoints — no `arl` needed, memoised like the rest. All return a
202
+ `{data, total?, next?}` list unless noted.
203
+
204
+ | Method | Returns |
205
+ | :--- | :--- |
206
+ | `getGenres()` | Deezer's genre list (`id` `0` = "All"). |
207
+ | `getChart(genreId = 0, limit = 10)` | `{tracks, albums, artists, playlists, podcasts}` — the ranked lists for a genre. |
208
+ | `getChartTracks(genreId = 0, limit = 100, index = 0)` | just the track chart, each with a `position`. |
209
+ | `getGenreArtists(genreId)` | artists filed under a genre. |
210
+ | `getEditorialList()` | Deezer's editorial sections. |
211
+ | `getEditorialReleases(editorialId = 0, limit = 25, index = 0)` | new releases for a section. |
212
+ | `getEditorialSelection(editorialId = 0)` | albums the editors are pushing. |
213
+ | `getEditorialCharts(editorialId = 0)` | a section's charts (same 5-list shape as `getChart`). |
214
+ | `getArtistTopTracks(artistId, limit = 50)` | an artist's most popular tracks. |
215
+ | `getRelatedArtists(artistId, limit = 20)` | similar / related artists. |
216
+ | `getArtistAlbums(artistId, limit = 50, index = 0)` | the artist's discography. |
217
+ | `getArtistPlaylists(artistId, limit = 25)` | playlists featuring the artist. |
218
+ | `getArtistRadioTracks(artistId)` | a ready-made radio seeded from the artist. |
219
+ | `getTrackByISRC(isrc)` | the public-API track for an ISRC (`bpm`, `gain`, `preview`, …). |
220
+ | `getAlbumByUPC(upc)` | the public-API album (with its `tracks`) for a UPC/EAN barcode. |
221
+
222
+ ```js
223
+ const {data: genres} = await getGenres();
224
+ const rock = genres.find((g) => g.name === 'Rock');
225
+ const {tracks} = await getChart(rock.id, 20); // this week's rock chart
226
+ const similar = await getRelatedArtists(27); // artists like Daft Punk
227
+ const track = await getTrackByISRC('USUM71311296'); // "Get Lucky"
228
+ ```
229
+
230
+ `getTrackByISRC` / `getAlbumByUPC` return raw public-API objects. To download,
231
+ pass the `id` to `getTrackInfo` / `getAlbumTracks` (or use the converter's
232
+ `isrc2deezer` / `upc2deezer`, which hydrate a gw track for you).
233
+
165
234
  ### `.getTrackDownloadUrl(track, quality);`
166
235
 
167
236
  | Parameters | Required | Type | Description |
package/dist/api/api.js CHANGED
@@ -177,6 +177,9 @@ const getPlaylistChannel = async (page) => {
177
177
  lang: 'en',
178
178
  timezone_offset: '6',
179
179
  };
180
- return await (0, request_1.requestGet)('app_page_get', { gateway_input }, page);
180
+ // `gateway_input` is a nested object; it must reach the gateway as a JSON
181
+ // string, otherwise it serialises to `[object Object]` and Deezer reports the
182
+ // inner `page` as missing (`MISSING_PARAMETER_PAGE`).
183
+ return await (0, request_1.requestGet)('app_page_get', { gateway_input: JSON.stringify(gateway_input) }, page);
181
184
  };
182
185
  exports.getPlaylistChannel = getPlaylistChannel;
@@ -0,0 +1,49 @@
1
+ import type { albumTypePublicApi, artistAlbumResult, chartType, editorialType, genreType, publicApiList, searchResultArtist, searchResultPlaylist, searchResultTrack, trackTypePublicApi } from '../types';
2
+ /**
3
+ * The five ranked lists Deezer publishes for a genre: `tracks`, `albums`,
4
+ * `artists`, `playlists`, `podcasts`. `genreId` `0` (the default) is "all
5
+ * genres"; other ids come from {@link getGenres}.
6
+ */
7
+ export declare const getChart: (genreId?: number | string, limit?: number) => Promise<chartType>;
8
+ /** Just the track chart for a genre (`0` = all). Handy as a ready-to-play list. */
9
+ export declare const getChartTracks: (genreId?: number | string, limit?: number, index?: number) => Promise<publicApiList<searchResultTrack & {
10
+ position: number;
11
+ }>>;
12
+ /** Deezer's genre list — the `id`s feed {@link getChart}, {@link getGenreArtists}, advanced search. */
13
+ export declare const getGenres: () => Promise<publicApiList<genreType>>;
14
+ /** Artists Deezer files under a genre. */
15
+ export declare const getGenreArtists: (genreId: number | string) => Promise<publicApiList<searchResultArtist>>;
16
+ /** Deezer's editorial sections (the `id`s feed {@link getEditorialReleases} / {@link getEditorialSelection}). */
17
+ export declare const getEditorialList: () => Promise<publicApiList<editorialType>>;
18
+ /** New releases for an editorial section (`0` = the default section). */
19
+ export declare const getEditorialReleases: (editorialId?: number | string, limit?: number, index?: number) => Promise<publicApiList<artistAlbumResult>>;
20
+ /** The albums Deezer's editors are currently pushing for a section. */
21
+ export declare const getEditorialSelection: (editorialId?: number | string) => Promise<publicApiList<artistAlbumResult>>;
22
+ /** The editorial charts for a section — same five-list shape as {@link getChart}. */
23
+ export declare const getEditorialCharts: (editorialId?: number | string) => Promise<chartType>;
24
+ /** An artist's most popular tracks. */
25
+ export declare const getArtistTopTracks: (artistId: number | string, limit?: number) => Promise<publicApiList<searchResultTrack>>;
26
+ /** Artists Deezer considers related / similar. */
27
+ export declare const getRelatedArtists: (artistId: number | string, limit?: number) => Promise<publicApiList<searchResultArtist>>;
28
+ /** An artist's discography (public-API album shape). */
29
+ export declare const getArtistAlbums: (artistId: number | string, limit?: number, index?: number) => Promise<publicApiList<artistAlbumResult>>;
30
+ /** Playlists featuring an artist. */
31
+ export declare const getArtistPlaylists: (artistId: number | string, limit?: number) => Promise<publicApiList<searchResultPlaylist>>;
32
+ /** A ready-made radio (track list) seeded from an artist. */
33
+ export declare const getArtistRadioTracks: (artistId: number | string) => Promise<publicApiList<searchResultTrack>>;
34
+ /**
35
+ * Resolve an ISRC to the Deezer **public-API** track (with `bpm`, `gain`,
36
+ * `isrc`, `preview`, `contributors`). Unlike the converter's `isrc2deezer`, this
37
+ * does not hydrate a gw track — pass `result.id` to `getTrackInfo` for that.
38
+ *
39
+ * @throws when Deezer has no track for the code
40
+ */
41
+ export declare const getTrackByISRC: (isrc: string) => Promise<trackTypePublicApi>;
42
+ /**
43
+ * Resolve a UPC / EAN barcode to the Deezer **public-API** album (with its
44
+ * `tracks`). A 13-digit barcode with a leading `0` is trimmed to 12, matching
45
+ * Deezer's own lookup.
46
+ *
47
+ * @throws when Deezer has no album for the code
48
+ */
49
+ export declare const getAlbumByUPC: (upc: string) => Promise<albumTypePublicApi>;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAlbumByUPC = exports.getTrackByISRC = exports.getArtistRadioTracks = exports.getArtistPlaylists = exports.getArtistAlbums = exports.getRelatedArtists = exports.getArtistTopTracks = exports.getEditorialCharts = exports.getEditorialSelection = exports.getEditorialReleases = exports.getEditorialList = exports.getGenreArtists = exports.getGenres = exports.getChartTracks = exports.getChart = 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
+ // ─── Charts ──────────────────────────────────────────────────────────────────
16
+ /**
17
+ * The five ranked lists Deezer publishes for a genre: `tracks`, `albums`,
18
+ * `artists`, `playlists`, `podcasts`. `genreId` `0` (the default) is "all
19
+ * genres"; other ids come from {@link getGenres}.
20
+ */
21
+ const getChart = (genreId = 0, limit = 10) => (0, request_1.requestPublicApi)(withParams(`/chart/${genreId}`, { limit }));
22
+ exports.getChart = getChart;
23
+ /** Just the track chart for a genre (`0` = all). Handy as a ready-to-play list. */
24
+ const getChartTracks = (genreId = 0, limit = 100, index = 0) => (0, request_1.requestPublicApi)(withParams(`/chart/${genreId}/tracks`, { limit, index }));
25
+ exports.getChartTracks = getChartTracks;
26
+ // ─── Genres & editorial ──────────────────────────────────────────────────────
27
+ /** Deezer's genre list — the `id`s feed {@link getChart}, {@link getGenreArtists}, advanced search. */
28
+ const getGenres = () => (0, request_1.requestPublicApi)('/genre');
29
+ exports.getGenres = getGenres;
30
+ /** Artists Deezer files under a genre. */
31
+ const getGenreArtists = (genreId) => (0, request_1.requestPublicApi)(`/genre/${genreId}/artists`);
32
+ exports.getGenreArtists = getGenreArtists;
33
+ /** Deezer's editorial sections (the `id`s feed {@link getEditorialReleases} / {@link getEditorialSelection}). */
34
+ const getEditorialList = () => (0, request_1.requestPublicApi)('/editorial');
35
+ exports.getEditorialList = getEditorialList;
36
+ /** New releases for an editorial section (`0` = the default section). */
37
+ const getEditorialReleases = (editorialId = 0, limit = 25, index = 0) => (0, request_1.requestPublicApi)(withParams(`/editorial/${editorialId}/releases`, { limit, index }));
38
+ exports.getEditorialReleases = getEditorialReleases;
39
+ /** The albums Deezer's editors are currently pushing for a section. */
40
+ const getEditorialSelection = (editorialId = 0) => (0, request_1.requestPublicApi)(`/editorial/${editorialId}/selection`);
41
+ exports.getEditorialSelection = getEditorialSelection;
42
+ /** The editorial charts for a section — same five-list shape as {@link getChart}. */
43
+ const getEditorialCharts = (editorialId = 0) => (0, request_1.requestPublicApi)(`/editorial/${editorialId}/charts`);
44
+ exports.getEditorialCharts = getEditorialCharts;
45
+ // ─── Artist discovery ────────────────────────────────────────────────────────
46
+ /** An artist's most popular tracks. */
47
+ const getArtistTopTracks = (artistId, limit = 50) => (0, request_1.requestPublicApi)(withParams(`/artist/${artistId}/top`, { limit }));
48
+ exports.getArtistTopTracks = getArtistTopTracks;
49
+ /** Artists Deezer considers related / similar. */
50
+ const getRelatedArtists = (artistId, limit = 20) => (0, request_1.requestPublicApi)(withParams(`/artist/${artistId}/related`, { limit }));
51
+ exports.getRelatedArtists = getRelatedArtists;
52
+ /** An artist's discography (public-API album shape). */
53
+ const getArtistAlbums = (artistId, limit = 50, index = 0) => (0, request_1.requestPublicApi)(withParams(`/artist/${artistId}/albums`, { limit, index }));
54
+ exports.getArtistAlbums = getArtistAlbums;
55
+ /** Playlists featuring an artist. */
56
+ const getArtistPlaylists = (artistId, limit = 25) => (0, request_1.requestPublicApi)(withParams(`/artist/${artistId}/playlists`, { limit }));
57
+ exports.getArtistPlaylists = getArtistPlaylists;
58
+ /** A ready-made radio (track list) seeded from an artist. */
59
+ const getArtistRadioTracks = (artistId) => (0, request_1.requestPublicApi)(`/artist/${artistId}/radio`);
60
+ exports.getArtistRadioTracks = getArtistRadioTracks;
61
+ // ─── ISRC / UPC resolution ───────────────────────────────────────────────────
62
+ /**
63
+ * Resolve an ISRC to the Deezer **public-API** track (with `bpm`, `gain`,
64
+ * `isrc`, `preview`, `contributors`). Unlike the converter's `isrc2deezer`, this
65
+ * does not hydrate a gw track — pass `result.id` to `getTrackInfo` for that.
66
+ *
67
+ * @throws when Deezer has no track for the code
68
+ */
69
+ const getTrackByISRC = (isrc) => (0, request_1.requestPublicApi)(`/track/isrc:${encodeURIComponent(isrc)}`);
70
+ exports.getTrackByISRC = getTrackByISRC;
71
+ /**
72
+ * Resolve a UPC / EAN barcode to the Deezer **public-API** album (with its
73
+ * `tracks`). A 13-digit barcode with a leading `0` is trimmed to 12, matching
74
+ * Deezer's own lookup.
75
+ *
76
+ * @throws when Deezer has no album for the code
77
+ */
78
+ const getAlbumByUPC = (upc) => {
79
+ const code = upc.length > 12 && upc.startsWith('0') ? upc.slice(-12) : upc;
80
+ return (0, request_1.requestPublicApi)(`/album/upc:${encodeURIComponent(code)}`);
81
+ };
82
+ exports.getAlbumByUPC = getAlbumByUPC;
@@ -1,2 +1,4 @@
1
1
  export * from './api';
2
2
  export * from './request';
3
+ export * from './search';
4
+ export * from './browse';
package/dist/api/index.js CHANGED
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./api"), exports);
18
18
  __exportStar(require("./request"), exports);
19
+ __exportStar(require("./search"), exports);
20
+ __exportStar(require("./browse"), exports);
@@ -0,0 +1,49 @@
1
+ import type { advancedSearchFilters, publicApiSearchOptions, publicApiSearchResponse, searchResultAlbum, searchResultArtist, searchResultPlaylist, searchResultTrack, suggestResult } from '../types';
2
+ /**
3
+ * Compose Deezer's advanced search operators into one query string.
4
+ *
5
+ * ```ts
6
+ * buildAdvancedQuery({artist: 'daft punk', durMin: 200, durMax: 400});
7
+ * // => 'artist:"daft punk" dur_min:200 dur_max:400'
8
+ * ```
9
+ *
10
+ * Free-text `query` is emitted first and bare. Deezer treats the operators as
11
+ * ranking hints rather than hard filters (and honours `track:` only now and
12
+ * then) — this only builds the string their docs describe. Pair it with
13
+ * `searchPublicApi(query, {strict: true})` to tighten what Deezer allows.
14
+ *
15
+ * The operators are reliable only on the **track** index (`searchPublicApi` /
16
+ * `searchTracks`). `/search/album` and `/search/artist` ignore or mishandle
17
+ * `artist:` / `album:` — use a plain string there.
18
+ */
19
+ export declare const buildAdvancedQuery: (filters: advancedSearchFilters) => string;
20
+ /**
21
+ * Search the **public** Deezer REST API (`api.deezer.com/search`).
22
+ *
23
+ * Unlike `searchMusic` (which drives the internal `deezer.pageSearch` gateway),
24
+ * this returns clean public-API objects — with `isrc`, `preview`, `rank`, real
25
+ * numeric ids — and accepts the advanced query operators (see
26
+ * `buildAdvancedQuery`), an `order`, and `limit` / `index` paging. No auth
27
+ * required; results are memoised by the api layer.
28
+ *
29
+ * @param query a plain string, or the output of `buildAdvancedQuery`
30
+ * @param options `type` (`'track'` default), `order`, `strict`, `limit`, `index`
31
+ */
32
+ export declare const searchPublicApi: <T = searchResultTrack>(query: string, options?: publicApiSearchOptions) => Promise<publicApiSearchResponse<T>>;
33
+ /** `searchPublicApi` fixed to tracks. */
34
+ export declare const searchTracks: (query: string, options?: Omit<publicApiSearchOptions, 'type'>) => Promise<publicApiSearchResponse<searchResultTrack>>;
35
+ /** `searchPublicApi` fixed to albums. */
36
+ export declare const searchAlbums: (query: string, options?: Omit<publicApiSearchOptions, 'type'>) => Promise<publicApiSearchResponse<searchResultAlbum>>;
37
+ /** `searchPublicApi` fixed to artists. */
38
+ export declare const searchArtists: (query: string, options?: Omit<publicApiSearchOptions, 'type'>) => Promise<publicApiSearchResponse<searchResultArtist>>;
39
+ /** `searchPublicApi` fixed to playlists. */
40
+ export declare const searchPlaylists: (query: string, options?: Omit<publicApiSearchOptions, 'type'>) => Promise<publicApiSearchResponse<searchResultPlaylist>>;
41
+ /**
42
+ * `deezer.suggest` — the internal autocomplete endpoint. Cheaper and faster than
43
+ * a full `searchMusic`, and it powers "as you type" suggestion UIs. Needs an
44
+ * initialised session (`initDeezerApi`), like every gateway call.
45
+ *
46
+ * @param query partial query text
47
+ * @param nb max items per type (default 5)
48
+ */
49
+ export declare const suggest: (query: string, nb?: number) => Promise<suggestResult>;
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.suggest = exports.searchPlaylists = exports.searchArtists = exports.searchAlbums = exports.searchTracks = exports.searchPublicApi = exports.buildAdvancedQuery = void 0;
4
+ const request_1 = require("./request");
5
+ /**
6
+ * Compose Deezer's advanced search operators into one query string.
7
+ *
8
+ * ```ts
9
+ * buildAdvancedQuery({artist: 'daft punk', durMin: 200, durMax: 400});
10
+ * // => 'artist:"daft punk" dur_min:200 dur_max:400'
11
+ * ```
12
+ *
13
+ * Free-text `query` is emitted first and bare. Deezer treats the operators as
14
+ * ranking hints rather than hard filters (and honours `track:` only now and
15
+ * then) — this only builds the string their docs describe. Pair it with
16
+ * `searchPublicApi(query, {strict: true})` to tighten what Deezer allows.
17
+ *
18
+ * The operators are reliable only on the **track** index (`searchPublicApi` /
19
+ * `searchTracks`). `/search/album` and `/search/artist` ignore or mishandle
20
+ * `artist:` / `album:` — use a plain string there.
21
+ */
22
+ const buildAdvancedQuery = (filters) => {
23
+ const { query, artist, album, track, label, durMin, durMax, bpmMin, bpmMax } = filters;
24
+ const parts = [];
25
+ if (query && query.trim()) {
26
+ parts.push(query.trim());
27
+ }
28
+ const textOps = [
29
+ ['artist', artist],
30
+ ['album', album],
31
+ ['track', track],
32
+ ['label', label],
33
+ ];
34
+ for (const [op, value] of textOps) {
35
+ if (value && value.trim()) {
36
+ parts.push(`${op}:"${value.trim().replace(/"/g, '')}"`);
37
+ }
38
+ }
39
+ const rangeOps = [
40
+ ['dur_min', durMin],
41
+ ['dur_max', durMax],
42
+ ['bpm_min', bpmMin],
43
+ ['bpm_max', bpmMax],
44
+ ];
45
+ for (const [op, value] of rangeOps) {
46
+ if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {
47
+ parts.push(`${op}:${Math.round(value)}`);
48
+ }
49
+ }
50
+ return parts.join(' ');
51
+ };
52
+ exports.buildAdvancedQuery = buildAdvancedQuery;
53
+ const SEARCH_ENTITIES = ['track', 'album', 'artist', 'playlist', 'user', 'radio', 'podcast'];
54
+ /**
55
+ * Search the **public** Deezer REST API (`api.deezer.com/search`).
56
+ *
57
+ * Unlike `searchMusic` (which drives the internal `deezer.pageSearch` gateway),
58
+ * this returns clean public-API objects — with `isrc`, `preview`, `rank`, real
59
+ * numeric ids — and accepts the advanced query operators (see
60
+ * `buildAdvancedQuery`), an `order`, and `limit` / `index` paging. No auth
61
+ * required; results are memoised by the api layer.
62
+ *
63
+ * @param query a plain string, or the output of `buildAdvancedQuery`
64
+ * @param options `type` (`'track'` default), `order`, `strict`, `limit`, `index`
65
+ */
66
+ const searchPublicApi = (query, options = {}) => {
67
+ const { type, order, strict, limit, index } = options;
68
+ const segment = type && type !== 'track' && SEARCH_ENTITIES.includes(type) ? `/${type}` : '';
69
+ const search = new URLSearchParams({ q: query });
70
+ if (strict) {
71
+ search.set('strict', 'on');
72
+ }
73
+ if (order) {
74
+ search.set('order', order);
75
+ }
76
+ if (typeof limit === 'number') {
77
+ search.set('limit', String(limit));
78
+ }
79
+ if (typeof index === 'number') {
80
+ search.set('index', String(index));
81
+ }
82
+ return (0, request_1.requestPublicApi)(`/search${segment}?${search.toString()}`);
83
+ };
84
+ exports.searchPublicApi = searchPublicApi;
85
+ /** `searchPublicApi` fixed to tracks. */
86
+ const searchTracks = (query, options = {}) => (0, exports.searchPublicApi)(query, { ...options, type: 'track' });
87
+ exports.searchTracks = searchTracks;
88
+ /** `searchPublicApi` fixed to albums. */
89
+ const searchAlbums = (query, options = {}) => (0, exports.searchPublicApi)(query, { ...options, type: 'album' });
90
+ exports.searchAlbums = searchAlbums;
91
+ /** `searchPublicApi` fixed to artists. */
92
+ const searchArtists = (query, options = {}) => (0, exports.searchPublicApi)(query, { ...options, type: 'artist' });
93
+ exports.searchArtists = searchArtists;
94
+ /** `searchPublicApi` fixed to playlists. */
95
+ const searchPlaylists = (query, options = {}) => (0, exports.searchPublicApi)(query, { ...options, type: 'playlist' });
96
+ exports.searchPlaylists = searchPlaylists;
97
+ /**
98
+ * `deezer.suggest` — the internal autocomplete endpoint. Cheaper and faster than
99
+ * a full `searchMusic`, and it powers "as you type" suggestion UIs. Needs an
100
+ * initialised session (`initDeezerApi`), like every gateway call.
101
+ *
102
+ * @param query partial query text
103
+ * @param nb max items per type (default 5)
104
+ */
105
+ const suggest = (query, nb = 5) => (0, request_1.requestLight)({
106
+ QUERY: query,
107
+ NB: nb,
108
+ TYPES: { ALBUM: true, ARTIST: true, TRACK: true, PLAYLIST: true, RADIO: true, SHOW: true },
109
+ }, 'deezer.suggest');
110
+ exports.suggest = suggest;
@@ -0,0 +1,85 @@
1
+ import type { searchResultAlbum, searchResultArtist, searchResultPlaylist, searchResultTrack } from './search';
2
+ /** The `{data, total, next?, prev?}` envelope every paginated public-API list uses. */
3
+ export interface publicApiList<T> {
4
+ data: T[];
5
+ total?: number;
6
+ /** absolute URL of the next page, when there is one */
7
+ next?: string;
8
+ prev?: string;
9
+ }
10
+ export interface chartTrack extends searchResultTrack {
11
+ /** 1-based position in the chart */
12
+ position: number;
13
+ }
14
+ export interface chartArtist extends searchResultArtist {
15
+ position: number;
16
+ }
17
+ export interface chartAlbum extends searchResultAlbum {
18
+ position: number;
19
+ }
20
+ export interface chartPlaylist extends searchResultPlaylist {
21
+ position: number;
22
+ }
23
+ export interface chartPodcast {
24
+ id: number;
25
+ title: string;
26
+ description?: string;
27
+ available?: boolean;
28
+ fans?: number;
29
+ link?: string;
30
+ share?: string;
31
+ picture: string;
32
+ picture_small?: string;
33
+ picture_medium?: string;
34
+ picture_big?: string;
35
+ picture_xl?: string;
36
+ position: number;
37
+ type: 'podcast';
38
+ }
39
+ /** `/chart/{genreId}` — the five ranked lists Deezer publishes per genre (`0` = all genres). */
40
+ export interface chartType {
41
+ tracks: publicApiList<chartTrack>;
42
+ albums: publicApiList<chartAlbum>;
43
+ artists: publicApiList<chartArtist>;
44
+ playlists: publicApiList<chartPlaylist>;
45
+ podcasts: publicApiList<chartPodcast>;
46
+ }
47
+ export interface genreType {
48
+ id: number;
49
+ name: string;
50
+ picture: string;
51
+ picture_small?: string;
52
+ picture_medium?: string;
53
+ picture_big?: string;
54
+ picture_xl?: string;
55
+ type: 'genre';
56
+ }
57
+ export interface editorialType {
58
+ id: number;
59
+ name: string;
60
+ picture: string;
61
+ picture_small?: string;
62
+ picture_medium?: string;
63
+ picture_big?: string;
64
+ picture_xl?: string;
65
+ type: 'editorial';
66
+ }
67
+ /** An album from `/artist/{id}/albums` — the artist's own discography, public-API shape. */
68
+ export interface artistAlbumResult {
69
+ id: number;
70
+ title: string;
71
+ link: string;
72
+ cover: string;
73
+ cover_small?: string;
74
+ cover_medium?: string;
75
+ cover_big?: string;
76
+ cover_xl?: string;
77
+ md5_image: string;
78
+ genre_id: number;
79
+ fans?: number;
80
+ release_date: string;
81
+ record_type: string;
82
+ tracklist: string;
83
+ explicit_lyrics: boolean;
84
+ type: 'album';
85
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,5 +1,6 @@
1
1
  export * from './album';
2
2
  export * from './artist';
3
+ export * from './browse';
3
4
  export * from './show';
4
5
  export * from './playlist';
5
6
  export * from './playlist-channel';
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./album"), exports);
18
18
  __exportStar(require("./artist"), exports);
19
+ __exportStar(require("./browse"), exports);
19
20
  __exportStar(require("./show"), exports);
20
21
  __exportStar(require("./playlist"), exports);
21
22
  __exportStar(require("./playlist-channel"), exports);
@@ -47,6 +47,163 @@ export interface discographyType {
47
47
  start: number;
48
48
  nb: number;
49
49
  }
50
+ /**
51
+ * Deezer's advanced search operators. Compose these into one query string with
52
+ * `buildAdvancedQuery()` — e.g. `{artist: 'daft punk', durMin: 200}` becomes
53
+ * `artist:"daft punk" dur_min:200`.
54
+ *
55
+ * Deezer applies these as ranking hints, not hard filters, and honours `track:`
56
+ * only intermittently on their side — this type just mirrors the syntax they
57
+ * document.
58
+ */
59
+ export interface advancedSearchFilters {
60
+ /** free-text terms, emitted first and bare */
61
+ query?: string;
62
+ artist?: string;
63
+ album?: string;
64
+ track?: string;
65
+ label?: string;
66
+ /** minimum duration, seconds */
67
+ durMin?: number;
68
+ /** maximum duration, seconds */
69
+ durMax?: number;
70
+ /** minimum beats-per-minute */
71
+ bpmMin?: number;
72
+ /** maximum beats-per-minute */
73
+ bpmMax?: number;
74
+ }
75
+ export type searchOrder = 'RANKING' | 'TRACK_ASC' | 'TRACK_DESC' | 'ARTIST_ASC' | 'ARTIST_DESC' | 'ALBUM_ASC' | 'ALBUM_DESC' | 'RATING_ASC' | 'RATING_DESC' | 'DURATION_ASC' | 'DURATION_DESC';
76
+ export type searchEntity = 'track' | 'album' | 'artist' | 'playlist' | 'user' | 'radio' | 'podcast';
77
+ export interface publicApiSearchOptions {
78
+ /** which index to hit — `track` (default), `album`, `artist`, `playlist`, `user`, `radio`, `podcast` */
79
+ type?: searchEntity;
80
+ order?: searchOrder;
81
+ /** send Deezer's `strict=on` — disables the fuzzy fallback */
82
+ strict?: boolean;
83
+ /** page size; Deezer caps this near 100 */
84
+ limit?: number;
85
+ /** offset into the result set */
86
+ index?: number;
87
+ }
88
+ export interface publicApiSearchResponse<T> {
89
+ data: T[];
90
+ total: number;
91
+ /** absolute URL of the next page, when there is one */
92
+ next?: string;
93
+ prev?: string;
94
+ }
95
+ interface searchArtistRef {
96
+ id: number;
97
+ name: string;
98
+ link?: string;
99
+ picture?: string;
100
+ picture_small?: string;
101
+ picture_medium?: string;
102
+ picture_big?: string;
103
+ picture_xl?: string;
104
+ tracklist?: string;
105
+ type: 'artist';
106
+ }
107
+ interface searchAlbumRef {
108
+ id: number;
109
+ title: string;
110
+ cover?: string;
111
+ cover_small?: string;
112
+ cover_medium?: string;
113
+ cover_big?: string;
114
+ cover_xl?: string;
115
+ md5_image?: string;
116
+ tracklist?: string;
117
+ type: 'album';
118
+ }
119
+ export interface searchResultTrack {
120
+ id: number;
121
+ readable: boolean;
122
+ title: string;
123
+ title_short: string;
124
+ title_version?: string;
125
+ link: string;
126
+ duration: number;
127
+ rank: number;
128
+ explicit_lyrics: boolean;
129
+ explicit_content_lyrics: number;
130
+ explicit_content_cover: number;
131
+ preview: string;
132
+ md5_image: string;
133
+ isrc?: string;
134
+ artist: searchArtistRef;
135
+ album: searchAlbumRef;
136
+ type: 'track';
137
+ }
138
+ export interface searchResultAlbum {
139
+ id: number;
140
+ title: string;
141
+ link: string;
142
+ cover: string;
143
+ cover_small?: string;
144
+ cover_medium?: string;
145
+ cover_big?: string;
146
+ cover_xl?: string;
147
+ md5_image: string;
148
+ genre_id: number;
149
+ nb_tracks: number;
150
+ record_type: string;
151
+ explicit_lyrics: boolean;
152
+ artist: searchArtistRef;
153
+ type: 'album';
154
+ }
155
+ export interface searchResultArtist {
156
+ id: number;
157
+ name: string;
158
+ link: string;
159
+ picture: string;
160
+ picture_small?: string;
161
+ picture_medium?: string;
162
+ picture_big?: string;
163
+ picture_xl?: string;
164
+ nb_album: number;
165
+ nb_fan: number;
166
+ radio: boolean;
167
+ tracklist: string;
168
+ type: 'artist';
169
+ }
170
+ export interface searchResultPlaylist {
171
+ id: number;
172
+ title: string;
173
+ public: boolean;
174
+ nb_tracks: number;
175
+ link: string;
176
+ picture: string;
177
+ picture_small?: string;
178
+ picture_medium?: string;
179
+ picture_big?: string;
180
+ picture_xl?: string;
181
+ checksum?: string;
182
+ tracklist: string;
183
+ creation_date?: string;
184
+ user?: {
185
+ id: number;
186
+ name: string;
187
+ tracklist?: string;
188
+ type: 'user';
189
+ };
190
+ type: 'playlist';
191
+ }
192
+ /**
193
+ * `deezer.suggest` — lightweight autocomplete off the internal gateway. Each
194
+ * per-type array is gw-shaped (uppercase keys), same as `searchMusic`'s.
195
+ */
196
+ export interface suggestResult {
197
+ QUERY: string;
198
+ TOP_RESULT: unknown[];
199
+ ORDER: string[];
200
+ ALBUM?: albumTypeMinimal[];
201
+ ARTIST?: artistInfoTypeMinimal[];
202
+ TRACK?: trackType[];
203
+ PLAYLIST?: playlistInfoMinimal[];
204
+ SHOW?: unknown[];
205
+ RADIO?: radioType[];
206
+ }
50
207
  export interface searchType {
51
208
  QUERY: string;
52
209
  FUZZINNESS: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gerdur-core",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Core module for gerdur.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",