Partially implemented decryption and continued working on downloader

Also added eslint
This commit is contained in:
RemixDev 2021-04-09 18:45:57 +02:00
parent a6383eb5f4
commit 1f2fddc693
15 changed files with 2693 additions and 81 deletions

13
.eslintrc.json Normal file
View file

@ -0,0 +1,13 @@
{
"env": {
"commonjs": true,
"es2021": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12
},
"rules": {
}
}

120
deemix/decryption.js Normal file
View file

@ -0,0 +1,120 @@
const crypto = require('crypto')
const got = require('got');
function _md5 (data, type = 'binary') {
let md5sum = crypto.createHash('md5')
md5sum.update(Buffer.from(data, type))
return md5sum.digest('hex')
}
function _ecbCrypt (key, data) {
let cipher = crypto.createCipheriv("aes-128-ecb", Buffer.from(key), Buffer.from(""));
return Buffer.concat([cipher.update(data, 'binary'), cipher.final()]).toString("hex").toLowerCase();
}
function _ecbDecrypt (key, data) {
let cipher = crypto.createDecipheriv("aes-128-ecb", Buffer.from(key), Buffer.from(""));
return Buffer.concat([cipher.update(data, 'binary'), cipher.final()]).toString("hex").toLowerCase();
}
function generateStreamPath(sngID, md5, mediaVersion, format){
let urlPart = md5+"¤"+format+"¤"+sngID+"¤"+mediaVersion
let md5val = _md5(urlPart)
let step2 = md5val+"¤"+urlPart+"¤"
step2 += ('.' * (16 - (step2.length % 16)))
urlPart = _ecbCrypt('jo6aey6haid2Teih', step2)
return urlPart
}
function reverseStreamPath(urlPart){
let step2 = _ecbDecrypt('jo6aey6haid2Teih', urlPart)
let [, md5, format, sngID, mediaVersion] = step2.split("¤")
return [sngID, md5, mediaVersion, format]
}
function generateStreamURL(sngID, md5, mediaVersion, format){
let urlPart = generateStreamPath(sngID, md5, mediaVersion, format)
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/api/1/" + urlPart
}
function reverseStreamURL(url){
let urlPart = url.substring(url.find("/1/")+3)
return reverseStreamPath(urlPart)
}
function streamTrack(outputStream, track, start=0, downloadObject, listener){
let headers = {'User-Agent': ""}
let chunkLength = start
let complete = 0
got.get(track.downloadUrl, {
headers: headers,
stream: true,
timeout: 10000
}).on('response', (response)=>{
complete = parseInt(response.headers["Content-Length"])
if (complete == 0) throw new DownloadEmpty
if (start != 0){
let responseRange = response.headers["Content-Range"]
console.log(`downloading range ${responseRange}`)
}else {
console.log(`downloading ${complete} bytes`)
}
}).on("data", (data)=>{
outputStream.write(data)
chunkLength += data.length
if (downloadObject){
let chunkProgres
if (downloadObject.__type__ === "Single"){
chunkProgres = (chunkLength / (complete + start)) * 100
downloadObject.progressNext = chunkProgres
}else{
chunkProgres = (data.length / (complete + start)) / downloadObject.size * 100
downloadObject.progressNext += chunkProgres
}
downloadObject.updateProgress(listener)
}
}).on("error", (error)=>{
console.error(error)
return streamTrack(outputStream, track, chunkLength, downloadObject, listener)
})
}
class DownloadEmpty extends Error {
constructor(message) {
super(message);
this.name = "DownloadEmpty"
}
}
/*
function generateBlowfishKey(trackId) {
const SECRET = 'g4el58wc0zvf9na1';
const idMd5 = _md5(trackId.toString(), 'ascii')
let bfKey = ''
for (let i = 0; i < 16; i++) {
bfKey += String.fromCharCode(idMd5.charCodeAt(i) ^ idMd5.charCodeAt(i + 16) ^ SECRET.charCodeAt(i))
}
return bfKey;
}
function generateCryptedStreamURL(sngID, md5, mediaVersion, format){
let urlPart = generateStreamPath(sngID, md5, mediaVersion, format)
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/mobile/1/" + urlPart
}
function decryptChunk(chunk, blowFishKey){
var cipher = crypto.createDecipheriv('bf-cbc', blowFishKey, Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]))
cipher.setAutoPadding(false)
return cipher.update(chunk, 'binary', 'binary') + cipher.final()
}
*/
module.exports = {
generateStreamPath,
generateStreamURL,
reverseStreamPath,
reverseStreamURL,
streamTrack
}

View file

@ -1,3 +1,69 @@
const { Track } = require('./types/Track.js')
const { streamTrack, generateStreamURL } = require('./decryption.js')
const { TrackFormats } = require('deezer-js')
function getPreferredBitrate(track, bitrate, shouldFallback, uuid, listener){
bitrate = parseInt(bitrate)
if (track.localTrack) { return TrackFormats.LOCAL }
let falledBack = false
const formats_non_360 = {
"FLAC": TrackFormats.FLAC,
"MP3_320": TrackFormats.MP3_320,
"MP3_128": TrackFormats.MP3_128
}
const formats_360 = {
"MP4_RA3": TrackFormats.MP4_RA3,
"MP4_RA2": TrackFormats.MP4_RA2,
"MP4_RA1": TrackFormats.MP4_RA1
}
const is360Format = Object.values(formats_360).contains(bitrate)
let formats
if (!shouldFallback){
formats = {...formats_360, ...formats_non_360}
}else if (is360Format){
formats = {...formats_360}
}else{
formats = {...formats_non_360}
}
for (let i = 0; i < Object.keys(formats).length; i++){
let formatName = Object.keys(formats)[i]
let formatNumber = formats[formatName]
if (formatNumber >= bitrate) { continue }
if (Object.keys(track.filesizes).contains(`FILESIZE_${formatName}`)){
if (parseInt(track.filesizes[`FILESIZE_${formatName}`]) != 0) return formatNumber
if (!track.filesizes[`FILESIZE_${formatName}_TESTED`]){
// Try getting the streamURL
generateStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber)
track.filesizes[`FILESIZE_${formatName}_TESTED`] = true
}
}
if (!shouldFallback){
throw new PreferredBitrateNotFound
}else if (!falledBack){
falledBack = true
if (listener && uuid){
listener.send("queueUpdate", {
uuid,
bitrateFallback: true,
data:{
id: track.id,
title: track.title,
artist: track.mainArtist.name
}
})
}
}
}
if (is360Format) throw new TrackNot360
return TrackFormats.DEFAULT
}
class Downloader {
constructor(dz, downloadObject, settings, listener){
@ -13,21 +79,34 @@ class Downloader {
}
start(){
if (typeof this.downloadObject == "Single"){
} else if (typeof this.downloadObject == "Collection") {
if (this.downloadObject.__type__ === "Single"){
this.download({
trackAPI_gw: this.downloadObject.single.trackAPI_gw,
trackAPI: this.downloadObject.single.trackAPI,
albumAPI: this.downloadObject.single.albumAPI
})
} else if (this.downloadObject.__type__ === "Collection") {
let tracks = []
this.downloadObject.collection.tracks_gw.forEach((track, pos) => {
tracks[pos] = this.download({
trackAPI_gw: track,
albumAPI: this.downloadObject.collection.albumAPI,
playlistAPI: this.downloadObject.collection.playlistAPI
})
})
}
if (this.listener) this.listener.send("finishedDownload", this.downloadObject.uuid)
}
download(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track){
async download(extraData, track){
const { trackAPI_gw, trackAPI, albumAPI, playlistAPI } = extraData
if (trackAPI_gw.SNG_ID == "0") throw new DownloadFailed("notOnDeezer")
// Generate track object
if (!track){
track = Track().parseData(
track = Track()
await track.parseData(
this.dz,
trackAPI_gw,
trackAPI,
@ -51,5 +130,42 @@ class Downloader {
track.album.bitrate = selectedFormat
// Download the track
track.downloadURL = generateStreamURL(track.id, track.MD5, track.mediaVersion, track.bitrate)
}
}
class DownloadError extends Error {
constructor(message) {
super(message);
this.name = "DownloadError"
}
}
class DownloadFailed extends DownloadError {
constructor(errid, track) {
super(errid);
this.name = "ISRCnotOnDeezer"
this.track = track
}
}
class TrackNot360 extends Error {
constructor(message) {
super(message);
this.name = "TrackNot360"
}
}
class PreferredBitrateNotFound extends Error {
constructor(message) {
super(message);
this.name = "PreferredBitrateNotFound"
}
}
module.exports = {
Downloader,
DownloadError,
DownloadFailed
}

View file

@ -1,8 +1,9 @@
const {
Single,
Collection,
Convertable
Collection
} = require('./types/DownloadObjects.js')
const { LyricsStatus } = require('deezer-js').gw
const { map_user_playlist } = require('deezer-js').utils
async function generateTrackItem(dz, id, bitrate, trackAPI, albumAPI){
// Check if is an isrc: url
@ -22,6 +23,7 @@ async function generateTrackItem(dz, id, bitrate, trackAPI, albumAPI){
}
// Get essential track info
let trackAPI_gw
try {
trackAPI_gw = await dz.gw.get_track_with_fallback(id)
} catch (e){
@ -75,12 +77,13 @@ async function generateAlbumItem(dz, id, bitrate, rootArtist){
return generateTrackItem(dz, albumAPI.tracks.data[0].id, bitrate, null, albumAPI)
}
tracksArray = await dz.gw.get_album_tracks(id)
let tracksArray = await dz.gw.get_album_tracks(id)
let cover
if (albumAPI.cover_small){
const cover = albumAPI.cover_small.substring(0, albumAPI.cover_small.length-24) + '/75x75-000000-80-0-0.jpg'
cover = albumAPI.cover_small.substring(0, albumAPI.cover_small.length-24) + '/75x75-000000-80-0-0.jpg'
}else{
const cover = `https://e-cdns-images.dzcdn.net/images/cover/${albumAPI_gw.ALB_PICTURE}/75x75-000000-80-0-0.jpg`
cover = `https://e-cdns-images.dzcdn.net/images/cover/${albumAPI_gw.ALB_PICTURE}/75x75-000000-80-0-0.jpg`
}
const totalSize = tracksArray.length
@ -92,7 +95,7 @@ async function generateAlbumItem(dz, id, bitrate, rootArtist){
collection.push(trackAPI)
})
explicit = [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT].includes(albumAPI_gw.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS || LyricsStatus.UNKNOWN)
let explicit = [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT].includes(albumAPI_gw.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS || LyricsStatus.UNKNOWN)
return new Collection({
type: 'album',
@ -130,7 +133,7 @@ async function generatePlaylistItem(dz, id, bitrate, playlistAPI, playlistTracks
}
}
// Check if private playlist and owner
if (!playlsitAPI.public && playlistAPI.creator.id != dz.current_user.id){
if (!playlistAPI.public && playlistAPI.creator.id != dz.current_user.id){
throw new NotYourPrivatePlaylist()
}
}
@ -216,8 +219,8 @@ async function generateArtistDiscographyItem(dz, id, bitrate, listener){
}
if (listener) { listener.send("startAddingArtist", rootArtist) }
let artistDiscographyAPI = dz.gw.get_artist_discography_tabs(id, 100)
artistDiscographyAPI.pop('all', None)
let artistDiscographyAPI = await dz.gw.get_artist_discography_tabs(id, 100)
artistDiscographyAPI.pop('all', null)
let albumList = []
artistDiscographyAPI.forEach((type) => {
type.forEach(async (album) => {
@ -307,6 +310,7 @@ module.exports = {
generateArtistItem,
generateArtistDiscographyItem,
generateArtistTopItem,
GenerationError,
ISRCnotOnDeezer,
NotYourPrivatePlaylist

View file

@ -10,10 +10,10 @@ class Album {
constructor(id = 0, title = "", pic_md5 = ""){
this.id = id
this.title = title
this.pic = Picture(md5=pic_md5, type="cover")
this.pic = new Picture(pic_md5, "cover")
this.artist = {"Main": []}
this.artists = []
self.mainArtist = null
this.mainArtist = null
this.date = Date()
this.dateString = ""
this.trackTotal = "0"
@ -26,8 +26,8 @@ class Album {
this.label = "Unknown"
this.recordType = "album"
this.bitrate = 0
self.rootArtist = null
self.variousArtists = null
this.rootArtist = null
this.variousArtists = null
}
parseAlbum(albumAPI){
@ -37,27 +37,29 @@ class Album {
// ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
let art_pic = albumAPI.artist.picture_small
art_pic = art_pic.substring( art_pic.indexOf('artist/')+7, art_pic.length-24 )
this.mainArtist = Artist(
this.mainArtist = new Artist(
albumAPI.artist.id,
albumAPI.artist.name,
pic_md5 = art_pic
"Main",
art_pic
)
if (albumAPI.root_artist){
let art_pic = albumAPI.root_artist.picture_small
art_pic = art_pic.substring( art_pic.indexOf('artist/')+7, art_pic.length-24 )
this.rootArtist = Artist(
this.rootArtist = new Artist(
albumAPI.root_artist.id,
albumAPI.root_artist.name,
pic_md5 = art_pic
"Root",
art_pic
)
}
albumAPI.contributors.forEach(artist => {
let isVariousArtists = str(artist.id) == VARIOUS_ARTISTS
let isVariousArtists = String(artist.id) == VARIOUS_ARTISTS
let isMainArtist = artist.role == "Main"
if (isVariousArtists){
this.variousArtists = Artist(
this.variousArtists = new Artist(
artist.id,
artist.name,
artist.role
@ -80,7 +82,7 @@ class Album {
this.barcode = albumAPI.upc || this.barcode
this.label = albumAPI.label || this.label
this.explicit = bool(albumAPI.explicit_lyrics || false)
this.explicit = Boolean(albumAPI.explicit_lyrics || false)
if (albumAPI.release_date){
this.date.year = albumAPI.release_date.substring(0,4)
this.date.month = albumAPI.release_date.substring(5,7)
@ -107,7 +109,7 @@ class Album {
parseAlbumGW(albumAPI_gw){
this.title = albumAPI_gw.ALB_TITLE
this.mainArtist = Aritst(
this.mainArtist = new Artist(
albumAPI_gw.ART_ID,
albumAPI_gw.ART_NAME
)
@ -118,6 +120,7 @@ class Album {
this.label = albumAPI_gw.LABEL_NAME || this.label
let explicitLyricsStatus = albumAPI_gw.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS
this.explicit = [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT].contains(explicitLyricsStatus)
if (this.pic.md5 == ""){
this.pic.md5 = albumAPI_gw.ALB_PICTURE
@ -150,11 +153,11 @@ class Album {
}
removeDuplicateArtists(){
[self.artist, self.artists] = removeDuplicateArtists(self.artist, self.artists)
[this.artist, this.artists] = removeDuplicateArtists(this.artist, this.artists)
}
getCleanTitle(){
return removeFeatures(self.title)
return removeFeatures(this.title)
}
}

View file

@ -3,11 +3,11 @@ const { VARIOUS_ARTISTS } = require('../index.js')
class Artist {
constructor(id="0", name="", role = "", pic_md5 = ""){
this.id = str(id)
this.id = String(id)
this.name = name
this.pic = Picture(md5 = pic_md5, type="artist")
this.pic = new Picture(pic_md5, "artist")
this.role = role
this.save = True
this.save = true
}
ifVariousArtist(){

View file

@ -7,7 +7,7 @@ class Date {
}
fixDayMonth(){
if (int(this.month) > 12){
if (parseInt(this.month) > 12){
let temp = this.month
this.month = this.day
this.day = temp

View file

@ -39,7 +39,7 @@ class IDownloadObject{
}
getResettedDict(){
item = this.toDict()
let item = this.toDict()
item.downloaded = 0
item.failed = 0
item.progress = 0
@ -49,8 +49,8 @@ class IDownloadObject{
}
getSlimmedDict(){
light = this.toDict()
propertiesToDelete = ['single', 'collection', 'convertable']
let light = this.toDict()
let propertiesToDelete = ['single', 'collection', 'convertable']
propertiesToDelete.forEach((property) => {
if (Object.keys(light).includes(property)){
delete light[property]
@ -77,7 +77,7 @@ class Single extends IDownloadObject{
}
toDict(){
item = super.toDict()
let item = super.toDict()
item.single = this.single
return item
}
@ -101,7 +101,7 @@ class Collection extends IDownloadObject{
}
toDict(){
item = super.toDict()
let item = super.toDict()
item.collection = this.collection
return item
}
@ -126,7 +126,7 @@ class Convertable extends Collection{
}
toDict(){
item = super.toDict()
let item = super.toDict()
item.plugin = this.plugin
item.conversion_data = this.conversion_data
}

View file

@ -15,7 +15,7 @@ class Lyrics {
for (let line = 0; line < syncLyricsJson.length; line++) {
if (syncLyricsJson[line].line != ""){
timestamp = syncLyricsJson[line].lrc_timestamp
milliseconds = int(syncLyricsJson[line].milliseconds)
milliseconds = parseInt(syncLyricsJson[line].milliseconds)
this.syncID3.push([syncLyricsJson[line].line, milliseconds])
}else{
let notEmptyLine = line + 1

View file

@ -1,20 +1,19 @@
class Picture {
constructor(md5 = "", type = "", url) {
constructor(md5 = "", type = "") {
this.md5 = md5
this.type = type
this.staticUrl = url
}
getURL(size, format) {
if (this.staticUrl) return this.staticUrl
let url = `https://e-cdns-images.dzcdn.net/images/${this.type}/${this.md5}/${str(size)}x${str(size)}`
let url = `https://e-cdns-images.dzcdn.net/images/${this.type}/${this.md5}/${size}x${size}`
if (format.startsWith('jpg')){
let quality = 80
if (format.indexOf('-') != -1) quality = int(format.substr(4))
if (format.indexOf('-') != -1) quality = parseInt(format.substr(4))
format = 'jpg'
return url+`-000000-${str(quality)}-0-0.jpg`
return url+`-000000-${quality}-0-0.jpg`
}
if (format == 'png'){
return url+`-none-100-0-0.png`
@ -25,6 +24,17 @@ class Picture {
}
module.exports = {
Picture
class StaticPicture {
constructor(url){
this.staticUrl = url
}
getUrl() {
return this.staticUrl
}
}
module.exports = {
Picture,
StaticPicture
}

View file

@ -1,10 +1,10 @@
const { Artist } = require('./Artist.js')
const { Date } = require('./Date.js')
const { Picture } = require('./Picture.js')
const { Picture, StaticPicture } = require('./Picture.js')
class Playlist {
constructor(playlistAPI) {
this.id = `pl_${str(playlistAPI.id)}`
this.id = `pl_${playlistAPI.id}`
this.title = playlistAPI.title
this.artist = {"Main": []}
this.artists = []
@ -25,23 +25,23 @@ class Playlist {
this.owner = playlistAPI.creator
if (playlistAPI.picture_small.indexOf("dzcdn.net") != -1) {
url = playlistAPI.picture_small
let url = playlistAPI.picture_small
let picType = url.substring(url.indexOf('images/')+7)
picType = picType.substring(0, picType.indexOf('/'))
let md5 = url.substring( url.indexOf(picType+'/') + picType.length+1, url.length-24 )
this.pic = Picture(md5 = md5, type = picType)
this.pic = Picture(md5, picType)
} else {
this.pic = Picture(url = playlistAPI.picture_xl)
this.pic = StaticPicture(playlistAPI.picture_xl)
}
if (playlistAPI.various_artist) {
let pic_md5 = playlistAPI.various_artist.picture_small
pic_md5 = pic_md5.substring( pic_md5.indexOf('artist/')+7, pic_md5.length-24 )
this.variousArtists = Artist(
id = playlistAPI.various_artist.id,
name = playlistAPI.various_artist.name,
role = playlistAPI.various_artist.role,
pic_md5 = pic_md5
playlistAPI.various_artist.id,
playlistAPI.various_artist.name,
playlistAPI.various_artist.role,
pic_md5
)
this.mainArtist = this.variousArtists
}

View file

@ -1,4 +1,17 @@
const got = require('got')
const { Artist } = require('./Artist.js')
const { Album } = require('./Album.js')
const { Playlist } = require('./Playlist.js')
const { Picture } = require('./Picture.js')
const { Lyrics } = require('./Lyrics.js')
const { VARIOUS_ARTISTS } = require('./index.js')
const {
generateReplayGainString,
removeDuplicateArtists,
removeFeatures,
andCommaConcat
} = require('../utils/index.js')
class Track {
constructor(){
@ -36,14 +49,14 @@ class Track {
}
parseEssentialData(trackAPI_gw, trackAPI){
this.id = str(trackAPI_gw.SNG_ID)
this.id = String(trackAPI_gw.SNG_ID)
this.duration = trackAPI_gw.DURATION
this.MD5 = trackAPI_gw.MD5_ORIGIN
if (!this.MD5){
if (trackAPI && trackAPI.md5_origin){
this.MD5 = trackAPI.md5_origin
}else{
throw MD5NotFound
throw new MD5NotFound
}
}
this.mediaVersion = trackAPI_gw.MEDIA_VERSION
@ -51,13 +64,14 @@ class Track {
if (trackAPI_gw.FALLBACK){
this.fallbackID = trackAPI_gw.FALLBACK.SNG_ID
}
this.localTrack = int(this.id) < 0
this.localTrack = parseInt(this.id) < 0
}
async retriveFilesizes(dz){
const guest_sid = await dz.cookie_jar.getCookies('deezer.com').sid
let result_json
try{
const result_json = await got.post("https://api.deezer.com/1.0/gateway.php",{
result_json = await got.post("https://api.deezer.com/1.0/gateway.php",{
searchParams:{
api_key: "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
sid: guest_sid,
@ -69,12 +83,12 @@ class Track {
headers: dz.headers,
timeout: 30000
}).json()
}catch{
console.log(e)
}catch (e){
console.error(e)
await new Promise(r => setTimeout(r, 2000)) // sleep(2000ms)
return this.retriveFilesizes(dz)
}
if (result_json.error.length){ throw APIError }
if (result_json.error.length){ throw new TrackError(result_json.error) }
const response = result_json.results
let filesizes = {}
Object.entries(response).forEach((value, key) => {
@ -88,7 +102,7 @@ class Track {
async parseData(dz, id, trackAPI_gw, trackAPI, albumAPI_gw, albumAPI, playlistAPI){
if (id && !trackAPI_gw) { trackAPI_gw = await dz.gw.get_track_with_fallback(id) }
else if (!trackAPI_gw) { throw NoDataToParse }
else if (!trackAPI_gw) { throw new NoDataToParse }
if (!trackAPI) {
try { trackAPI = await dz.api.get_track(trackAPI_gw.SNG_ID) }
@ -111,18 +125,18 @@ class Track {
if (this.lyrics.id != "0"){ this.lyrics.parseLyrics(trackAPI_gw.LYRICS) }
// Parse Album Data
this.album = Album(trackAPI_gw.ALB_ID, trackAPI_gw.ALB_TITLE, trackAPI_gw.ALB_PICTURE || "")
this.album = new Album(trackAPI_gw.ALB_ID, trackAPI_gw.ALB_TITLE, trackAPI_gw.ALB_PICTURE || "")
// Get album Data
if (!albumAPI){
try { albumAPI = await dz.api.get_album(this.album.id) }
catch { albumAPI = None }
catch { albumAPI = null }
}
// Get album_gw Data
if (!albumAPI_gw){
try { albumAPI_gw = await dz.gw.get_album(this.album.id) }
catch { albumAPI_gw = None }
catch { albumAPI_gw = null }
}
if (albumAPI){
@ -132,10 +146,10 @@ class Track {
// albumAPI_gw doesn't contain the artist cover
// Getting artist image ID
// ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
const artistAPI = await dz.api.get_artist(self.album.mainArtist.id)
self.album.mainArtist.pic.md5 = artistAPI.picture_small.substring( artistAPI.picture_small.search('artist/')+7, artistAPI.picture_small.length-24 )
const artistAPI = await dz.api.get_artist(this.album.mainArtist.id)
this.album.mainArtist.pic.md5 = artistAPI.picture_small.substring( artistAPI.picture_small.search('artist/')+7, artistAPI.picture_small.length-24 )
}else{
throw AlbumDoesntExists
throw new AlbumDoesntExists
}
// Fill missing data
@ -156,7 +170,7 @@ class Track {
this.position = trackAPI_gw.POSITION
if (playlistAPI) { this.playlist = Playlist(playlistAPI) }
if (playlistAPI) { this.playlist = new Playlist(playlistAPI) }
this.generateMainFeatStrings()
return this
@ -166,12 +180,12 @@ class Track {
// Local tracks has only the trackAPI_gw page and
// contains only the tags provided by the file
this.title = trackAPI_gw.SNG_TITLE
this.album = Album(trackAPI_gw.ALB_TITLE)
this.album.pic = Picture(
this.album = new Album(trackAPI_gw.ALB_TITLE)
this.album.pic = new Picture(
trackAPI_gw.ALB_PICTURE || "",
"cover"
)
this.mainArtist = Artist(trackAPI_gw.ART_NAME)
this.mainArtist = new Artist(trackAPI_gw.ART_NAME)
this.artists = [trackAPI_gw.ART_NAME]
this.artist = {
'Main': [trackAPI_gw.ART_NAME]
@ -190,16 +204,16 @@ class Track {
}
this.discNumber = trackAPI_gw.DISK_NUMBER
this.explicit = bool(int(trackAPI_gw.EXPLICIT_LYRICS || "0"))
this.explicit = Boolean(parseInt(trackAPI_gw.EXPLICIT_LYRICS || "0"))
this.copyright = trackAPI_gw.COPYRIGHT
if (trackAPI_gw.GAIN) this.replayGain = generateReplayGainString(trackAPI_gw.GAIN)
this.ISRC = trackAPI_gw.ISRC
this.trackNumber = trackAPI_gw.TRACK_NUMBER
this.contributors = trackAPI_gw.SNG_CONTRIBUTORS
this.lyrics = Lyrics(trackAPI_gw.LYRICS_ID || "0")
this.lyrics = new Lyrics(trackAPI_gw.LYRICS_ID || "0")
this.mainArtist = Artist(
this.mainArtist = new Artist(
trackAPI_gw.ART_ID,
trackAPI_gw.ART_NAME,
trackAPI_gw.ART_PICTRUE
@ -221,7 +235,7 @@ class Track {
if (!this.discNumber) this.discNumber = trackAPI.disk_number
trackAPI.contributors.forEach(artist => {
const isVariousArtists = str(artist.id) == VARIOUS_ARTISTS
const isVariousArtists = String(artist.id) == VARIOUS_ARTISTS
const isMainArtist = artist.role == "Main"
if (trackAPI.contributors.length > 1 && isVariousArtists) return
@ -230,7 +244,7 @@ class Track {
this.artsits.push(artist.name)
if (isMainArtist || !this.artsit.Main.contains(artist.name) && !isMainArtist){
if (!this.artist[aritst.role])
if (!this.artist[artist.role])
this.artist[artist.role] = []
this.artist[artist.role].push(artist.name)
}
@ -260,8 +274,37 @@ class Track {
}
}
applySettings(settings, TEMPDIR, embeddedImageFormat){
applySettings(settings){
// TODO: Applay settings
settings;
}
}
class TrackError extends Error {
constructor(message) {
super(message);
this.name = "TrackError";
}
}
class MD5NotFound extends TrackError {
constructor(message) {
super(message);
this.name = "MD5NotFound";
}
}
class NoDataToParse extends TrackError {
constructor(message) {
super(message);
this.name = "NoDataToParse";
}
}
class AlbumDoesntExists extends TrackError {
constructor(message) {
super(message);
this.name = "AlbumDoesntExists";
}
}

View file

@ -1,5 +1,5 @@
function generateReplayGainString(trackGain){
return `${Math.round((float(trackGain) + 18.4)*-100)/100} dB`
return `${Math.round((parseFloat(trackGain) + 18.4)*-100)/100} dB`
}
function removeFeatures(title){

2299
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,11 @@
"author": "RemixDev",
"license": "GPL-3.0-or-later",
"dependencies": {
"crypto": "^1.0.1",
"deezer-js": "^0.0.3",
"got": "^11.8.2"
},
"devDependencies": {
"eslint": "^7.23.0"
}
}