gerdur-core 2.0.0 → 2.1.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,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0 - 2026-08-31
4
+
5
+ Phase 2.5 — search, properly. All additive.
6
+
7
+ ### Added
8
+
9
+ - **`searchPublicApi(query, options?)`** — search the public REST API
10
+ (`api.deezer.com/search`) rather than the internal `pageSearch` gateway.
11
+ Returns clean public-API objects (`isrc`, `preview`, `rank`, numeric ids) and
12
+ takes `type` (`track` | `album` | `artist` | `playlist` | `user` | `radio` |
13
+ `podcast`), `order`, `strict`, and `limit` / `index` paging. No auth required.
14
+ - **`searchTracks` / `searchAlbums` / `searchArtists` / `searchPlaylists`** —
15
+ thin typed wrappers over `searchPublicApi` with the entity fixed.
16
+ - **`buildAdvancedQuery(filters)`** — pure helper that composes Deezer's advanced
17
+ operators into one query string: `{artist, album, track, label}` →
18
+ `artist:"…"`, `{durMin, durMax, bpmMin, bpmMax}` → `dur_min:NNN` / `bpm_min:NNN`,
19
+ free-text `query` first. Reliable on the track index only (Deezer's
20
+ `/search/album` and `/search/artist` ignore the operators).
21
+ - **`suggest(query, nb?)`** — the `deezer.suggest` autocomplete endpoint, for
22
+ "as you type" UIs; cheaper than a full `searchMusic`.
23
+ - New exported types: `advancedSearchFilters`, `searchOrder`, `searchEntity`,
24
+ `publicApiSearchOptions`, `publicApiSearchResponse<T>`, `searchResultTrack`,
25
+ `searchResultAlbum`, `searchResultArtist`, `searchResultPlaylist`,
26
+ `suggestResult`.
27
+
28
+ ### Fixed
29
+
30
+ - **`getPlaylistChannel`** returned `MISSING_PARAMETER_PAGE` — the nested
31
+ `gateway_input` was serialised as `[object Object]`. It is now JSON-stringified
32
+ before the request, so channel pages resolve again.
33
+
3
34
  ## 2.0.0 - 2026-08-31
4
35
 
5
36
  Metadata overhaul — extract everything Deezer actually exposes for a track.
package/README.md CHANGED
@@ -162,6 +162,40 @@ 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
+
165
199
  ### `.getTrackDownloadUrl(track, quality);`
166
200
 
167
201
  | 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;
@@ -1,2 +1,3 @@
1
1
  export * from './api';
2
2
  export * from './request';
3
+ export * from './search';
package/dist/api/index.js CHANGED
@@ -16,3 +16,4 @@ 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);
@@ -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;
@@ -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.1.0",
4
4
  "description": "Core module for gerdur.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",