commit f2ac5665e639e48d6e044c07bbb9190611a82fec Author: RemixDev Date: Fri Mar 12 14:58:48 2021 +0100 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/deezer/api.js b/deezer/api.js new file mode 100644 index 0000000..73c6029 --- /dev/null +++ b/deezer/api.js @@ -0,0 +1,423 @@ +const got = require('got') + +// Possible values for order parameter in search +export const SearchOrder = { + RANKING : "RANKING" + TRACK_ASC : "TRACK_ASC" + TRACK_DESC : "TRACK_DESC" + ARTIST_ASC : "ARTIST_ASC" + ARTIST_DESC : "ARTIST_DESC" + ALBUM_ASC : "ALBUM_ASC" + ALBUM_DESC : "ALBUM_DESC" + RATING_ASC : "RATING_ASC" + RATING_DESC : "RATING_DESC" + DURATION_ASC : "DURATION_ASC" + DURATION_DESC : "DURATION_DESC" +} + +export class API{ + constructor(cookie_jar, headers){ + this.http_headers = headers + this.cookie_jar = cookie_jar + this.access_token = null + } + + async api_call(method, args){ + if (typeof args == "undefined") args = {} + if this.access_token: args.access_token = this.access_token + try { + const result_json = await got.get("https://api.deezer.com/" + method, { + searchParams: args, + cookieJar: this.cookie_jar, + headers: this.http_headers, + timeout: 30000 + }).json() + } catch (error) { + await new Promise(r => setTimeout(r, 2000)) // sleep(2000ms) + return await this.api_call(method, args) + } + if (result_json.error){ + if (result_json.error.code){ + if ([4, 700].indexOf(result_json.error.code) != -1) { + await new Promise(r => setTimeout(r, 5000)) // sleep(5000ms) + return await this.api_call(method, args) + } + if (result_json.error.code == 100) throw new ItemsLimitExceededException(`ItemsLimitExceededException: ${method} ${result_json.error.message || ""}`) + if (result_json.error.code == 200) throw new PermissionException(`PermissionException: ${method} ${result_json.error.message || ""}`) + if (result_json.error.code == 300) throw new InvalidTokenException(`InvalidTokenException: ${method} ${result_json.error.message || ""}`) + if (result_json.error.code == 500) throw new WrongParameterException(`ParameterException: ${method} ${result_json.error.message || ""}`) + if (result_json.error.code == 501) throw new MissingParameterException(`MissingParameterException: ${method} ${result_json.error.message || ""}`) + if (result_json.error.code == 600) throw new InvalidQueryException(`InvalidQueryException: ${method} ${result_json.error.message || ""}`) + if (result_json.error.code == 800) throw new DataException(`DataException: ${method} ${result_json.error.message || ""}`) + if (result_json.error.code == 901) throw new IndividualAccountChangedNotAllowedException(`IndividualAccountChangedNotAllowedException: ${method} ${result_json.error.message || ""}`) + } + throw APIError(result_json.error)) + } + return result_json + } + + async get_album(album_id){ + return await this.api_call(`album/${album_id}`) + } + + async get_album_by_UPC(upc){ + return await this.get_album(`upc:${upc}`) + } + + async get_album_comments(album_id, index=0, limit=10){ + return await this.api_call(`album/${album_id}/comments`, {index, limit}) + } + + async get_album_fans(album_id, index=0, limit=100){ + return await this.api_call(`album/${album_id}/fans`, {index, limit}) + } + + async get_album_tracks(album_id, index=0, limit=-1){ + return await this.api_call(`album/${album_id}/tracks`, {index, limit}) + } + + async get_artist(artist_id){ + return await this.api_call(`artist/${artist_id}`) + } + + async get_artist_top(artist_id, index=0, limit=10){ + return await this.api_call(`artist/${artist_id}/top`, {index, limit}) + } + + async get_artist_albums(artist_id, index=0, limit=-1){ + return await this.api_call(`artist/${artist_id}/albums`, {index, limit}) + } + + async get_artist_comments(artist_id, index=0, limit=10){ + return await this.api_call(`artist/${artist_id}/comments`, {index, limit}) + } + + async get_artist_fans(artist_id, index=0, limit=100){ + return await this.api_call(`artist/${artist_id}/fans`, {index, limit}) + } + + async get_artist_related(artist_id, index=0, limit=20){ + return await this.api_call(`artist/${artist_id}/related`, {index, limit}) + } + + async get_artist_radio(artist_id, index=0, limit=25){ + return await this.api_call(`artist/${artist_id}/radio`, {index, limit}) + } + + async get_artist_playlists(artist_id, index=0, limit=-1){ + return await this.api_call(`artist/${artist_id}/playlists`, {index, limit}) + } + + async get_chart(genre_id=0, index=0, limit=10){ + return await this.api_call(`chart/${genre_id}`, {index, limit}) + } + + async get_chart_tracks(genre_id=0, index=0, limit=10){ + return await this.api_call(`chart/${genre_id}/tracks`, {index, limit}) + } + + async get_chart_albums(genre_id=0, index=0, limit=10){ + return await this.api_call(`chart/${genre_id}/albums`, {index, limit}) + } + + async get_chart_artists(genre_id=0, index=0, limit=10){ + return await this.api_call(`chart/${genre_id}/artists`, {index, limit}) + } + + async get_chart_playlists(genre_id=0, index=0, limit=10){ + return await this.api_call(`chart/${genre_id}/playlists`, {index, limit}) + } + + async get_chart_podcasts(genre_id=0, index=0, limit=10){ + return await this.api_call(`chart/${genre_id}/podcasts`, {index, limit}) + } + + async get_comment(comment_id){ + return await this.api_call(`comment/${comment_id}`) + } + + async get_editorials(index=0, limit=10){ + return await this.api_call('editorial', {index, limit}) + } + + async get_editorial(genre_id=0){ + return await this.api_call(`editorial/${genre_id}`) + } + + async get_editorial_selection(genre_id=0, index=0, limit=10){ + return await this.api_call(`editorial/${genre_id}/selection`, {index, limit}) + } + + async get_editorial_charts(genre_id=0, index=0, limit=10){ + return await this.api_call(`editorial/${genre_id}/charts`, {index, limit}) + } + + async get_editorial_releases(genre_id=0, index=0, limit=10){ + return await this.api_call(`editorial/${genre_id}/releases`, {index, limit}) + } + + async get_genres(index=0, limit=10){ + return await this.api_call('genre', {index, limit}) + } + + async get_genre(genre_id=0){ + return await this.api_call(`genre/${genre_id}`) + } + + async get_genre_artists(genre_id=0, index=0, limit=10){ + return await this.api_call(`genre/${genre_id}/artists`, {index, limit}) + } + + async get_genre_radios( genre_id=0, index=0, limit=10){ + return await this.api_call(`genre/${genre_id}/radios`, {index, limit}) + } + + async get_infos(){ + return await this.api_call('infos') + } + + async get_options(){ + return await this.api_call('options') + } + + async get_playlist(playlist_id){ + return await this.api_call(`playlist/${playlist_id}`) + } + + async get_playlist_comments(album_id, index=0, limit=10){ + return await this.api_call(`playlist/${album_id}/comments`, {index, limit}) + } + + async get_playlist_fans(album_id, index=0, limit=100){ + return await this.api_call(`playlist/${album_id}/fans`, {index, limit}) + } + + async get_playlist_tracks(album_id, index=0, limit=-1){ + return await this.api_call(`playlist/${album_id}/tracks`, {index, limit}) + } + + async get_playlist_radio(album_id, index=0, limit=100){ + return await this.api_call(`playlist/${album_id}/radio`, {index, limit}) + } + + async get_radios(index=0, limit=10){ + return await this.api_call('radio', {index, limit}) + } + + async get_radios_genres(index=0, limit=25){ + return await this.api_call('radio/genres', {index, limit}) + } + + async get_radios_top(index=0, limit=50){ + return await this.api_call('radio/top', {index, limit}) + } + + async get_radios_lists(index=0, limit=25){ + return await this.api_call('radio/lists', {index, limit}) + } + + async get_radio(radio_id){ + return await this.api_call(`radio/${radio_id}`) + } + + async get_radio_tracks(radio_id, index=0, limit=40){ + return await this.api_call(`radio/${radio_id}/tracks`, {index, limit}) + } + + _generate_search_advanced_query(artist="", album="", track="", label="", dur_min=0, dur_max=0, bpm_min=0, bpm_max=0){ + let query = "" + if (artist != "") query += `artist:"${artist}" ` + if (album != "") query += `album:"${album}" ` + if (track != "") query += `track:"${track}" ` + if (label != "") query += `label:"${label}" ` + if (dur_min != 0) query += `dur_min:"${dur_min}" ` + if (dur_max != 0) query += `dur_max:"${dur_max}" ` + if (bpm_min != 0) query += `bpm_min:"${bpm_min}" ` + if (bpm_max != 0) query += `bpm_max:"${bpm_max}" ` + return query.trim() + } + + _generate_search_args(query, strict=false, order, index=0, limit=25){ + let args = {q: query, index, limit} + if (strict) args.strict = 'on' + if (order) args.order = order + return args + } + + async search(query, strict=false, order, index=0, limit=25){ + const args = this._generate_search_args(query, strict, order, index, limit) + return await this.api_call('search', args) + } + + async advanced_search(artist="", album="", track="", label="", dur_min=0, dur_max=0, bpm_min=0, bpm_max=0, strict=false, order, index=0, limit=25){ + const query = this._generate_search_advanced_query(artist, album, track, label, dur_min, dur_max, bpm_min, bpm_max) + return await this.search(query, strict, order, index, limit) + } + + async search_album(query, strict=false, order, index=0, limit=25){ + const args = this._generate_search_args(query, strict, order, index, limit) + return await this.api_call('search/album', args) + } + + async search_artist(query, strict=false, order, index=0, limit=25){ + const args = this._generate_search_args(query, strict, order, index, limit) + return await this.api_call('search/artist', args) + } + + async search_playlist(query, strict=false, order, index=0, limit=25){ + const args = this._generate_search_args(query, strict, order, index, limit) + return await this.api_call('search/playlist', args) + } + + async search_radio(query, strict=false, order, index=0, limit=25){ + const args = this._generate_search_args(query, strict, order, index, limit) + return await this.api_call('search/radio', args) + } + + async search_track(query, strict=false, order, index=0, limit=25){ + const args = this._generate_search_args(query, strict, order, index, limit) + return await this.api_call('search/track', args) + } + + async search_user(query, strict=false, order, index=0, limit=25){ + const args = this._generate_search_args(query, strict, order, index, limit) + return await this.api_call('search/user', args) + } + + async get_track(song_id){ + return await this.api_call(`track/${song_id}`) + } + + async get_track_by_ISRC(isrc){ + return await this.get_track(`isrc:${isrc}`) + } + + async get_user(user_id){ + return await this.api_call(`user/${user_id}`) + } + + async get_user_albums(user_id, index=0, limit=25){ + return await this.api_call(`user/${user_id}/albums`, {index, limit}) + } + + async get_user_artists(user_id, index=0, limit=25){ + return await this.api_call(`user/${user_id}/artists`, {index, limit}) + } + + async get_user_artists(user_id, index=0, limit=25){ + return await this.api_call(`user/${user_id}/artists`, {index, limit}) + } + + async get_user_flow(user_id, index=0, limit=25){ + return await this.api_call(`user/${user_id}/flow`, {index, limit}) + } + + async get_user_following(user_id, index=0, limit=25){ + return await this.api_call(`user/${user_id}/followings`, {index, limit}) + } + + async get_user_followers(user_id, index=0, limit=25){ + return await this.api_call(`user/${user_id}/followers`, {index, limit}) + } + + async get_user_playlists(user_id, index=0, limit=25){ + return await this.api_call(`user/${user_id}/playlists`, {index, limit}) + } + + async get_user_radios(user_id, index=0, limit=25){ + return await this.api_call(`user/${user_id}/radios`, {index, limit}) + } + + async get_user_tracks(user_id, index=0, limit=25){ + return await this.api_call(`user/${user_id}/tracks`, {index, limit}) + } + + // Extra calls + + async get_countries_charts(){ + let temp = await this.get_user_playlists('637006841', limit=-1)['data'] + result = temp.sort((a, b) => a.title.localeCompare(b.title)) // Sort all playlists + if (!result[0].title.startsWith('Top')) result.shift() // Remove loved tracks playlist + return result + } + + + async get_track_id_from_metadata(artist, track, album){ + artist = artist.replace("–", "-").replace("’", "'") + track = track.replace("–", "-").replace("’", "'") + album = album.replace("–", "-").replace("’", "'") + + let resp = await this.advanced_search(artist=artist, track=track, album=album, limit=1) + if (resp.data.length > 0) return resp.data[0].id + + resp = await this.advanced_search(artist=artist, track=track, limit=1) + if (resp.data.length > 0) return resp.data[0].id + + // Try removing version + if ( track.indexOf("(") != -1 && track.indexOf(")") != -1 && track.indexOf("(") < track.indexOf(")") ){ + resp = await this.advanced_search(artist=artist, track=track.split("(")[0], limit=1) + if (resp.data.length > 0) return resp.data[0].id + } else if ( track.indexOf(" - ") != -1) { + resp = await this.advanced_search(artist=artist, track=track.split(" - ")[0], limit=1) + if (resp.data.length > 0) return resp.data[0].id + } + + return "0" + } +} + +// Base class for Deezer exceptions +export class APIError extends Error { + constructor(message) { + super(message); + this.name = "APIError"; + } +} +export class ItemsLimitExceededException extends APIError { + constructor(message) { + super(message); + this.name = "ItemsLimitExceededException"; + } +} +export class PermissionException extends APIError { + constructor(message) { + super(message); + this.name = "PermissionException"; + } +} +export class InvalidTokenException extends APIError { + constructor(message) { + super(message); + this.name = "InvalidTokenException"; + } +} +export class WrongParameterException extends APIError { + constructor(message) { + super(message); + this.name = "WrongParameterException"; + } +} +export class MissingParameterException extends APIError { + constructor(message) { + super(message); + this.name = "MissingParameterException"; + } +} +export class InvalidQueryException extends APIError { + constructor(message) { + super(message); + this.name = "InvalidQueryException"; + } +} +export class DataException extends APIError { + constructor(message) { + super(message); + this.name = "DataException"; + } +} +export class IndividualAccountChangedNotAllowedException extends APIError { + constructor(message) { + super(message); + this.name = "IndividualAccountChangedNotAllowedException"; + } +} diff --git a/deezer/gw.js b/deezer/gw.js new file mode 100644 index 0000000..f79c90e --- /dev/null +++ b/deezer/gw.js @@ -0,0 +1,429 @@ +const got = require('got') +const {map_artist_album, map_user_track, map_user_artist, map_user_album, map_user_playlist} = require('./utils.js') + +// Explicit Content Lyrics +export const LyricsStatus = { + NOT_EXPLICIT: 0 // Not Explicit + EXPLICIT: 1 // Explicit + UNKNOWN: 2 // Unknown + EDITED: 3 // Edited + PARTIALLY_EXPLICIT: 4 // Partially Explicit (Album "lyrics" only) + PARTIALLY_UNKNOWN: 5 // Partially Unknown (Album "lyrics" only) + NO_ADVICE: 6 // No Advice Available + PARTIALLY_NO_ADVICE: 7 // Partially No Advice Available (Album "lyrics" only) +} + +export const PlaylistStatus = { + PUBLIC: 0 + PRIVATE: 1 + COLLABORATIVE: 2 +} + + +export const EMPTY_TRACK_OBJ = { + SNG_ID: 0, + SNG_TITLE: '', + DURATION: 0, + MD5_ORIGIN: 0, + MEDIA_VERSION: 0, + FILESIZE: 0, + ALB_TITLE: "", + ALB_PICTURE: "", + ART_ID: 0, + ART_NAME: "" +} + +export class GW{ + constructor(cookie_jar, headers){ + this.http_headers = headers + this.cookie_jar = cookie_jar + } + + async api_call(method, args, params){ + if (typeof args == "unasyncined") args = {} + if (typeof params == "unasyncined") params = {} + p = { + api_version: "1.0", + api_token: method == 'deezer.getUserData' ? 'null' : this._get_token(), + input: '3', + method: method, + ..params + } + try{ + const result_json = await got.get("http://www.deezer.com/ajax/gw-light.php", { + searchParams: p, + json: args, + cookieJar: this.cookie_jar, + headers: this.http_headers, + timeout: 30000 + }).json() + }catch{ + await new Promise(r => setTimeout(r, 2000)) // sleep(2000ms) + return await this.api_call(method, args, params) + } + if (result_json.error.length): + throw new GWAPIError(result_json.error) + return result_json.results + } + + async _get_token(){ + let token_data = await this.get_user_data() + return token_data.checkForm + } + + async get_user_data(){ + return await this.api_call('deezer.getUserData') + } + + async get_user_profile_page(user_id, tab, limit=10){ + return await this.api_call('deezer.pageProfile', {user_id, tab, nb: limit}) + } + + async get_child_accounts(){ + return await this.api_call('deezer.getChildAccounts') + } + + async get_track(sng_id){ + return await this.api_call('song.getData', {sng_id}) + } + + async get_track_page(sng_id){ + return await this.api_call('deezer.pageTrack', {sng_id}) + } + + async get_track_lyrics(sng_id){ + return await this.api_call('song.getLyrics', {sng_id}) + } + + async get_tracks_gw(sng_ids){ + let tracks_array = [] + let body = await this.api_call('song.getListData', {sng_ids}) + let errors = 0 + for (let i = 0; i < sng_ids.length; i++){ + if (sng_ids[0] != 0){ + tracks_array.push(body.data[i - errors]) + } else { + errors++ + tracks_array.push(EMPTY_TRACK_OBJ) + } + } + return tracks_array + } + + async get_album(alb_id){ + return await this.api_call('album.getData', {alb_id}) + } + + async get_album_page(alb_id){ + return await this.api_call('deezer.pageAlbum', { + alb_id, + lang: 'en', + header: True, + tab: 0 + }) + } + + async get_album_tracks(alb_id){ + let tracks_array = [] + let body = await this.api_call('song.getListByAlbum', {alb_id, nb: -1}) + body.data.forEach(track => { + let _track = track + _track.POSITION = body.data.indexOf(track) + tracks_array.push(_track) + }) + return tracks_array + } + + async get_artist(art_id){ + return await this.api_call('artist.getData', {art_id}) + } + + async get_artist_page(art_id){ + return this.api_call('deezer.pageArtist', { + art_id, + lang: 'en', + header: True, + tab: 0 + }) + } + + async get_artist_top_tracks(art_id, limit=100){ + let tracks_array = [] + let body = await this.api_call('artist.getTopTrack', {art_id, nb: limit}) + body.data.forEach(track => { + track.POSITION = body.data.indexOf(track) + tracks_array.push(_track) + }) + return tracks_array + } + + async get_artist_discography(art_id, index=0, limit=25){ + return await this.api_call('album.getDiscography', { + art_id, + discography_mode:"all", + nb: limit, + nb_songs: 0, + start: index + }) + } + + async get_playlist(playlist_id){ + return await this.api_call('playlist.getData', {playlist_id}) + } + + async get_playlist_page(playlist_id){ + return await this.api_call('deezer.pagePlaylist', { + playlist_id, + lang: 'en', + header: True, + tab: 0 + }) + } + + async get_playlist_tracks(playlist_id){ + let tracks_array = [] + let body = await this.api_call('playlist.getSongs', {playlist_id, nb: -1}) + body.data.forEach(track => { + track.POSITION = body.data.indexOf(track) + tracks_array.push(_track) + }) + return tracks_array + } + + async create_playlist(title, status=PlaylistStatus.PUBLIC, description, songs=[]){ + newSongs = [] + songs.forEach(song => { + newSongs.push([song, 0]) + }); + return await this.api_call('playlist.create', { + title, + status, + description, + songs: newSongs + }) + } + + async edit_playlist(playlist_id, title, status, description, songs=[]){ + newSongs = [] + songs.forEach(song => { + newSongs.push([song, 0]) + }); + return await this.api_call('playlist.update', { + playlist_id, + title, + status, + description, + songs: newSongs + }) + } + + async add_songs_to_playlist(playlist_id, songs, offset=-1){ + newSongs = [] + songs.forEach(song => { + newSongs.push([song, 0]) + }); + return await this.api_call('playlist.addSongs', { + playlist_id, + songs: newSongs, + offset + }) + } + + async add_song_to_playlist(playlist_id, sng_id, offset=-1){ + return await this.add_songs_to_playlist(playlist_id, [sng_id], offset) + } + + async remove_songs_from_playlist(playlist_id, songs){ + newSongs = [] + songs.forEach(song => { + newSongs.push([song, 0]) + }); + return await this.api_call('playlist.deleteSongs', { + playlist_id, + songs: newSongs + }) + } + + async remove_song_from_playlist(playlist_id, sng_id){ + return await this.remove_songs_from_playlist(playlist_id, [sng_id]) + } + + async delete_playlist(playlist_id){ + return await this.api_call('playlist.delete', {playlist_id}) + } + + async add_song_to_favorites(sng_id){ + return await this.gw_api_call('favorite_song.add', {sng_id}) + } + + async remove_song_from_favorites(sng_id){ + return await this.gw_api_call('favorite_song.remove', {sng_id}) + } + + async add_album_to_favorites(alb_id){ + return await this.gw_api_call('album.addFavorite', {alb_id}) + } + + async remove_album_from_favorites(alb_id){ + return await this.gw_api_call('album.deleteFavorite', {alb_id}) + } + + async add_artist_to_favorites(art_id){ + return await this.gw_api_call('artist.addFavorite', {art_id}) + } + + async remove_artist_from_favorites(art_id){ + return await this.gw_api_call('artist.deleteFavorite', {art_id}) + } + + async add_playlist_to_favorites(playlist_id){ + return await this.gw_api_call('playlist.addFavorite', {PARENT_PLAYLIST_ID: playlist_id}) + } + + async remove_playlist_from_favorites(playlist_id){ + return await this.gw_api_call('playlist.deleteFavorite', {PLAYLIST_ID: playlist_id}) + } + + async get_page(page){ + let params = { + gateway_input: JSON.stringify({ + PAGE: page, + VERSION: '2.3', + SUPPORT: { + grid: [ + 'channel', + 'album' + ], + horizontal-grid: [ + 'album' + ], + }, + LANG: 'en' + }) + } + return await this.api_call('page.get', params=params) + } + + async search(query, index=0, limit=10, suggest=true, artist_suggest=true, top_tracks=true){ + return await this.api_call('deezer.pageSearch', { + query, + start: index, + nb: limit, + suggest, + artist_suggest, + top_tracks + }) + } + + async search_music(query, type, index=0, limit=10){ + return await this.api_call('search.music', { + query, + filter: "ALL", + output: type, + start: index, + nb: limit + }) + } + + // Extra calls + + async get_artist_discography_tabs(art_id, limit=100){ + let index = 0 + let releases = [] + let result = {all: []} + let ids = [] + + // Get all releases + do { + response = await this.get_artist_discography(art_id, index=index, limit=limit) + releases.concat(response.data) + index += limit + } while (index < response.total) + + releases.forEach(release => { + if (ids.indexOf(release.ALB_ID) == -1){ + ids.push(release.ALB_ID) + obj = map_artist_album(release) + if ((release.ART_ID == art_id || release.ART_ID != art_id && release.ROLE_ID == 0) && release.ARTISTS_ALBUMS_IS_OFFICIAL){ + // Handle all base record types + if (!result[obj.record_type]) result[obj.record_type] = [] + result[obj.record_type].push(obj) + result.all.push(obj) + } + } else { + if (release.ROLE_ID == 5) { // Handle albums where the artist is featured + if (!result.featured) result.featured = [] + result.featured.push(obj) + } else if release.ROLE_ID == 0 { // Handle "more" albums + if (!result.more) result.more = [] + result.more.push(obj) + result.all.push(obj) + } + } + }) + return result + } + + async get_track_with_fallback(sng_id){ + let body + if (int(sng_id) > 0){ + try{ body = await this.get_track_page(sng_id) } + catch (e) {} + } + + if (body){ + if (body.LYRICS) body.DATA.LYRICS = body.LYRICS + body = body.DATA + } else { + body = await this.get_track(sng_id) + } + return body + } + + async get_user_playlists(user_id, limit=25){ + let user_profile_page = await this.get_user_profile_page(user_id, 'playlists', limit=limit) + let blog_name = user_profile_page.DATA.USER.BLOG_NAME || "Unknown" + let data = user_profile_page.TAB.playlists.data + let result = [] + data.forEach(playlist => { + result.push(map_user_playlist(playlist, blog_name)) + }) + return result + } + + async get_user_albums(user_id, limit=25){ + let data = await this.get_user_profile_page(user_id, 'albums', limit=limit).TAB.albums.data + let result = [] + data.forEach(album => { + result.push(map_user_album(album)) + }) + return result + } + + async get_user_artists(user_id, limit=25){ + let data = this.get_user_profile_page(user_id, 'artists', limit=limit).TAB.artists.data + let result = [] + data.forEach(artist => { + result.push(map_user_artist(artist)) + }) + return result + } + + async get_user_tracks(user_id, limit=25){ + let data = this.get_user_profile_page(user_id, 'loved', limit=limit).TAB.loved.data + let result = [] + data.forEach(track => { + result.push(map_user_track(track)) + }) + return result + } + +} + +// Base class for Deezer exceptions +export class GWAPIError extends Error { + constructor(message) { + super(message); + this.name = "APIError"; + } +} diff --git a/deezer/index.js b/deezer/index.js new file mode 100644 index 0000000..9abaaf4 --- /dev/null +++ b/deezer/index.js @@ -0,0 +1,127 @@ +const got = require('got') +const {CookieJar, Cookie} = require('tough-cookie') +const { API } = require('./api.js') +const { GW } = require('./gw.js') + +// Number associtation for formats +export const TrackFormats = { + FLAC : 9 + MP3_320 : 3 + MP3_128 : 1 + MP4_RA3 : 15 + MP4_RA2 : 14 + MP4_RA1 : 13 + DEFAULT : 8 + LOCAL : 0 +} + +export class Deezer{ + constructor(accept_language=""){ + this.http_headers = { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36", + "Accept-Language": accept_language + } + this.cookie_jar = new CookieJar() + + this.logged_in = false + this.current_user = {} + this.childs = [] + this.selected_account = 0 + + this.api = new API(this.cookie_jar, this.http_headers) + this.gw = new GW(this.cookie_jar, this.http_headers) + } + + get_accept_language(){ + return this.http_headers['Accept-Language'] + } + + set_accept_language(lang){ + this.http_headers['Accept-Language'] = lang + } + + async login(email, password, re_captcha_token, child=0){ + // Check if user already logged in + let user_data = await this.gw.get_user_data() + if (user_data.USER.USER_ID == 0){ + this.logged_in = false + return false + } + // Get the checkFormLogin + let check_form_login = user_data.checkFormLogin + let login = await got.post("https://www.deezer.com/ajax/action.php", { + headers: this.http_headers, + cookieJar: this.cookie_jar, + form:{ + type: 'login', + mail: email, + password: password, + checkFormLogin: check_form_login, + reCaptchaToken: re_captcha_token + } + }).text() + // Check if user logged in + if (login.text.indexOf('success') == -1){ + this.logged_in = false + return false + } + user_data = await this.gw.get_user_data() + await this._post_login(user_data) + this.change_account(child) + this.logged_in = true + return true + } + + async login_via_arl(arl, child=0){ + arl = arl.trim() + // TODO: Check how to do this + let cookie_obj = Cookie({ + key: 'arl', + value: arl, + path: "/", + httpOnly: true + }) + this.cookie_jar.setCookie(cookie_obj, '.deezer.com') + + let user_data = await this.gw.get_user_data() + // Check if user logged in + if (user_data.USER.USER_ID == 0){ + this.logged_in = false + return false + } + await this._post_login(user_data) + this.change_account(child) + this.logged_in = true + return true + } + + async _post_login(user_data){ + this.childs = [] + let family = user_data.USER.MULTI_ACCOUNT.ENABLED + if (family){ + let childs = await this.gw.get_child_accounts() + childs.forEach(child => { + this.childs.push({ + 'id': child.USER_ID, + 'name': child.BLOG_NAME, + 'picture': child.USER_PICTURE || "" + }) + }) + } else { + this.childs.append({ + 'id': user_data.USER.USER_ID, + 'name': user_data.USER.BLOG_NAME, + 'picture': user_data.USER.USER_PICTURE || "" + }) + } + } + + change_account(child_n){ + if (this.childs.length-1 < child_n) child_n = 0 + this.current_user = this.childs[child_n] + this.selected_account = child_n + + return [this.current_user, this.selected_account] + } + +} diff --git a/deezer/utils.js b/deezer/utils.js new file mode 100644 index 0000000..742b539 --- /dev/null +++ b/deezer/utils.js @@ -0,0 +1,212 @@ +const RELEASE_TYPE = ["single", "album", "compile", "ep", "bundle"] + +// maps gw-light api user/tracks to standard api +export function map_user_track(track){ + return { + id: track.SNG_ID, + title: track.SNG_TITLE, + link: 'https://www.deezer.com/track/'+track.SNG_ID, + duration: track.DURATION, + rank: track.RANK_SNG, + explicit_lyrics: int(track.EXPLICIT_LYRICS) > 0, + explicit_content_lyrics: track.EXPLICIT_TRACK_CONTENT.EXPLICIT_COVER_STATUS, + explicit_content_cover: track.EXPLICIT_TRACK_CONTENT.EXPLICIT_LYRICS_STATUS, + time_add: track.DATE_ADD, + album: { + id: track.ALB_ID, + title: track.ALB_TITLE, + cover: 'https://api.deezer.com/album/'+track.ALB_ID+'/image', + cover_small: 'https://e-cdns-images.dzcdn.net/images/cover/'+track.ALB_PICTURE+'/56x56-000000-80-0-0.jpg', + cover_medium: 'https://e-cdns-images.dzcdn.net/images/cover/'+track.ALB_PICTURE+'/250x250-000000-80-0-0.jpg', + cover_big: 'https://e-cdns-images.dzcdn.net/images/cover/'+track.ALB_PICTURE+'/500x500-000000-80-0-0.jpg', + cover_xl: 'https://e-cdns-images.dzcdn.net/images/cover/'+track.ALB_PICTURE+'/1000x1000-000000-80-0-0.jpg', + tracklist: 'https://api.deezer.com/album/'+track.ALB_ID+'/tracks', + type: 'album' + }, + artist: { + id: track.ART_ID, + name: track.ART_NAME, + picture: 'https://api.deezer.com/artist/'+track.ART_ID+'/image', + picture_small: 'https://e-cdns-images.dzcdn.net/images/artist/'+track.ART_PICTURE+'/56x56-000000-80-0-0.jpg', + picture_medium: 'https://e-cdns-images.dzcdn.net/images/artist/'+track.ART_PICTURE+'/250x250-000000-80-0-0.jpg', + picture_big: 'https://e-cdns-images.dzcdn.net/images/artist/'+track.ART_PICTURE+'/500x500-000000-80-0-0.jpg', + picture_xl: 'https://e-cdns-images.dzcdn.net/images/artist/'+track.ART_PICTURE+'/1000x1000-000000-80-0-0.jpg', + tracklist: 'https://api.deezer.com/artist/'+track.ART_ID+'/top?limit=50', + type: 'artist' + }, + type: 'track' + } +} + +// maps gw-light api user/artists to standard api +export function map_user_artist(artist){ + return { + id: artist.ART_ID, + name: artist.ART_NAME, + link: 'https://www.deezer.com/artist/'+artist.ART_ID, + picture: 'https://api.deezer.com/artist/'+artist.ART_ID+'/image', + picture_small: 'https://e-cdns-images.dzcdn.net/images/artist/'+artist.ART_PICTURE+'/56x56-000000-80-0-0.jpg', + picture_medium: 'https://e-cdns-images.dzcdn.net/images/artist/'+artist.ART_PICTURE+'/250x250-000000-80-0-0.jpg', + picture_big: 'https://e-cdns-images.dzcdn.net/images/artist/'+artist.ART_PICTURE+'/500x500-000000-80-0-0.jpg', + picture_xl: 'https://e-cdns-images.dzcdn.net/images/artist/'+artist.ART_PICTURE+'/1000x1000-000000-80-0-0.jpg', + nb_fan: artist.NB_FAN, + tracklist: 'https://api.deezer.com/artist/'+artist.ART_ID+'/top?limit=50', + type: 'artist' + } +} + + +// maps gw-light api user/albums to standard api +export function map_user_album(album){ + return { + id: album.ALB_ID, + title: album.ALB_TITLE, + link: 'https://www.deezer.com/album/'+album.ALB_ID, + cover: 'https://api.deezer.com/album/'+album.ALB_ID+'/image', + cover_small: 'https://e-cdns-images.dzcdn.net/images/cover/'+album.ALB_PICTURE+'/56x56-000000-80-0-0.jpg', + cover_medium: 'https://e-cdns-images.dzcdn.net/images/cover/'+album.ALB_PICTURE+'/250x250-000000-80-0-0.jpg', + cover_big: 'https://e-cdns-images.dzcdn.net/images/cover/'+album.ALB_PICTURE+'/500x500-000000-80-0-0.jpg', + cover_xl: 'https://e-cdns-images.dzcdn.net/images/cover/'+album.ALB_PICTURE+'/1000x1000-000000-80-0-0.jpg', + tracklist: 'https://api.deezer.com/album/'+album.ALB_ID+'/tracks', + explicit_lyrics: album.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS > 0, + artist: { + id: album.ART_ID, + name: album.ART_NAME, + picture: 'https://api.deezer.com/artist/'+album.ART_ID+'image', + tracklist: 'https://api.deezer.com/artist/'+album.ART_ID+'/top?limit=50' + }, + type: 'album' + } +} + + +// maps gw-light api user/playlists to standard api +export function map_user_playlist(playlist, default_user_name=""){ + return { + id: playlist.PLAYLIST_ID, + title: playlist.TITLE, + description: playlist.DESCRIPTION || "", + nb_tracks: playlist.NB_SONG, + link: 'https://www.deezer.com/playlist/'+playlist.PLAYLIST_ID, + picture: 'https://api.deezer.com/playlist/'+playlist.PLAYLIST_ID+'/image', + picture_small: 'https://e-cdns-images.dzcdn.net/images/'+playlist.PICTURE_TYPE+'/'+playlist.PLAYLIST_PICTURE+'/56x56-000000-80-0-0.jpg', + picture_medium: 'https://e-cdns-images.dzcdn.net/images/'+playlist.PICTURE_TYPE+'/'+playlist.PLAYLIST_PICTURE+'/250x250-000000-80-0-0.jpg', + picture_big: 'https://e-cdns-images.dzcdn.net/images/'+playlist.PICTURE_TYPE+'/'+playlist.PLAYLIST_PICTURE+'/500x500-000000-80-0-0.jpg', + picture_xl: 'https://e-cdns-images.dzcdn.net/images/'+playlist.PICTURE_TYPE+'/'+playlist.PLAYLIST_PICTURE+'/1000x1000-000000-80-0-0.jpg', + tracklist: 'https://api.deezer.com/playlist/'+playlist.PLAYLIST_ID+'/tracks', + creation_date: playlist.DATE_ADD, + creator: { + id: playlist.PARENT_USER_ID, + name: playlist.PARENT_USERNAME || default_user_name + }, + type: 'playlist' + } +} + + +// maps gw-light api albums to standard api +export function map_album(album){ + return { + id: album.ALB_ID, + title: album.ALB_TITLE, + upc: "", // TODO: Needs to be checked + link: `https://www.deezer.com/album/${album.ALB_ID}`, + share: "", // TODO: Needs to be checked + cover: `https://api.deezer.com/album/${album.ALB_ID}/image`, + cover_small: `https://cdns-images.dzcdn.net/images/cover/${album.ALB_PICTURE}/56x56-000000-80-0-0.jpg`, + cover_medium: `https://cdns-images.dzcdn.net/images/cover/${album.ALB_PICTURE}/250x250-000000-80-0-0.jpg`, + cover_big: `https://cdns-images.dzcdn.net/images/cover/${album.ALB_PICTURE}/500x500-000000-80-0-0.jpg`, + cover_xl: `https://cdns-images.dzcdn.net/images/cover/${album.ALB_PICTURE}/1000x1000-000000-80-0-0.jpg`, + md5_image: album.ALB_PICTURE, + genre_id: album.GENRE_ID, + genres: [], // TODO: Needs to be checked + label: "", // TODO: Needs to be checked + nb_tracks: album.NUMBER_TRACK, + duration: 0, // TODO: Needs to be checked + fans: album.RANK, + rating: 0, // TODO: Needs to be checked + release_date: album.PHYSICAL_RELEASE_DATE, + record_type: RELEASE_TYPE[int(album.TYPE)] || "unknown", + available: true, // TODO: Needs to be checked + alternative: null, // TODO: Needs to be checked + tracklist: `https://api.deezer.com/album/${album.ALB_ID}/tracks`, + explicit_lyrics: int(album.EXPLICIT_LYRICS) > 0, + explicit_content_lyrics: 2, // TODO: Needs to be checked + explicit_content_cover: 2, // TODO: Needs to be checked + contributors: [], // TODO: Needs to be checked + artist: null, // TODO: Needs to be checked + tracks: [], // TODO: Needs to be checked + type: album.__TYPE__, + // Extras + nb_disk: album.NUMBER_DISK + } +} + + +// maps gw-light api artist/albums to standard api +export function map_artist_album(album){ + return { + id: album.ALB_ID, + title: album.ALB_TITLE, + link: `https://www.deezer.com/album/${album.ALB_ID}`, + cover: `https://api.deezer.com/album/${album.ALB_ID}/image`, + cover_small: `https://cdns-images.dzcdn.net/images/cover/${album.ALB_PICTURE}/56x56-000000-80-0-0.jpg`, + cover_medium: `https://cdns-images.dzcdn.net/images/cover/${album.ALB_PICTURE}/250x250-000000-80-0-0.jpg`, + cover_big: `https://cdns-images.dzcdn.net/images/cover/${album.ALB_PICTURE}/500x500-000000-80-0-0.jpg`, + cover_xl: `https://cdns-images.dzcdn.net/images/cover/${album.ALB_PICTURE}/1000x1000-000000-80-0-0.jpg`, + genre_id: album.GENRE_ID, + fans: album.RANK, + release_date: album.PHYSICAL_RELEASE_DATE, + record_type: RELEASE_TYPE[int(album.TYPE)] || "unknown", + tracklist: `https://api.deezer.com/album/${album.ALB_ID}/tracks`, + explicit_lyrics: int(album.EXPLICIT_LYRICS) > 0, + type: album.__TYPE__, + // Extras + nb_tracks: album.NUMBER_TRACK, + nb_disk: album.NUMBER_DISK + } +} + + +// maps gw-light api playlists to standard api +export function map_playlist(playlist){ + return { + id: playlist.PLAYLIST_ID, + title: playlist.TITLE, + description: playlist.DESCRIPTION, + duration: playlist.DURATION, + public: playlist.STATUS == 1, + is_loved_track: playlist.TYPE == 4, + collaborative: playlist.STATUS == 2, + nb_tracks: playlist.NB_SONG, + fans: playlist.NB_FAN, + link: "https://www.deezer.com/playlist/"+playlist.PLAYLIST_ID, + share: "https://www.deezer.com/playlist/"+playlist.PLAYLIST_ID, + picture: "https://api.deezer.com/playlist/"+playlist.PLAYLIST_ID+"/image", + picture_small: "https://cdns-images.dzcdn.net/images/"+playlist.PICTURE_TYPE+"/"+playlist.PLAYLIST_PICTURE+"/56x56-000000-80-0-0.jpg", + picture_medium: "https://cdns-images.dzcdn.net/images/"+playlist.PICTURE_TYPE+"/"+playlist.PLAYLIST_PICTURE+"/250x250-000000-80-0-0.jpg", + picture_big: "https://cdns-images.dzcdn.net/images/"+playlist.PICTURE_TYPE+"/"+playlist.PLAYLIST_PICTURE+"/500x500-000000-80-0-0.jpg", + picture_xl: "https://cdns-images.dzcdn.net/images/"+playlist.PICTURE_TYPE+"/"+playlist.PLAYLIST_PICTURE+"/1000x1000-000000-80-0-0.jpg", + checksum: playlist.CHECKSUM, + tracklist: "https://api.deezer.com/playlist/"+playlist.PLAYLIST_ID+"/tracks", + creation_date: playlist.DATE_ADD, + creator: { + id: playlist.PARENT_USER_ID, + name: playlist.PARENT_USERNAME, + tracklist: "https://api.deezer.com/user/"+playlist.PARENT_USER_ID+"/flow", + type: "user" + }, + type: "playlist" + } +} + + +// Cleanup terms that can hurt search results +export function clean_search_query(term){ + term = term.replaceAll(/ feat[\.]? /g, " ") + term = term.replaceAll(/ ft[\.]? /g, " ") + term = term.replaceAll(/\(feat[\.]? /g, " ") + term = term.replaceAll(/\(ft[\.]? /g, " ") + term = term.replace(' & ', " ").replace('–', "-").replace('—', "-") + return term +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e639b44 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,574 @@ +{ + "name": "deezer-js", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "dependencies": { + "got": "^11.8.2", + "tough-cookie": "^4.0.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", + "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + }, + "node_modules/@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "14.14.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.33.tgz", + "integrity": "sha512-oJqcTrgPUF29oUP8AsUqbXGJNuPutsetaa9kTQAQce5Lx5dTYWV02ScBiT/k1BX/Z7pKeqedmvp39Wu4zR7N7g==" + }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", + "integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==" + }, + "node_modules/responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + }, + "dependencies": { + "@sindresorhus/is": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", + "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==" + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.14.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.33.tgz", + "integrity": "sha512-oJqcTrgPUF29oUP8AsUqbXGJNuPutsetaa9kTQAQce5Lx5dTYWV02ScBiT/k1BX/Z7pKeqedmvp39Wu4zR7N7g==" + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", + "integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==" + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..868a045 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "deezer-js", + "version": "0.0.1", + "description": "A wrapper for all Deezer's APIs", + "main": "deezer/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://git.rip/RemixDev/deezer-js" + }, + "author": "RemixDev", + "license": "GPL-3.0-or-later", + "dependencies": { + "got": "^11.8.2", + "tough-cookie": "^4.0.0" + } +}