UNPKG

deezer-ts

Version:

Deezer API wrapper for Node.js

1,187 lines (1,171 loc) 34.5 kB
// src/exceptions.ts var DeezerAPIException = class extends Error { constructor(message) { super(message); this.name = "DeezerAPIException"; } }; var DeezerRetryableException = class extends DeezerAPIException { constructor(message) { super(message); this.name = "DeezerRetryableException"; } }; var DeezerQuotaExceededError = class extends DeezerRetryableException { constructor() { super("Quota limit exceeded"); this.name = "DeezerQuotaExceededError"; } }; var DeezerHTTPError = class _DeezerHTTPError extends DeezerAPIException { constructor(response) { super(`HTTP ${response.status}: ${response.statusText}`); this.name = "DeezerHTTPError"; } /** * Creates the appropriate HTTP error based on the response status code. * * @param response - The HTTP response object * @returns A specific HTTP error instance * @internal */ static fromResponse(response) { if ([502, 503, 504].includes(response.status)) { return new DeezerRetryableHTTPError(response); } if (response.status === 403) { return new DeezerForbiddenError(response); } if (response.status === 404) { return new DeezerNotFoundError(response); } if (response.status === 429) { return new DeezerQuotaExceededError(); } return new _DeezerHTTPError(response); } }; var DeezerRetryableHTTPError = class extends DeezerRetryableException { constructor(response) { super(`Retryable HTTP ${response.status}: ${response.statusText}`); this.name = "DeezerRetryableHTTPError"; } }; var DeezerForbiddenError = class extends DeezerHTTPError { constructor(response) { super(response); this.name = "DeezerForbiddenError"; } }; var DeezerNotFoundError = class extends DeezerHTTPError { constructor(response) { super(response); this.name = "DeezerNotFoundError"; } }; var DeezerErrorResponse = class extends DeezerAPIException { constructor(error) { super(error.message || "Unknown API error"); this.name = "DeezerErrorResponse"; if (error.message === "Quota limit exceeded") { throw new DeezerQuotaExceededError(); } } }; var DeezerUnknownResource = class extends DeezerAPIException { constructor(message) { super(message); this.name = "DeezerUnknownResource"; } }; // src/pagination.ts var PaginatedList = class { constructor(client, basePath, parent, params) { this.client = client; this.basePath = basePath; this.parent = parent; this.params = params; this.elements = []; this.nextParams = {}; this.totalItems = null; this.nextPath = basePath; this.nextParams = { ...params }; } async *[Symbol.asyncIterator]() { for (const element of this.elements) { yield element; } while (await this.couldGrow()) { const newElements = await this.grow(); for (const element of newElements) { yield element; } } } /** * The total number of items in the list, mirroring what Deezer returns. * * @returns {Promise<number | null>} - The total number of items in the list. */ async total() { if (this.totalItems === null) { const params = { ...this.params, limit: "1" }; const response = await this.client.request( "GET", this.basePath, true, this.parent, void 0, void 0, params ); this.totalItems = response.total; } return this.totalItems; } /** * Returns a slice of the list. * * @param {number} start - The index to start the slice at. * @param {number} end - The index to end the slice at. * * @returns {Promise<T[]>} - The slice of the list. */ async slice(start = 0, end) { const results = []; let count = 0; for await (const item of this) { if (count >= start) { results.push(item); } count++; if (end !== void 0 && count >= end) { break; } } return results; } /** * Returns the item at the given index. * * @param {number} index - The index of the item to return. * * @returns {Promise<T>} - The item at the given index. */ async get(index) { const items = await this.slice(index, index + 1); if (items.length === 0 || !items[0]) { throw new Error(`Index ${index} is out of bounds`); } return items[0]; } /** * Returns the list as an array. * * This method is not recommended for large lists, as it will fetch all items at once. * * @returns {Promise<T[]>} - The list as an array. */ async toArray() { const results = []; for await (const item of this) { results.push(item); } return results; } async couldGrow() { return this.nextPath !== null; } async grow() { const newElements = await this.fetchNextPage(); this.elements.push(...newElements); return newElements; } async fetchNextPage() { if (!this.nextPath) { return []; } const response = await this.client.request( "GET", this.nextPath, true, this.parent, void 0, void 0, this.nextParams ); this.totalItems = response.total; if (response.next) { const url = new URL(response.next); this.nextPath = url.pathname.substring(1); this.nextParams = Object.fromEntries(url.searchParams); } else { this.nextPath = null; } return response.data; } }; // src/date.ts function parseDate(dateStr) { if (!dateStr || dateStr.startsWith("0000-00-00")) { return null; } const date = new Date(dateStr); return isNaN(date.getTime()) ? null : date; } // src/resources/resource.ts var Resource = class _Resource { // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(client, json) { this._fetched = false; this.client = client; for (const fieldName of Object.keys(json)) { const parseFunc = this[`_parse_${fieldName}`]; if (typeof parseFunc === "function") { json[fieldName] = parseFunc.call(this, json[fieldName]); } } this._fields = Object.keys(json); Object.assign(this, json); } // eslint-disable-next-line @typescript-eslint/no-explicit-any toJSON() { const result = {}; for (const key of this._fields) { let value = this[key]; if (Array.isArray(value)) { value = value.map((i) => i instanceof _Resource ? i.toJSON() : i); } else if (value instanceof _Resource) { value = value.toJSON(); } else if (value instanceof Date) { value = value.toISOString(); if (value.endsWith("T00:00:00.000Z")) { value = value.slice(0, 10); } } result[key] = value; } return result; } async getRelation(relation, resourceType, params, fwdParent = true) { return this.client.request( "GET", `${this.type}/${this.id}/${relation}`, false, fwdParent ? this : void 0, resourceType, void 0, params ); } getPaginatedList(relation, params) { return new PaginatedList( this.client, `${this.type}/${this.id}/${relation}`, this, params ); } /** * Ensures a field is loaded, fetching the full resource if necessary. * * @param fieldName The name of the field to ensure * @returns The value of the field * @throws Error if the field cannot be loaded */ async ensureField(fieldName) { if (!this._fields.includes(fieldName) || // eslint-disable-next-line @typescript-eslint/no-explicit-any this[fieldName] === void 0) { if (!this._fetched) { const fullResource = await this.get(); const missingFields = Object.keys(fullResource).filter( (field) => !this._fields.includes(field) && !["client", "_fields", "_fetched"].includes(field) ); for (const field of missingFields) { this[field] = fullResource[field]; this._fields.push(field); } if (this._fields.includes(fieldName) && // eslint-disable-next-line @typescript-eslint/no-explicit-any this[fieldName] !== void 0) { return this[fieldName]; } } throw new Error( `Field '${fieldName}' is not available on ${this.constructor.name} with id ${this.id}` ); } return this[fieldName]; } /** * Get the resource from the API. * * @returns {Promise<this>} */ async get() { this._fetched = true; return await this.client.request("GET", `${this.type}/${this.id}`, false); } }; // src/resources/artist.ts var Artist = class extends Resource { /** * Get the top tracks of an artist. * * @returns {Promise<PaginatedList<Track>>} - a {@link PaginatedList} of {@link Track} instances. */ async getTop(params) { return this.getPaginatedList("top", params); } /** * Get a list of related artists. * * @returns {Promise<PaginatedList<Artist>>} - a {@link PaginatedList} of {@link Artist} instances. */ async getRelated(params) { return this.getPaginatedList("related", params); } /** * Get a list of tracks. * * @returns {Promise<Track[]>} - list of {@link Track} instances. */ async getRadio(params) { return this.getRelation("radio", void 0, params, false); } /** * Get a list of artist's albums. * * @returns {Promise<PaginatedList<Album>>} - a {@link PaginatedList} of {@link Album} instances. */ async getAlbums(params) { return this.getPaginatedList("albums", params); } /** * Get a list of artist's playlists. * * @returns {Promise<PaginatedList<Playlist>>} - a {@link PaginatedList} of {@link Playlist} instances. */ async getPlaylists(params) { return this.getPaginatedList("playlists", params); } }; // src/resources/album.ts var Album = class extends Resource { /** * @internal */ _parse_release_date(params) { return parseDate(params); } /** * @internal */ _parse_contributors(rawValue) { return rawValue.map((val) => new Artist(this.client, val)); } /** * Get the artist of the Album. * * @returns {Promise<Artist>} - the class {@link Artist} of the Album. */ async getArtist() { const artist = await this.ensureField("artist"); return this.client.getArtist(artist.id); } /** * Get a list of album's tracks. * * @returns {Promise<PaginatedList<Track>>} - a {@link PaginatedList} of {@link Track}. */ async getTracks(params) { return this.getPaginatedList("tracks", params); } }; // src/resources/chart.ts var Chart = class extends Resource { constructor() { super(...arguments); this.type = "chart"; } /** * Return the chart for tracks. * * @returns {Promise<PaginatedList<Track>>} - a {@link PaginatedList} of {@link Track} instances. */ async getTracks(params) { return this.getPaginatedList("tracks", params); } /** * Return the chart for albums. * * @returns {Promise<PaginatedList<Album>>} - a {@link PaginatedList} of {@link Album} instances. */ async getAlbums(params) { return this.getPaginatedList("albums", params); } /** * Return the chart for artists. * * @returns {Promise<PaginatedList<Artist>>} - a {@link PaginatedList} of {@link Artist} instances. */ async getArtists(params) { return this.getPaginatedList("artists", params); } /** * Return the chart for playlists. * * @returns {Promise<PaginatedList<Playlist>>} - a {@link PaginatedList} of {@link Playlist} instances. */ async getPlaylists(params) { return this.getPaginatedList("playlists", params); } /** * Return the chart for podcasts. * * @returns {Promise<PaginatedList<Podcast>>} - a {@link PaginatedList} of {@link Podcast} instances. */ async getPodcasts(params) { return this.getPaginatedList("podcasts", params); } }; // src/resources/editorial.ts var Editorial = class extends Resource { /** * Get a list of albums selected every week by the Deezer Team. * * @returns {Promise<Album[]>} - list of {@link Album} instances. */ async getSelection() { return this.getRelation("selection"); } /** * Get top charts for tracks, albums, artists and playlists. * * @returns {Promise<Chart>} - a {@link Chart} instances. */ async getChart() { return this.getRelation("charts", Chart); } /** * Get the new releases per genre for the current country. * * @returns {Promise<PaginatedList<Album>>} - a {@link PaginatedList} of {@link Album} instances. */ async getReleases(params) { return this.getPaginatedList("releases", params); } }; // src/resources/episode.ts var Episode = class extends Resource { /** * @internal */ _parse_release_date(params) { return parseDate(params); } }; // src/resources/genre.ts var Genre = class extends Resource { /** * Get all artists for a genre. * * @returns {Promise<Artist[]>} - list of {@link Artist} instances. */ async getArtists(params) { return this.getRelation("artists", void 0, params); } /** * Get all podcasts for a genre. * * @returns {Promise<PaginatedList<Podcast>>} - a {@link PaginatedList} of {@link Podcast} instances. */ async getPodcasts(params) { return this.getPaginatedList("podcasts", params); } /** * Get all radios for a genre. * * @returns {Promise<Radio[]>} - list of {@link Artist} instances. */ async getRadios(params) { return this.getRelation("radios", void 0, params); } }; // src/resources/playlist.ts var Playlist = class extends Resource { /** * Get tracks from a playlist. * * @returns {Promise<PaginatedList<Track>>} - a {@link PaginatedList} of {@link Track} instances. */ async getTracks(params) { return this.getPaginatedList("tracks", params); } /** * Get fans from a playlist. * * @returns {Promise<PaginatedList<User>>} - a {@link PaginatedList} of {@link User} instances. */ async getFans(params) { return this.getPaginatedList("fans", params); } }; // src/resources/podcast.ts var Podcast = class extends Resource { /** * Get episodes from a podcast. * * @returns {Promise<PaginatedList<Episode>>} - a {@link PaginatedList} of {@link Episode} instances. */ async getEpisodes(params) { return this.getPaginatedList("episodes", params); } }; // src/resources/radio.ts var Radio = class extends Resource { /** * Get first 40 tracks in the radio. * * Note that this endpoint is NOT paginated. * * @returns {Promise<Track[]>} - list of {@link Track} instances. */ async getTracks() { return this.getRelation("tracks"); } }; // src/resources/track.ts var Track = class extends Resource { /** * @internal */ _parse_release_date(params) { return new Date(params); } /** * @internal */ _parse_contributors(rawValue) { return rawValue.map((val) => new Artist(this.client, val)); } /** * Get the artist of the Track. * * @returns {Promise<Artist>} - the class {@link Artist} of the Track. */ async getArtist() { return this.client.getArtist(this.artist.id); } /** * Get the album of the Track. * * @returns {Promise<Album>} - the class {@link Album} of the Track. */ async getAlbum() { return this.client.getAlbum(this.album.id); } }; // src/resources/user.ts var User = class extends Resource { /** * Get user's favorite albums. * * @returns {Promise<PaginatedList<Album>>} - a {@link PaginatedList} of {@link Album} instances. */ async getAlbums(params) { return this.getPaginatedList("albums", params); } /** * Get user's favorite artists. * * @returns {Promise<PaginatedList<Artist>>} - a {@link PaginatedList} of {@link Artist} instances. */ async getArtists(params) { return this.getPaginatedList("artists", params); } /** * Get user's followings. * * @returns {Promise<PaginatedList<User>>} - a {@link PaginatedList} of {@link User} instances. */ async getFollowers(params) { return this.getPaginatedList("followers", params); } /** * Get user's followers. * * @returns {Promise<PaginatedList<User>>} - a {@link PaginatedList} of {@link User} instances. */ async getFollowings(params) { return this.getPaginatedList("followings", params); } /** * Get user's public playlists. * * @returns {Promise<PaginatedList<Playlist>>} - a {@link PaginatedList} of {@link Playlist} instances. */ async getPlaylists(params) { return this.getPaginatedList("playlists", params); } }; // src/client.ts var _Client = class _Client { // 45 requests per 5 seconds /** * Create a new client instance. * * @param {Record<string, string>} [headers] - Additional headers to pass. */ constructor(headers) { this.headers = headers; /** * @internal */ this.baseUrl = "https://api.deezer.com"; /** * @internal */ this.objectsTypes = { album: Album, artist: Artist, chart: Chart, editorial: Editorial, episode: Episode, genre: Genre, playlist: Playlist, podcast: Podcast, radio: Radio, search: null, track: Track, user: User }; } /** * @internal * Ensures we don't exceed Deezer's rate limit of 50 requests per 5 seconds */ async enforceRateLimit() { const now = Date.now(); _Client.requestQueue = _Client.requestQueue.filter( (req) => now - req.timestamp < _Client.QUOTA_WINDOW ); if (_Client.requestQueue.length >= _Client.QUOTA_LIMIT) { const oldestRequest = _Client.requestQueue[0]; if (oldestRequest) { const waitTime = _Client.QUOTA_WINDOW - (now - oldestRequest.timestamp); if (waitTime > 0) { await new Promise((resolve) => setTimeout(resolve, waitTime)); } } } _Client.requestQueue.push({ timestamp: Date.now() }); } /** * @internal * Retries an operation with exponential backoff when encountering retryable errors. * * This method implements an exponential backoff strategy for retrying failed operations: * - Initial delay is baseDelay (default 2000ms) * - Each retry doubles the delay * - Adds random jitter (75-125% of calculated delay) to prevent thundering herd * - Only retries on quota exceeded or other retryable errors * * @param {() => Promise<T>} operation - The async operation to retry * @param {number} maxRetries - Maximum number of retry attempts (default: 3) * @param {number} baseDelay - Base delay in milliseconds before first retry (default: 2000) * @returns {Promise<T>} The result of the operation if successful * @throws {Error} The last error encountered if all retries fail * @throws {Error} Non-retryable errors immediately without retry * @template T - The return type of the operation */ async retryWithBackoff(operation, maxRetries = 3, baseDelay = 5e3) { let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await operation(); } catch (error) { if (error instanceof DeezerQuotaExceededError || error instanceof DeezerRetryableException) { lastError = error; const delay = baseDelay * Math.pow(2, attempt) * (0.75 + Math.random() * 0.5); await new Promise((resolve) => setTimeout(resolve, delay)); continue; } throw error; } } throw lastError; } /** * @internal * * Recursively convert dictionary to Resource object. * * @param item - the JSON response as object. * @param parent - A reference to the parent resource, to avoid fetching again. * @param resourceType - The resource class to use as top level. * @param resourceId - The resource id to use as top level. * @param paginateList - Whether to wrap list into a pagination object. * @returns instance of Resource * @throws DeezerUnknownResource */ _processJson(item, paginateList = false, parent, resourceType, resourceId) { if (item.data) { const parsedData = item.data.map( (i) => this._processJson(i, false, parent) ); if (!paginateList) { return parsedData; } item.data = parsedData; return item; } const result = {}; for (const key in item) { let value = item[key]; if (value !== null && typeof value === "object" && ("type" in value || "data" in value)) { value = this._processJson(value, false, parent); } result[key] = value; } if (parent) { result[parent.type] = parent; } if (result.id === void 0 && resourceId !== null) { result.id = resourceId; } let objectClass = null; if (result.type && result.type in this.objectsTypes) { objectClass = this.objectsTypes[result.type]; } else if (result.type || !resourceType && result.id) { objectClass = Resource; } else if (resourceType) { objectClass = resourceType; } else { throw new DeezerUnknownResource( `Unable to find resource type for ${JSON.stringify(result)}` ); } if (objectClass === null) { throw new DeezerUnknownResource( `Object class is null for ${JSON.stringify(result)}` ); } debugger; return new objectClass(this, result); } async request(method, path, paginateList = false, parent, resourceType, resourceId, params) { return this.retryWithBackoff(async () => { await this.enforceRateLimit(); const url = new URL(`${this.baseUrl}/${path}`); if (params) { Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, String(value)); }); } const response = await fetch(url, { method, headers: { ...this.headers } }); if (!response.ok) { throw DeezerHTTPError.fromResponse(response); } const data = await response.json(); if (data.error) { throw new DeezerErrorResponse(data.error); } return this._processJson( data, paginateList, parent, resourceType, resourceId ); }); } /** * @internal * @private */ getPaginatedList(path, params) { return new PaginatedList(this, path, void 0, params); } /** * Get the album with the given id. * * @param {number} albumId - The id of the album to get. * @returns {@link Album} - An album object. */ async getAlbum(albumId) { return this.request("GET", `album/${albumId}`, false); } /** * Get the artist with the given id. * * @param {number} artistId - The id of the artist to get. * @returns {@link Artist} - An artist object. */ async getArtist(artistId) { return this.request("GET", `artist/${artistId}`, false); } /** * Get overall charts for tracks, albums, artists and playlists for the given genre ID. * * Combine charts of several resources in one endpoint. * * @param {number} genreId - The genre id, default to `All` genre (genreId = 0). * @returns {@link Chart} - A chart instance. */ async getChart(genreId = 0) { return this.request( "GET", `chart/${genreId}`, false, void 0, Chart, genreId ); } /** * Get top tracks for the given genre id. * * @param {number} genreId - The genre id, default to `All` genre (genreId = 0). * @returns A list of {@link Track} instances. */ async getTracksChart(genreId = 0) { return this.request("GET", `chart/${genreId}/tracks`, false); } /** * Get top albums for the given genre id. * * @param {number} genreId - The genre id, default to `All` genre (genreId = 0). * @returns A list of {@link Album} instances. */ async getAlbumsChart(genreId = 0) { return this.request("GET", `chart/${genreId}/albums`, false); } /** * Get top artists for the given genre id. * * @param {number} genreId - The genre id, default to `All` genre (genreId = 0). * @returns A list of {@link Artist} instances. */ async getArtistsChart(genreId = 0) { return this.request("GET", `chart/${genreId}/artists`, false); } /** * Get top playlists for the given genre id. * * @param {number} genreId - The genre id, default to `All` genre (genreId = 0). * @returns A list of {@link Playlist} instances. */ async getPlaylistsChart(genreId = 0) { return this.request("GET", `chart/${genreId}/playlists`, false); } /** * Get top podcasts for the given genre id. * * @param {number} genreId - The genre id, default to `All` genre (genreId = 0). * @returns A list of {@link Podcast} instances. */ async getPodcastsChart(genreId = 0) { return this.request("GET", `chart/${genreId}/podcasts`, false); } /** * Get the editorial with the given id. * * @param {number} editorialId - The id of the editorial to get. * @returns a {@link Editorial} object. */ async getEditorial(editorialId) { return this.request("GET", `editorial/${editorialId}`, false); } /** * List editorials. * * @returns {@link PaginatedList} of {@link Editorial} - An editorial object. */ async listEditorials() { return this.getPaginatedList("editorial"); } /** * Get the episode with the given id. * * @param {number} episodeId - The id of the episode to get. * @returns {@link Episode} - An episode object. */ async getEpisode(episodeId) { return this.request("GET", `episode/${episodeId}`, false); } /** * Get the genre with the given id. * * @param {number} genreId - The id of the genre to get. * @returns {@link Genre} - A genre object. */ async getGenre(genreId) { return this.request("GET", `genre/${genreId}`, false); } /** * Get the playlist with the given id. * * @param {number} playlistId - The id of the playlist to get. * @returns {@link Playlist} - A playlist object. */ async getPlaylist(playlistId) { return this.request("GET", `playlist/${playlistId}`, false); } /** * Get the podcast with the given id. * * @param {number} podcastId - The id of the podcast to get. * @returns {@link Podcast} - A podcast object. */ async getPodcast(podcastId) { return this.request("GET", `podcast/${podcastId}`, false); } /** * Get the radio with the given id. * * @param {number} radioId - The id of the radio to get. * @returns {@link Radio} - A radio object. */ async getRadio(radioId) { return this.request("GET", `radio/${radioId}`, false); } /** * List radios. * * @returns A list of {@link Radio} instances. */ async listRadios() { return this.request("GET", "radio", false); } /** * Get the top radios. * * @returns {@link PaginatedList} of {@link Radio} objects. */ async getRadioTop() { return this.getPaginatedList("radio/top"); } /** * Get the track with the given id. * * @param {number} trackId - The id of the track to get. * @returns {@link Track} - A track object. */ async getTrack(trackId) { return this.request("GET", `track/${trackId}`, false); } /** * Get the user with the given id. * * @param {number} userId - The id of the user to get. * @returns {@link User} - A user object. */ async getUser(userId) { return this.request("GET", `user/${userId}`, false); } /** * Get the flow of the user with the given id. * * @param {number} userId - The id of the user to get. * @param {Record<string, string>} [params] - Additional parameters to pass. * @returns {@link PaginatedList} of {@link Track} - A list of tracks. */ async getUserFlow(userId, params) { return this.getPaginatedList(`user/${userId}/flow`, params); } /** * Get the favourites albums for the given userId. * * @param {number} userId - The user id to get the favourite albums. * @returns {@link PaginatedList} of {@link Album} - A list of albums. */ async getUserAlbums(userId) { return this.getPaginatedList(`user/${userId}/albums`); } /** * Get the favourite artists for the given userId. * * @param {number} userId - The user id to get the favourite artists. * @returns {@link PaginatedList} of {@link Artist} - A list of artists. */ async getUserArtists(userId) { return this.getPaginatedList(`user/${userId}/artists`); } /** * Get the followers for the given userId. * * @param {number} userId - The user id to get followers. * @returns {@link PaginatedList} of {@link User} - A list of followers. */ async getUserFollowers(userId) { return this.getPaginatedList(`user/${userId}/followers`); } /** * Get the followings for the given userId. * * @param {number} userId - The user id to get followings. * @returns {@link PaginatedList} of {@link User} - A list of followings. */ async getUserFollowings(userId) { return this.getPaginatedList(`user/${userId}/followings`); } /** * Get the favourites tracks for the given userId. * * @param {number} userId - The user id to get the favourite tracks. * @returns {@link PaginatedList} of {@link Track} - A list of tracks. */ async getUserTracks(userId) { return this.getPaginatedList(`user/${userId}/tracks`); } /** * Get the playlists for the given userId. * * @param {number} userId - The user id to get the playlists. * @returns {@link PaginatedList} of {@link Playlist} - A list of playlists. */ async getUserPlaylists(userId) { return this.getPaginatedList(`user/${userId}/playlists`); } // -------------------------------------------------- /** * Get the podcasts for the given userId. * * @param {number} userId - The user id to get the podcasts. * @returns {@link PaginatedList} of {@link Podcast} - A list of podcasts. */ async getUserPodcasts(userId) { return this.getPaginatedList(`user/${userId}/podcasts`); } /** * Get the radios for the given userId. * * @param {number} userId - The user id to get the radios. * @returns {@link PaginatedList} of {@link Radio} - A list of radios. */ async getUserRadios(userId) { return this.getPaginatedList(`user/${userId}/radios`); } /** * Get the charts for the given userId. * * @param {number} userId - The user id to get the charts. * @returns {@link PaginatedList} of {@link Chart} - A list of charts. */ async getUserCharts(userId) { return this.getPaginatedList(`user/${userId}/charts`); } // -------------------------------------------------- /** * @internal */ async _search(path, query = "", strict, ordering, advancedParams = {}) { const optionalParams = {}; if (strict === true) { optionalParams["strict"] = "on"; } if (ordering) { optionalParams["ordering"] = ordering; } const queryParts = []; if (query) { queryParts.push(query); } Object.entries(advancedParams).filter(([_, value]) => value !== null).forEach(([paramName, paramValue]) => { queryParts.push(`${paramName}:"${paramValue}"`); }); return this.getPaginatedList(path ? `search/${path}` : "search", { q: queryParts.join(" "), ...optionalParams }); } /** * Search tracks. * * Advanced search is available by either formatting the query yourself or * by using the dedicated keywords arguments. * * @param {string} query - the query to search for, this is directly passed as q query. * @param {boolean} strict - whether to disable fuzzy search and enable strict mode. * @param {string} ordering - see Deezer API docs for possible values.. * @param {Object} advancedParams - Additional parameters to pass. * @param {string} advancedParams.artist - The artist to search for * @param {string} advancedParams.album - The album to search for * @param {string} advancedParams.track - The track to search for * @param {string} advancedParams.label - The label to search for * @param {number} advancedParams.dur_min - The minimum duration of the track * @param {number} advancedParams.dur_max - The maximum duration of the track * @param {number} advancedParams.bpm_min - The minimum BPM of the track * @param {number} advancedParams.bpm_max - The maximum BPM of the track * * @returns {@link PaginatedList} of {@link Track} - A list of tracks. */ async search(query = "", strict, ordering, advancedParams = {}) { return this._search("", query, strict, ordering, advancedParams); } /** * Search albums matching the given query. * * @param query - the query to search for, this is directly passed as q query. * @param strict - whether to disable fuzzy search and enable strict mode. * @param ordering - see Deezer API docs for possible values. */ async searchAlbums(query = "", strict, ordering) { return this._search("album", query, strict, ordering); } /** * Search artists matching the given query. * * @param query - the query to search for, this is directly passed as q query. * @param strict - whether to disable fuzzy search and enable strict mode. * @param ordering - see Deezer API docs for possible values. */ async searchArtists(query = "", strict, ordering) { return this._search("artist", query, strict, ordering); } /** * Search playlists matching the given query. * * @param query - the query to search for, this is directly passed as q query. * @param strict - whether to disable fuzzy search and enable strict mode. * @param ordering - see Deezer API docs for possible values. */ async searchPlaylists(query = "", strict, ordering) { return this._search("playlist", query, strict, ordering); } }; _Client.requestQueue = []; _Client.QUOTA_WINDOW = 5e3; // 5 seconds in ms _Client.QUOTA_LIMIT = 45; var Client = _Client; export { Album, Artist, Chart, Client, DeezerAPIException, DeezerErrorResponse, DeezerForbiddenError, DeezerHTTPError, DeezerNotFoundError, DeezerQuotaExceededError, DeezerRetryableException, DeezerRetryableHTTPError, DeezerUnknownResource, Editorial, Episode, Genre, PaginatedList, Playlist, Podcast, Radio, Resource, Track, User };