mirror of
https://gitlab.com/RemixDev/deezer-js.git
synced 2024-12-28 18:36:23 +00:00
479 lines
13 KiB
JavaScript
479 lines
13 KiB
JavaScript
const got = require('got')
|
|
const {map_artist_album, map_user_track, map_user_artist, map_user_album, map_user_playlist} = require('./utils.js')
|
|
const { GWAPIError } = require('./errors.js')
|
|
|
|
const PlaylistStatus = {
|
|
PUBLIC: 0,
|
|
PRIVATE: 1,
|
|
COLLABORATIVE: 2,
|
|
}
|
|
|
|
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: ""
|
|
}
|
|
|
|
class GW{
|
|
constructor(cookie_jar, headers){
|
|
this.http_headers = headers
|
|
this.cookie_jar = cookie_jar
|
|
this.api_token = null
|
|
}
|
|
|
|
async api_call(method, args, params){
|
|
if (typeof args === undefined) args = {}
|
|
if (typeof params === undefined) params = {}
|
|
if (!this.api_token && method != 'deezer.getUserData') this.api_token = await this._get_token()
|
|
let p = {
|
|
api_version: "1.0",
|
|
api_token: method == 'deezer.getUserData' ? 'null' : this.api_token,
|
|
input: '3',
|
|
method: method,
|
|
...params
|
|
}
|
|
let result_json
|
|
try{
|
|
result_json = await got.post("http://www.deezer.com/ajax/gw-light.php", {
|
|
searchParams: p,
|
|
json: args,
|
|
cookieJar: this.cookie_jar,
|
|
headers: this.http_headers,
|
|
https: {
|
|
rejectUnauthorized: false
|
|
},
|
|
timeout: 30000
|
|
}).json()
|
|
}catch (e){
|
|
console.debug("[ERROR] deezer.gw", method, args, e.name, e.message)
|
|
if (["ECONNABORTED", "ECONNREFUSED", "ECONNRESET", "ENETRESET", "ETIMEDOUT"].includes(e.code)){
|
|
await new Promise(r => setTimeout(r, 2000)) // sleep(2000ms)
|
|
return this.api_call(method, args, params)
|
|
}
|
|
throw new GWAPIError(`${method} ${args}:: ${e.name}: ${e.message}`)
|
|
}
|
|
if (result_json.error.length || Object.keys(result_json.error).length) {
|
|
if (
|
|
JSON.stringify(result_json.error) == '{"GATEWAY_ERROR":"invalid api token"}' ||
|
|
JSON.stringify(result_json.error) == '{"VALID_TOKEN_REQUIRED":"Invalid CSRF token"}'
|
|
){
|
|
this.api_token = await this._get_token()
|
|
return this.api_call(method, args, params)
|
|
}
|
|
if (result_json.payload && result_json.payload.FALLBACK){
|
|
Object.keys(result_json.payload.FALLBACK).forEach(key => {
|
|
args[key] = result_json.payload.FALLBACK[key]
|
|
})
|
|
return this.api_call(method, args, params)
|
|
}
|
|
throw new GWAPIError(JSON.stringify(result_json.error))
|
|
}
|
|
if (!this.api_token && method == 'deezer.getUserData') this.api_token = result_json.results.checkForm
|
|
return result_json.results
|
|
}
|
|
|
|
async _get_token(){
|
|
let token_data = await this.get_user_data()
|
|
return token_data.checkForm
|
|
}
|
|
|
|
get_user_data(){
|
|
return this.api_call('deezer.getUserData')
|
|
}
|
|
|
|
get_user_profile_page(user_id, tab, options={}){
|
|
const limit = options.limit || 10
|
|
return this.api_call('deezer.pageProfile', {USER_ID: user_id, tab, nb: limit})
|
|
}
|
|
|
|
get_user_favorite_ids(checksum = null, options={}){
|
|
const limit = options.limit || 10000
|
|
const start = options.start || 0
|
|
return this.api_call('song.getFavoriteIds', {nb: limit, start, checksum})
|
|
}
|
|
|
|
get_child_accounts(){
|
|
return this.api_call('deezer.getChildAccounts')
|
|
}
|
|
|
|
get_track(sng_id){
|
|
return this.api_call('song.getData', {SNG_ID: sng_id})
|
|
}
|
|
|
|
get_track_page(sng_id){
|
|
return this.api_call('deezer.pageTrack', {SNG_ID: sng_id})
|
|
}
|
|
|
|
get_track_lyrics(sng_id){
|
|
return this.api_call('song.getLyrics', {SNG_ID: sng_id})
|
|
}
|
|
|
|
async get_tracks(sng_ids){
|
|
let tracks_array = []
|
|
let body = await this.api_call('song.getListData', {SNG_IDS: 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
|
|
}
|
|
|
|
get_album(alb_id){
|
|
return this.api_call('album.getData', {ALB_ID: alb_id})
|
|
}
|
|
|
|
get_album_page(alb_id){
|
|
return this.api_call('deezer.pageAlbum', {
|
|
ALB_ID: 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: alb_id, nb: -1})
|
|
body.data.forEach(track => {
|
|
let _track = track
|
|
_track.POSITION = body.data.indexOf(track)
|
|
tracks_array.push(_track)
|
|
})
|
|
return tracks_array
|
|
}
|
|
|
|
get_artist(art_id){
|
|
return this.api_call('artist.getData', {ART_ID: art_id})
|
|
}
|
|
|
|
get_artist_page(art_id){
|
|
return this.api_call('deezer.pageArtist', {
|
|
ART_ID: art_id,
|
|
lang: 'en',
|
|
header: true,
|
|
tab: 0
|
|
})
|
|
}
|
|
|
|
async get_artist_top_tracks(art_id, options={}){
|
|
const limit = options.limit || 100
|
|
let tracks_array = []
|
|
let body = await this.api_call('artist.getTopTrack', {ART_ID: art_id, nb: limit})
|
|
body.data.forEach(track => {
|
|
track.POSITION = body.data.indexOf(track)
|
|
tracks_array.push(track)
|
|
})
|
|
return tracks_array
|
|
}
|
|
|
|
get_artist_discography(art_id, options={}){
|
|
const index = options.index || 0
|
|
const limit = options.limit || 25
|
|
return this.api_call('album.getDiscography', {
|
|
ART_ID: art_id,
|
|
discography_mode:"all",
|
|
nb: limit,
|
|
nb_songs: 0,
|
|
start: index
|
|
})
|
|
}
|
|
|
|
get_playlist(playlist_id){
|
|
return this.get_playlist_page(playlist_id)
|
|
}
|
|
|
|
get_playlist_page(playlist_id){
|
|
return this.api_call('deezer.pagePlaylist', {
|
|
PLAYLIST_ID: 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: playlist_id, nb: -1})
|
|
body.data.forEach(track => {
|
|
track.POSITION = body.data.indexOf(track)
|
|
tracks_array.push(track)
|
|
})
|
|
return tracks_array
|
|
}
|
|
|
|
create_playlist(title, status=PlaylistStatus.PUBLIC, description, songs=[]){
|
|
let newSongs = []
|
|
songs.forEach(song => {
|
|
newSongs.push([song, 0])
|
|
});
|
|
return this.api_call('playlist.create', {
|
|
title,
|
|
status,
|
|
description,
|
|
songs: newSongs
|
|
})
|
|
}
|
|
|
|
edit_playlist(playlist_id, title, status, description, songs=[]){
|
|
let newSongs = []
|
|
songs.forEach(song => {
|
|
newSongs.push([song, 0])
|
|
});
|
|
return this.api_call('playlist.update', {
|
|
PLAYLIST_ID: playlist_id,
|
|
title,
|
|
status,
|
|
description,
|
|
songs: newSongs
|
|
})
|
|
}
|
|
|
|
add_songs_to_playlist(playlist_id, songs, offset=-1){
|
|
let newSongs = []
|
|
songs.forEach(song => {
|
|
newSongs.push([song, 0])
|
|
});
|
|
return this.api_call('playlist.addSongs', {
|
|
PLAYLIST_ID: playlist_id,
|
|
songs: newSongs,
|
|
offset
|
|
})
|
|
}
|
|
|
|
add_song_to_playlist(playlist_id, sng_id, offset=-1){
|
|
return this.add_songs_to_playlist(playlist_id, [sng_id], offset)
|
|
}
|
|
|
|
remove_songs_from_playlist(playlist_id, songs){
|
|
let newSongs = []
|
|
songs.forEach(song => {
|
|
newSongs.push([song, 0])
|
|
});
|
|
return this.api_call('playlist.deleteSongs', {
|
|
PLAYLIST_ID: playlist_id,
|
|
songs: newSongs
|
|
})
|
|
}
|
|
|
|
remove_song_from_playlist(playlist_id, sng_id){
|
|
return this.remove_songs_from_playlist(playlist_id, [sng_id])
|
|
}
|
|
|
|
delete_playlist(playlist_id){
|
|
return this.api_call('playlist.delete', {PLAYLIST_ID: playlist_id})
|
|
}
|
|
|
|
add_song_to_favorites(sng_id){
|
|
return this.gw_api_call('favorite_song.add', {SNG_ID: sng_id})
|
|
}
|
|
|
|
remove_song_from_favorites(sng_id){
|
|
return this.gw_api_call('favorite_song.remove', {SNG_ID: sng_id})
|
|
}
|
|
|
|
add_album_to_favorites(alb_id){
|
|
return this.gw_api_call('album.addFavorite', {ALB_ID: alb_id})
|
|
}
|
|
|
|
remove_album_from_favorites(alb_id){
|
|
return this.gw_api_call('album.deleteFavorite', {ALB_ID: alb_id})
|
|
}
|
|
|
|
add_artist_to_favorites(art_id){
|
|
return this.gw_api_call('artist.addFavorite', {ART_ID: art_id})
|
|
}
|
|
|
|
remove_artist_from_favorites(art_id){
|
|
return this.gw_api_call('artist.deleteFavorite', {ART_ID: art_id})
|
|
}
|
|
|
|
add_playlist_to_favorites(playlist_id){
|
|
return this.gw_api_call('playlist.addFavorite', {PARENT_PLAYLIST_ID: playlist_id})
|
|
}
|
|
|
|
remove_playlist_from_favorites(playlist_id){
|
|
return this.gw_api_call('playlist.deleteFavorite', {PLAYLIST_ID: playlist_id})
|
|
}
|
|
|
|
get_page(page){
|
|
let params = {
|
|
gateway_input: JSON.stringify({
|
|
PAGE: page,
|
|
VERSION: '2.3',
|
|
SUPPORT: {
|
|
grid: [
|
|
'channel',
|
|
'album'
|
|
],
|
|
'horizontal-grid': [
|
|
'album'
|
|
],
|
|
},
|
|
LANG: 'en'
|
|
})
|
|
}
|
|
return this.api_call('page.get', {}, params)
|
|
}
|
|
|
|
search(query, index=0, limit=10, suggest=true, artist_suggest=true, top_tracks=true){
|
|
return this.api_call('deezer.pageSearch', {
|
|
query,
|
|
start: index,
|
|
nb: limit,
|
|
suggest,
|
|
artist_suggest,
|
|
top_tracks
|
|
})
|
|
}
|
|
|
|
search_music(query, type, options={}){
|
|
const index = options.index || 0
|
|
const limit = options.limit || 10
|
|
return this.api_call('search.music', {
|
|
query,
|
|
filter: "ALL",
|
|
output: type,
|
|
start: index,
|
|
nb: limit
|
|
})
|
|
}
|
|
|
|
// Extra calls
|
|
|
|
async get_artist_discography_tabs(art_id, options={}){
|
|
const limit = options.limit || 100
|
|
let index = 0
|
|
let releases = []
|
|
let result = {all: []}
|
|
let ids = []
|
|
|
|
// Get all releases
|
|
let response
|
|
do {
|
|
response = await this.get_artist_discography(art_id, {index, limit})
|
|
releases = 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)
|
|
let 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 (parseInt(sng_id) > 0){
|
|
try{ body = await this.get_track_page(sng_id) }
|
|
catch (e) { /*nothing*/ }
|
|
}
|
|
|
|
if (body){
|
|
if (body.LYRICS) body.DATA.LYRICS = body.LYRICS
|
|
if (body.ISRC) body.DATA.ALBUM_FALLBACK = body.ISRC
|
|
body = body.DATA
|
|
} else {
|
|
body = await this.get_track(sng_id)
|
|
}
|
|
return body
|
|
}
|
|
|
|
async get_user_playlists(user_id, options={}){
|
|
const limit = options.limit || 25
|
|
let user_profile_page = await this.get_user_profile_page(user_id, 'playlists', {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, options={}){
|
|
const limit = options.limit || 25
|
|
let data = await this.get_user_profile_page(user_id, 'albums', {limit})
|
|
data = data.TAB.albums.data
|
|
let result = []
|
|
data.forEach(album => {
|
|
result.push(map_user_album(album))
|
|
})
|
|
return result
|
|
}
|
|
|
|
async get_user_artists(user_id, options={}){
|
|
const limit = options.limit || 25
|
|
let data = await this.get_user_profile_page(user_id, 'artists', {limit})
|
|
data = data.TAB.artists.data
|
|
let result = []
|
|
data.forEach(artist => {
|
|
result.push(map_user_artist(artist))
|
|
})
|
|
return result
|
|
}
|
|
|
|
async get_user_tracks(user_id, options={}){
|
|
let user_data = await this.get_user_data()
|
|
if (user_data.USER.USER_ID == user_id) return this.get_my_favorite_tracks(options)
|
|
const limit = options.limit || 25
|
|
let data = this.get_user_profile_page(user_id, 'loved', {limit})
|
|
data = data.TAB.loved.data
|
|
let result = []
|
|
data.forEach(track => {
|
|
result.push(map_user_track(track))
|
|
})
|
|
return result
|
|
}
|
|
|
|
async get_my_favorite_tracks(options={}){
|
|
const limit = options.limit || 25
|
|
const ids_raw = await this.get_user_favorite_ids(null, {limit})
|
|
const ids = ids_raw.data.map(x => x.SNG_ID)
|
|
if (!ids.length) return []
|
|
let data = await this.get_tracks(ids)
|
|
let result = []
|
|
data.forEach((track, i) => {
|
|
track = {...track, ...ids_raw.data[i]}
|
|
result.push(map_user_track(track))
|
|
})
|
|
return result
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = {
|
|
PlaylistStatus,
|
|
EMPTY_TRACK_OBJ,
|
|
GW
|
|
}
|