const got = require('got') const {CookieJar, Cookie} = require('tough-cookie') const { API } = require('./api.js') const { GW } = require('./gw.js') const { DeezerError, WrongLicense, WrongGeolocation } = require('./errors.js') // Number associtation for formats const TrackFormats = { FLAC : 9, MP3_320 : 3, MP3_128 : 1, MP4_RA3 : 15, MP4_RA2 : 14, MP4_RA1 : 13, DEFAULT : 8, LOCAL : 0, } class Deezer{ constructor(){ 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" } 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) } async login(email, password, re_captcha_token, child=0){ if (child) child = parseInt(child) // Check if user already logged in let user_data = await this.gw.get_user_data() if (!user_data || user_data && Object.keys(user_data).length === 0) return this.logged_in = false if (user_data.USER.USER_ID == 0) return this.logged_in = 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, https: { rejectUnauthorized: false }, 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() if (child) child = parseInt(child) // Create cookie let cookie_obj = new Cookie({ key: 'arl', value: arl, domain: '.deezer.com', path: "/", httpOnly: true }) await this.cookie_jar.setCookie(cookie_obj.toString(), "https://www.deezer.com") let user_data = await this.gw.get_user_data() // Check if user logged in if (!user_data || user_data && Object.keys(user_data).length === 0) return this.logged_in = false if (user_data.USER.USER_ID == 0) return this.logged_in = 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 && !user_data.USER.MULTI_ACCOUNT.IS_SUB_ACCOUNT if (family){ let childs = await this.gw.get_child_accounts() childs.forEach(child => { if (child.EXTRA_FAMILY.IS_LOGGABLE_AS) { this.childs.push({ 'id': child.USER_ID, 'name': child.BLOG_NAME, 'picture': child.USER_PICTURE || "", 'license_token': user_data.USER.OPTIONS.license_token, 'can_stream_hq': user_data.USER.OPTIONS.web_hq || user_data.USER.OPTIONS.mobile_hq, 'can_stream_lossless': user_data.USER.OPTIONS.web_lossless || user_data.USER.OPTIONS.mobile_lossless, 'country': user_data.USER.OPTIONS.license_country, 'language': user_data.USER.SETTING.global.language, 'loved_tracks': child.LOVEDTRACKS_ID }) } }) } else { this.childs.push({ 'id': user_data.USER.USER_ID, 'name': user_data.USER.BLOG_NAME, 'picture': user_data.USER.USER_PICTURE || "", 'license_token': user_data.USER.OPTIONS.license_token, 'can_stream_hq': user_data.USER.OPTIONS.web_hq || user_data.USER.OPTIONS.mobile_hq, 'can_stream_lossless': user_data.USER.OPTIONS.web_lossless || user_data.USER.OPTIONS.mobile_lossless, 'country': user_data.USER.OPTIONS.license_country, 'language': user_data.USER.SETTING.global.language, 'loved_tracks': user_data.USER.LOVEDTRACKS_ID }) } } 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 this.http_headers["Accept-Language"] = this.current_user.language.replace(/[^0-9A-Za-z *,-.;=]/g, '') return [this.current_user, this.selected_account] } async get_track_url(track_token, format) { let tracks = await this.get_tracks_url([track_token, ], format) if (tracks.length > 0){ if (tracks[0] instanceof DeezerError) throw tracks[0] else return tracks[0] } return null } async get_tracks_url(track_tokens, format){ if (!Array.isArray(track_tokens)) track_tokens = [track_tokens, ] if (!this.current_user.license_token) return [] if ( (format === "FLAC" || format.startsWith("MP4_RA")) && !this.current_user.can_stream_lossless || format === "MP3_320" && !this.current_user.can_stream_hq ) throw new WrongLicense(format) let response let result = [] try { response = await got.post("https://media.deezer.com/v1/get_url", { headers: this.http_headers, cookieJar: this.cookie_jar, https: { rejectUnauthorized: false }, json: { license_token: this.current_user.license_token, media: [{ type: "FULL", formats: [{ cipher: "BF_CBC_STRIPE", format: format }] }], track_tokens } }).json() } catch (e){ return [] } if (response.data.length){ response.data.forEach(data =>{ if (data.errors){ if (data.errors[0].code === 2002){ result.push(new WrongGeolocation(this.current_user.country)) }else { result.push(new DeezerError(JSON.stringify(response))) } } if (data.media) result.push(data.media[0].sources[0].url) else result.push(null) }) } return result } } module.exports = { TrackFormats, Deezer, api: {...require('./api.js')}, gw: {...require('./gw.js')}, utils: {...require('./utils.js')}, errors: {...require('./errors.js')} }